masqmail
view src/parse.c @ 281:ea5f86e0a81c
modes are now enforced exclusive
Other MTAs (exim, postfix) are more relaxing, but as combinations
of exclusive modes are senseless we behave more obvious if we
fail early. This makes understanding the behavior easier too.
author | markus schnalke <meillo@marmaro.de> |
---|---|
date | Tue, 07 Dec 2010 14:04:56 -0300 |
parents | 00724782b6c9 |
children | 41958685480d |
line source
1 /* MasqMail
2 Copyright (C) 1999-2001 Oliver Kurth
3 Copyright (C) 2010 markus schnalke <meillo@marmaro.de>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 */
20 #ifndef PARSE_TEST
21 #include "masqmail.h"
22 #endif
24 /* This is really dangerous. I hope that I was careful enough,
25 but maybe there is some malformed address possible that causes
26 this to segfault or be caught in endless loops.
28 If you find something like that, PLEASE mail the string to me
29 (no matter how idiotic it is), so that I can debug that.
30 Those things really should not happen.
31 */
33 static gchar *specials = "()<>@,;:\\\".[]`";
35 char *parse_error = NULL;
37 static gchar*
38 skip_comment(gchar * p)
39 {
41 #ifdef PARSE_TEST
42 g_print("skip_comment: %s\n", p);
43 #endif
45 p++;
46 while (*p && *p != ')') {
47 p++;
48 if (*p == '(') {
49 p = skip_comment(p);
50 }
51 }
52 p++;
54 return p;
55 }
57 static gboolean
58 read_word(gchar * p, gchar ** b, gchar ** e)
59 {
60 #ifdef PARSE_TEST
61 g_print("read_word: %s\n", p);
62 #endif
63 /* eat leading spaces */
64 while (*p && isspace(*p)) {
65 p++;
66 }
68 *b = p;
69 /* b = &p; */
70 if (*p == '\"') {
71 /* quoted-string */
72 p++;
73 while (*p && (*p != '\"')) {
74 p++;
75 }
76 p++;
77 } else {
78 /* atom */
79 while (*p && !strchr(specials, *p) && !iscntrl(*p) && !isspace(*p)) {
80 p++;
81 }
82 }
83 *e = p;
84 return TRUE;
85 }
87 static gboolean
88 read_word_with_dots(gchar * p, gchar ** b, gchar ** e)
89 {
90 gchar *b0 = p;
92 #ifdef PARSE_TEST
93 g_print("read_word_with_dots: %s\n", p);
94 #endif
95 while (TRUE) {
96 if (!read_word(p, b, e)) {
97 return FALSE;
98 }
99 p = *e;
100 if (*p != '.') {
101 break;
102 }
103 p++;
104 }
105 *b = b0;
106 *e = p;
107 return TRUE;
108 }
110 static gboolean
111 read_domain(gchar * p, gchar ** b, gchar ** e)
112 {
113 #ifdef PARSE_TEST
114 g_print("read_domain: %s\n", p);
115 #endif
116 *b = p;
117 if (*p != '[') {
118 while (isalnum(*p) || (*p == '-') || (*p == '.')) {
119 p++;
120 }
121 } else {
122 p++;
123 while (isalpha(*p) || (*p == '.')) {
124 p++;
125 }
126 if (*p != ']') {
127 parse_error = g_strdup_printf("']' expected at end of literal address %s", *b);
128 return FALSE;
129 }
130 p++;
131 }
132 *e = p;
133 return TRUE;
134 }
136 gboolean
137 parse_address_rfc822(gchar* string, gchar** local_begin, gchar** local_end, gchar** domain_begin,
138 gchar** domain_end, gchar** address_end)
139 {
140 gint angle_brackets = 0;
142 gchar *p = string;
143 gchar *b, *e;
145 *local_begin = *local_end = NULL;
146 *domain_begin = *domain_end = NULL;
148 /* might be some memory left from previous call: */
149 if (parse_error) {
150 g_free(parse_error);
151 parse_error = NULL;
152 }
154 /* leading spaces and angle brackets */
155 while (*p && (isspace(*p) || (*p == '<'))) {
156 if (*p == '<') {
157 angle_brackets++;
158 }
159 p++;
160 }
162 if (!*p) {
163 return FALSE;
164 }
166 while (TRUE) {
167 if (!read_word_with_dots(p, &b, &e)) {
168 return FALSE;
169 }
171 p = e;
172 #ifdef PARSE_TEST
173 g_print("after read_word_with_dots: %s\n", p);
174 #endif
175 /* eat white spaces and comments */
176 while ((*p && (isspace(*p))) || (*p == '(')) {
177 if (*p == '(') {
178 if (!(p = skip_comment(p))) {
179 parse_error = g_strdup("missing right bracket ')'");
180 return FALSE;
181 }
182 } else {
183 p++;
184 }
185 }
186 /* we now have a non-space char that is not
187 the beginning of a comment */
189 if (*p == '@' || *p == ',') {
190 /* the last word was the local_part of an addr-spec */
191 *local_begin = b;
192 *local_end = e;
193 #ifdef PARSE_TEST
194 g_print("found local part: %s\n", *local_begin);
195 #endif
196 if (*p == '@') {
197 p++; /* skip @ */
198 /* now the domain */
199 if (!read_domain(p, &b, &e)) {
200 return FALSE;
201 }
202 p = e;
203 *domain_begin = b;
204 *domain_end = e;
205 } else {
206 /* unqualified? */
207 /* something like `To: alice, bob' with -t */
208 *domain_begin = *domain_end = NULL;
209 }
210 break;
212 } else if (*p == '<') {
213 /* addr-spec follows */
214 while (isspace(*p) || (*p == '<')) {
215 if (*p == '<') {
216 angle_brackets++;
217 }
218 p++;
219 }
220 if (!read_word_with_dots(p, &b, &e)) {
221 return FALSE;
222 }
223 p = e;
224 *local_begin = b;
225 *local_end = e;
226 #ifdef PARSE_TEST
227 g_print("found local part: %s\n", *local_begin);
228 #endif
229 if (*p == '@') {
230 p++;
231 if (!read_domain(p, &b, &e)) {
232 return FALSE;
233 }
234 p = e;
235 *domain_begin = b;
236 *domain_end = e;
237 } else {
238 /* may be unqualified address */
239 *domain_begin = *domain_end = NULL;
240 }
241 break;
243 } else if (!*p || *p == '>') {
244 *local_begin = b;
245 *local_end = e;
246 #ifdef PARSE_TEST
247 g_print("found local part: %s\n", *local_begin);
248 #endif
249 *domain_begin = *domain_end = NULL;
250 break;
252 } else if (strchr(specials, *p) || iscntrl(*p) || isspace(*p)) {
253 parse_error = g_strdup_printf("unexpected character: %c", *p);
254 #ifdef PARSE_TEST
255 g_print("unexpected character: %c", *p);
256 #endif
257 return FALSE;
258 }
259 }
261 /* trailing spaces and angle brackets */
262 #ifdef PARSE_TEST
263 g_print("down counting trailing '>'\n");
264 #endif
265 while (*p && (isspace(*p) || (*p == '>'))) {
266 if (*p == '>') {
267 angle_brackets--;
268 }
269 p++;
270 }
272 *address_end = p;
274 if (angle_brackets > 0) {
275 parse_error = g_strdup("missing '>' at end of string");
276 return FALSE;
277 } else if (angle_brackets < 0) {
278 parse_error = g_strdup("superfluous '>' at end of string");
279 return FALSE;
280 }
282 /* we successfully parsed the address */
283 return TRUE;
284 }
286 gboolean
287 parse_address_rfc821(gchar* string, gchar** local_begin, gchar** local_end, gchar** domain_begin,
288 gchar** domain_end, gchar** address_end)
289 {
290 gint angle_brackets = 0;
292 gchar *p = string;
293 gchar *b, *e;
295 *local_begin = *local_end = NULL;
296 *domain_begin = *domain_end = NULL;
298 /* might be some memory left from previous call: */
299 if (parse_error != NULL) {
300 g_free(parse_error);
301 parse_error = NULL;
302 }
304 /* leading spaces and angle brackets */
305 while (*p && (isspace(*p) || (*p == '<'))) {
306 if (*p == '<') {
307 angle_brackets++;
308 }
309 p++;
310 }
312 if (!*p) {
313 return FALSE;
314 }
316 while (TRUE) {
317 if (!read_word_with_dots(p, &b, &e)) {
318 return FALSE;
319 }
321 p = e;
322 #ifdef PARSE_TEST
323 g_print("after read_word_with_dots: %s\n", p);
324 #endif
325 *local_begin = b;
326 *local_end = e;
327 #ifdef PARSE_TEST
328 g_print("found local part: %s\n", *local_begin);
329 g_print("local_end = %s\n", *local_end);
330 #endif
331 if (!(*p) || isspace(*p) || (*p == '>')) {
332 /* unqualified ? */
333 domain_begin = domain_end = NULL;
334 break;
335 } else if (*p == '@') {
336 p++;
337 if (read_domain(p, &b, &e)) {
338 p = e;
339 *domain_begin = b;
340 *domain_end = e;
341 }
342 break;
343 } else {
344 parse_error = g_strdup_printf ("unexpected character after local part '%c'", *p);
345 return FALSE;
346 }
347 }
349 /* trailing spaces and angle brackets */
350 #ifdef PARSE_TEST
351 g_print("down counting trailing '>'\n");
352 #endif
353 while (*p && (isspace(*p) || (*p == '>'))) {
354 if (*p == '>') {
355 angle_brackets--;
356 }
357 p++;
358 }
359 *address_end = p;
361 if (angle_brackets > 0) {
362 parse_error = g_strdup("missing '>' at end of string");
363 return FALSE;
364 } else if (angle_brackets < 0) {
365 parse_error = g_strdup("superfluous '>' at end of string");
366 return FALSE;
367 }
369 /* we successfully parsed the address */
370 return TRUE;
371 }
373 /*
374 allocate address, reading from string.
375 On failure, returns NULL.
376 after call, end contains a pointer to the end of the parsed string
377 end may be NULL, if we are not interested.
379 parses both rfc 821 and rfc 822 addresses, depending on flag is_rfc821
380 */
381 address*
382 _create_address(gchar * string, gchar ** end, gboolean is_rfc821)
383 {
384 gchar *loc_beg, *loc_end;
385 gchar *dom_beg, *dom_end;
386 gchar *addr_end;
387 gboolean ret;
389 /* TODO: what about (string == NULL)? */
390 if (string && (string[0] == '\0')) {
391 address *addr = g_malloc(sizeof(address));
392 addr->address = g_strdup("");
393 addr->local_part = g_strdup("");
394 /* 'NULL' address (failure notice),
395 "" makes sure it will not be qualified with a hostname */
396 addr->domain = g_strdup("");
397 return addr;
398 }
400 if (is_rfc821) {
401 ret = parse_address_rfc821(string, &loc_beg, &loc_end, &dom_beg, &dom_end, &addr_end);
402 } else {
403 ret = parse_address_rfc822(string, &loc_beg, &loc_end, &dom_beg, &dom_end, &addr_end);
404 }
405 if (!ret) {
406 return NULL;
407 }
409 address *addr = g_malloc(sizeof(address));
410 gchar *p = addr_end;
412 memset(addr, 0, sizeof(address));
414 if (loc_beg[0] == '|') {
415 parse_error = g_strdup("no pipe allowed for RFC 822/821 address");
416 return NULL;
417 }
419 while (*p && (*p != ',')) {
420 p++;
421 }
422 addr->address = g_strndup(string, p - string);
423 addr->local_part = g_strndup(loc_beg, loc_end - loc_beg);
425 #ifdef PARSE_TEST
426 g_print("addr->local_part = %s\n", addr->local_part);
427 #endif
429 if (dom_beg != NULL) {
430 addr->domain = g_strndup(dom_beg, dom_end - dom_beg);
431 } else if (addr->local_part[0] == '\0') {
432 /* 'NULL' address (failure notice),
433 "" makes sure it will not be qualified with a hostname */
434 addr->domain = g_strdup("");
435 } else {
436 addr->domain = NULL;
437 }
439 if (end) {
440 *end = p;
441 }
443 #ifndef PARSE_TEST
444 addr_unmark_delivered(addr);
445 #endif
447 return addr;
448 }
450 address*
451 create_address_rfc822(gchar * string, gchar ** end)
452 {
453 return _create_address(string, end, FALSE);
454 }
456 address*
457 create_address_rfc821(gchar * string, gchar ** end)
458 {
459 return _create_address(string, end, TRUE);
460 }
462 GList*
463 addr_list_append_rfc822(GList * addr_list, gchar * string, gchar * domain)
464 {
465 gchar *p = string;
466 gchar *end;
468 while (*p) {
469 #ifdef PARSE_TEST
470 g_print("string: %s\n", p);
471 #endif
473 address *addr = _create_address(p, &end, FALSE);
474 if (!addr) {
475 break;
476 }
478 #ifdef PARSE_TEST
479 g_print("addr: %s (%s<@>%s)", addr->address, addr->local_part, addr->domain);
480 #endif
481 if (domain && !addr->domain) {
482 addr->domain = g_strdup(domain);
483 }
484 #ifdef PARSE_TEST
485 g_print(" (%s<@>%s)\n", addr->local_part, addr->domain);
486 #endif
488 addr_list = g_list_append(addr_list, addr);
489 p = end;
491 while (*p == ',' || isspace(*p)) {
492 p++;
493 }
494 }
495 return addr_list;
496 }