meillo@0: /* MasqMail meillo@0: Copyright (C) 1999-2001 Oliver Kurth meillo@271: Copyright (C) 2010 markus schnalke meillo@0: meillo@0: This program is free software; you can redistribute it and/or modify meillo@0: it under the terms of the GNU General Public License as published by meillo@0: the Free Software Foundation; either version 2 of the License, or meillo@0: (at your option) any later version. meillo@0: meillo@0: This program is distributed in the hope that it will be useful, meillo@0: but WITHOUT ANY WARRANTY; without even the implied warranty of meillo@0: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the meillo@0: GNU General Public License for more details. meillo@0: meillo@0: You should have received a copy of the GNU General Public License meillo@0: along with this program; if not, write to the Free Software meillo@0: Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. meillo@0: */ meillo@0: meillo@0: #ifndef PARSE_TEST meillo@0: #include "masqmail.h" meillo@0: #endif meillo@0: meillo@0: /* This is really dangerous. I hope that I was careful enough, meillo@0: but maybe there is some malformed address possible that causes meillo@0: this to segfault or be caught in endless loops. meillo@0: meillo@0: If you find something like that, PLEASE mail the string to me meillo@0: (no matter how idiotic it is), so that I can debug that. meillo@0: Those things really should not happen. meillo@0: */ meillo@0: meillo@0: static gchar *specials = "()<>@,;:\\\".[]`"; meillo@0: meillo@0: char *parse_error = NULL; meillo@0: meillo@10: static gchar* meillo@10: skip_comment(gchar * p) meillo@0: { meillo@0: meillo@0: #ifdef PARSE_TEST meillo@10: g_print("skip_comment: %s\n", p); meillo@0: #endif meillo@0: meillo@10: p++; meillo@10: while (*p && *p != ')') { meillo@10: p++; meillo@271: if (*p == '(') { meillo@10: p = skip_comment(p); meillo@271: } meillo@10: } meillo@10: p++; meillo@0: meillo@10: return p; meillo@0: } meillo@0: meillo@10: static gboolean meillo@10: read_word(gchar * p, gchar ** b, gchar ** e) meillo@0: { meillo@0: #ifdef PARSE_TEST meillo@10: g_print("read_word: %s\n", p); meillo@0: #endif meillo@10: /* eat leading spaces */ meillo@271: while (*p && isspace(*p)) { meillo@10: p++; meillo@271: } meillo@10: meillo@10: *b = p; meillo@10: /* b = &p; */ meillo@10: if (*p == '\"') { meillo@10: /* quoted-string */ meillo@10: p++; meillo@271: while (*p && (*p != '\"')) { meillo@10: p++; meillo@271: } meillo@10: p++; meillo@10: } else { meillo@10: /* atom */ meillo@271: while (*p && !strchr(specials, *p) && !iscntrl(*p) && !isspace(*p)) { meillo@10: p++; meillo@271: } meillo@10: } meillo@10: *e = p; meillo@10: return TRUE; meillo@0: } meillo@0: meillo@10: static gboolean meillo@10: read_word_with_dots(gchar * p, gchar ** b, gchar ** e) meillo@0: { meillo@10: gchar *b0 = p; meillo@0: meillo@0: #ifdef PARSE_TEST meillo@10: g_print("read_word_with_dots: %s\n", p); meillo@0: #endif meillo@10: while (TRUE) { meillo@271: if (!read_word(p, b, e)) { meillo@10: return FALSE; meillo@271: } meillo@10: p = *e; meillo@271: if (*p != '.') { meillo@10: break; meillo@271: } meillo@10: p++; meillo@10: } meillo@10: *b = b0; meillo@10: *e = p; meillo@10: return TRUE; meillo@0: } meillo@0: meillo@10: static gboolean meillo@10: read_domain(gchar * p, gchar ** b, gchar ** e) meillo@0: { meillo@0: #ifdef PARSE_TEST meillo@10: g_print("read_domain: %s\n", p); meillo@0: #endif meillo@10: *b = p; meillo@10: if (*p != '[') { meillo@271: while (isalnum(*p) || (*p == '-') || (*p == '.')) { meillo@10: p++; meillo@271: } meillo@10: } else { meillo@10: p++; meillo@271: while (isalpha(*p) || (*p == '.')) { meillo@10: p++; meillo@271: } meillo@10: if (*p != ']') { meillo@10: parse_error = g_strdup_printf("']' expected at end of literal address %s", *b); meillo@10: return FALSE; meillo@10: } meillo@10: p++; meillo@10: } meillo@10: *e = p; meillo@10: return TRUE; meillo@0: } meillo@0: meillo@10: gboolean meillo@15: parse_address_rfc822(gchar* string, gchar** local_begin, gchar** local_end, gchar** domain_begin, meillo@15: gchar** domain_end, gchar** address_end) meillo@0: { meillo@10: gint angle_brackets = 0; meillo@0: meillo@10: gchar *p = string; meillo@10: gchar *b, *e; meillo@0: meillo@10: *local_begin = *local_end = NULL; meillo@10: *domain_begin = *domain_end = NULL; meillo@0: meillo@10: /* might be some memory left from previous call: */ meillo@273: if (parse_error) { meillo@10: g_free(parse_error); meillo@10: parse_error = NULL; meillo@10: } meillo@0: meillo@10: /* leading spaces and angle brackets */ meillo@10: while (*p && (isspace(*p) || (*p == '<'))) { meillo@271: if (*p == '<') { meillo@10: angle_brackets++; meillo@271: } meillo@10: p++; meillo@10: } meillo@10: meillo@271: if (!*p) { meillo@271: return FALSE; meillo@271: } meillo@271: meillo@271: while (TRUE) { meillo@271: if (!read_word_with_dots(p, &b, &e)) { meillo@271: return FALSE; meillo@271: } meillo@271: meillo@271: p = e; meillo@0: #ifdef PARSE_TEST meillo@271: g_print("after read_word_with_dots: %s\n", p); meillo@0: #endif meillo@271: /* eat white spaces and comments */ meillo@271: while ((*p && (isspace(*p))) || (*p == '(')) { meillo@271: if (*p == '(') { meillo@271: if (!(p = skip_comment(p))) { meillo@271: parse_error = g_strdup("missing right bracket ')'"); meillo@10: return FALSE; meillo@10: } meillo@271: } else { meillo@271: p++; meillo@271: } meillo@271: } meillo@271: /* we now have a non-space char that is not meillo@271: the beginning of a comment */ meillo@271: meillo@271: if (*p == '@') { meillo@271: /* the last word was the local_part of an addr-spec */ meillo@271: *local_begin = b; meillo@271: *local_end = e; meillo@271: #ifdef PARSE_TEST meillo@271: g_print("found local part: %s\n", *local_begin); meillo@271: #endif meillo@271: if (*p == '@') { meillo@271: p++; /* skip @ */ meillo@271: /* now the domain */ meillo@271: if (!read_domain(p, &b, &e)) { meillo@271: return FALSE; meillo@271: } meillo@271: p = e; meillo@271: *domain_begin = b; meillo@271: *domain_end = e; meillo@271: } else { meillo@271: /* unqualified? */ meillo@271: *domain_begin = *domain_end = NULL; meillo@271: } meillo@271: break; meillo@271: meillo@271: } else if (*p == '<') { meillo@271: /* addr-spec follows */ meillo@271: while (isspace(*p) || (*p == '<')) { meillo@271: if (*p == '<') { meillo@271: angle_brackets++; meillo@271: } meillo@271: p++; meillo@271: } meillo@271: if (!read_word_with_dots(p, &b, &e)) { meillo@10: return FALSE; meillo@271: } meillo@271: p = e; meillo@271: *local_begin = b; meillo@271: *local_end = e; meillo@271: #ifdef PARSE_TEST meillo@271: g_print("found local part: %s\n", *local_begin); meillo@271: #endif meillo@271: if (*p == '@') { meillo@271: p++; meillo@271: if (!read_domain(p, &b, &e)) { meillo@271: return FALSE; meillo@271: } meillo@271: p = e; meillo@271: *domain_begin = b; meillo@271: *domain_end = e; meillo@271: } else { meillo@271: /* may be unqualified address */ meillo@271: *domain_begin = *domain_end = NULL; meillo@271: } meillo@271: break; meillo@271: meillo@271: } else if (!*p || *p == '>') { meillo@271: *local_begin = b; meillo@271: *local_end = e; meillo@271: #ifdef PARSE_TEST meillo@271: g_print("found local part: %s\n", *local_begin); meillo@271: #endif meillo@271: *domain_begin = *domain_end = NULL; meillo@271: break; meillo@271: meillo@271: } else if (strchr(specials, *p) || iscntrl(*p) || isspace(*p)) { meillo@271: parse_error = g_strdup_printf("unexpected character: %c", *p); meillo@273: #ifdef PARSE_TEST meillo@273: g_print("unexpected character: %c", *p); meillo@273: #endif meillo@271: return FALSE; meillo@10: } meillo@271: } meillo@271: meillo@271: /* trailing spaces and angle brackets */ meillo@10: #ifdef PARSE_TEST meillo@271: g_print("down counting trailing '>'\n"); meillo@10: #endif meillo@271: while (*p && (isspace(*p) || (*p == '>'))) { meillo@271: if (*p == '>') { meillo@271: angle_brackets--; meillo@10: } meillo@271: p++; meillo@271: } meillo@10: meillo@271: *address_end = p; meillo@10: meillo@271: if (angle_brackets > 0) { meillo@271: parse_error = g_strdup("missing '>' at end of string"); meillo@271: return FALSE; meillo@271: } else if (angle_brackets < 0) { meillo@271: parse_error = g_strdup("superfluous '>' at end of string"); meillo@271: return FALSE; meillo@0: } meillo@271: meillo@271: /* we successfully parsed the address */ meillo@271: return TRUE; meillo@0: } meillo@0: meillo@10: gboolean meillo@15: parse_address_rfc821(gchar* string, gchar** local_begin, gchar** local_end, gchar** domain_begin, meillo@15: gchar** domain_end, gchar** address_end) meillo@0: { meillo@10: gint angle_brackets = 0; meillo@0: meillo@10: gchar *p = string; meillo@10: gchar *b, *e; meillo@0: meillo@10: *local_begin = *local_end = NULL; meillo@10: *domain_begin = *domain_end = NULL; meillo@0: meillo@10: /* might be some memory left from previous call: */ meillo@10: if (parse_error != NULL) { meillo@10: g_free(parse_error); meillo@10: parse_error = NULL; meillo@10: } meillo@0: meillo@10: /* leading spaces and angle brackets */ meillo@10: while (*p && (isspace(*p) || (*p == '<'))) { meillo@271: if (*p == '<') { meillo@10: angle_brackets++; meillo@271: } meillo@10: p++; meillo@10: } meillo@10: meillo@271: if (!*p) { meillo@271: return FALSE; meillo@271: } meillo@271: meillo@271: while (TRUE) { meillo@271: if (!read_word_with_dots(p, &b, &e)) { meillo@271: return FALSE; meillo@10: } meillo@10: meillo@271: p = e; meillo@10: #ifdef PARSE_TEST meillo@271: g_print("after read_word_with_dots: %s\n", p); meillo@10: #endif meillo@271: *local_begin = b; meillo@271: *local_end = e; meillo@271: #ifdef PARSE_TEST meillo@271: g_print("found local part: %s\n", *local_begin); meillo@271: g_print("local_end = %s\n", *local_end); meillo@271: #endif meillo@271: if (!(*p) || isspace(*p) || (*p == '>')) { meillo@271: /* unqualified ? */ meillo@271: domain_begin = domain_end = NULL; meillo@271: break; meillo@271: } else if (*p == '@') { meillo@10: p++; meillo@271: if (read_domain(p, &b, &e)) { meillo@271: p = e; meillo@271: *domain_begin = b; meillo@271: *domain_end = e; meillo@271: } meillo@271: break; meillo@271: } else { meillo@271: parse_error = g_strdup_printf ("unexpected character after local part '%c'", *p); meillo@271: return FALSE; meillo@10: } meillo@271: } meillo@10: meillo@271: /* trailing spaces and angle brackets */ meillo@271: #ifdef PARSE_TEST meillo@271: g_print("down counting trailing '>'\n"); meillo@271: #endif meillo@271: while (*p && (isspace(*p) || (*p == '>'))) { meillo@271: if (*p == '>') { meillo@271: angle_brackets--; meillo@10: } meillo@271: p++; meillo@0: } meillo@271: *address_end = p; meillo@271: meillo@271: if (angle_brackets > 0) { meillo@271: parse_error = g_strdup("missing '>' at end of string"); meillo@271: return FALSE; meillo@271: } else if (angle_brackets < 0) { meillo@271: parse_error = g_strdup("superfluous '>' at end of string"); meillo@271: return FALSE; meillo@271: } meillo@271: meillo@271: /* we successfully parsed the address */ meillo@271: return TRUE; meillo@0: } meillo@0: meillo@0: /* meillo@0: allocate address, reading from string. meillo@0: On failure, returns NULL. meillo@114: after call, end contains a pointer to the end of the parsed string meillo@0: end may be NULL, if we are not interested. meillo@0: meillo@0: parses both rfc 821 and rfc 822 addresses, depending on flag is_rfc821 meillo@0: */ meillo@10: address* meillo@10: _create_address(gchar * string, gchar ** end, gboolean is_rfc821) meillo@0: { meillo@10: gchar *loc_beg, *loc_end; meillo@10: gchar *dom_beg, *dom_end; meillo@10: gchar *addr_end; meillo@271: gboolean ret; meillo@0: meillo@273: /* TODO: what about (string == NULL)? */ meillo@271: if (string && (string[0] == '\0')) { meillo@10: address *addr = g_malloc(sizeof(address)); meillo@10: addr->address = g_strdup(""); meillo@10: addr->local_part = g_strdup(""); meillo@271: /* 'NULL' address (failure notice), meillo@271: "" makes sure it will not be qualified with a hostname */ meillo@271: addr->domain = g_strdup(""); meillo@10: return addr; meillo@10: } meillo@0: meillo@271: if (is_rfc821) { meillo@271: ret = parse_address_rfc821(string, &loc_beg, &loc_end, &dom_beg, &dom_end, &addr_end); meillo@271: } else { meillo@271: ret = parse_address_rfc822(string, &loc_beg, &loc_end, &dom_beg, &dom_end, &addr_end); meillo@271: } meillo@271: if (!ret) { meillo@271: return NULL; meillo@271: } meillo@0: meillo@271: address *addr = g_malloc(sizeof(address)); meillo@271: gchar *p = addr_end; meillo@0: meillo@271: memset(addr, 0, sizeof(address)); meillo@0: meillo@271: if (loc_beg[0] == '|') { meillo@271: parse_error = g_strdup("no pipe allowed for RFC 822/821 address"); meillo@271: return NULL; meillo@271: } meillo@10: meillo@271: while (*p && (*p != ',')) { meillo@271: p++; meillo@271: } meillo@271: addr->address = g_strndup(string, p - string); meillo@271: addr->local_part = g_strndup(loc_beg, loc_end - loc_beg); meillo@0: meillo@0: #ifdef PARSE_TEST meillo@271: g_print("addr->local_part = %s\n", addr->local_part); meillo@0: #endif meillo@0: meillo@271: if (dom_beg != NULL) { meillo@271: addr->domain = g_strndup(dom_beg, dom_end - dom_beg); meillo@273: } else if (addr->local_part[0] == '\0') { meillo@271: /* 'NULL' address (failure notice), meillo@271: "" makes sure it will not be qualified with a hostname */ meillo@271: addr->domain = g_strdup(""); meillo@271: } else { meillo@271: addr->domain = NULL; meillo@271: } meillo@0: meillo@273: if (end) { meillo@271: *end = p; meillo@271: } meillo@0: meillo@0: #ifndef PARSE_TEST meillo@271: addr_unmark_delivered(addr); meillo@0: #endif meillo@0: meillo@271: return addr; meillo@0: } meillo@0: meillo@10: address* meillo@10: create_address_rfc822(gchar * string, gchar ** end) meillo@10: { meillo@10: return _create_address(string, end, FALSE); meillo@0: } meillo@0: meillo@10: address* meillo@10: create_address_rfc821(gchar * string, gchar ** end) meillo@10: { meillo@10: return _create_address(string, end, TRUE); meillo@0: } meillo@0: meillo@10: GList* meillo@10: addr_list_append_rfc822(GList * addr_list, gchar * string, gchar * domain) meillo@0: { meillo@10: gchar *p = string; meillo@10: gchar *end; meillo@0: meillo@10: while (*p) { meillo@273: #ifdef PARSE_TEST meillo@273: g_print("string: %s\n", p); meillo@273: #endif meillo@273: meillo@10: address *addr = _create_address(p, &end, FALSE); meillo@271: if (!addr) { meillo@10: break; meillo@271: } meillo@271: meillo@273: #ifdef PARSE_TEST meillo@273: g_print("addr: %s (%s<@>%s)", addr->address, addr->local_part, addr->domain); meillo@273: #endif meillo@271: if (domain && !addr->domain) { meillo@271: addr->domain = g_strdup(domain); meillo@271: } meillo@273: #ifdef PARSE_TEST meillo@273: g_print(" (%s<@>%s)\n", addr->local_part, addr->domain); meillo@273: #endif meillo@271: meillo@271: addr_list = g_list_append(addr_list, addr); meillo@271: p = end; meillo@271: meillo@271: while (*p == ',' || isspace(*p)) { meillo@10: p++; meillo@271: } meillo@10: } meillo@10: return addr_list; meillo@0: }