diff options
author | Sergey Poznyakoff <gray@gnu.org> | 2020-07-10 11:49:36 +0300 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org> | 2020-07-10 11:49:36 +0300 |
commit | 816bf513094698ae2338de87112ac0a2579b7896 (patch) | |
tree | 7885cad22cdc647c10bc95f3447ccf29751f3f8e /src | |
parent | 838f7a81cce4c48dcbb1b825e6b25e291ffab43a (diff) | |
download | mailfromd-816bf513094698ae2338de87112ac0a2579b7896.tar.gz mailfromd-816bf513094698ae2338de87112ac0a2579b7896.tar.bz2 |
Rewrite DKIM parser and formatter
Diffstat (limited to 'src')
-rw-r--r-- | src/dkim.c | 509 |
1 files changed, 346 insertions, 163 deletions
@@ -588,6 +588,8 @@ dkim_format_wrap(struct dkim_format_buf *fb, size_t len) static void dkim_format_tag(struct dkim_format_buf *fb, char const *tag, char const *val) { + if (!val) + return; if (!dkim_format_wrap(fb, strlen(tag) + strlen(val) + 2)) dkim_format_wrap(fb, strlen(tag) + 1); mu_stream_write(fb->str, tag, strlen(tag), NULL); @@ -598,31 +600,6 @@ dkim_format_tag(struct dkim_format_buf *fb, char const *tag, char const *val) } static void -dkim_format_tag_h(struct dkim_format_buf *fb, char const *val) -{ - void *save; - char const *hval; - - if (!dkim_format_wrap(fb, 2)) - dkim_format_wrap(fb, strlen(val) + 3); - - mu_stream_write(fb->str, "h=", 2, NULL); - - if ((hval = dkim_header_list_first(val, &save)) != NULL) { - dkim_format_wrap(fb, strlen(hval)); - mu_stream_write(fb->str, hval, strlen(hval), NULL); - while ((hval = dkim_header_list_next(save)) != NULL) { - dkim_format_wrap(fb, strlen(hval) + 1); - mu_stream_write(fb->str, ":", 1, NULL); - mu_stream_write(fb->str, hval, strlen(hval), NULL); - } - } - dkim_header_list_end(save); - dkim_format_wrap(fb, 1); - mu_stream_write(fb->str, ";", 1, NULL); -} - -static void dkim_format_tag_base64(struct dkim_format_buf *fb, char const *tag, char const *val) { @@ -644,88 +621,6 @@ dkim_format_tag_base64(struct dkim_format_buf *fb, char const *tag, } mu_stream_write(fb->str, ";", 1, NULL); } - -int -dkim_signature_format(struct dkim_signature *sig, int crlf, char **result) -{ - struct dkim_format_buf fb; - int rc; - static char header_field[] = DKIM_SIGNATURE_HEADER ": "; - mu_off_t off; - size_t size; - char *text; - - /* Initialize format buffer */ - fb.crlf = crlf; - rc = mu_memory_stream_create(&fb.str, MU_STREAM_RDWR); - if (rc) { - mu_diag_funcall(MU_DIAG_ERROR, - "mu_memory_stream_create", - NULL, rc); - return rc; - } - mu_stream_set_stat(fb.str, - MU_STREAM_STAT_MASK (MU_STREAM_STAT_OUT) | - MU_STREAM_STAT_MASK (MU_STREAM_STAT_OUTLN), - fb.stat); - mu_stream_write(fb.str, header_field, strlen(header_field), NULL); - dkim_format_tag(&fb, "v", sig->v); - dkim_format_tag(&fb, "a", sig->a ? sig->a : DKIM_ALGORITHM); - dkim_format_tag(&fb, "d", sig->d); - dkim_format_tag(&fb, "s", sig->s); - if (!(sig->canon[0] == DKIM_CANON_SIMPLE && - sig->canon[0] == sig->canon[1])) { - char v[(DKIM_CANON_STRING_MAX+1)*2]; - strcpy(v, dkim_canon_string[sig->canon[0]]); - strcat(v, "/"); - strcat(v, dkim_canon_string[sig->canon[1]]); - dkim_format_tag(&fb, "c", v); - } - dkim_format_tag(&fb, "q", sig->q ? sig->q : DKIM_QUERY_METHOD); - if (sig->i) - dkim_format_tag(&fb, "i", sig->i); - dkim_format_tag_h(&fb, sig->h); - if (sig->l != DKIM_LENGTH_ALL) { - char tbuf[80]; - snprintf(tbuf, sizeof(tbuf), "%zu", sig->l); - dkim_format_tag(&fb, "l", tbuf); - } - if (sig->t) { - char tbuf[80]; - snprintf(tbuf, sizeof(tbuf), "%lu", (long unsigned) sig->t); - dkim_format_tag(&fb, "t", tbuf); - } - if (sig->x) { - char tbuf[80]; - snprintf(tbuf, sizeof(tbuf), "%lu", (long unsigned) sig->x); - dkim_format_tag(&fb, "x", tbuf); - } - dkim_format_tag_base64(&fb, "bh", (char*)sig->bh); - dkim_format_nl(&fb); - dkim_format_tag_base64(&fb, "b", sig->b ? (char*)sig->b : ""); - - if (mu_stream_err(fb.str)) { - rc = mu_stream_last_error(fb.str); - mu_diag_funcall(MU_DIAG_ERROR, - "dkim_signature_format", NULL, rc); - } else { - rc = 0; - mu_stream_seek(fb.str, 0, MU_SEEK_CUR, &off); - - //FIXME: assert(size < (size_t)~0) - size = off; - text = mu_alloc(size + 1); - - mu_stream_seek(fb.str, 0, MU_SEEK_SET, NULL); - - mu_stream_read(fb.str, text, size, NULL); - text[size] = 0; - *result = text; - } - mu_stream_destroy(&fb.str); - - return rc; -} void dkim_signature_free(struct dkim_signature *sig) @@ -740,20 +635,298 @@ dkim_signature_free(struct dkim_signature *sig) free(sig->q); free(sig->v); } + +/* + * Auxiliary functions for parsing and formatting dkim_signature fields. + * + * Each parser is declared as + * int P(char const *value, void *data) + * Its arguments are: + * value - actual tag value obtained from the header. + * data - pointer to the member of struct dkim_signature. + * The parser returns 0 on success and non-zero on error. It is not supposed + * to emit any diagnostic messages. + * + * Each formatter is declared as + * int F(struct dkim_format_buf *fb, char const *tag, void *data) + * Its arguments are: + * fb - formatting buffer, + * tag - the tag name, + * value - pointer to the member of struct dkim_signature. + */ + +/* General purpose parser for char* (or uint8_t*) fields */ +static int +dkim_tag_char_parser(char const *value, void *data) +{ + char **cptr = data; + *cptr = mu_strdup(value); + return 0; +} + +/* General purpose formatter for char* (or uint8_t*) fields */ +static void +dkim_tag_char_formatter(struct dkim_format_buf *fb, + char const *tag, + void *data) +{ + char **cptr = data; + dkim_format_tag(fb, tag, *cptr); +} + +/* Formatter for the a= tag */ +static void +dkim_tag_a_formatter(struct dkim_format_buf *fb, + char const *tag, + void *data) +{ + char **cptr = data; + dkim_format_tag(fb, tag, *cptr ? *cptr : DKIM_ALGORITHM); +} + +/* Formatter for the q= tag */ +static void +dkim_tag_q_formatter(struct dkim_format_buf *fb, + char const *tag, + void *data) +{ + char **cptr = data; + dkim_format_tag(fb, tag, *cptr ? *cptr : DKIM_QUERY_METHOD); +} + +/* Special formatter for the h= tag, that ensures proper wrapping. */ +static void +dkim_tag_h_formatter(struct dkim_format_buf *fb, + char const *tag, + void *data) +{ + char **cptr = data; + void *save; + char const *hval; + + if (!dkim_format_wrap(fb, 2)) + dkim_format_wrap(fb, strlen(*cptr) + 3); + + mu_stream_write(fb->str, "h=", 2, NULL); + + if ((hval = dkim_header_list_first(*cptr, &save)) != NULL) { + dkim_format_wrap(fb, strlen(hval)); + mu_stream_write(fb->str, hval, strlen(hval), NULL); + while ((hval = dkim_header_list_next(save)) != NULL) { + dkim_format_wrap(fb, strlen(hval) + 1); + mu_stream_write(fb->str, ":", 1, NULL); + mu_stream_write(fb->str, hval, strlen(hval), NULL); + } + } + dkim_header_list_end(save); + dkim_format_wrap(fb, 1); + mu_stream_write(fb->str, ";", 1, NULL); +} + +/* Parser and formatter for the c= tag. */ +static int +dkim_tag_c_parser(char const *value, void *data) +{ + int *canon = data; + char *s; + + if ((canon[0] = dkim_str_to_canon_type(value, &s)) == DKIM_CANON_ERR) + return -1; + if (*s == 0) + canon[1] = canon[0]; + else if (*s != '/' || + (canon[1] = dkim_str_to_canon_type(s + 1, NULL)) == DKIM_CANON_ERR) + return -1; + return 0; +} + +static void +dkim_tag_c_formatter(struct dkim_format_buf *fb, + char const *tag, + void *data) +{ + int *canon = data; + char v[(DKIM_CANON_STRING_MAX+1)*2]; + strcpy(v, dkim_canon_string[canon[0]]); + strcat(v, "/"); + strcat(v, dkim_canon_string[canon[1]]); + dkim_format_tag(fb, tag, v); +} +/* General-purpose parser and formatter for time_t members (t and x) */ static int -str_to_time(char const *str, time_t *pt) +dkim_tag_time_parser(char const *value, void *data) { + time_t *tptr = data; unsigned long n; char *p; errno = 0; - n = strtoul(str, &p, 10); + n = strtoul(value, &p, 10); if (errno || *p) return -1; - *pt = n; + *tptr = n; return 0; } +static void +dkim_tag_time_formatter(struct dkim_format_buf *fb, + char const *tag, + void *data) +{ + time_t *tptr = data; + + if (*tptr) { + char tbuf[80]; + snprintf(tbuf, sizeof(tbuf), "%lu", (long unsigned) *tptr); + dkim_format_tag(fb, tag, tbuf); + } +} + +/* Parser and formatter for the l= tag */ +static int +dkim_tag_l_parser(char const *value, void *data) +{ + size_t *sptr = data; + unsigned long n; + char *endp; + errno = 0; + n = strtoul(value, &endp, 10); + if (errno || *endp) + return -1; + *sptr = n; + return 0; +} + +static void +dkim_tag_l_formatter(struct dkim_format_buf *fb, + char const *tag, + void *data) +{ + size_t *sptr = data; + char tbuf[80]; + if (*sptr != DKIM_LENGTH_ALL) { + snprintf(tbuf, sizeof(tbuf), "%zu", *sptr); + dkim_format_tag(fb, tag, tbuf); + } +} + +/* Formatter for the bh= tag. */ +static void +dkim_tag_bh_formatter(struct dkim_format_buf *fb, + char const *tag, + void *data) +{ + char **sptr = data; + dkim_format_tag_base64(fb, tag, *sptr); +} + +/* Formatter for the b= tag. */ +static void +dkim_tag_b_formatter(struct dkim_format_buf *fb, + char const *tag, + void *data) +{ + char **sptr = data; + dkim_format_nl(fb); + dkim_format_tag_base64(fb, tag, *sptr ? *sptr : ""); +} + +/* Tag definition structure */ +struct dkim_tag_descr { + char *tag; /* Tag name. */ + size_t off; /* Field offset in struct dkim_signature. */ + int (*parser)(char const *, void *); + /* Parser function. */ + void (*formatter)(struct dkim_format_buf *, char const *, void *); + /* Formatter function. */ +}; + +/* Order of entries in this array defines the order in which tags are + formatted. +*/ +static struct dkim_tag_descr tag_descr[] = { + { + "v", + offsetof(struct dkim_signature, v), + dkim_tag_char_parser, + dkim_tag_char_formatter, + }, + { + "a", + offsetof(struct dkim_signature, a), + dkim_tag_char_parser, + dkim_tag_a_formatter + }, + { + "d", + offsetof(struct dkim_signature, d), + dkim_tag_char_parser, + dkim_tag_char_formatter, + }, + { + "s", + offsetof(struct dkim_signature, s), + dkim_tag_char_parser, + dkim_tag_char_formatter, + }, + { + "c", + offsetof(struct dkim_signature, canon), + dkim_tag_c_parser, + dkim_tag_c_formatter + }, + { + "q", + offsetof(struct dkim_signature, q), + dkim_tag_char_parser, + dkim_tag_q_formatter + }, + { + "h", + offsetof(struct dkim_signature, h), + dkim_tag_char_parser, + dkim_tag_h_formatter, + }, + { + "i", + offsetof(struct dkim_signature, i), + dkim_tag_char_parser, + dkim_tag_char_formatter, + }, + { + "l", + offsetof(struct dkim_signature, l), + dkim_tag_l_parser, + dkim_tag_l_formatter + }, + { + "t", + offsetof(struct dkim_signature, t), + dkim_tag_time_parser, + dkim_tag_time_formatter + }, + { + "x", + offsetof(struct dkim_signature, x), + dkim_tag_time_parser, + dkim_tag_time_formatter + }, + { + "bh", + offsetof(struct dkim_signature, bh), + dkim_tag_char_parser, + dkim_tag_bh_formatter + }, + { + "b", + offsetof(struct dkim_signature, b), + dkim_tag_char_parser, + dkim_tag_b_formatter + }, + { NULL } +}; + +/* Table-driven DKIM-Signature parser. */ int dkim_signature_parse(char *str, struct dkim_signature *ret_sig) { @@ -786,6 +959,7 @@ dkim_signature_parse(char *str, struct dkim_signature *ret_sig) sig.canon[0] = sig.canon[1] = DKIM_CANON_SIMPLE; sig.l = DKIM_LENGTH_ALL; for (i = 0; i < ws.ws_wordc; i++) { + struct dkim_tag_descr *tg; char *k = ws.ws_wordv[i]; char *p = strchr(k, '='); if (!p) { @@ -797,60 +971,13 @@ dkim_signature_parse(char *str, struct dkim_signature *ret_sig) mu_rtrim_class(k, MU_CTYPE_BLANK); mu_ltrim_class(p, MU_CTYPE_BLANK); /* Handle known tags */ - if (strcmp(k, "a") == 0) { - sig.a = (char*) mu_strdup(p); - } else if (strcmp(k, "b") == 0) { - sig.b = (uint8_t*) mu_strdup(p); - } else if (strcmp(k, "bh") == 0) { - sig.bh = (uint8_t*) mu_strdup(p); - } else if (strcmp(k, "q") == 0) { - sig.q = mu_strdup(p); - } else if (strcmp(k, "c") == 0) { - char *s; - - if ((sig.canon[0] = dkim_str_to_canon_type(p, &s)) - == DKIM_CANON_ERR) { - rc = -1; - goto end; - } - - if (*s == 0) - sig.canon[1] = sig.canon[0]; - else if (*s != '/' || - (sig.canon[1] = dkim_str_to_canon_type(s + 1, NULL)) == DKIM_CANON_ERR) { - rc = -1; - goto end; - } - } else if (strcmp(k, "d") == 0) { - sig.d = mu_strdup(p); - } else if (strcmp(k, "s") == 0) { - sig.s = mu_strdup(p); - } else if (strcmp(k, "h") == 0) { - sig.h = mu_strdup(p); - } else if (strcmp(k, "i") == 0) { - sig.i = mu_strdup(p); - } else if (strcmp(k, "l") == 0) { - unsigned long n; - char *endp; - errno = 0; - n = strtoul(p, &endp, 10); - if (errno || *endp) { - rc = -1; - goto end; - } - sig.l = n; - } else if (strcmp(k, "t") == 0) { - if (str_to_time(p, &sig.t)) { - rc = -1; - goto end; - } - } else if (strcmp(k, "x") == 0) { - if (str_to_time(p, &sig.x)) { - rc = -1; - goto end; + for (tg = tag_descr; tg->tag; tg++) { + if (strcmp(k, tg->tag) == 0) { + rc = tg->parser(p, (char*)&sig + tg->off); + if (rc) + goto end; + break; } - } else if (strcmp(k, "v") == 0) { - sig.v = mu_strdup(p); } } end: @@ -861,6 +988,62 @@ end: *ret_sig = sig; return rc; } + +/* + * Table-driven DKIM-Signature formatter. + * The CRLF parameter defines what delimiter to use for wrapping. + */ +int +dkim_signature_format(struct dkim_signature *sig, int crlf, char **result) +{ + struct dkim_format_buf fb; + int rc; + static char header_field[] = DKIM_SIGNATURE_HEADER ": "; + mu_off_t off; + size_t size; + char *text; + struct dkim_tag_descr *tg; + + /* Initialize format buffer */ + fb.crlf = crlf; + rc = mu_memory_stream_create(&fb.str, MU_STREAM_RDWR); + if (rc) { + mu_diag_funcall(MU_DIAG_ERROR, + "mu_memory_stream_create", + NULL, rc); + return rc; + } + mu_stream_set_stat(fb.str, + MU_STREAM_STAT_MASK (MU_STREAM_STAT_OUT) | + MU_STREAM_STAT_MASK (MU_STREAM_STAT_OUTLN), + fb.stat); + mu_stream_write(fb.str, header_field, strlen(header_field), NULL); + for (tg = tag_descr; tg->tag; tg++) { + tg->formatter(&fb, tg->tag, ((char*)sig + tg->off)); + } + + if (mu_stream_err(fb.str)) { + rc = mu_stream_last_error(fb.str); + mu_diag_funcall(MU_DIAG_ERROR, + "dkim_signature_format", NULL, rc); + } else { + rc = 0; + mu_stream_seek(fb.str, 0, MU_SEEK_CUR, &off); + + //FIXME: assert(size < (size_t)~0) + size = off; + text = mu_alloc(size + 1); + + mu_stream_seek(fb.str, 0, MU_SEEK_SET, NULL); + + mu_stream_read(fb.str, text, size, NULL); + text[size] = 0; + *result = text; + } + mu_stream_destroy(&fb.str); + + return rc; +} /* Canonicalize the message into a stream. * Arguments: |