/* Delivery User Functions * Aaron Stone, 9 Feb 2004 */ /* Copyright (C) 2004 Aaron Stone aaron at serendipity dot cx This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "dbmail.h" #define THIS_MODULE "dsn" /* Enhanced Status Codes from RFC 1893 * Nota Bene: should be updated to include * its successor, RFC 3463, too. * */ /* Top level codes */ static const char * const DSN_STRINGS_CLASS[] = { /* nop */ "", "", /* 2.. */ "Success", /* nop */ "", /* 4.. */ "Persistent Transient Failure", /* 5.. */ "Permanent Failure" }; /* Second the Third level codes */ static const char * const DSN_STRINGS_SUBJECT[] = { /* nop */ "", /* .1. */ "Address Status", /* .2. */ "Mailbox Status", /* .3. */ "Mail System Status", /* .4. */ "Network and Routing Status", /* .5. */ "Mail Delivery Protocol Status", /* .6. */ "Message Content or Message Media Status", /* .7. */ "Security or Policy Status" }; /* Empty subject */ static const char * const DSN_STRINGS_DETAIL_ZERO[] = { /* .0.0 */ "" }; /* Address Status */ static const char * const DSN_STRINGS_DETAIL_ONE[] = { /* .1.0 */ "Other address status", /* .1.1 */ "Bad destination mailbox address", /* .1.2 */ "Bad destination system address", /* .1.3 */ "Bad destination mailbox address syntax", /* .1.4 */ "Destination mailbox address ambiguous", /* .1.5 */ "Destination mailbox address valid", /* .1.6 */ "Mailbox has moved", /* .1.7 */ "Bad sender's mailbox address syntax", /* .1.8 */ "Bad sender's system address" }; /* Mailbox Status */ static const char * const DSN_STRINGS_DETAIL_TWO[] = { /* .2.0 */ "Other or undefined mailbox status", /* .2.1 */ "Mailbox disabled, not accepting messages", /* .2.2 */ "Mailbox full", /* .2.3 */ "Message length exceeds administrative limit", /* .2.4 */ "Mailing list expansion problem" }; /* Mail System Status */ static const char * const DSN_STRINGS_DETAIL_THREE[] = { /* .3.0 */ "Other or undefined mail system status", /* .3.1 */ "Mail system full", /* .3.2 */ "System not accepting network messages", /* .3.3 */ "System not capable of selected features", /* .3.4 */ "Message too big for system" }; /* Network and Routing Status */ static const char * const DSN_STRINGS_DETAIL_FOUR[] = { /* .4.0 */ "Other or undefined network or routing status", /* .4.1 */ "No answer from host", /* .4.2 */ "Bad connection", /* .4.3 */ "Routing server failure", /* .4.4 */ "Unable to route", /* .4.5 */ "Network congestion", /* .4.6 */ "Routing loop detected", /* .4.7 */ "Delivery time expired" }; /* Mail Delivery Protocol Status */ static const char * const DSN_STRINGS_DETAIL_FIVE[] = { /* .5.0 */ "Other or undefined protocol status", /* .5.1 */ "Invalid command", /* .5.2 */ "Syntax error", /* .5.3 */ "Too many recipients", /* .5.4 */ "Invalid command arguments", /* .5.5 */ "Wrong protocol version" }; /* Message Content or Message Media Status */ static const char * const DSN_STRINGS_DETAIL_SIX[] = { /* .6.0 */ "Other or undefined media error", /* .6.1 */ "Media not supported", /* .6.2 */ "Conversion required and prohibited", /* .6.3 */ "Conversion required but not supported", /* .6.4 */ "Conversion with loss performed", /* .6.5 */ "Conversion failed" }; /* Security or Policy Status */ static const char * const DSN_STRINGS_DETAIL_SEVEN[] = { /* .7.0 */ "Other or undefined security status", /* .7.1 */ "Delivery not authorized, message refused", /* .7.2 */ "Mailing list expansion prohibited", /* .7.3 */ "Security conversion required but not possible", /* .7.4 */ "Security features not supported", /* .7.5 */ "Cryptographic failure", /* .7.6 */ "Cryptographic algorithm not supported", /* .7.7 */ "Message integrity failure" }; /* MAGIC: max detail indexes for each subject detail array */ static int DSN_STRINGS_DETAIL_VALID[] = { 0, 8, 4, 4, 7, 5 ,5, 7 }; static const char * const * const DSN_STRINGS_DETAIL[] = { DSN_STRINGS_DETAIL_ZERO, DSN_STRINGS_DETAIL_ONE, DSN_STRINGS_DETAIL_TWO, DSN_STRINGS_DETAIL_THREE, DSN_STRINGS_DETAIL_FOUR, DSN_STRINGS_DETAIL_FIVE, DSN_STRINGS_DETAIL_SIX, DSN_STRINGS_DETAIL_SEVEN, }; /* Convert the DSN code into a descriptive message. * Returns 0 on success, -1 on failure. */ int dsn_tostring(delivery_status_t dsn, const char ** const class, const char ** const subject, const char ** const detail) { assert(class); assert(subject); assert(detail); *class = *subject = *detail = NULL; if (dsn.class == 2 || dsn.class == 4 || dsn.class == 5) *class = DSN_STRINGS_CLASS[dsn.class]; if (dsn.subject >= 0 && dsn.subject <= 7) /* MAGIC: max index of DSN_STRINGS_SUBJECT */ *subject = DSN_STRINGS_SUBJECT[dsn.subject]; if (dsn.subject >= 0 && dsn.subject <= 7 /* MAGIC: max index of DSN_STRINGS_SUBJECT */ && dsn.detail >= 0 && dsn.detail <= DSN_STRINGS_DETAIL_VALID[dsn.subject]) *detail = DSN_STRINGS_DETAIL[dsn.subject][dsn.detail]; if (!*class || !*subject || !*detail) { TRACE(TRACE_INFO, "Invalid dsn code received [%d][%d][%d]", dsn.class, dsn.subject, dsn.detail); /* Allow downstream code to assume the return pointers * are valid strings, though not particularly useful. */ *class = *subject = *detail = ""; return -1; } return 0; } int dsnuser_init(deliver_to_user_t * dsnuser) { dsnuser->useridnr = 0; dsnuser->dsn.class = 0; dsnuser->dsn.subject = 0; dsnuser->dsn.detail = 0; dsnuser->address = NULL; dsnuser->mailbox = NULL; dsnuser->source = BOX_NONE; dsnuser->userids = g_new0(struct dm_list, 1); if (dsnuser->userids == NULL) return -1; dsnuser->forwards = g_new0(struct dm_list, 1); if (dsnuser->forwards == NULL) { g_free(dsnuser->userids); return -1; } dm_list_init(dsnuser->userids); dm_list_init(dsnuser->forwards); TRACE(TRACE_DEBUG, "dsnuser initialized"); return 0; } void dsnuser_free(deliver_to_user_t * dsnuser) { dsnuser->useridnr = 0; dsnuser->dsn.class = 0; dsnuser->dsn.subject = 0; dsnuser->dsn.detail = 0; dsnuser->source = BOX_NONE; dm_list_free(&dsnuser->userids->start); dm_list_free(&dsnuser->forwards->start); if (dsnuser->userids) g_free(dsnuser->userids); if (dsnuser->forwards) g_free(dsnuser->forwards); dsnuser->address = NULL; if (dsnuser->mailbox) g_free(dsnuser->mailbox); //allocated by dbmail-smtp main.c line ~ 319 g_strdup dsnuser->mailbox = NULL; dsnuser->userids = NULL; dsnuser->forwards = NULL; TRACE(TRACE_DEBUG, "dsnuser freed"); } void set_dsn(delivery_status_t *dsn, int foo, int bar, int qux) { dsn->class = foo; dsn->subject = bar; dsn->detail = qux; } static int address_has_alias(deliver_to_user_t *delivery) { int alias_count; if (!delivery->address) return 0; alias_count = auth_check_user_ext(delivery->address, delivery->userids, delivery->forwards, 0); TRACE(TRACE_DEBUG, "user [%s] found total of [%d] aliases", delivery->address, alias_count); if (alias_count > 0) return 1; return 0; } // address is in format username+mailbox@domain // and we want to cut out the mailbox and produce // an address in format username@domain, then check it. static int address_has_alias_mailbox(deliver_to_user_t *delivery) { int alias_count; char *newaddress; size_t newaddress_len, zapped_len; if (!delivery->address) return 0; if (zap_between(delivery->address, -'+', '@', &newaddress, &newaddress_len, &zapped_len) != 0) return 0; alias_count = auth_check_user_ext(newaddress, delivery->userids, delivery->forwards, 0); TRACE(TRACE_DEBUG, "user [%s] found total of [%d] aliases", newaddress, alias_count); g_free(newaddress); if (alias_count > 0) return 1; return 0; } static int address_is_username_mailbox(deliver_to_user_t *delivery) { int user_exists; u64_t userid; char *newaddress; size_t newaddress_len, zapped_len; if (!delivery->address) return 0; if (zap_between(delivery->address, -'+', '@', &newaddress, &newaddress_len, &zapped_len) != 0) return 0; user_exists = auth_user_exists(newaddress, &userid); if (user_exists < 0) { /* An error occurred. */ TRACE(TRACE_ERROR, "error checking user [%s]", newaddress); g_free(newaddress); return -1; } if (user_exists == 0) { /* User does not exist. */ TRACE(TRACE_INFO, "username not found [%s]", newaddress); g_free(newaddress); return 0; } if (dm_list_nodeadd(delivery->userids, &userid, sizeof(u64_t)) == 0) { TRACE(TRACE_ERROR, "out of memory"); g_free(newaddress); return -1; } TRACE(TRACE_DEBUG, "added user [%s] id [%llu] to delivery list", newaddress, userid); g_free(newaddress); return 1; } static int address_is_username(deliver_to_user_t *delivery) { int user_exists; u64_t userid; if (!delivery->address) return 0; user_exists = auth_user_exists(delivery->address, &userid); if (user_exists < 0) { /* An error occurred. */ TRACE(TRACE_ERROR, "error checking user [%s]", delivery->address); return -1; } if (user_exists == 0) { /* User does not exist. */ TRACE(TRACE_INFO, "username not found [%s]", delivery->address); return 0; } if (dm_list_nodeadd(delivery->userids, &userid, sizeof(u64_t)) == 0) { TRACE(TRACE_ERROR, "out of memory"); return -1; } TRACE(TRACE_DEBUG, "added user [%s] id [%llu] to delivery list", delivery->address, userid); return 1; } static int address_is_domain_catchall(deliver_to_user_t *delivery) { char *domain; int domain_count; if (!delivery->address) return 0; TRACE(TRACE_INFO, "user [%s] checking for domain forwards.", delivery->address); domain = strchr(delivery->address, '@'); if (domain == NULL) { return 0; } char *my_domain = g_strdup(domain); char *my_domain_dot = my_domain; while (1) { TRACE(TRACE_DEBUG, "domain [%s] checking for domain forwards", my_domain); /* Checking for domain aliases */ domain_count = auth_check_user_ext(my_domain, delivery->userids, delivery->forwards, 0); if (domain_count > 0) { /* This is the way to succeed out. */ break; } /* On each loop, lop off a chunk between @ and . */ my_domain_dot = strchr(my_domain, '.'); if (!my_domain_dot || my_domain_dot == my_domain) { /* This is one way to fail out, it means we have * somethign like @foo or a missing at-sign. */ break; } if (my_domain_dot == my_domain + 1) { /* We're looking at something like @.foo.bar.qux, * and my_domain_dot is pointed at .foo.bar.qux, * so we have to look one more character ahead. */ my_domain_dot = strchr(my_domain_dot + 1, '.'); if (!my_domain_dot) { /* We're looking at @. so we're done. */ break; } } /* Copy everything from the next dot to one after the at-sign, * including the trailing nul byte. */ int move_len = strlen(my_domain_dot); memmove(my_domain + 1, my_domain_dot, move_len + 1); } TRACE(TRACE_DEBUG, "domain [%s] found total of [%d] aliases", my_domain, domain_count); g_free(my_domain); if (domain_count > 0) { return 1; } return 0; } static int address_is_userpart_catchall(deliver_to_user_t *delivery) { char *userpart = g_strdup(delivery->address); char *userpartcut; int userpart_count; if (!delivery->address) return 0; TRACE(TRACE_INFO, "user [%s] checking for userpart forwards.", userpart); userpartcut = strchr(userpart, '@'); if (userpartcut == NULL) { return 0; } /* Stomp _after_ the @-sign. */ *(userpartcut + 1) = '\0'; TRACE(TRACE_DEBUG, "userpart [%s] checking for userpart forwards", userpart); /* Checking for userpart aliases */ userpart_count = auth_check_user_ext(userpart, delivery->userids, delivery->forwards, 0); TRACE(TRACE_DEBUG, "userpart [%s] found total of [%d] aliases", userpart, userpart_count); if (userpart_count == 0) { return 0; } return 1; } void dsnuser_free_list(struct dm_list *deliveries) { struct element *tmp; for (tmp = dm_list_getstart(deliveries); tmp != NULL; tmp = tmp->nextnode) dsnuser_free((deliver_to_user_t *) tmp->data); dm_list_free(&deliveries->start); } dsn_class_t dsnuser_worstcase_int(int ok, int temp, int fail, int fail_quota) { if (temp) return DSN_CLASS_TEMP; if (fail) return DSN_CLASS_FAIL; if (fail_quota) return DSN_CLASS_QUOTA; if (ok) return DSN_CLASS_OK; return DSN_CLASS_NONE; } dsn_class_t dsnuser_worstcase_list(struct dm_list * deliveries) { delivery_status_t dsn; struct element *tmp; int ok = 0, temp = 0, fail = 0, fail_quota = 0; /* Get one reasonable error code for everyone. */ for (tmp = dm_list_getstart(deliveries); tmp != NULL; tmp = tmp->nextnode) { dsn = ((deliver_to_user_t *) tmp->data)->dsn; switch (dsn.class) { case DSN_CLASS_OK: /* Success. */ ok = 1; break; case DSN_CLASS_TEMP: /* Temporary transient failure. */ temp = 1; break; case DSN_CLASS_FAIL: case DSN_CLASS_QUOTA: /* Permanent failure. */ if (dsn.subject == 2) fail_quota = 1; else fail = 1; break; case DSN_CLASS_NONE: /* Nothing doing. */ break; } } /* If we never made it into the list, all zeroes will * yield a temporary failure, which is pretty reasonable. */ return dsnuser_worstcase_int(ok, temp, fail, fail_quota); } int dsnuser_resolve_list(struct dm_list *deliveries) { int ret; struct element *element; /* Loop through the users list */ for (element = dm_list_getstart(deliveries); element != NULL; element = element->nextnode) { if ((ret = dsnuser_resolve((deliver_to_user_t *) element->data)) != 0) { return ret; } } return 0; } int dsnuser_resolve(deliver_to_user_t *delivery) { /* If the userid is already set, then we're doing direct-to-userid. * We just want to make sure that the userid actually exists... */ if (delivery->useridnr != 0) { TRACE(TRACE_INFO, "checking if [%llu] is a valid useridnr.", delivery->useridnr); switch (auth_check_userid(delivery->useridnr)) { case -1: /* Temp fail. Address related. D.N.E. */ set_dsn(&delivery->dsn, DSN_CLASS_TEMP, 1, 1); TRACE(TRACE_INFO, "useridnr [%llu] temporary lookup failure.", delivery->useridnr); break; case 1: /* Failure. Address related. D.N.E. */ set_dsn(&delivery->dsn, DSN_CLASS_FAIL, 1, 1); TRACE(TRACE_INFO, "useridnr [%llu] does not exist.", delivery->useridnr); break; case 0: /* Copy the delivery useridnr into the userids list. */ if (dm_list_nodeadd(delivery->userids, &delivery->useridnr, sizeof(delivery->useridnr)) == 0) { TRACE(TRACE_ERROR, "out of memory"); return -1; } /* Success. Address related. Valid. */ set_dsn(&delivery->dsn, DSN_CLASS_OK, 1, 5); TRACE(TRACE_INFO, "delivery [%llu] directly to a useridnr.", delivery->useridnr); break; } /* Ok, we don't have a useridnr, maybe we have an address? */ } else if (strlen(delivery->address) > 0) { TRACE(TRACE_INFO, "checking if [%s] is a valid username, alias, or catchall.", delivery->address); if (address_has_alias(delivery)) { /* The address had aliases and they've * been resolved into the delivery struct. */ /* Success. Address related. Valid. */ set_dsn(&delivery->dsn, DSN_CLASS_OK, 1, 5); TRACE(TRACE_INFO, "delivering [%s] as an alias.", delivery->address); } else if (address_has_alias_mailbox(delivery)) { /* Success. Address related. Valid. */ set_dsn(&delivery->dsn, DSN_CLASS_OK, 1, 5); TRACE(TRACE_INFO, "delivering [%s] as an alias with mailbox.", delivery->address); } else if (address_is_username(delivery)) { /* Success. Address related. Valid. */ set_dsn(&delivery->dsn, DSN_CLASS_OK, 1, 5); TRACE(TRACE_INFO, "delivering [%s] as a username.", delivery->address); } else if (address_is_username_mailbox(delivery)) { /* Success. Address related. Valid. */ set_dsn(&delivery->dsn, DSN_CLASS_OK, 1, 5); TRACE(TRACE_INFO, "delivering [%s] as a username with mailbox.", delivery->address); } else if (address_is_domain_catchall(delivery)) { /* Success. Address related. Valid. */ set_dsn(&delivery->dsn, DSN_CLASS_OK, 1, 5); TRACE(TRACE_INFO, "delivering [%s] as a domain catchall.", delivery->address); } else if (address_is_userpart_catchall(delivery)) { /* Success. Address related. Valid. */ set_dsn(&delivery->dsn, DSN_CLASS_OK, 1, 5); TRACE(TRACE_INFO, "delivering [%s] as a userpart catchall.", delivery->address); } else { /* Failure. Address related. D.N.E. */ set_dsn(&delivery->dsn, DSN_CLASS_FAIL, 1, 1); TRACE(TRACE_INFO, "could not find [%s] at all.", delivery->address); } /* Neither useridnr nor address. * Something is wrong upstream. */ } else { TRACE(TRACE_ERROR, "this delivery had neither useridnr nor address."); return -1; } return 0; }