masqmail

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