diff options
Diffstat (limited to 'smtpd/rfc5322.c')
-rw-r--r-- | smtpd/rfc5322.c | 266 |
1 files changed, 266 insertions, 0 deletions
diff --git a/smtpd/rfc5322.c b/smtpd/rfc5322.c new file mode 100644 index 00000000..0af66772 --- /dev/null +++ b/smtpd/rfc5322.c @@ -0,0 +1,266 @@ +/* $OpenBSD: rfc5322.c,v 1.2 2018/10/24 18:59:29 gilles Exp $ */ + +/* + * Copyright (c) 2018 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <ctype.h> +#include <errno.h> +#include <limits.h> +#include <stdlib.h> +#include <string.h> + +#include "rfc5322.h" + +struct buf { + char *buf; + size_t bufsz; + size_t buflen; + size_t bufmax; +}; + +static int buf_alloc(struct buf *, size_t); +static int buf_grow(struct buf *, size_t); +static int buf_cat(struct buf *, const char *); + +struct rfc5322_parser { + const char *line; + int state; /* last parser state */ + int next; /* parser needs data */ + int unfold; + const char *currhdr; + struct buf hdr; + struct buf val; +}; + +struct rfc5322_parser * +rfc5322_parser_new(void) +{ + struct rfc5322_parser *parser; + + parser = calloc(1, sizeof(*parser)); + if (parser == NULL) + return NULL; + + rfc5322_clear(parser); + parser->hdr.bufmax = 1024; + parser->val.bufmax = 65536; + + return parser; +} + +void +rfc5322_free(struct rfc5322_parser *parser) +{ + free(parser->hdr.buf); + free(parser->val.buf); + free(parser); +} + +void +rfc5322_clear(struct rfc5322_parser *parser) +{ + parser->line = NULL; + parser->state = RFC5322_NONE; + parser->next = 0; + parser->hdr.buflen = 0; + parser->val.buflen = 0; +} + +int +rfc5322_push(struct rfc5322_parser *parser, const char *line) +{ + if (parser->line) { + errno = EALREADY; + return -1; + } + + parser->line = line; + parser->next = 0; + + return 0; +} + +int +rfc5322_unfold_header(struct rfc5322_parser *parser) +{ + if (parser->unfold) { + errno = EALREADY; + return -1; + } + + if (parser->currhdr == NULL) { + errno = EOPNOTSUPP; + return -1; + } + + if (buf_cat(&parser->val, parser->currhdr) == -1) + return -1; + + parser->currhdr = NULL; + parser->unfold = 1; + + return 0; +} + +static int +_rfc5322_next(struct rfc5322_parser *parser, struct rfc5322_result *res) +{ + size_t len; + const char *pos, *line; + + line = parser->line; + + switch(parser->state) { + + case RFC5322_HEADER_START: + case RFC5322_HEADER_CONT: + res->hdr = parser->hdr.buf; + + if (line && (line[0] == ' ' || line[0] == '\t')) { + parser->line = NULL; + parser->next = 1; + if (parser->unfold) { + if (buf_cat(&parser->val, "\n") == -1 || + buf_cat(&parser->val, line) == -1) + return -1; + } + res->value = line; + return RFC5322_HEADER_CONT; + } + + if (parser->unfold) { + parser->val.buflen = 0; + parser->unfold = 0; + res->value = parser->val.buf; + } + return RFC5322_HEADER_END; + + case RFC5322_NONE: + case RFC5322_HEADER_END: + if (line && (pos = strchr(line, ':'))) { + len = pos - line; + if (buf_grow(&parser->hdr, len + 1) == -1) + return -1; + (void)memcpy(parser->hdr.buf, line, len); + parser->hdr.buf[len] = '\0'; + parser->hdr.buflen = len + 1; + parser->line = NULL; + parser->next = 1; + parser->currhdr = pos + 1; + res->hdr = parser->hdr.buf; + res->value = pos + 1; + return RFC5322_HEADER_START; + } + + return RFC5322_END_OF_HEADERS; + + case RFC5322_END_OF_HEADERS: + if (line == NULL) + return RFC5322_END_OF_MESSAGE; + + if (line[0] == '\0') { + parser->line = NULL; + parser->next = 1; + res->value = line; + return RFC5322_BODY_START; + } + + errno = EINVAL; + return -1; + + case RFC5322_BODY_START: + case RFC5322_BODY: + if (line == NULL) + return RFC5322_END_OF_MESSAGE; + + parser->line = NULL; + parser->next = 1; + res->value = line; + return RFC5322_BODY; + + case RFC5322_END_OF_MESSAGE: + errno = ENOMSG; + return -1; + + default: + errno = EINVAL; + return -1; + } +} + +int +rfc5322_next(struct rfc5322_parser *parser, struct rfc5322_result *res) +{ + memset(res, 0, sizeof(*res)); + + if (parser->next) + return RFC5322_NONE; + + return (parser->state = _rfc5322_next(parser, res)); +} + +static int +buf_alloc(struct buf *b, size_t need) +{ + char *buf; + size_t alloc; + + if (b->buf && b->bufsz >= need) + return 0; + + if (need >= b->bufmax) { + errno = ERANGE; + return -1; + } + +#define N 256 + alloc = N * (need / N) + ((need % N) ? N : 0); +#undef N + buf = reallocarray(b->buf, alloc, 1); + if (buf == NULL) + return -1; + + b->buf = buf; + b->bufsz = alloc; + + return 0; +} + +static int +buf_grow(struct buf *b, size_t sz) +{ + if (SIZE_T_MAX - b->buflen <= sz) { + errno = ERANGE; + return -1; + } + + return buf_alloc(b, b->buflen + sz); +} + +static int +buf_cat(struct buf *b, const char *s) +{ + size_t len = strlen(s); + + if (buf_grow(b, len + 1) == -1) + return -1; + + (void)memmove(b->buf + b->buflen, s, len + 1); + b->buflen += len; + return 0; +} |