From f3564f47f403549fbda0b27203882f24f8581604 Mon Sep 17 00:00:00 2001 From: Simon Arlott Date: Wed, 28 Jun 2017 21:24:10 +0100 Subject: [PATCH] msgbuf: correctly split buffers into IRCv3 tags and RFC1459 message data --- include/client.h | 1 - include/ircd_defs.h | 3 + include/msgbuf.h | 17 +-- ircd/msgbuf.c | 263 ++++++++++++++++++++++++++++++++------------ ircd/s_serv.c | 10 +- ircd/send.c | 5 +- 6 files changed, 207 insertions(+), 92 deletions(-) diff --git a/include/client.h b/include/client.h index 39f31e77..8ab93ce4 100644 --- a/include/client.h +++ b/include/client.h @@ -43,7 +43,6 @@ struct Blacklist; #define HOSTIPLEN 53 /* sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255.ipv6") */ #define PASSWDLEN 128 #define CIPHERKEYLEN 64 /* 512bit */ -#define CLIENT_BUFSIZE 512 /* must be at least 512 bytes */ #define IDLEN 10 diff --git a/include/ircd_defs.h b/include/ircd_defs.h index 40db5383..4e9ea037 100644 --- a/include/ircd_defs.h +++ b/include/ircd_defs.h @@ -89,7 +89,10 @@ /* 23+1 for \0 */ #define KEYLEN 24 +#define TAGSLEN 512 /* IRCv3 message tags */ +#define DATALEN 510 /* RFC1459 message data */ #define BUFSIZE 512 /* WARNING: *DONT* CHANGE THIS!!!! */ +#define EXT_BUFSIZE (TAGSLEN + DATALEN + 1) #define OPERNICKLEN (NICKLEN*2) /* Length of OPERNICKs. */ #define USERHOST_REPLYLEN (NICKLEN+HOSTLEN+USERLEN+5) diff --git a/include/msgbuf.h b/include/msgbuf.h index ed7a04ad..1bcff5ed 100644 --- a/include/msgbuf.h +++ b/include/msgbuf.h @@ -55,7 +55,7 @@ int msgbuf_parse(struct MsgBuf *msgbuf, char *line); * cmd may not be NULL. * returns 0 on success, 1 on error. */ -int msgbuf_unparse(char *buf, size_t buflen, struct MsgBuf *msgbuf, unsigned int capmask); +int msgbuf_unparse(char *buf, size_t buflen, const struct MsgBuf *msgbuf, unsigned int capmask); /* * unparse a MsgBuf header plus payload into a buffer. @@ -63,10 +63,10 @@ int msgbuf_unparse(char *buf, size_t buflen, struct MsgBuf *msgbuf, unsigned int * cmd may not be NULL. * returns 0 on success, 1 on error. */ -int msgbuf_unparse_fmt(char *buf, size_t buflen, struct MsgBuf *head, unsigned int capmask, const char *fmt, ...) AFP(5, 6); -int msgbuf_vunparse_fmt(char *buf, size_t buflen, struct MsgBuf *head, unsigned int capmask, const char *fmt, va_list va); +int msgbuf_unparse_fmt(char *buf, size_t buflen, const struct MsgBuf *head, unsigned int capmask, const char *fmt, ...) AFP(5, 6); +int msgbuf_vunparse_fmt(char *buf, size_t buflen, const struct MsgBuf *head, unsigned int capmask, const char *fmt, va_list va); -void msgbuf_unparse_prefix(char *buf, size_t buflen, struct MsgBuf *msgbuf, unsigned int capmask); +void msgbuf_unparse_prefix(char *buf, size_t *buflen, const struct MsgBuf *msgbuf, unsigned int capmask); static inline void msgbuf_init(struct MsgBuf *msgbuf) @@ -85,13 +85,4 @@ msgbuf_append_tag(struct MsgBuf *msgbuf, const char *key, const char *value, uns } } -static inline void -msgbuf_append_para(struct MsgBuf *msgbuf, const char *para) -{ - if (msgbuf->n_para < MAXPARA) { - msgbuf->para[msgbuf->n_para] = para; - msgbuf->n_para++; - } -} - #endif diff --git a/ircd/msgbuf.c b/ircd/msgbuf.c index 1336c096..dd6c1832 100644 --- a/ircd/msgbuf.c +++ b/ircd/msgbuf.c @@ -25,6 +25,60 @@ #include "client.h" #include "ircd.h" +static const char tag_escape_table[256] = { + /* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF */ + /* 0x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'n', 0, 0, 'r', 0, 0, + /* 1x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 2x */ 's', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 3x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ':', 0, 0, 0, 0, + /* 4x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 5x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'\\', 0, 0, 0, +}; + +static const char tag_unescape_table[256] = { + /* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF */ + /* 0x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 1x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 2x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 3x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ';', 0, 0, 0, 0, 0, + /* 4x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 5x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'\\', 0, 0, 0, + /* 6x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'\n', 0, + /* 7x */ 0, 0,'\r', ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +static void +msgbuf_unescape_value(char *value) +{ + char *in = value; + char *out = value; + + if (value == NULL) + return; + + while (*in != '\0') { + if (*in == '\\') { + const char unescape = tag_unescape_table[(unsigned char)*++in]; + + /* "\\\0" is unescaped to the character itself, "\0" */ + if (*in == '\0') + break; + + if (unescape) { + *out++ = unescape; + *in++; + } else { + *out++ = *in++; + } + } else { + *out++ = *in++; + } + } + + /* copy final '\0' */ + *out = *in; +} + /* * parse a message into a MsgBuf. * returns 0 on success, 1 on error. @@ -32,29 +86,29 @@ int msgbuf_parse(struct MsgBuf *msgbuf, char *line) { - char *ch; - char *parv[MAXPARA]; - - /* skip any leading spaces */ - for (ch = line; *ch && *ch == ' '; ch++) - ; + char *ch = line; msgbuf_init(msgbuf); - if (*ch == '@') - { + if (*ch == '@') { char *t = ch + 1; ch = strchr(ch, ' '); - if (ch != NULL) - { - while (1) - { + + /* truncate tags if they're too long */ + if ((ch != NULL && (ch - line) + 1 > TAGSLEN) || (ch == NULL && strlen(line) >= TAGSLEN)) { + ch = &line[TAGSLEN - 1]; + } + + if (ch != NULL) { + /* NULL terminate the tags string */ + *ch++ = '\0'; + + while (1) { char *next = strchr(t, ';'); char *eq = strchr(t, '='); - if (next != NULL) - { + if (next != NULL) { *next = '\0'; if (eq > next) @@ -64,123 +118,189 @@ msgbuf_parse(struct MsgBuf *msgbuf, char *line) if (eq != NULL) *eq++ = '\0'; - if (*t && *t != ' ') + if (*t != '\0') { + msgbuf_unescape_value(eq); msgbuf_append_tag(msgbuf, t, eq, 0); - else - break; + } - if (next != NULL) + if (next != NULL) { t = next + 1; - else + } else { break; + } } - - *ch++ = '\0'; + } else { + return 1; } - else - ch = t; } - /* skip any whitespace between tags and origin */ - for (; *ch && *ch == ' '; ch++) - ; + /* truncate message if it's too long */ + if (strlen(ch) > DATALEN) { + ch[DATALEN] = '\0'; + } - if (*ch == ':') - { + if (*ch == ':') { ch++; msgbuf->origin = ch; char *end = strchr(ch, ' '); if (end == NULL) - return 1; + return 4; *end = '\0'; - - for (ch = end + 1; *ch && *ch == ' '; ch++) - ; + ch = end + 1; } if (*ch == '\0') - return 1; + return 2; msgbuf->n_para = rb_string_to_array(ch, (char **)msgbuf->para, MAXPARA); if (msgbuf->n_para == 0) - return 1; + return 3; msgbuf->cmd = msgbuf->para[0]; return 0; } -static void -msgbuf_unparse_tags(char *buf, size_t buflen, struct MsgBuf *msgbuf, unsigned int capmask) +/* + * Unparse msgbuf tags into a buffer + * returns the length of the tags written + */ +static size_t +msgbuf_unparse_tags(char *buf, size_t buflen, const struct MsgBuf *msgbuf, unsigned int capmask) { bool has_tags = false; + char *commit = buf; + char *output = buf; + const char * const end = &buf[buflen - 2]; /* this is where the final ' ' goes */ + + for (size_t i = 0; i < msgbuf->n_tags; i++) { + size_t len; - for (size_t i = 0; i < msgbuf->n_tags; i++) - { if ((msgbuf->tags[i].capmask & capmask) == 0) continue; if (has_tags) { - rb_strlcat(buf, ";", buflen); + if (output >= end) + break; + *output++ = ';'; } else { - *buf = '@'; - has_tags = true; + if (output >= end) + break; + *output++ = '@'; } - rb_strlcat(buf, msgbuf->tags[i].key, buflen); + if (msgbuf->tags[i].key == NULL) + continue; - /* XXX properly handle escaping */ - if (msgbuf->tags[i].value) - { - rb_strlcat(buf, "=", buflen); - rb_strlcat(buf, msgbuf->tags[i].value, buflen); + len = strlen(msgbuf->tags[i].key); + if (len == 0) + continue; + + if (output + len > end) + break; + strcat(output, msgbuf->tags[i].key); + output += len; + + if (msgbuf->tags[i].value != NULL) { + if (output >= end) + break; + *output++ = '='; + + len = strlen(msgbuf->tags[i].value); + /* this only checks the unescaped length, + * but the escaped length could be longer + */ + if (output + len > end) + break; + + for (size_t n = 0; n < len; n++) { + const unsigned char c = msgbuf->tags[i].value[n]; + const char escape = tag_escape_table[c]; + + if (escape) { + if (output + 2 > end) + break; + + *output++ = '\\'; + *output++ = escape; + } else { + if (output >= end) + break; + + *output++ = c; + } + } } + + has_tags = true; + commit = output; } if (has_tags) - rb_strlcat(buf, " ", buflen); + *commit++ = ' '; + + *commit = 0; + return commit - buf; } +/* + * unparse a MsgBuf and message prefix into a buffer + * if origin is NULL, me.name will be used. + * cmd should not be NULL. + * updates buflen to correctly allow remaining message data to be added + */ void -msgbuf_unparse_prefix(char *buf, size_t buflen, struct MsgBuf *msgbuf, unsigned int capmask) +msgbuf_unparse_prefix(char *buf, size_t *buflen, const struct MsgBuf *msgbuf, unsigned int capmask) { - memset(buf, 0, buflen); + size_t tags_buflen; + size_t tags_used = 0; + + memset(buf, 0, *buflen); + + tags_buflen = *buflen; + if (tags_buflen > TAGSLEN + 1) + tags_buflen = TAGSLEN + 1; if (msgbuf->n_tags > 0) - msgbuf_unparse_tags(buf, buflen, msgbuf, capmask); + tags_used = msgbuf_unparse_tags(buf, tags_buflen, msgbuf, capmask); - rb_snprintf_append(buf, buflen, ":%s ", msgbuf->origin != NULL ? msgbuf->origin : me.name); + const size_t data_bufmax = (tags_used + DATALEN + 1); + if (*buflen > data_bufmax) + *buflen = data_bufmax; + + rb_snprintf_append(buf, *buflen, ":%s ", msgbuf->origin != NULL ? msgbuf->origin : me.name); if (msgbuf->cmd != NULL) - rb_snprintf_append(buf, buflen, "%s ", msgbuf->cmd); + rb_snprintf_append(buf, *buflen, "%s ", msgbuf->cmd); if (msgbuf->target != NULL) - rb_snprintf_append(buf, buflen, "%s ", msgbuf->target); + rb_snprintf_append(buf, *buflen, "%s ", msgbuf->target); } /* * unparse a pure MsgBuf into a buffer. * if origin is NULL, me.name will be used. - * cmd may not be NULL. + * cmd should not be NULL. * returns 0 on success, 1 on error. */ int -msgbuf_unparse(char *buf, size_t buflen, struct MsgBuf *msgbuf, unsigned int capmask) +msgbuf_unparse(char *buf, size_t buflen, const struct MsgBuf *msgbuf, unsigned int capmask) { - msgbuf_unparse_prefix(buf, buflen, msgbuf, capmask); + size_t buflen_copy = buflen; - for (size_t i = msgbuf->cmd != NULL ? 0 : 1; i < msgbuf->n_para; i++) - { - if (i == (msgbuf->n_para - 1)) - { - if (strchr(msgbuf->para[i], ' ') != NULL) - rb_snprintf_append(buf, buflen, ":%s", msgbuf->para[i]); - else - rb_strlcat(buf, msgbuf->para[i], buflen); + msgbuf_unparse_prefix(buf, &buflen_copy, msgbuf, capmask); + + for (size_t i = 0; i < msgbuf->n_para; i++) { + const char *fmt; + + if (i == (msgbuf->n_para - 1) && strchr(msgbuf->para[i], ' ') != NULL) { + fmt = (i == 0) ? ":%s" : " :%s"; + } else { + fmt = (i == 0) ? "%s" : " %s"; } - else - rb_strlcat(buf, msgbuf->para[i], buflen); + + rb_snprintf_append(buf, buflen_copy, fmt, msgbuf->para[i]); } return 0; @@ -193,16 +313,17 @@ msgbuf_unparse(char *buf, size_t buflen, struct MsgBuf *msgbuf, unsigned int cap * returns 0 on success, 1 on error. */ int -msgbuf_vunparse_fmt(char *buf, size_t buflen, struct MsgBuf *head, unsigned int capmask, const char *fmt, va_list va) +msgbuf_vunparse_fmt(char *buf, size_t buflen, const struct MsgBuf *head, unsigned int capmask, const char *fmt, va_list va) { + size_t buflen_copy = buflen; char *ws; size_t prefixlen; - msgbuf_unparse_prefix(buf, buflen, head, capmask); + msgbuf_unparse_prefix(buf, &buflen_copy, head, capmask); prefixlen = strlen(buf); ws = buf + prefixlen; - vsnprintf(ws, buflen - prefixlen, fmt, va); + vsnprintf(ws, buflen_copy - prefixlen, fmt, va); return 0; } @@ -214,7 +335,7 @@ msgbuf_vunparse_fmt(char *buf, size_t buflen, struct MsgBuf *head, unsigned int * returns 0 on success, 1 on error. */ int -msgbuf_unparse_fmt(char *buf, size_t buflen, struct MsgBuf *head, unsigned int capmask, const char *fmt, ...) +msgbuf_unparse_fmt(char *buf, size_t buflen, const struct MsgBuf *head, unsigned int capmask, const char *fmt, ...) { va_list va; int res; diff --git a/ircd/s_serv.c b/ircd/s_serv.c index 7e353e7e..738912d4 100644 --- a/ircd/s_serv.c +++ b/ircd/s_serv.c @@ -57,7 +57,7 @@ int MaxConnectionCount = 1; int MaxClientCount = 1; int refresh_user_links = 0; -static char buf[BUFSIZE]; +static char buf[EXT_BUFSIZE]; /* * list of recognized server capabilities. "TS" is not on the list @@ -559,7 +559,7 @@ burst_modes_TS6(struct Client *client_p, struct Channel *chptr, tlen = strlen(banptr->banstr) + (banptr->forward ? strlen(banptr->forward) + 1 : 0) + 1; /* uh oh */ - if(cur_len + tlen > BUFSIZE - 3) + if(cur_len + tlen > EXT_BUFSIZE - 3) { /* the one we're trying to send doesnt fit at all! */ if(cur_len == mlen) @@ -601,7 +601,7 @@ burst_modes_TS6(struct Client *client_p, struct Channel *chptr, static void burst_TS6(struct Client *client_p) { - char ubuf[BUFSIZE]; + char ubuf[EXT_BUFSIZE]; struct Client *target_p; struct Channel *chptr; struct membership *msptr; @@ -698,7 +698,7 @@ burst_TS6(struct Client *client_p) if(is_voiced(msptr)) tlen++; - if(cur_len + tlen >= BUFSIZE - 3) + if(cur_len + tlen >= EXT_BUFSIZE - 3) { *(t-1) = '\0'; sendto_one(client_p, "%s", buf); @@ -764,7 +764,7 @@ burst_TS6(struct Client *client_p) const char * show_capabilities(struct Client *target_p) { - static char msgbuf[BUFSIZE]; + static char msgbuf[EXT_BUFSIZE]; *msgbuf = '\0'; diff --git a/ircd/send.c b/ircd/send.c index ee0dabb0..22ce0e4d 100644 --- a/ircd/send.c +++ b/ircd/send.c @@ -218,10 +218,11 @@ send_queued_write(rb_fde_t *F, void *data) static void linebuf_put_msgvbuf(struct MsgBuf *msgbuf, buf_head_t *linebuf, unsigned int capmask, const char *pattern, va_list *va) { - char buf[BUFSIZE]; + char buf[EXT_BUFSIZE]; + size_t buflen = sizeof(buf); rb_linebuf_newbuf(linebuf); - msgbuf_unparse_prefix(buf, sizeof buf, msgbuf, capmask); + msgbuf_unparse_prefix(buf, &buflen, msgbuf, capmask); rb_linebuf_putprefix(linebuf, pattern, va, buf); }