/* This file is part of Mailfromd. Copyright (C) 2020 Sergey Poznyakoff This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include /* * This file defines a mailutils filter implementing the two DKIM * canonicalization algrorithms: "simple" and "relaxed" (see * RFC 6376, 3.4. "Canonicalization" (page 13). * * The filter reads a LF-delimited message stream from its input * and outpus a canonicalized message, with header and body parts * processed using the selected algorithm. * * For further processing the output stream must be passed through a * CRLF encoder. * * The filter is governed by the following structure: */ struct encoder_state { int canon[2]; /* Canonicalization algorithm. 0 - for headers, 1 - for body. See the DKIM_CANON_ constants in dkim.h */ int state; /* Encoder state (see below) */ size_t nlcount; /* Number of contiguous empty lines for "relaxed" body algorithm. */ }; /* Enoder states: */ enum { /* Header "simple" canonicalization states */ HS_INIT, HS_NL, /* Header "relaxed" canonicalization states */ HR_NAME, HR_SPACE, HR_COLON, HR_VALUE, HR_WS, HR_NL, /* Delimiter */ ST_DELIM, /* Body "simple" canonicalization states */ BS_INIT, BS_NL, /* Body "relaxed" canonicalization states */ BR_INIT, BR_WS, BR_NL }; /* Initial states states */ static int init_state[][2] = { { HS_INIT, HR_NAME }, { BS_INIT, BR_INIT } }; static enum mu_filter_result dkim_canonicalizer(void *xd, enum mu_filter_command cmd, struct mu_filter_io *iobuf) { struct encoder_state *encoder = xd; const char *iptr, *iendptr; char *optr, *oendptr; switch (cmd) { case mu_filter_init: encoder->state = init_state[0][encoder->canon[0]]; case mu_filter_done: return mu_filter_ok; default: break; } iptr = iobuf->input; iendptr = iptr + iobuf->isize; optr = iobuf->output; oendptr = optr + iobuf->osize; while (iptr < iendptr && optr < oendptr) { switch (encoder->state) { /* Header "simple" canonicalization */ case HS_INIT: if (*iptr == '\n') encoder->state = HS_NL; else *optr++ = *iptr; iptr++; break; case HS_NL: if (*iptr == '\n') { encoder->state = ST_DELIM; } else { *optr++ = '\n'; encoder->state = HS_INIT; } break; /* Header "relaxed" canonicalization */ case HR_NAME: if (mu_isblank(*iptr)) { iptr++; encoder->state = HR_SPACE; } else if (*iptr == ':') { *optr++ = *iptr++; encoder->state = HR_COLON; } else if (mu_isheadr(*iptr) || /* * Work around the bug in mailutils 3.9: * the MU_CTYPE_HEADR class did not include * underscore. */ *iptr == '_') { *optr++ = mu_tolower(*iptr++); } else { iobuf->errcode = MU_ERR_USER0; return mu_filter_failure; } break; case HR_SPACE: if (mu_isblank(*iptr)) iptr++; else if (*iptr == ':') { *optr++ = *iptr++; encoder->state = HR_COLON; } else { iobuf->errcode = MU_ERR_USER0; return mu_filter_failure; } break; case HR_COLON: if (mu_isblank(*iptr)) iptr++; else encoder->state = HR_VALUE; break; case HR_VALUE: if (mu_isblank(*iptr)) { iptr++; encoder->state = HR_WS; } else if (*iptr == '\n') { iptr++; encoder->state = HR_NL; } else *optr++ = *iptr++; break; case HR_WS: if (mu_isblank(*iptr)) iptr++; else if (*iptr == '\n') { iptr++; encoder->state = HR_NL; } else { *optr++ = ' '; encoder->state = HR_VALUE; } break; case HR_NL: if (*iptr == '\n') { encoder->state = ST_DELIM; } else if (mu_isblank(*iptr)) { iptr++; encoder->state = HR_WS; } else { *optr++ = '\n'; encoder->state = HR_NAME; } break; /* Delimiter between header and body */ case ST_DELIM: if (oendptr - optr < 2) goto end; iptr++; *optr++ = '\n'; *optr++ = '\n'; encoder->state = init_state[1][encoder->canon[1]]; break; /* Body "simple" canonicalization */ case BS_INIT: if (*iptr == '\n') { iptr++; encoder->nlcount++; encoder->state = BS_NL; } else *optr++ = *iptr++; break; case BS_NL: if (*iptr == '\n') { iptr++; encoder->nlcount++; } else { for (; encoder->nlcount; encoder->nlcount--) { if (optr == oendptr) goto end; *optr++ = '\n'; } encoder->state = BS_INIT; } break; /* Body "relaxed" canonicalization */ case BR_INIT: if (*iptr == '\n') { encoder->nlcount++; encoder->state = BR_NL; } else if (mu_isblank (*iptr)) encoder->state = BR_WS; else *optr++ = *iptr; iptr++; break; case BR_WS: if (*iptr == '\n') { iptr++; encoder->nlcount++; encoder->state = BR_NL; } else if (!mu_isblank(*iptr)) { *optr++ = ' '; encoder->state = BR_INIT; } else { iptr++; } break; case BR_NL: if (*iptr == '\n') { iptr++; encoder->nlcount++; } else { for (; encoder->nlcount; encoder->nlcount--) { if (optr == oendptr) goto end; *optr++ = '\n'; } encoder->state = BR_INIT; } break; } } if (cmd == mu_filter_lastbuf && iptr == iendptr && encoder->state >= BS_INIT) { if (oendptr == optr) { iobuf->osize++; return mu_filter_moreoutput; } *optr++ = '\n'; } end: iobuf->isize = iptr - iobuf->input; iobuf->osize = optr - iobuf->output; return mu_filter_ok; } /* * Create a message canonicalizer. Arguments: * * pstream return pointer * stream input stream * canon_header header canonicalization algorithm * canon_body body canonicalization algorithm * * Return value: mailutils error code. */ int dkim_canonicalizer_create(mu_stream_t *pstream, mu_stream_t stream, int canon_header, int canon_body, int flags) { struct encoder_state *encoder = malloc(sizeof (*encoder)); if (!encoder) return ENOMEM; memset(encoder, 0, sizeof(*encoder)); encoder->canon[0] = canon_header; encoder->canon[1] = canon_body; return mu_filter_stream_create(pstream, stream, MU_FILTER_ENCODE, dkim_canonicalizer, encoder, flags); } #if 0 /* * The canonicalizer can be registered as a regular mailutils filter. * If such approach is ever needed, uncomment this block. */ static int alloc_state(void **pret, int mode, int argc, const char **argv) { struct encoder_state *encoder; switch (mode) { case MU_FILTER_ENCODE: encoder = malloc(sizeof(*encoder)); if (!encoder) return ENOMEM; memset(encoder, 0, sizeof(*encoder)); *pret = encoder; break; case MU_FILTER_DECODE:; } return 0; } static struct _mu_filter_record dkim_canonicalize_filter_s = { "DKIM", alloc_state, dkim_canonicalizer, NULL }; mu_filter_record_t dkim_canonicalize_filter = &dkim_canonicalize_filter_s; #endif