/* lmtpd.c -- Program to deliver mail to a mailbox * * $Id: lmtpd.c,v 1.12 2005/08/10 21:38:18 dasenbro Exp $ * Copyright (c) 1998-2003 Carnegie Mellon University. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The name "Carnegie Mellon University" must not be used to * endorse or promote products derived from this software without * prior written permission. For permission or any other legal * details, please contact * Office of Technology Transfer * Carnegie Mellon University * 5000 Forbes Avenue * Pittsburgh, PA 15213-3890 * (412) 268-4387, fax: (412) 268-7395 * tech-transfer@andrew.cmu.edu * * 4. Redistributions of any form whatsoever must retain the following * acknowledgment: * "This product includes software developed by Computing Services * at Carnegie Mellon University (http://www.cmu.edu/computing/)." * * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * */ #include #ifdef HAVE_UNISTD_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "acl.h" #include "assert.h" #include "util.h" #include "auth.h" #include "prot.h" #include "imparse.h" #include "lock.h" #include "global.h" #include "exitcodes.h" #include "imap_err.h" #include "mailbox.h" #include "xmalloc.h" #include "version.h" #include "duplicate.h" #include "append.h" #include "mboxlist.h" #include "notify.h" #include "idle.h" #include "tls.h" #include "lmtpengine.h" #include "lmtpstats.h" #include #include "AppleOD.h" #ifdef USE_SIEVE #include "lmtp_sieve.h" static sieve_interp_t *sieve_interp = NULL; #endif /* forward declarations */ static int deliver(message_data_t *msgdata, char *authuser, struct auth_state *authstate); static int verify_user(const char *user, const char *domain, const char *mailbox, long quotacheck, struct auth_state *authstate); static char *generate_notify(message_data_t *m); void shut_down(int code); static FILE *spoolfile(message_data_t *msgdata); static void removespool(message_data_t *msgdata); /* current namespace */ static struct namespace lmtpd_namespace; /* current user mail options */ extern struct od_user_opts *gUserOpts; extern char *gLUser_relay_str; struct lmtp_func mylmtp = { &deliver, &verify_user, &shut_down, &spoolfile, &removespool, &lmtpd_namespace, 0, 1, 0 }; static void usage(); /* global state */ const int config_need_data = CONFIG_NEED_PARTITION_DATA; extern int optind; extern char *optarg; static int dupelim = 1; /* eliminate duplicate messages with same message-id */ static int singleinstance = 1; /* attempt single instance store */ const char *BB = ""; struct stagemsg *stage = NULL; /* per-user/session state */ static struct protstream *deliver_out, *deliver_in; int deliver_logfd = -1; /* used in lmtpengine.c */ static struct sasl_callback mysasl_cb[] = { { SASL_CB_GETOPT, &mysasl_config, NULL }, { SASL_CB_PROXY_POLICY, &mysasl_proxy_policy, NULL }, { SASL_CB_CANON_USER, &mysasl_canon_user, NULL }, { SASL_CB_LIST_END, NULL, NULL } }; int service_init(int argc __attribute__((unused)), char **argv __attribute__((unused)), char **envp __attribute__((unused))) { int r; if (geteuid() == 0) return 1; signals_set_shutdown(&shut_down); signal(SIGPIPE, SIG_IGN); singleinstance = config_getswitch(IMAPOPT_SINGLEINSTANCESTORE); BB = config_getstring(IMAPOPT_POSTUSER); global_sasl_init(0, 1, mysasl_cb); dupelim = config_getswitch(IMAPOPT_DUPLICATESUPPRESSION); #ifdef USE_SIEVE mylmtp.addheaders = xzmalloc(2 * sizeof(struct addheader)); mylmtp.addheaders[0].name = "X-Sieve"; mylmtp.addheaders[0].body = SIEVE_VERSION; /* setup sieve support */ sieve_interp = setup_sieve(); #else if (dupelim) #endif { /* initialize duplicate delivery database */ if (duplicate_init(NULL, 0) != 0) { fatal("lmtpd: unable to init duplicate delivery database", EC_SOFTWARE); } } /* so we can do mboxlist operations */ mboxlist_init(0); mboxlist_open(NULL); /* so we can do quota operations */ quotadb_init(0); quotadb_open(NULL); /* setup for sending IMAP IDLE notifications */ idle_enabled(); /* Set namespace */ if ((r = mboxname_init_namespace(&lmtpd_namespace, 0)) != 0) { syslog(LOG_ERR, error_message(r)); fatal(error_message(r), EC_CONFIG); } /* create connection to the SNMP listener, if available. */ snmp_connect(); /* ignore return code */ snmp_set_str(SERVER_NAME_VERSION, CYRUS_VERSION); return 0; } /* * run for each accepted connection */ int service_main(int argc, char **argv, char **envp __attribute__((unused))) { int opt; deliver_in = prot_new(0, 0); deliver_out = prot_new(1, 1); prot_setflushonread(deliver_in, deliver_out); prot_settimeout(deliver_in, 360); while ((opt = getopt(argc, argv, "a")) != EOF) { switch(opt) { case 'a': mylmtp.preauth = 1; break; default: usage(); } } snmp_increment(TOTAL_CONNECTIONS, 1); snmp_increment(ACTIVE_CONNECTIONS, 1); if ( gUserOpts == NULL ) { gUserOpts = xzmalloc( sizeof(struct od_user_opts) ); } lmtpmode(&mylmtp, deliver_in, deliver_out, 0); /* free session state */ if (deliver_in) prot_free(deliver_in); if (deliver_out) prot_free(deliver_out); deliver_in = deliver_out = NULL; if (deliver_logfd != -1) { close(deliver_logfd); deliver_logfd = -1; } cyrus_reset_stdio(); return 0; } /* Called by service API to shut down the service */ void service_abort(int error) { shut_down(error); } static void usage() { fprintf(stderr, "421-4.3.0 usage: lmtpd [-C ] [-a]\r\n"); fprintf(stderr, "421 4.3.0 %s\n", CYRUS_VERSION); exit(EC_USAGE); } /* places msg in mailbox mailboxname. * if you wish to use single instance store, pass stage as non-NULL * if you want to deliver message regardless of duplicates, pass id as NULL * if you want to notify, pass user * if you want to force delivery (to force delivery to INBOX, for instance) * pass acloverride */ int deliver_mailbox(struct protstream *msg, struct stagemsg *stage, unsigned size __attribute__((unused)), char **flag, int nflags, char *authuser, struct auth_state *authstate, char *id, const char *user, char *notifyheader, const char *mailboxname, int quotaoverride, int acloverride) { int r; struct appendstate as; time_t now = time(NULL); unsigned long uid; const char *notifier; if (dupelim && id && duplicate_check(id, strlen(id), mailboxname, strlen(mailboxname))) { /* duplicate message */ duplicate_log(id, mailboxname, "delivery"); return 0; } /* alloc global user opts struct if not already */ r = append_setup(&as, mailboxname, MAILBOX_FORMAT_NORMAL, authuser, authstate, acloverride ? 0 : ACL_POST, quotaoverride ? -1 : 0); if (!r) { prot_rewind(msg); r = append_fromstage(&as, stage, now, (const char **) flag, nflags, !singleinstance); if (!r) append_commit(&as, quotaoverride ? -1 : 0, NULL, &uid, NULL); else append_abort(&as); } if (user && *user != '@') { char *tmpName = xmalloc( strlen( user ) + 1 ); strlcpy( tmpName, user, strlen( user ) + 1 ); mboxname_hiersep_toexternal( &lmtpd_namespace, tmpName, 0); odGetUserOpts( tmpName, gUserOpts ); free( tmpName ); mboxname_hiersep_tointernal(&lmtpd_namespace, gUserOpts->fRecNamePtr, config_virtdomains ? strcspn(user, "@") : 0); } else { if ( user != NULL ) { if ( gUserOpts->fRecNamePtr != NULL ) { free( gUserOpts->fRecNamePtr ); gUserOpts->fRecNamePtr = NULL; } gUserOpts->fRecNamePtr = xmalloc( strlen( user ) + 1 ); strlcpy( gUserOpts->fRecNamePtr, user, strlen( user ) + 1 ); } } if (!r && gUserOpts->fRecNamePtr && (notifier = config_getstring(IMAPOPT_MAILNOTIFIER))) { char inbox[MAX_MAILBOX_NAME+1]; char namebuf[MAX_MAILBOX_NAME+1]; char userbuf[MAX_MAILBOX_NAME+1]; const char *notify_mailbox = mailboxname; int r2; /* translate user.foo to INBOX */ if (!(*lmtpd_namespace.mboxname_tointernal)(&lmtpd_namespace, "INBOX", gUserOpts->fRecNamePtr, inbox)) { int inboxlen = strlen(inbox); if (strlen(mailboxname) >= inboxlen && !strncmp(mailboxname, inbox, inboxlen) && (!mailboxname[inboxlen] || mailboxname[inboxlen] == '.')) { strlcpy(inbox, "INBOX", sizeof(inbox)); strlcat(inbox, mailboxname+inboxlen, sizeof(inbox)); notify_mailbox = inbox; } } /* translate mailboxname */ r2 = (*lmtpd_namespace.mboxname_toexternal)(&lmtpd_namespace, notify_mailbox, gUserOpts->fRecNamePtr, namebuf); if (!r2) { strlcpy(userbuf, gUserOpts->fRecNamePtr, sizeof(userbuf)); /* translate any separators in user */ mboxname_hiersep_toexternal(&lmtpd_namespace, userbuf, config_virtdomains ? strcspn(userbuf, "@") : 0); notify(notifier, "MAIL", NULL, userbuf, namebuf, 0, NULL, notifyheader ? notifyheader : ""); } } if (!r && dupelim && id) duplicate_mark(id, strlen(id), mailboxname, strlen(mailboxname), now, uid); return r; } static int auto_forward ( const char *forwardto, char *return_path, struct protstream *file ) { FILE *sm; const char *smbuf[10]; int sm_stat; char buf[1024]; pid_t sm_pid; int body = 0, skip; smbuf[0] = "sendmail"; smbuf[1] = "-i"; /* ignore dots */ if ( return_path && *return_path ) { smbuf[2] = "-f"; smbuf[3] = return_path; } else { smbuf[2] = "-f"; smbuf[3] = "<>"; } smbuf[4] = "--"; smbuf[5] = forwardto; smbuf[6] = NULL; sm_pid = open_sendmail(smbuf, &sm); if (sm == NULL) { return -1; } prot_rewind(file); while (prot_fgets(buf, sizeof(buf), file)) { if (!body && buf[0] == '\r' && buf[1] == '\n') { /* blank line between header and body */ body = 1; } skip = 0; if (!body) { if (!strncasecmp(buf, "Return-Path:", 12)) { /* strip the Return-Path */ skip = 1; } } do { if (!skip) fwrite(buf, strlen(buf), 1, sm); } while (buf[strlen(buf)-1] != '\n' && prot_fgets(buf, sizeof(buf), file)); } fclose(sm); while (waitpid(sm_pid, &sm_stat, 0) < 0); return sm_stat; /* sendmail exit value */ } /* auto_forward */ int deliver(message_data_t *msgdata, char *authuser, struct auth_state *authstate) { int n, nrcpts; char *notifyheader; #ifdef USE_SIEVE sieve_msgdata_t mydata; #endif assert(msgdata); nrcpts = msg_getnumrcpt(msgdata); assert(nrcpts); notifyheader = generate_notify(msgdata); #ifdef USE_SIEVE /* create 'mydata', our per-delivery data */ mydata.m = msgdata; mydata.stage = stage; mydata.notifyheader = notifyheader; mydata.namespace = &lmtpd_namespace; mydata.authuser = authuser; mydata.authstate = authstate; #endif /* loop through each recipient, attempting delivery for each */ for (n = 0; n < nrcpts; n++) { char namebuf[MAX_MAILBOX_NAME+1] = ""; const char *user, *domain, *mailbox, *auto_fwd = NULL; int quotaoverride = msg_getrcpt_ignorequota(msgdata, n); int r = 0; msg_getrcpt(msgdata, n, &user, &domain, &mailbox, &auto_fwd); if (domain) snprintf(namebuf, sizeof(namebuf), "%s!", domain); /* case 1: shared mailbox request */ if (!user) { strlcat(namebuf, mailbox, sizeof(namebuf)); r = deliver_mailbox(msgdata->data, stage, msgdata->size, NULL, 0, authuser, authstate, msgdata->id, NULL, notifyheader, namebuf, quotaoverride, 0); } /* case 2: auto-forward */ else if ( auto_fwd ) { syslog( LOG_DEBUG, "auto-forwarding user: %s to: %s", user, auto_fwd ); r = auto_forward( auto_fwd, msgdata->return_path, msgdata->data ); } /* case 3: ordinary user, might have Sieve script */ else { char userbuf[MAX_MAILBOX_NAME+1]; char *tail; strlcat(namebuf, "user.", sizeof(namebuf)); strlcat(namebuf, user, sizeof(namebuf)); tail = namebuf + strlen(namebuf); strlcpy(userbuf, user, sizeof(userbuf)); if (domain) { strlcat(userbuf, "@", sizeof(userbuf)); strlcat(userbuf, domain, sizeof(userbuf)); } #ifdef USE_SIEVE mydata.cur_rcpt = n; r = run_sieve(user, domain, mailbox, sieve_interp, &mydata); /* if there was no sieve script, or an error during execution, r is non-zero and we'll do normal delivery */ #else r = 1; /* normal delivery */ #endif if (r && mailbox) { /* normal delivery to + mailbox */ strlcat(namebuf, ".", sizeof(namebuf)); strlcat(namebuf, mailbox, sizeof(namebuf)); r = deliver_mailbox(msgdata->data, stage, msgdata->size, NULL, 0, authuser, authstate, msgdata->id, userbuf, notifyheader, namebuf, quotaoverride, 0); } if (r) { /* normal delivery to INBOX */ *tail = '\0'; /* ignore ACL's trying to deliver to INBOX */ r = deliver_mailbox(msgdata->data, stage, msgdata->size, NULL, 0, authuser, authstate, msgdata->id, userbuf, notifyheader, namebuf, quotaoverride, 1); } } msg_setrcpt_status(msgdata, n, r); } append_removestage(stage); stage = NULL; if (notifyheader) free(notifyheader); return 0; } void fatal(const char* s, int code) { static int recurse_code = 0; if(recurse_code) { /* We were called recursively. Just give up */ snmp_increment(ACTIVE_CONNECTIONS, -1); exit(recurse_code); } recurse_code = code; if(deliver_out) { prot_printf(deliver_out,"421 4.3.0 lmtpd: %s\r\n", s); prot_flush(deliver_out); } if (stage) append_removestage(stage); syslog(LOG_ERR, "FATAL: %s", s); /* shouldn't return */ shut_down(code); exit(code); } /* * Cleanly shut down and exit */ void shut_down(int code) __attribute__((noreturn)); void shut_down(int code) { #ifdef USE_SIEVE sieve_interp_free(&sieve_interp); #else if (dupelim) #endif duplicate_done(); mboxlist_close(); mboxlist_done(); quotadb_close(); quotadb_done(); if (gUserOpts) { odFreeUserOpts(gUserOpts, 1); free(gUserOpts); gUserOpts = NULL; } if(gLUser_relay_str != NULL){ free(gLUser_relay_str); gLUser_relay_str=NULL; } #ifdef HAVE_SSL tls_shutdown_serverengine(); #endif if (deliver_out) { prot_flush(deliver_out); /* one less active connection */ snmp_increment(ACTIVE_CONNECTIONS, -1); } cyrus_done(); exit(code); } static int verify_user(const char *user, const char *domain, const char *mailbox, long quotacheck, struct auth_state *authstate) { char namebuf[MAX_MAILBOX_NAME+1] = ""; int r = 0; if ( gUserOpts == NULL ) { gUserOpts = xzmalloc( sizeof(struct od_user_opts) ); } if ((!user && !mailbox) || (domain && (strlen(domain) + 1 > sizeof(namebuf)))) { r = IMAP_MAILBOX_NONEXISTENT; } else { /* construct the mailbox that we will verify */ if (domain) snprintf(namebuf, sizeof(namebuf), "%s!", domain); if (!user) { /* shared folder */ if (strlen(namebuf) + strlen(mailbox) > sizeof(namebuf)) { r = IMAP_MAILBOX_NONEXISTENT; } else { strlcat(namebuf, mailbox, sizeof(namebuf)); } } else { /* Translate any separators in mailboxname */ if ( gUserOpts->fRecNamePtr == NULL ) { char *tmpName = xmalloc( strlen( user ) + 1 ); strlcpy( tmpName, user, strlen( user ) + 1 ); mboxname_hiersep_toexternal( &lmtpd_namespace, tmpName, 0); odGetUserOpts( tmpName, gUserOpts ); free( tmpName ); } if ( !(gUserOpts->fAccountState & eAccountEnabled) && !(gUserOpts->fAccountState & eAutoForwardedEnabled) ) { if ( gUserOpts->fAccountState & eACLNotMember ) { syslog( LOG_WARNING, "warning: unable to post message for user: %s, service ACL is not enabled for this user", user ); } else { syslog( LOG_WARNING, "warning: unable to post message for user: %s, mail is not enabled for this user", user ); } r = IMAP_MAILBOX_NONEXISTENT; } else { /* ordinary user -- check INBOX */ if (strlen(namebuf) + 5 + strlen(user) > sizeof(namebuf)) { r = IMAP_MAILBOX_NONEXISTENT; } else { strlcat(namebuf, "user.", sizeof(namebuf)); /* check for dotted record name and create temp converted string to create mailbox with */ if ( strchr( gUserOpts->fRecNamePtr, '.' ) ) { char *tmpStr = xstrdup( gUserOpts->fRecNamePtr ); if ( tmpStr != NULL ) { mboxname_hiersep_tointernal( &lmtpd_namespace, tmpStr, 0 ); strlcat(namebuf, tmpStr, sizeof(namebuf)); free( tmpStr ); } else { strlcat(namebuf, gUserOpts->fRecNamePtr, sizeof(namebuf)); } } else { strlcat(namebuf, gUserOpts->fRecNamePtr, sizeof(namebuf)); } } } } } if (!r) { /* * check to see if mailbox exists and we can append to it: * * - must have posting privileges on shared folders * - don't care about ACL on INBOX (always allow post) * - don't care about message size (1 msg over quota allowed) */ r = append_check(namebuf, MAILBOX_FORMAT_NORMAL, authstate, !user ? ACL_POST : 0, quotacheck > 0 ? 0 : quotacheck); /* Create the INBOX if it doesn't exist */ if ( r == IMAP_MAILBOX_NONEXISTENT ) { char *partition = NULL; if ( gUserOpts->fAltDataLocPtr == NULL ) { partition = gUserOpts->fAltDataLocPtr; } r = mboxlist_createmailbox( namebuf, MAILBOX_FORMAT_NORMAL, partition, 1, (char *)gUserOpts->fRecNamePtr, authstate, 0, 0, 0 ); } /* set any quotas */ if ( gUserOpts->fDiskQuota == 0 ) { /* make sure that quotas are set so that the /quota tool works */ mboxlist_setquota( namebuf, INT32_MAX, 0 ); } else { mboxlist_setquota( namebuf, gUserOpts->fDiskQuota * 1024, 0 ); } } if ( gUserOpts->fAccountState & eAutoForwardedEnabled ) { if ( (gUserOpts->fAutoFwdPtr == NULL) || (strlen( gUserOpts->fAutoFwdPtr ) == 0) ) { syslog( LOG_WARNING, "warning: invalid auto-forward address for user: %s", user ); r = IMAP_MAILBOX_NONEXISTENT; } else { syslog( LOG_INFO, "forwarding message for: %s to: %s", user, gUserOpts->fAutoFwdPtr ); r = IMAP_AUTO_FORWARD_USER; } } if (r && (r != IMAP_AUTO_FORWARD_USER)) syslog(LOG_DEBUG, "verify_user(%s) failed: %s", namebuf, error_message(r)); return r; } const char *notifyheaders[] = { "From", "Subject", "To", 0 }; /* returns a malloc'd string that should be sent to users for successful delivery of 'm'. */ char *generate_notify(message_data_t *m) { const char **body; char *ret = NULL; unsigned int len = 0; unsigned int pos = 0; int i; for (i = 0; notifyheaders[i]; i++) { const char *h = notifyheaders[i]; body = msg_getheader(m, h); if (body) { int j; for (j = 0; body[j] != NULL; j++) { /* put the header */ /* need: length + ": " + '\0'*/ while (pos + strlen(h) + 3 > len) { ret = xrealloc(ret, len += 1024); } pos += sprintf(ret + pos, "%s: ", h); /* put the header body. xxx it would be nice to linewrap.*/ /* need: length + '\n' + '\0' */ while (pos + strlen(body[j]) + 2 > len) { ret = xrealloc(ret, len += 1024); } pos += sprintf(ret + pos, "%s\n", body[j]); } } } return ret; } FILE *spoolfile(message_data_t *msgdata) { int i, n; time_t now = time(NULL); FILE *f = NULL; /* spool to the stage of one of the recipients */ n = msg_getnumrcpt(msgdata); for (i = 0; !f && (i < n); i++) { int r = 0; char namebuf[MAX_MAILBOX_NAME+1] = ""; const char *user, *domain, *mailbox; /* build the mailboxname from the recipient address */ msg_getrcpt(msgdata, i, &user, &domain, &mailbox, NULL); if (domain) snprintf(namebuf, sizeof(namebuf), "%s!", domain); /* case 1: shared mailbox request */ if (!user) { strlcat(namebuf, mailbox, sizeof(namebuf)); } /* case 2: ordinary user */ else { /* assume delivery to INBOX for now */ strlcat(namebuf, "user.", sizeof(namebuf)); strlcat(namebuf, user, sizeof(namebuf)); } #if 0 /* case 3: unable to handle rcpt */ else { /* force error and we'll fallback to using /tmp */ r = 1; } #endif if (!r) { /* setup stage for later use by deliver() */ f = append_newstage(namebuf, now, 0, &stage); } } return f; } void removespool(message_data_t *msgdata __attribute__((unused))) { append_removestage(stage); stage = NULL; }