]> jfr.im git - irc/charybdis-ircd/charybdis.git/commitdiff
msgbuf: correctly split buffers into IRCv3 tags and RFC1459 message data
authorSimon Arlott <sa.me.uk>
Wed, 28 Jun 2017 20:24:10 +0000 (21:24 +0100)
committerSimon Arlott <sa.me.uk>
Sat, 29 Jul 2017 21:46:07 +0000 (22:46 +0100)
include/client.h
include/ircd_defs.h
include/msgbuf.h
ircd/msgbuf.c
ircd/s_serv.c
ircd/send.c

index 39f31e776e50e4719a219faa6e125d09f879f58a..8ab93ce4ac87e95a3d9b59f1e2cef222af5e19b7 100644 (file)
@@ -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
 
index 40db5383963960d9c8c2611bf6e254bd65ae40f6..4e9ea0373e4016ab9b6873647208910caf00133e 100644 (file)
 
 /* 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)
index ed7a04adf30442d3626ae3d382707e4f993514e3..1bcff5edc6b1ac12cb4d2c39e55b73015f84e91d 100644 (file)
@@ -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
index 1336c096bfb76502f12f7ed796971d7531f86747..dd6c1832ed15e49e16857e4a219d7db927b16174 100644 (file)
 #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.
 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;
+
+               len = strlen(msgbuf->tags[i].key);
+               if (len == 0)
+                       continue;
 
-               /* XXX properly handle escaping */
-               if (msgbuf->tags[i].value)
-               {
-                       rb_strlcat(buf, "=", buflen);
-                       rb_strlcat(buf, msgbuf->tags[i].value, buflen);
+               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);
+
+       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);
+       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);
-
-       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);
+       size_t buflen_copy = 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;
index 7e353e7ecf75f65b7770678a38159ef971d3ec17..738912d4f9c64daf9a98ee5e808a559a7b681ade 100644 (file)
@@ -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';
 
index ee0dabb05d6d2ba02ca6f5bff71b375954b03895..22ce0e4d8fc693d9fbe03e64fff693a4ae020596 100644 (file)
@@ -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);
 }