1 /*	$OpenBSD: rfc2822.c,v 1.7 2016/02/04 22:35:17 eric Exp $	*/
2 
3 /*
4  * Copyright (c) 2014 Gilles Chehade <gilles@poolp.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/types.h>
20 #include <sys/queue.h>
21 #include <sys/tree.h>
22 
23 #include <ctype.h>
24 #include <errno.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <unistd.h>
29 
30 #include "rfc2822.h"
31 
32 /* default no-op callbacks */
hdr_dflt_cb(const struct rfc2822_header * hdr,void * arg)33 static void hdr_dflt_cb(const struct rfc2822_header *hdr, void *arg) {}
body_dflt_cb(const char * line,void * arg)34 static void body_dflt_cb(const char *line, void *arg) {}
35 
36 static void
header_reset(struct rfc2822_header * hdr)37 header_reset(struct rfc2822_header *hdr)
38 {
39 	struct rfc2822_line	*line;
40 
41 	while ((line = TAILQ_FIRST(&hdr->lines))) {
42 		TAILQ_REMOVE(&hdr->lines, line, next);
43 		free(line);
44 	}
45 }
46 
47 static void
header_callback(struct rfc2822_parser * rp)48 header_callback(struct rfc2822_parser *rp)
49 {
50 	struct rfc2822_hdr_cb		*hdr_cb;
51 	struct rfc2822_hdr_miss_cb	*hdr_miss_cb;
52 
53 	TAILQ_FOREACH(hdr_cb, &rp->hdr_cb, next)
54 	    if (strcasecmp(hdr_cb->name, rp->header.name) == 0) {
55 		    hdr_cb->func(&rp->header, hdr_cb->arg);
56 		    goto end;
57 	    }
58 	rp->hdr_dflt_cb.func(&rp->header, rp->hdr_dflt_cb.arg);
59 
60 end:
61 	TAILQ_FOREACH(hdr_miss_cb, &rp->hdr_miss_cb, next)
62 	    if (strcasecmp(hdr_miss_cb->name, rp->header.name) == 0)
63 		    break;
64 	if (hdr_miss_cb)
65 		TAILQ_REMOVE(&rp->hdr_miss_cb, hdr_miss_cb, next);
66 	free(hdr_miss_cb);
67 	header_reset(&rp->header);
68 	rp->in_hdr = 0;
69 	return;
70 }
71 
72 static void
missing_headers_callback(struct rfc2822_parser * rp)73 missing_headers_callback(struct rfc2822_parser *rp)
74 {
75 	struct rfc2822_hdr_miss_cb	*hdr_miss_cb;
76 
77 	while ((hdr_miss_cb = TAILQ_FIRST(&rp->hdr_miss_cb))) {
78 		hdr_miss_cb->func(hdr_miss_cb->name, hdr_miss_cb->arg);
79 		TAILQ_REMOVE(&rp->hdr_miss_cb, hdr_miss_cb, next);
80 		free(hdr_miss_cb);
81 	}
82 }
83 
84 static void
eoh_callback(struct rfc2822_parser * rp)85 eoh_callback(struct rfc2822_parser *rp)
86 {
87 	if (rp->eoh_cb.func)
88 		rp->eoh_cb.func(rp->eoh_cb.arg);
89 }
90 
91 static void
body_callback(struct rfc2822_parser * rp,const char * line)92 body_callback(struct rfc2822_parser *rp, const char *line)
93 {
94 	rp->body_line_cb.func(line, rp->body_line_cb.arg);
95 }
96 
97 static int
parser_feed_header(struct rfc2822_parser * rp,char * line)98 parser_feed_header(struct rfc2822_parser *rp, char *line)
99 {
100 	struct rfc2822_line	*hdrline;
101 	char			*pos;
102 
103 	/* new header */
104 	if (!isspace(*line) && *line != '\0') {
105 		rp->in_hdr = 1;
106 		if ((pos = strchr(line, ':')) == NULL)
107 			return 0;
108 		memset(rp->header.name, 0, sizeof rp->header.name);
109 		(void)memcpy(rp->header.name, line, pos - line);
110 		if (isspace(*(pos + 1)))
111 			return parser_feed_header(rp, pos + 1);
112 		else {
113 			*pos = ' ';
114 			return parser_feed_header(rp, pos);
115 		}
116 	}
117 
118 	/* continuation */
119 	if (!rp->in_hdr)
120 		return 0;
121 
122 	/* append line to header */
123 	if ((hdrline = calloc(1, sizeof *hdrline)) == NULL)
124 		return -1;
125 	(void)strlcpy(hdrline->buffer, line, sizeof hdrline->buffer);
126 	TAILQ_INSERT_TAIL(&rp->header.lines, hdrline, next);
127 	return 1;
128 }
129 
130 static int
parser_feed_body(struct rfc2822_parser * rp,const char * line)131 parser_feed_body(struct rfc2822_parser *rp, const char *line)
132 {
133 	/* for now, we only support per-line callbacks */
134 	body_callback(rp, line);
135 	return 1;
136 }
137 
138 
139 void
rfc2822_parser_init(struct rfc2822_parser * rp)140 rfc2822_parser_init(struct rfc2822_parser *rp)
141 {
142 	memset(rp, 0, sizeof *rp);
143 	TAILQ_INIT(&rp->hdr_cb);
144 	TAILQ_INIT(&rp->hdr_miss_cb);
145 	TAILQ_INIT(&rp->header.lines);
146 	rfc2822_header_default_callback(rp, hdr_dflt_cb, NULL);
147 	rfc2822_body_callback(rp, body_dflt_cb, NULL);
148 }
149 
150 void
rfc2822_parser_flush(struct rfc2822_parser * rp)151 rfc2822_parser_flush(struct rfc2822_parser *rp)
152 {
153 	if (!rp->in_hdrs)
154 		return;
155 
156 	header_callback(rp);
157 }
158 
159 void
rfc2822_parser_reset(struct rfc2822_parser * rp)160 rfc2822_parser_reset(struct rfc2822_parser *rp)
161 {
162 	header_reset(&rp->header);
163 	rp->in_hdrs = 1;
164 }
165 
166 void
rfc2822_parser_release(struct rfc2822_parser * rp)167 rfc2822_parser_release(struct rfc2822_parser *rp)
168 {
169 	struct rfc2822_hdr_cb		*cb;
170 	struct rfc2822_hdr_miss_cb	*mcb;
171 
172 	rfc2822_parser_reset(rp);
173 	while ((cb = TAILQ_FIRST(&rp->hdr_cb))) {
174 		TAILQ_REMOVE(&rp->hdr_cb, cb, next);
175 		free(cb);
176 	}
177 	while ((mcb = TAILQ_FIRST(&rp->hdr_miss_cb))) {
178 		TAILQ_REMOVE(&rp->hdr_miss_cb, mcb, next);
179 		free(mcb);
180 	}
181 }
182 
183 int
rfc2822_parser_feed(struct rfc2822_parser * rp,const char * line)184 rfc2822_parser_feed(struct rfc2822_parser *rp, const char *line)
185 {
186 	char			buffer[RFC2822_MAX_LINE_SIZE+1];
187 
188 	/* in header and line is not a continuation, execute callback */
189 	if (rp->in_hdr && (*line == '\0' || !isspace(*line)))
190 		header_callback(rp);
191 
192 	/* no longer in headers */
193 	if (*line == '\0') {
194 		if (rp->in_hdrs) {
195 			missing_headers_callback(rp);
196 			eoh_callback(rp);
197 		}
198 		rp->in_hdrs = 0;
199 	}
200 
201 	if (rp->in_hdrs) {
202 		/* line exceeds RFC maximum size requirement */
203 		if (strlcpy(buffer, line, sizeof buffer) >= sizeof buffer)
204 			return 0;
205 		return parser_feed_header(rp, buffer);
206 	}
207 
208 	/* don't enforce line max length on content, too many MUA break */
209 	return parser_feed_body(rp, line);
210 }
211 
212 int
rfc2822_header_callback(struct rfc2822_parser * rp,const char * header,void (* func)(const struct rfc2822_header *,void *),void * arg)213 rfc2822_header_callback(struct rfc2822_parser *rp, const char *header,
214     void (*func)(const struct rfc2822_header *, void *), void *arg)
215 {
216 	struct rfc2822_hdr_cb  *cb;
217 	struct rfc2822_hdr_cb  *cb_tmp;
218 	char			buffer[RFC2822_MAX_LINE_SIZE+1];
219 
220 	/* line exceeds RFC maximum size requirement */
221 	if (strlcpy(buffer, header, sizeof buffer) >= sizeof buffer)
222 		return 0;
223 
224 	TAILQ_FOREACH_SAFE(cb, &rp->hdr_cb, next, cb_tmp) {
225 		if (strcasecmp(cb->name, buffer) == 0) {
226 			TAILQ_REMOVE(&rp->hdr_cb, cb, next);
227 			free(cb);
228 		}
229 	}
230 
231 	if ((cb = calloc(1, sizeof *cb)) == NULL)
232 		return -1;
233 	(void)strlcpy(cb->name, buffer, sizeof cb->name);
234 	cb->func = func;
235 	cb->arg  = arg;
236 	TAILQ_INSERT_TAIL(&rp->hdr_cb, cb, next);
237 	return 1;
238 }
239 
240 int
rfc2822_missing_header_callback(struct rfc2822_parser * rp,const char * header,void (* func)(const char *,void *),void * arg)241 rfc2822_missing_header_callback(struct rfc2822_parser *rp, const char *header,
242     void (*func)(const char *, void *), void *arg)
243 {
244 	struct rfc2822_hdr_miss_cb  *cb;
245 	struct rfc2822_hdr_miss_cb  *cb_tmp;
246 	char			buffer[RFC2822_MAX_LINE_SIZE+1];
247 
248 	/* line exceeds RFC maximum size requirement */
249 	if (strlcpy(buffer, header, sizeof buffer) >= sizeof buffer)
250 		return 0;
251 
252 	TAILQ_FOREACH_SAFE(cb, &rp->hdr_miss_cb, next, cb_tmp) {
253 		if (strcasecmp(cb->name, buffer) == 0) {
254 			TAILQ_REMOVE(&rp->hdr_miss_cb, cb, next);
255 			free(cb);
256 		}
257 	}
258 
259 	if ((cb = calloc(1, sizeof *cb)) == NULL)
260 		return -1;
261 	(void)strlcpy(cb->name, buffer, sizeof cb->name);
262 	cb->func = func;
263 	cb->arg  = arg;
264 	TAILQ_INSERT_TAIL(&rp->hdr_miss_cb, cb, next);
265 	return 1;
266 }
267 
268 void
rfc2822_header_default_callback(struct rfc2822_parser * rp,void (* func)(const struct rfc2822_header *,void *),void * arg)269 rfc2822_header_default_callback(struct rfc2822_parser *rp,
270     void (*func)(const struct rfc2822_header *, void *), void *arg)
271 {
272 	struct rfc2822_hdr_cb	*cb;
273 
274 	cb = &rp->hdr_dflt_cb;
275 	cb->func = func;
276 	cb->arg  = arg;
277 }
278 
279 void
rfc2822_body_callback(struct rfc2822_parser * rp,void (* func)(const char *,void *),void * arg)280 rfc2822_body_callback(struct rfc2822_parser *rp,
281     void (*func)(const char *, void *), void *arg)
282 {
283 	struct rfc2822_line_cb	*cb;
284 
285 	cb = &rp->body_line_cb;
286 	cb->func = func;
287 	cb->arg  = arg;
288 }
289 
290 void
rfc2822_eoh_callback(struct rfc2822_parser * rp,void (* func)(void *),void * arg)291 rfc2822_eoh_callback(struct rfc2822_parser *rp,
292     void (*func)(void *), void *arg)
293 {
294 	struct rfc2822_eoh_cb	*cb;
295 
296 	cb = &rp->eoh_cb;
297 	cb->func = func;
298 	cb->arg  = arg;
299 }
300 
301