/* Copyright (C) 1999-2004 IC & S dbmail@ic-s.nl 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. */ /* $Id: pipe.c 1690 2005-03-18 12:48:34Z paul $ * * Functions for reading the pipe from the MTA */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "db.h" #include "auth.h" #include "debug.h" #include "list.h" #include "forward.h" #include "sort.h" #include "dbmail.h" #include "pipe.h" #include "debug.h" #include "misc.h" #include #include #include #include #include #include #include #include "dbmd5.h" #include "misc.h" #include "dsn.h" #define HEADER_BLOCK_SIZE 1024 #define QUERY_SIZE 255 #define MAX_U64_STRINGSIZE 40 #define MAX_COMM_SIZE 512 #define RING_SIZE 6 #define AUTO_NOTIFY_SENDER "autonotify@dbmail" #define AUTO_NOTIFY_SUBJECT "NEW MAIL NOTIFICATION" /* Must be at least 998 or 1000 by RFC's */ #define MAX_LINE_SIZE 1024 #define DBMAIL_TEMPMBOX "INBOX" /** * store a messagebody (without headers in one or more blocks in the database * \param message the message * \param message_size size of message * \param msgidnr idnr of message * \return * - -1 on error * - 1 on success */ static int store_message_in_blocks(const char* message, u64_t message_size, u64_t msgidnr); /* * Send an automatic notification using sendmail */ static int send_notification(const char *to, const char *from, const char *subject) { FILE *mailpipe = NULL; char *sendmail_command = NULL; field_t sendmail; int result; size_t sendmail_command_maxlen; if (GetConfigValue("SENDMAIL", "SMTP", sendmail) < 0) trace(TRACE_FATAL, "%s,%s: error getting Config Values", __FILE__, __func__); if (sendmail[0] == '\0') trace(TRACE_FATAL, "send_notification(): SENDMAIL not configured (see config file). Stop."); trace(TRACE_DEBUG, "send_notification(): found sendmail command to be [%s]", sendmail); sendmail_command_maxlen = strlen((char *) to) + strlen(sendmail) + 2; sendmail_command = (char *) dm_malloc(sendmail_command_maxlen * sizeof(char)); if (!sendmail_command) { trace(TRACE_ERROR, "send_notification(): out of memory"); return -1; } trace(TRACE_DEBUG, "send_notification(): allocated memory for" " external command call"); (void) snprintf(sendmail_command, sendmail_command_maxlen, "%s %s", sendmail, to); trace(TRACE_INFO, "send_notification(): opening pipe to command " "%s", sendmail_command); if (!(mailpipe = popen(sendmail_command, "w"))) { trace(TRACE_ERROR, "send_notification(): could not open pipe to sendmail using cmd [%s]", sendmail); dm_free(sendmail_command); return 1; } trace(TRACE_DEBUG, "send_notification(): pipe opened, sending data"); fprintf(mailpipe, "To: %s\n", to); fprintf(mailpipe, "From: %s\n", from); fprintf(mailpipe, "Subject: %s\n", subject); fprintf(mailpipe, "\n"); result = pclose(mailpipe); trace(TRACE_DEBUG, "send_notification(): pipe closed"); if (result != 0) trace(TRACE_ERROR, "send_notification(): reply could not be sent: sendmail error"); dm_free(sendmail_command); return 0; } /* * Send an automatic reply using sendmail */ static int send_reply(struct list *headerfields, const char *body) { struct element *el; struct mime_record *record; char *from = NULL, *to = NULL, *replyto = NULL, *subject = NULL; FILE *mailpipe = NULL; char *send_address; char *escaped_send_address; char comm[MAX_COMM_SIZE]; /**< command sent to sendmail (needs to escaped) */ field_t sendmail; int result; unsigned int i, j; if (GetConfigValue("SENDMAIL", "SMTP", sendmail) < 0) trace(TRACE_FATAL, "%s,%s: error getting config", __FILE__, __func__); if (sendmail[0] == '\0') trace(TRACE_FATAL, "send_reply(): SENDMAIL not configured (see config file). Stop."); trace(TRACE_DEBUG, "send_reply(): found sendmail command to be [%s]", sendmail); /* find To: and Reply-To:/From: field */ el = list_getstart(headerfields); while (el) { record = (struct mime_record *) el->data; if (strcasecmp(record->field, "from") == 0) { from = record->value; trace(TRACE_DEBUG, "send_reply(): found FROM [%s]", from); } else if (strcasecmp(record->field, "reply-to") == 0) { replyto = record->value; trace(TRACE_DEBUG, "send_reply(): found REPLY-TO [%s]", replyto); } else if (strcasecmp(record->field, "subject") == 0) { subject = record->value; trace(TRACE_DEBUG, "send_reply(): found SUBJECT [%s]", subject); } else if (strcasecmp(record->field, "deliver-to") == 0) { to = record->value; trace(TRACE_DEBUG, "send_reply(): found TO [%s]", to); } el = el->nextnode; } if (!from && !replyto) { trace(TRACE_ERROR, "send_reply(): no address to send to"); return 0; } trace(TRACE_DEBUG, "send_reply(): header fields scanned; opening pipe to sendmail"); send_address = replyto ? replyto : from; /* allocate a string twice the size of send_address */ escaped_send_address = (char *) dm_malloc((strlen(send_address) + 1) * 2 * sizeof(char)); if (!escaped_send_address) { trace(TRACE_ERROR, "%s,%s: unable to allocate memory. Memory " "full?", __FILE__, __func__); return 0; } memset(escaped_send_address, '\0', (strlen(send_address) + 1) * 2 * sizeof(char)); i = 0; j = 0; /* get all characters from send_address, and escape every ' */ while (i < (strlen(send_address) + 1)) { if (send_address[i] == '\'') escaped_send_address[j++] = '\\'; escaped_send_address[j++] = send_address[i++]; } (void) snprintf(comm, MAX_COMM_SIZE, "%s '%s'", sendmail, escaped_send_address); if (!(mailpipe = popen(comm, "w"))) { trace(TRACE_ERROR, "send_reply(): could not open pipe to sendmail using cmd [%s]", comm); dm_free(escaped_send_address); return 1; } trace(TRACE_DEBUG, "send_reply(): sending data"); fprintf(mailpipe, "To: %s\n", replyto ? replyto : from); fprintf(mailpipe, "From: %s\n", to ? to : "(unknown)"); fprintf(mailpipe, "Subject: AW: %s\n", subject ? subject : ""); fprintf(mailpipe, "\n"); fprintf(mailpipe, "%s\n", body ? body : "--"); result = pclose(mailpipe); trace(TRACE_DEBUG, "send_reply(): pipe closed"); if (result != 0) trace(TRACE_ERROR, "send_reply(): reply could not be sent: sendmail error"); dm_free(escaped_send_address); return 0; } /* Yeah, RAN. That's Reply And Notify ;-) */ static int execute_auto_ran(u64_t useridnr, struct list *headerfields) { field_t val; int do_auto_notify = 0, do_auto_reply = 0; char *reply_body = NULL; char *notify_address = NULL; /* message has been succesfully inserted, perform auto-notification & auto-reply */ if (GetConfigValue("AUTO_NOTIFY", "SMTP", val) < 0) trace(TRACE_FATAL, "%s,%s error getting config", __FILE__, __func__); if (strcasecmp(val, "yes") == 0) do_auto_notify = 1; if (GetConfigValue("AUTO_REPLY", "SMTP", val) < 0) trace(TRACE_FATAL, "%s,%s error getting config", __FILE__, __func__); if (strcasecmp(val, "yes") == 0) do_auto_reply = 1; if (do_auto_notify != 0) { trace(TRACE_DEBUG, "execute_auto_ran(): starting auto-notification procedure"); if (db_get_notify_address(useridnr, ¬ify_address) != 0) trace(TRACE_ERROR, "execute_auto_ran(): error fetching notification address"); else { if (notify_address == NULL) trace(TRACE_DEBUG, "execute_auto_ran(): no notification address specified, skipping"); else { trace(TRACE_DEBUG, "execute_auto_ran(): sending notifcation to [%s]", notify_address); if (send_notification(notify_address, AUTO_NOTIFY_SENDER, AUTO_NOTIFY_SUBJECT) < 0) { trace(TRACE_ERROR, "%s,%s: error in call to send_notification.", __FILE__, __func__); dm_free(notify_address); return -1; } dm_free(notify_address); } } } if (do_auto_reply != 0) { trace(TRACE_DEBUG, "execute_auto_ran(): starting auto-reply procedure"); if (db_get_reply_body(useridnr, &reply_body) != 0) trace(TRACE_ERROR, "execute_auto_ran(): error fetching reply body"); else { if (reply_body == NULL || reply_body[0] == '\0') trace(TRACE_DEBUG, "execute_auto_ran(): no reply body specified, skipping"); else { if (send_reply(headerfields, reply_body) < 0) { trace(TRACE_ERROR, "%s,%s: error in call to send_reply", __FILE__, __func__); dm_free(reply_body); return -1; } dm_free(reply_body); } } } return 0; } /* read from instream, but simply discard all input! */ int discard_client_input(FILE * instream) { char *tmpline; tmpline = (char *) dm_malloc(MAX_LINE_SIZE + 1); if (tmpline == NULL) { trace(TRACE_ERROR, "%s,%s: unable to allocate memory.", __FILE__, __func__); return -1; } while (!feof(instream)) { if (fgets(tmpline, MAX_LINE_SIZE, instream) == NULL) break; trace(TRACE_DEBUG, "%s,%s: tmpline = [%s]", __FILE__, __func__, tmpline); if (strcmp(tmpline, ".\r\n") == 0) break; } dm_free(tmpline); return 0; } /** * store a temporary copy of a message. * \param header the header to the message * \param body body of the message * \param headersize size of header * \param headerrfcsize rfc size of header * \param bodysize size of body * \param bodyrfcsize rfc size of body * \param[out] temp_message_idnr message idnr of temporary message * \return * - -1 on error * - 1 on success */ static int store_message_temp(const char *header, const char *body, u64_t headersize, u64_t headerrfcsize, u64_t bodysize, u64_t bodyrfcsize, /*@out@*/ u64_t * temp_message_idnr) { int result; u64_t user_idnr; u64_t msgidnr; u64_t messageblk_idnr; char unique_id[UID_SIZE]; result = auth_user_exists(DBMAIL_DELIVERY_USERNAME, &user_idnr); if (result < 0) { trace(TRACE_ERROR, "%s,%s: unable to find user_idnr for user " "[%s]\n", __FILE__, __func__, DBMAIL_DELIVERY_USERNAME); return -1; } if (result == 0) { trace(TRACE_ERROR, "%s,%s: unable to find user_idnr for user " "[%s]. Make sure this system user is in the database!\n", __FILE__, __func__, DBMAIL_DELIVERY_USERNAME); return -1; } create_unique_id(unique_id, user_idnr); /* create a message record */ switch (db_insert_message(user_idnr, DBMAIL_TEMPMBOX, CREATE_IF_MBOX_NOT_FOUND, unique_id, &msgidnr)) { case -1: trace(TRACE_ERROR, "store_message_temp(): returned -1, aborting"); return -1; } switch (db_insert_message_block (header, headersize, msgidnr, &messageblk_idnr,HEAD_BLOCK)) { case -1: trace(TRACE_ERROR, "store_message_temp(): error inserting msgblock [header]"); return -1; } trace(TRACE_DEBUG, "store_message_temp(): allocating [%ld] bytes of memory " "for readblock", READ_BLOCK_SIZE); /* store body in several blocks (if needed */ if (store_message_in_blocks(body, bodysize, msgidnr) < 0) { trace(TRACE_STOP, "store_message_temp(): db_insert_message_block " "failed"); return -1; } if (db_update_message(msgidnr, unique_id, (headersize + bodysize), (headerrfcsize + bodyrfcsize)) < 0) { trace(TRACE_ERROR, "%s,%s: error updating message [%llu]. " "Trying to clean up", __FILE__, __func__, msgidnr); if (db_delete_message(msgidnr) < 0) trace(TRACE_ERROR, "%s,%s error deleting message " "[%llu]. Database might be inconsistent, run " "dbmail-util", __FILE__, __func__, msgidnr); return -1; } *temp_message_idnr = msgidnr; return 1; } int store_message_in_blocks(const char *message, u64_t message_size, u64_t msgidnr) { u64_t tmp_messageblk_idnr; u64_t rest_size = message_size; u64_t block_size = 0; unsigned block_nr = 0; size_t offset; while (rest_size > 0) { offset = block_nr * READ_BLOCK_SIZE; block_size = (rest_size < READ_BLOCK_SIZE ? rest_size : READ_BLOCK_SIZE); rest_size = (rest_size < READ_BLOCK_SIZE ? 0 : rest_size - READ_BLOCK_SIZE); trace(TRACE_DEBUG, "%s,%s: inserting message: %s", __FILE__, __func__, &message[offset]); if (db_insert_message_block(&message[offset], block_size, msgidnr, &tmp_messageblk_idnr,BODY_BLOCK) < 0) { trace(TRACE_ERROR, "%s,%s: " "db_insert_message_block() failed", __FILE__, __func__); return -1; } block_nr += 1; } return 1; } /* Here's the real *meat* of this source file! * * Function: insert_messages() * What we get: * - A pointer to the incoming message stream * - The header of the message * - A list of destination addresses / useridnr's * - The default mailbox to delivery to * * What we do: * - Read in the rest of the message * - Store the message to the DBMAIL user * - Process the destination addresses into lists: * - Local useridnr's * - External forwards * - No such user bounces * - Store the local useridnr's * - Run the message through each user's sorting rules * - Potentially alter the delivery: * - Different mailbox * - Bounce * - Reply with vacation message * - Forward to another address * - Check the user's quota before delivering * - Do this *after* their sorting rules, since the * sorting rules might not store the message anyways * - Send out the no such user bounces * - Send out the external forwards * - Delete the temporary message from the database * What we return: * - 0 on success * - -1 on full failure */ int insert_messages(const char *header, const char* body, u64_t headersize, u64_t headerrfcsize, u64_t bodysize, u64_t bodyrfcsize, struct list *headerfields, struct list *dsnusers, struct list *returnpath) { struct element *element, *ret_path; u64_t msgsize, rfcsize, tmpmsgidnr; delivery_status_t final_dsn; msgsize = headersize + bodysize; rfcsize = headerrfcsize + bodyrfcsize; /* first start a new database transaction */ if (db_begin_transaction() < 0) { trace(TRACE_ERROR, "%s,%s: error executing " "db_begin_transaction(). aborting delivery...", __FILE__, __func__); return -1; } /* Read in the rest of the stream and store it into a temporary message */ switch (store_message_temp (header, body, headersize, headerrfcsize, bodysize, bodyrfcsize, &tmpmsgidnr)) { case -1: /* Major trouble. Bail out immediately. */ trace(TRACE_ERROR, "%s, %s: failed to store temporary message.", __FILE__, __func__); db_rollback_transaction(); return -1; default: trace(TRACE_DEBUG, "%s, %s: temporary msgidnr is [%llu]", __FILE__, __func__, tmpmsgidnr); break; } /* Loop through the users list. */ for (element = list_getstart(dsnusers); element != NULL; element = element->nextnode) { struct element *userid_elem; int has_2 = 0, has_4 = 0, has_5 = 0, has_5_2 = 0; deliver_to_user_t *delivery = (deliver_to_user_t *) element->data; dsn_class_t dsn_result; /* If there was already an error during resolving, * let's skip this delivery. */ if (delivery->dsn.class != DSN_CLASS_OK) continue; /* Each user may have a list of user_idnr's for local * delivery. */ for (userid_elem = list_getstart(delivery->userids); userid_elem != NULL; userid_elem = userid_elem->nextnode) { u64_t useridnr = *(u64_t *) userid_elem->data; trace(TRACE_DEBUG, "%s, %s: calling sort_and_deliver for useridnr [%llu]", __FILE__, __func__, useridnr); dsn_result = sort_and_deliver(tmpmsgidnr, msgsize, useridnr, delivery->mailbox); switch (dsn_result) { case DSN_CLASS_OK: /* Indicate success. */ trace(TRACE_DEBUG, "%s, %s: successful sort_and_deliver for useridnr [%llu]", __FILE__, __func__, useridnr); has_2 = 1; break; case DSN_CLASS_FAIL: /* Indicate permanent failure. */ trace(TRACE_ERROR, "%s, %s: permanent failure sort_and_deliver for useridnr [%llu]", __FILE__, __func__, useridnr); has_5 = 1; break; case DSN_CLASS_QUOTA: /* Indicate over quota. */ trace(TRACE_ERROR, "%s, %s: temporary failure sort_and_deliver for useridnr [%llu]", __FILE__, __func__, useridnr); has_5_2 = 1; break; case DSN_CLASS_TEMP: default: /* Assume a temporary failure */ trace(TRACE_ERROR, "%s, %s: temporary failure sort_and_deliver for useridnr [%llu]", __FILE__, __func__, useridnr); has_4 = 1; break; } /* Automatic reply and notification */ if (execute_auto_ran(useridnr, headerfields) < 0) trace(TRACE_ERROR, "%s,%s: error in " "execute_auto_ran(), continuing", __FILE__, __func__); } /* from: the useridnr for loop */ final_dsn = dsnuser_worstcase_int(has_2, has_4, has_5, has_5_2); switch (final_dsn.class) { case DSN_CLASS_OK: delivery->dsn.class = DSN_CLASS_OK; /* Success. */ delivery->dsn.subject = 1; /* Address related. */ delivery->dsn.detail = 5; /* Valid. */ break; case DSN_CLASS_TEMP: /* sort_and_deliver returns TEMP is useridnr is 0, aka, * if nothing was delivered at all, or for any other failures. */ /* If there's a problem with the delivery address, but * there are proper forwarding addresses, we're OK. */ if (list_totalnodes(delivery->forwards) > 0) { delivery->dsn.class = DSN_CLASS_OK; delivery->dsn.subject = 1; /* Address related. */ delivery->dsn.detail = 5; /* Valid. */ break; } /* Fall through to FAIL. */ case DSN_CLASS_FAIL: delivery->dsn.class = DSN_CLASS_FAIL; /* Permanent failure. */ delivery->dsn.subject = 1; /* Address related. */ delivery->dsn.detail = 1; /* Does not exist. */ break; case DSN_CLASS_QUOTA: delivery->dsn.class = DSN_CLASS_FAIL; /* Permanent failure. */ delivery->dsn.subject = 2; /* Mailbox related. */ delivery->dsn.detail = 2; /* Over quota limit. */ break; case DSN_CLASS_NONE: /* Leave the DSN status at whatever dsnuser_resolve set it at. */ break; } trace(TRACE_DEBUG, "insert_messages(): we need to deliver [%ld] " "messages to external addresses", list_totalnodes(delivery->forwards)); /* Each user may also have a list of external forwarding addresses. */ if (list_totalnodes(delivery->forwards) > 0) { trace(TRACE_DEBUG, "insert_messages(): delivering to external addresses"); /* Only the last step of the returnpath is used. */ ret_path = list_getstart(returnpath); /* Forward using the temporary stored message. */ if (forward(tmpmsgidnr, delivery->forwards, (ret_path ? ret_path-> data : "DBMAIL-MAILER"), header, headersize) < 0) /* FIXME: if forward fails, we should do something * sensible. Currently, the message is just black- * holed! */ trace(TRACE_ERROR, "%s,%s: forward failed " "message lost", __FILE__, __func__); } } /* from: the delivery for loop */ /* Always delete the temporary message, even if the delivery failed. * It is the MTA's job to requeue or bounce the message, * and our job to keep a tidy database ;-) */ if (db_delete_message(tmpmsgidnr) < 0) trace(TRACE_ERROR, "%s,%s: failed to delete temporary message " "[%llu]", __FILE__, __func__, tmpmsgidnr); trace(TRACE_DEBUG, "insert_messages(): temporary message deleted from database"); trace(TRACE_DEBUG, "insert_messages(): End of function"); if (db_commit_transaction() < 0) return -1; return 0; }