int deliver_maildir(LOCAL_STATE state, USER_ATTR usr_attr) { const char *myname = "deliver_maildir"; char *newdir; char *tmpdir; char *curdir; char *tmpfile; char *newfile; DSN_BUF *why = state.msg_attr.why; VSTRING *buf; VSTREAM *dst; int mail_copy_status; int deliver_status; int copy_flags; struct stat st; struct timeval starttime; GETTIMEOFDAY(&starttime); /* * Make verbose logging easier to understand. */ state.level++; if (msg_verbose) MSG_LOG_STATE(myname, state); /* * Don't deliver trace-only requests. */ if (DEL_REQ_TRACE_ONLY(state.request->flags)) { dsb_simple(why, "2.0.0", "delivers to maildir"); return (sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr))); } /* * Initialize. Assume the operation will fail. Set the delivered * attribute to reflect the final recipient. */ if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0) msg_fatal("seek message file %s: %m", VSTREAM_PATH(state.msg_attr.fp)); state.msg_attr.delivered = state.msg_attr.rcpt.address; mail_copy_status = MAIL_COPY_STAT_WRITE; buf = vstring_alloc(100); copy_flags = MAIL_COPY_TOFILE | MAIL_COPY_RETURN_PATH | MAIL_COPY_DELIVERED | MAIL_COPY_ORIG_RCPT; newdir = concatenate(usr_attr.mailbox, "new/", (char *) 0); tmpdir = concatenate(usr_attr.mailbox, "tmp/", (char *) 0); curdir = concatenate(usr_attr.mailbox, "cur/", (char *) 0); /* * Create and write the file as the recipient, so that file quota work. * Create any missing directories on the fly. The file name is chosen * according to ftp://koobera.math.uic.edu/www/proto/maildir.html: * * "A unique name has three pieces, separated by dots. On the left is the * result of time(). On the right is the result of gethostname(). In the * middle is something that doesn't repeat within one second on a single * host. I fork a new process for each delivery, so I just use the * process ID. If you're delivering several messages from one process, * use starttime.pid_count.host, where starttime is the time that your * process started, and count is the number of messages you've * delivered." * * Well, that stopped working on fast machines, and on operating systems * that randomize process ID values. When creating a file in tmp/ we use * the process ID because it still is an exclusive resource. When moving * the file to new/ we use the device number and inode number. I do not * care if this breaks on a remote AFS file system, because people should * know better. * * On January 26, 2003, http://cr.yp.to/proto/maildir.html said: * * A unique name has three pieces, separated by dots. On the left is the * result of time() or the second counter from gettimeofday(). On the * right is the result of gethostname(). (To deal with invalid host * names, replace / with \057 and : with \072.) In the middle is a * delivery identifier, discussed below. * * [...] * * Modern delivery identifiers are created by concatenating enough of the * following strings to guarantee uniqueness: * * [...] * * In, where n is (in hexadecimal) the UNIX inode number of this file. * Unfortunately, inode numbers aren't always available through NFS. * * Vn, where n is (in hexadecimal) the UNIX device number of this file. * Unfortunately, device numbers aren't always available through NFS. * (Device numbers are also not helpful with the standard UNIX * filesystem: a maildir has to be within a single UNIX device for link() * and rename() to work.) * * Mn, where n is (in decimal) the microsecond counter from the same * gettimeofday() used for the left part of the unique name. * * Pn, where n is (in decimal) the process ID. * * [...] */ set_eugid(usr_attr.uid, usr_attr.gid); vstring_sprintf(buf, "%lu.P%d.%s", (unsigned long) starttime.tv_sec, var_pid, get_hostname()); tmpfile = concatenate(tmpdir, STR(buf), (char *) 0); newfile = 0; if ((dst = vstream_fopen(tmpfile, O_WRONLY | O_CREAT | O_EXCL, 0600)) == 0 && (errno != ENOENT || make_dirs(tmpdir, 0700) < 0 || (dst = vstream_fopen(tmpfile, O_WRONLY | O_CREAT | O_EXCL, 0600)) == 0)) { dsb_simple(why, mbox_dsn(errno, "4.2.0"), "create maildir file %s: %m", tmpfile); } else if (fstat(vstream_fileno(dst), &st) < 0) { /* * Coverity 200604: file descriptor leak in code that never executes. * Code replaced by msg_fatal(), as it is not worthwhile to continue * after an impossible error condition. */ msg_fatal("fstat %s: %m", tmpfile); } else { vstring_sprintf(buf, "%lu.V%lxI%lxM%lu.%s", (unsigned long) starttime.tv_sec, (unsigned long) st.st_dev, (unsigned long) st.st_ino, (unsigned long) starttime.tv_usec, get_hostname()); newfile = concatenate(newdir, STR(buf), (char *) 0); if ((mail_copy_status = mail_copy(COPY_ATTR(state.msg_attr), dst, copy_flags, "\n", why)) == 0) { if (sane_link(tmpfile, newfile) < 0 && (errno != ENOENT || (make_dirs(curdir, 0700), make_dirs(newdir, 0700)) < 0 || sane_link(tmpfile, newfile) < 0)) { dsb_simple(why, mbox_dsn(errno, "4.2.0"), "create maildir file %s: %m", newfile); mail_copy_status = MAIL_COPY_STAT_WRITE; } } if (unlink(tmpfile) < 0) msg_warn("remove %s: %m", tmpfile); } set_eugid(var_owner_uid, var_owner_gid); /* * The maildir location is controlled by the mail administrator. If * delivery fails, try again later. We would just bounce when the maildir * location possibly under user control. */ if (mail_copy_status & MAIL_COPY_STAT_CORRUPT) { deliver_status = DEL_STAT_DEFER; } else if (mail_copy_status != 0) { if (errno == EACCES) { msg_warn("maildir access problem for UID/GID=%lu/%lu: %s", (long) usr_attr.uid, (long) usr_attr.gid, STR(why->reason)); msg_warn("perhaps you need to create the maildirs in advance"); } vstring_sprintf_prepend(why->reason, "maildir delivery failed: "); deliver_status = (STR(why->status)[0] == '4' ? defer_append : bounce_append) (BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); } else { dsb_simple(why, "2.0.0", "delivered to maildir"); deliver_status = sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr)); } vstring_free(buf); myfree(newdir); myfree(tmpdir); myfree(curdir); myfree(tmpfile); if (newfile) myfree(newfile); return (deliver_status); }
static const char *dict_sockmap_lookup(DICT *dict, const char *key) { const char *myname = "dict_sockmap_lookup"; DICT_SOCKMAP *dp = (DICT_SOCKMAP *) dict; AUTO_CLNT *sockmap_clnt = DICT_SOCKMAP_RH_HANDLE(dp->client_info); VSTREAM *fp; int netstring_err; char *reply_payload; int except_count; const char *error_class; if (msg_verbose) msg_info("%s: key %s", myname, key); /* * Optionally fold the key. */ if (dict->flags & DICT_FLAG_FOLD_MUL) { if (dict->fold_buf == 0) dict->fold_buf = vstring_alloc(100); vstring_strcpy(dict->fold_buf, key); key = lowercase(STR(dict->fold_buf)); } /* * We retry connection-level errors once, to make server restarts * transparent. */ for (except_count = 0; /* see below */ ; except_count++) { /* * Look up the stream. */ if ((fp = auto_clnt_access(sockmap_clnt)) == 0) { msg_warn("table %s:%s lookup error: %m", dict->type, dict->name); dict->error = DICT_ERR_RETRY; return (0); } /* * Set up an exception handler. */ netstring_setup(fp, dict_sockmap_timeout); if ((netstring_err = vstream_setjmp(fp)) == 0) { /* * Send the query. This may raise an exception. */ vstring_sprintf(dp->rdwr_buf, "%s %s", dp->sockmap_name, key); NETSTRING_PUT_BUF(fp, dp->rdwr_buf); /* * Receive the response. This may raise an exception. */ netstring_get(fp, dp->rdwr_buf, dict_sockmap_max_reply); /* * If we got here, then no exception was raised. */ break; } /* * Handle exceptions. */ else { /* * We retry a broken connection only once. */ if (except_count == 0 && netstring_err == NETSTRING_ERR_EOF && errno != ETIMEDOUT) { auto_clnt_recover(sockmap_clnt); continue; } /* * We do not retry other errors. */ else { msg_warn("table %s:%s lookup error: %s", dict->type, dict->name, netstring_strerror(netstring_err)); dict->error = DICT_ERR_RETRY; return (0); } } } /* * Parse the reply. */ VSTRING_TERMINATE(dp->rdwr_buf); reply_payload = split_at(STR(dp->rdwr_buf), ' '); if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_OK) == 0) { dict->error = 0; return (reply_payload); } else if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_NOTFOUND) == 0) { dict->error = 0; return (0); } /* We got no definitive reply. */ if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_TEMP) == 0) { error_class = "temporary"; dict->error = DICT_ERR_RETRY; } else if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_TIMEOUT) == 0) { error_class = "timeout"; dict->error = DICT_ERR_RETRY; } else if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_PERM) == 0) { error_class = "permanent"; dict->error = DICT_ERR_CONFIG; } else { error_class = "unknown"; dict->error = DICT_ERR_RETRY; } while (reply_payload && ISSPACE(*reply_payload)) reply_payload++; msg_warn("%s:%s socketmap server %s error%s%.200s", dict->type, dict->name, error_class, reply_payload && *reply_payload ? ": " : "", reply_payload && *reply_payload ? printable(reply_payload, '?') : ""); return (0); }
static int deliver_mailbox_file(LOCAL_STATE state, USER_ATTR usr_attr) { const char *myname = "deliver_mailbox_file"; char *spool_dir; char *mailbox; DSN_BUF *why = state.msg_attr.why; MBOX *mp; int mail_copy_status; int deliver_status; int copy_flags; VSTRING *biff; long end; struct stat st; uid_t spool_uid; gid_t spool_gid; uid_t chown_uid; gid_t chown_gid; /* * Make verbose logging easier to understand. */ state.level++; if (msg_verbose) MSG_LOG_STATE(myname, state); /* * Don't deliver trace-only requests. */ if (DEL_REQ_TRACE_ONLY(state.request->flags)) { dsb_simple(why, "2.0.0", "delivers to mailbox"); return (sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr))); } /* * Initialize. Assume the operation will fail. Set the delivered * attribute to reflect the final recipient. */ if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0) msg_fatal("seek message file %s: %m", VSTREAM_PATH(state.msg_attr.fp)); if (var_frozen_delivered == 0) state.msg_attr.delivered = state.msg_attr.rcpt.address; mail_copy_status = MAIL_COPY_STAT_WRITE; if (*var_home_mailbox) { spool_dir = 0; mailbox = concatenate(usr_attr.home, "/", var_home_mailbox, (char *) 0); } else { spool_dir = var_mail_spool_dir; mailbox = concatenate(spool_dir, "/", state.msg_attr.user, (char *) 0); } /* * Mailbox delivery with least privilege. As long as we do not use root * privileges this code may also work over NFS. * * If delivering to the recipient's home directory, perform all operations * (including file locking) as that user (Mike Muuss, Army Research * Laboratory, USA). * * If delivering to the mail spool directory, and the spool directory is * world-writable, deliver as the recipient; if the spool directory is * group-writable, use the recipient user id and the mail spool group id. * * Otherwise, use root privileges and chown the mailbox. */ if (spool_dir == 0 || stat(spool_dir, &st) < 0 || (st.st_mode & S_IWOTH) != 0) { spool_uid = usr_attr.uid; spool_gid = usr_attr.gid; } else if ((st.st_mode & S_IWGRP) != 0) { spool_uid = usr_attr.uid; spool_gid = st.st_gid; } else { spool_uid = 0; spool_gid = 0; } if (spool_uid == usr_attr.uid) { chown_uid = -1; chown_gid = -1; } else { chown_uid = usr_attr.uid; chown_gid = usr_attr.gid; } if (msg_verbose) msg_info("spool_uid/gid %ld/%ld chown_uid/gid %ld/%ld", (long) spool_uid, (long) spool_gid, (long) chown_uid, (long) chown_gid); /* * Lock the mailbox and open/create the mailbox file. Depending on the * type of locking used, we lock first or we open first. * * Write the file as the recipient, so that file quota work. */ copy_flags = MAIL_COPY_MBOX; if ((local_deliver_hdr_mask & DELIVER_HDR_FILE) == 0) copy_flags &= ~MAIL_COPY_DELIVERED; set_eugid(spool_uid, spool_gid); mp = mbox_open(mailbox, O_APPEND | O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR, &st, chown_uid, chown_gid, local_mbox_lock_mask, "5.2.0", why); if (mp != 0) { if (spool_uid != usr_attr.uid || spool_gid != usr_attr.gid) set_eugid(usr_attr.uid, usr_attr.gid); if (S_ISREG(st.st_mode) == 0) { vstream_fclose(mp->fp); dsb_simple(why, "5.2.0", "destination %s is not a regular file", mailbox); } else if (var_strict_mbox_owner && st.st_uid != usr_attr.uid) { vstream_fclose(mp->fp); dsb_simple(why, "4.2.0", "destination %s is not owned by recipient", mailbox); msg_warn("specify \"%s = no\" to ignore mailbox ownership mismatch", VAR_STRICT_MBOX_OWNER); } else { end = vstream_fseek(mp->fp, (off_t) 0, SEEK_END); mail_copy_status = mail_copy(COPY_ATTR(state.msg_attr), mp->fp, copy_flags, "\n", why); } if (spool_uid != usr_attr.uid || spool_gid != usr_attr.gid) set_eugid(spool_uid, spool_gid); mbox_release(mp); } set_eugid(var_owner_uid, var_owner_gid); /* * As the mail system, bounce, defer delivery, or report success. */ if (mail_copy_status & MAIL_COPY_STAT_CORRUPT) { deliver_status = DEL_STAT_DEFER; } else if (mail_copy_status != 0) { vstring_sprintf_prepend(why->reason, "cannot update mailbox %s for user %s. ", mailbox, state.msg_attr.user); deliver_status = (STR(why->status)[0] == '4' ? defer_append : bounce_append) (BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); } else { dsb_simple(why, "2.0.0", "delivered to mailbox"); deliver_status = sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr)); if (var_biff) { biff = vstring_alloc(100); vstring_sprintf(biff, "%s@%ld", usr_attr.logname, (long) end); biff_notify(STR(biff), VSTRING_LEN(biff) + 1); vstring_free(biff); } } /* * Clean up. */ myfree(mailbox); return (deliver_status); }
static void resolve_addr(RES_CONTEXT *rp, char *addr, VSTRING *channel, VSTRING *nexthop, VSTRING *nextrcpt, int *flags) { char *myname = "resolve_addr"; VSTRING *addr_buf = vstring_alloc(100); TOK822 *tree = 0; TOK822 *saved_domain = 0; TOK822 *domain = 0; char *destination; const char *blame = 0; const char *rcpt_domain; int addr_len; int loop_count; int loop_max; char *local; char *oper; char *junk; *flags = 0; vstring_strcpy(channel, "CHANNEL NOT UPDATED"); vstring_strcpy(nexthop, "NEXTHOP NOT UPDATED"); vstring_strcpy(nextrcpt, "NEXTRCPT NOT UPDATED"); /* * The address is in internalized (unquoted) form. * * In an ideal world we would parse the externalized address form as given * to us by the sender. * * However, in the real world we have to look for routing characters like * %@! in the address local-part, even when that information is quoted * due to the presence of special characters or whitespace. Although * technically incorrect, this is needed to stop user@domain@domain relay * attempts when forwarding mail to a Sendmail MX host. * * This suggests that we parse the address in internalized (unquoted) form. * Unfortunately, if we do that, the unparser generates incorrect white * space between adjacent non-operator tokens. Example: ``first last'' * needs white space, but ``stuff[stuff]'' does not. This is is not a * problem when unparsing the result from parsing externalized forms, * because the parser/unparser were designed for valid externalized forms * where ``stuff[stuff]'' does not happen. * * As a workaround we start with the quoted form and then dequote the * local-part only where needed. This will do the right thing in most * (but not all) cases. */ addr_len = strlen(addr); quote_822_local(addr_buf, addr); tree = tok822_scan_addr(vstring_str(addr_buf)); /* * Let the optimizer replace multiple expansions of this macro by a GOTO * to a single instance. */ #define FREE_MEMORY_AND_RETURN { \ if (saved_domain) \ tok822_free_tree(saved_domain); \ if(tree) \ tok822_free_tree(tree); \ if (addr_buf) \ vstring_free(addr_buf); \ return; \ } /* * Preliminary resolver: strip off all instances of the local domain. * Terminate when no destination domain is left over, or when the * destination domain is remote. * * XXX To whom it may concern. If you change the resolver loop below, or * quote_822_local.c, or tok822_parse.c, be sure to re-run the tests * under "make resolve_clnt_test" in the global directory. */ #define RESOLVE_LOCAL(domain) \ resolve_local(STR(tok822_internalize(addr_buf, domain, TOK822_STR_DEFL))) dict_errno = 0; for (loop_count = 0, loop_max = addr_len + 100; /* void */ ; loop_count++) { /* * Grr. resolve_local() table lookups may fail. It may be OK for * local file lookup code to abort upon failure, but with * network-based tables it is preferable to return an error * indication to the requestor. */ if (dict_errno) { *flags |= RESOLVE_FLAG_FAIL; FREE_MEMORY_AND_RETURN; } /* * XXX Should never happen, but if this happens with some * pathological address, then that is not sufficient reason to * disrupt the operation of an MTA. */ if (loop_count > loop_max) { msg_warn("resolve_addr: <%s>: giving up after %d iterations", addr, loop_count); break; } /* * Strip trailing dot at end of domain, but not dot-dot or at-dot. * This merely makes diagnostics more accurate by leaving bogus * addresses alone. */ if (tree->tail && tree->tail->type == '.' && tok822_rfind_type(tree->tail, '@') != 0 && tree->tail->prev->type != '.' && tree->tail->prev->type != '@') tok822_free_tree(tok822_sub_keep_before(tree, tree->tail)); /* * Strip trailing @. */ if (var_resolve_nulldom && tree->tail && tree->tail->type == '@') tok822_free_tree(tok822_sub_keep_before(tree, tree->tail)); /* * Strip (and save) @domain if local. */ if ((domain = tok822_rfind_type(tree->tail, '@')) != 0) { if (domain->next && RESOLVE_LOCAL(domain->next) == 0) break; tok822_sub_keep_before(tree, domain); if (saved_domain) tok822_free_tree(saved_domain); saved_domain = domain; domain = 0; /* safety for future change */ } /* * After stripping the local domain, if any, replace foo%bar by * foo@bar, site!user by user@site, rewrite to canonical form, and * retry. */ if (tok822_rfind_type(tree->tail, '@') || (var_swap_bangpath && tok822_rfind_type(tree->tail, '!')) || (var_percent_hack && tok822_rfind_type(tree->tail, '%'))) { rewrite_tree(REWRITE_CANON, tree); continue; } /* * If the local-part is a quoted string, crack it open when we're * permitted to do so and look for routing operators. This is * technically incorrect, but is needed to stop relaying problems. * * XXX Do another feeble attempt to keep local-part info quoted. */ if (var_resolve_dequoted && tree->head && tree->head == tree->tail && tree->head->type == TOK822_QSTRING && ((oper = strrchr(local = STR(tree->head->vstr), '@')) != 0 || (var_percent_hack && (oper = strrchr(local, '%')) != 0) || (var_swap_bangpath && (oper = strrchr(local, '!')) != 0))) { if (*oper == '%') *oper = '@'; tok822_internalize(addr_buf, tree->head, TOK822_STR_DEFL); if (*oper == '@') { junk = mystrdup(STR(addr_buf)); quote_822_local(addr_buf, junk); myfree(junk); } tok822_free(tree->head); tree->head = tok822_scan(STR(addr_buf), &tree->tail); rewrite_tree(REWRITE_CANON, tree); continue; } /* * An empty local-part or an empty quoted string local-part becomes * the local MAILER-DAEMON, for consistency with our own From: * message headers. */ if (tree->head && tree->head == tree->tail && tree->head->type == TOK822_QSTRING && VSTRING_LEN(tree->head->vstr) == 0) { tok822_free(tree->head); tree->head = 0; } /* XXX must be localpart only, not user@domain form. */ if (tree->head == 0) tree->head = tok822_scan(var_empty_addr, &tree->tail); /* * We're done. There are no domains left to strip off the address, * and all null local-part information is sanitized. */ domain = 0; break; } vstring_free(addr_buf); addr_buf = 0; /* * Make sure the resolved envelope recipient has the user@domain form. If * no domain was specified in the address, assume the local machine. See * above for what happens with an empty address. */ if (domain == 0) { if (saved_domain) { tok822_sub_append(tree, saved_domain); saved_domain = 0; } else { tok822_sub_append(tree, tok822_alloc('@', (char *) 0)); tok822_sub_append(tree, tok822_scan(var_myhostname, (TOK822 **) 0)); } } /* * Transform the recipient address back to internal form. * * XXX This may produce incorrect results if we cracked open a quoted * local-part with routing operators; see discussion above at the top of * the big loop. */ tok822_internalize(nextrcpt, tree, TOK822_STR_DEFL); rcpt_domain = strrchr(STR(nextrcpt), '@') + 1; if (*rcpt_domain == '[' ? !valid_hostliteral(rcpt_domain, DONT_GRIPE) : (!valid_hostname(rcpt_domain, DONT_GRIPE) || valid_hostaddr(rcpt_domain, DONT_GRIPE))) *flags |= RESOLVE_FLAG_ERROR; tok822_free_tree(tree); tree = 0; /* * XXX Short-cut invalid address forms. */ if (*flags & RESOLVE_FLAG_ERROR) { *flags |= RESOLVE_CLASS_DEFAULT; FREE_MEMORY_AND_RETURN; } /* * Recognize routing operators in the local-part, even when we do not * recognize ! or % as valid routing operators locally. This is needed to * prevent backup MX hosts from relaying third-party destinations through * primary MX hosts, otherwise the backup host could end up on black * lists. Ignore local swap_bangpath and percent_hack settings because we * can't know how the next MX host is set up. */ if (strcmp(STR(nextrcpt) + strcspn(STR(nextrcpt), "@!%") + 1, rcpt_domain)) *flags |= RESOLVE_FLAG_ROUTED; /* * With local, virtual, relay, or other non-local destinations, give the * highest precedence to transport associated nexthop information. * * Otherwise, with relay or other non-local destinations, the relayhost * setting overrides the destination domain name. * * XXX Nag if the recipient domain is listed in multiple domain lists. The * result is implementation defined, and may break when internals change. * * For now, we distinguish only a fixed number of address classes. * Eventually this may become extensible, so that new classes can be * configured with their own domain list, delivery transport, and * recipient table. */ #define STREQ(x,y) (strcmp((x), (y)) == 0) dict_errno = 0; if (domain != 0) { /* * Virtual alias domain. */ if (virt_alias_doms && string_list_match(virt_alias_doms, rcpt_domain)) { if (var_helpful_warnings) { if (virt_mailbox_doms && string_list_match(virt_mailbox_doms, rcpt_domain)) msg_warn("do not list domain %s in BOTH %s and %s", rcpt_domain, VAR_VIRT_ALIAS_DOMS, VAR_VIRT_MAILBOX_DOMS); if (relay_domains && domain_list_match(relay_domains, rcpt_domain)) msg_warn("do not list domain %s in BOTH %s and %s", rcpt_domain, VAR_VIRT_ALIAS_DOMS, VAR_RELAY_DOMAINS); #if 0 if (strcasecmp(rcpt_domain, var_myorigin) == 0) msg_warn("do not list $%s (%s) in %s", VAR_MYORIGIN, var_myorigin, VAR_VIRT_ALIAS_DOMS); #endif } vstring_strcpy(channel, MAIL_SERVICE_ERROR); vstring_sprintf(nexthop, "User unknown%s", var_show_unk_rcpt_table ? " in virtual alias table" : ""); *flags |= RESOLVE_CLASS_ALIAS; } else if (dict_errno != 0) { msg_warn("%s lookup failure", VAR_VIRT_ALIAS_DOMS); *flags |= RESOLVE_FLAG_FAIL; FREE_MEMORY_AND_RETURN; } /* * Virtual mailbox domain. */ else if (virt_mailbox_doms && string_list_match(virt_mailbox_doms, rcpt_domain)) { if (var_helpful_warnings) { if (relay_domains && domain_list_match(relay_domains, rcpt_domain)) msg_warn("do not list domain %s in BOTH %s and %s", rcpt_domain, VAR_VIRT_MAILBOX_DOMS, VAR_RELAY_DOMAINS); } vstring_strcpy(channel, RES_PARAM_VALUE(rp->virt_transport)); vstring_strcpy(nexthop, rcpt_domain); blame = rp->virt_transport_name; *flags |= RESOLVE_CLASS_VIRTUAL; } else if (dict_errno != 0) { msg_warn("%s lookup failure", VAR_VIRT_MAILBOX_DOMS); *flags |= RESOLVE_FLAG_FAIL; FREE_MEMORY_AND_RETURN; } else { /* * Off-host relay destination. */ if (relay_domains && domain_list_match(relay_domains, rcpt_domain)) { vstring_strcpy(channel, RES_PARAM_VALUE(rp->relay_transport)); blame = rp->relay_transport_name; *flags |= RESOLVE_CLASS_RELAY; } else if (dict_errno != 0) { msg_warn("%s lookup failure", VAR_RELAY_DOMAINS); *flags |= RESOLVE_FLAG_FAIL; FREE_MEMORY_AND_RETURN; } /* * Other off-host destination. */ else { vstring_strcpy(channel, RES_PARAM_VALUE(rp->def_transport)); blame = rp->def_transport_name; *flags |= RESOLVE_CLASS_DEFAULT; } /* * With off-host delivery, relayhost overrides recipient domain. */ if (*RES_PARAM_VALUE(rp->relayhost)) vstring_strcpy(nexthop, RES_PARAM_VALUE(rp->relayhost)); else vstring_strcpy(nexthop, rcpt_domain); } } /* * Local delivery. * * XXX Nag if the domain is listed in multiple domain lists. The effect is * implementation defined, and may break when internals change. */ else { if (var_helpful_warnings) { if (virt_alias_doms && string_list_match(virt_alias_doms, rcpt_domain)) msg_warn("do not list domain %s in BOTH %s and %s", rcpt_domain, VAR_MYDEST, VAR_VIRT_ALIAS_DOMS); if (virt_mailbox_doms && string_list_match(virt_mailbox_doms, rcpt_domain)) msg_warn("do not list domain %s in BOTH %s and %s", rcpt_domain, VAR_MYDEST, VAR_VIRT_MAILBOX_DOMS); } vstring_strcpy(channel, RES_PARAM_VALUE(rp->local_transport)); vstring_strcpy(nexthop, rcpt_domain); blame = rp->local_transport_name; *flags |= RESOLVE_CLASS_LOCAL; } /* * An explicit main.cf transport:nexthop setting overrides the nexthop. * * XXX We depend on this mechanism to enforce per-recipient concurrencies * for local recipients. With "local_transport = local:$myhostname" we * force mail for any domain in $mydestination/${proxy,inet}_interfaces * to share the same queue. */ if ((destination = split_at(STR(channel), ':')) != 0 && *destination) vstring_strcpy(nexthop, destination); /* * Sanity checks. */ if (*STR(channel) == 0) { if (blame == 0) msg_panic("%s: null blame", myname); msg_warn("file %s/%s: parameter %s: null transport is not allowed", var_config_dir, MAIN_CONF_FILE, blame); *flags |= RESOLVE_FLAG_FAIL; FREE_MEMORY_AND_RETURN; } if (*STR(nexthop) == 0) msg_panic("%s: null nexthop", myname); /* * The transport map can selectively override any transport and/or * nexthop host info that is set up above. Unfortunately, the syntax for * nexthop information is transport specific. We therefore need sane and * intuitive semantics for transport map entries that specify a channel * but no nexthop. * * With non-error transports, the initial nexthop information is the * recipient domain. However, specific main.cf transport definitions may * specify a transport-specific destination, such as a host + TCP socket, * or the pathname of a UNIX-domain socket. With less precedence than * main.cf transport definitions, a main.cf relayhost definition may also * override nexthop information for off-host deliveries. * * With the error transport, the nexthop information is free text that * specifies the reason for non-delivery. * * Because nexthop syntax is transport specific we reset the nexthop * information to the recipient domain when the transport table specifies * a transport without also specifying the nexthop information. * * Subtle note: reset nexthop even when the transport table does not change * the transport. Otherwise it is hard to get rid of main.cf specified * nexthop information. * * XXX Don't override the virtual alias class (error:User unknown) result. */ if (rp->transport_info && !(*flags & RESOLVE_CLASS_ALIAS)) { if (transport_lookup(rp->transport_info, STR(nextrcpt), rcpt_domain, channel, nexthop) == 0 && dict_errno != 0) { msg_warn("%s lookup failure", rp->transport_maps_name); *flags |= RESOLVE_FLAG_FAIL; FREE_MEMORY_AND_RETURN; } } /* * Bounce recipients that have moved, regardless of domain address class. * We do this last, in anticipation of transport maps that can override * the recipient address. * * The downside of not doing this in delivery agents is that this table has * no effect on local alias expansion results. Such mail will have to * make almost an entire iteration through the mail system. */ #define IGNORE_ADDR_EXTENSION ((char **) 0) if (relocated_maps != 0) { const char *newloc; if ((newloc = mail_addr_find(relocated_maps, STR(nextrcpt), IGNORE_ADDR_EXTENSION)) != 0) { vstring_strcpy(channel, MAIL_SERVICE_ERROR); vstring_sprintf(nexthop, "User has moved to %s", newloc); } else if (dict_errno != 0) { msg_warn("%s lookup failure", VAR_RELOCATED_MAPS); *flags |= RESOLVE_FLAG_FAIL; FREE_MEMORY_AND_RETURN; } } /* * Clean up. */ FREE_MEMORY_AND_RETURN; }
static void psc_early_event(int event, char *context) { const char *myname = "psc_early_event"; PSC_STATE *state = (PSC_STATE *) context; char read_buf[PSC_READ_BUF_SIZE]; int read_count; DELTA_TIME elapsed; if (msg_verbose > 1) msg_info("%s: sq=%d cq=%d event %d on smtp socket %d from [%s]:%s flags=%s", myname, psc_post_queue_length, psc_check_queue_length, event, vstream_fileno(state->smtp_client_stream), state->smtp_client_addr, state->smtp_client_port, psc_print_state_flags(state->flags, myname)); PSC_CLEAR_EVENT_REQUEST(vstream_fileno(state->smtp_client_stream), psc_early_event, context); /* * XXX Be sure to empty the DNSBL lookup buffer otherwise we have a * memory leak. * * XXX We can avoid "forgetting" to do this by keeping a pointer to the * DNSBL lookup buffer in the PSC_STATE structure. This also allows us to * shave off a hash table lookup when retrieving the DNSBL result. * * A direct pointer increases the odds of dangling pointers. Hash-table * lookup is safer, and that is why it's done that way. */ switch (event) { /* * We either reached the end of the early tests time limit, or all * early tests completed before the pregreet timer would go off. */ case EVENT_TIME: /* * Check if the SMTP client spoke before its turn. */ if ((state->flags & PSC_STATE_FLAG_PREGR_TODO) != 0 && (state->flags & PSC_STATE_MASK_PREGR_FAIL_DONE) == 0) { state->pregr_stamp = event_time() + var_psc_pregr_ttl; PSC_PASS_SESSION_STATE(state, "pregreet test", PSC_STATE_FLAG_PREGR_PASS); } if ((state->flags & PSC_STATE_FLAG_PREGR_FAIL) && psc_pregr_action == PSC_ACT_IGNORE) { PSC_UNFAIL_SESSION_STATE(state, PSC_STATE_FLAG_PREGR_FAIL); /* Not: PSC_PASS_SESSION_STATE. Repeat this test the next time. */ } /* * Collect the DNSBL score, and whitelist other tests if applicable. * Note: this score will be partial when some DNS lookup did not * complete before the pregreet timer expired. * * If the client is DNS blocklisted, drop the connection, send the * client to a dummy protocol engine, or continue to the next test. */ #define PSC_DNSBL_FORMAT \ "%s 5.7.1 Service unavailable; client [%s] blocked using %s\r\n" #define NO_DNSBL_SCORE INT_MAX if (state->flags & PSC_STATE_FLAG_DNSBL_TODO) { if (state->dnsbl_score == NO_DNSBL_SCORE) { state->dnsbl_score = psc_dnsbl_retrieve(state->smtp_client_addr, &state->dnsbl_name, state->dnsbl_index); if (var_psc_dnsbl_wthresh < 0) psc_whitelist_non_dnsbl(state); } if (state->dnsbl_score < var_psc_dnsbl_thresh) { state->dnsbl_stamp = event_time() + var_psc_dnsbl_ttl; PSC_PASS_SESSION_STATE(state, "dnsbl test", PSC_STATE_FLAG_DNSBL_PASS); } else { msg_info("DNSBL rank %d for [%s]:%s", state->dnsbl_score, PSC_CLIENT_ADDR_PORT(state)); PSC_FAIL_SESSION_STATE(state, PSC_STATE_FLAG_DNSBL_FAIL); switch (psc_dnsbl_action) { case PSC_ACT_DROP: state->dnsbl_reply = vstring_sprintf(vstring_alloc(100), PSC_DNSBL_FORMAT, "521", state->smtp_client_addr, state->dnsbl_name); PSC_DROP_SESSION_STATE(state, STR(state->dnsbl_reply)); return; case PSC_ACT_ENFORCE: state->dnsbl_reply = vstring_sprintf(vstring_alloc(100), PSC_DNSBL_FORMAT, "550", state->smtp_client_addr, state->dnsbl_name); PSC_ENFORCE_SESSION_STATE(state, STR(state->dnsbl_reply)); break; case PSC_ACT_IGNORE: PSC_UNFAIL_SESSION_STATE(state, PSC_STATE_FLAG_DNSBL_FAIL); /* Not: PSC_PASS_SESSION_STATE. Repeat this test. */ break; default: msg_panic("%s: unknown dnsbl action value %d", myname, psc_dnsbl_action); } } } /* * Pass the connection to a real SMTP server, or enter the dummy * engine for deep tests. */ if ((state->flags & PSC_STATE_FLAG_NOFORWARD) != 0 || ((state->flags & PSC_STATE_MASK_SMTPD_PASS) != PSC_STATE_FLAGS_TODO_TO_PASS(state->flags & PSC_STATE_MASK_SMTPD_TODO))) psc_smtpd_tests(state); else psc_conclude(state); return; /* * EOF, or the client spoke before its turn. We simply drop the * connection, or we continue waiting and allow DNS replies to * trickle in. */ default: if ((read_count = recv(vstream_fileno(state->smtp_client_stream), read_buf, sizeof(read_buf) - 1, MSG_PEEK)) <= 0) { /* Avoid memory leak. */ if (state->dnsbl_score == NO_DNSBL_SCORE && (state->flags & PSC_STATE_FLAG_DNSBL_TODO)) (void) psc_dnsbl_retrieve(state->smtp_client_addr, &state->dnsbl_name, state->dnsbl_index); /* XXX Wait for DNS replies to come in. */ psc_hangup_event(state); return; } read_buf[read_count] = 0; escape(psc_escape_buf, read_buf, read_count); msg_info("PREGREET %d after %s from [%s]:%s: %.100s", read_count, psc_format_delta_time(psc_temp, state->start_time, &elapsed), PSC_CLIENT_ADDR_PORT(state), STR(psc_escape_buf)); PSC_FAIL_SESSION_STATE(state, PSC_STATE_FLAG_PREGR_FAIL); switch (psc_pregr_action) { case PSC_ACT_DROP: /* Avoid memory leak. */ if (state->dnsbl_score == NO_DNSBL_SCORE && (state->flags & PSC_STATE_FLAG_DNSBL_TODO)) (void) psc_dnsbl_retrieve(state->smtp_client_addr, &state->dnsbl_name, state->dnsbl_index); PSC_DROP_SESSION_STATE(state, "521 5.5.1 Protocol error\r\n"); return; case PSC_ACT_ENFORCE: /* We call psc_dnsbl_retrieve() when the timer expires. */ PSC_ENFORCE_SESSION_STATE(state, "550 5.5.1 Protocol error\r\n"); break; case PSC_ACT_IGNORE: /* We call psc_dnsbl_retrieve() when the timer expires. */ /* We must handle this case after the timer expires. */ break; default: msg_panic("%s: unknown pregreet action value %d", myname, psc_pregr_action); } /* * Terminate the greet delay if we're just waiting for the pregreet * test to complete. It is safe to call psc_early_event directly, * since we are already in that function. * * XXX After this code passes all tests, swap around the two blocks in * this switch statement and fall through from EVENT_READ into * EVENT_TIME, instead of calling psc_early_event recursively. */ state->flags |= PSC_STATE_FLAG_PREGR_DONE; if (elapsed.dt_sec >= PSC_EFF_GREET_WAIT || ((state->flags & PSC_STATE_MASK_EARLY_DONE) == PSC_STATE_FLAGS_TODO_TO_DONE(state->flags & PSC_STATE_MASK_EARLY_TODO))) psc_early_event(EVENT_TIME, context); else event_request_timer(psc_early_event, context, PSC_EFF_GREET_WAIT - elapsed.dt_sec); return; } }
static int deliver_message(DELIVER_REQUEST *request, char *service, char **argv) { const char *myname = "deliver_message"; static PIPE_PARAMS conf; static PIPE_ATTR attr; RECIPIENT_LIST *rcpt_list = &request->rcpt_list; DSN_BUF *why = dsb_create(); VSTRING *buf; ARGV *expanded_argv = 0; int deliver_status; int command_status; ARGV *export_env; const char *sender; #define DELIVER_MSG_CLEANUP() { \ dsb_free(why); \ if (expanded_argv) argv_free(expanded_argv); \ } if (msg_verbose) msg_info("%s: from <%s>", myname, request->sender); /* * Sanity checks. The get_service_params() and get_service_attr() * routines also do some sanity checks. Look up service attributes and * config information only once. This is safe since the information comes * from a trusted source, not from the delivery request. */ if (request->nexthop[0] == 0) msg_fatal("empty nexthop hostname"); if (rcpt_list->len <= 0) msg_fatal("recipient count: %d", rcpt_list->len); if (attr.command == 0) { get_service_params(&conf, service); get_service_attr(&attr, argv); } /* * The D flag cannot be specified for multi-recipient deliveries. */ if ((attr.flags & MAIL_COPY_DELIVERED) && (rcpt_list->len > 1)) { dsb_simple(why, "4.3.5", "mail system configuration error"); deliver_status = eval_command_status(PIPE_STAT_DEFER, service, request, &attr, why); msg_warn("pipe flag `D' requires %s_destination_recipient_limit = 1", service); DELIVER_MSG_CLEANUP(); return (deliver_status); } /* * The O flag cannot be specified for multi-recipient deliveries. */ if ((attr.flags & MAIL_COPY_ORIG_RCPT) && (rcpt_list->len > 1)) { dsb_simple(why, "4.3.5", "mail system configuration error"); deliver_status = eval_command_status(PIPE_STAT_DEFER, service, request, &attr, why); msg_warn("pipe flag `O' requires %s_destination_recipient_limit = 1", service); DELIVER_MSG_CLEANUP(); return (deliver_status); } /* * Check that this agent accepts messages this large. */ if (attr.size_limit != 0 && request->data_size > attr.size_limit) { if (msg_verbose) msg_info("%s: too big: size_limit = %ld, request->data_size = %ld", myname, (long) attr.size_limit, request->data_size); dsb_simple(why, "5.2.3", "message too large"); deliver_status = eval_command_status(PIPE_STAT_BOUNCE, service, request, &attr, why); DELIVER_MSG_CLEANUP(); return (deliver_status); } /* * Don't deliver a trace-only request. */ if (DEL_REQ_TRACE_ONLY(request->flags)) { RECIPIENT *rcpt; int status; int n; deliver_status = 0; dsb_simple(why, "2.0.0", "delivers to command: %s", attr.command[0]); (void) DSN_FROM_DSN_BUF(why); for (n = 0; n < request->rcpt_list.len; n++) { rcpt = request->rcpt_list.info + n; status = sent(DEL_REQ_TRACE_FLAGS(request->flags), request->queue_id, &request->msg_stats, rcpt, service, &why->dsn); if (status == 0 && (request->flags & DEL_REQ_FLAG_SUCCESS)) deliver_completed(request->fp, rcpt->offset); deliver_status |= status; } DELIVER_MSG_CLEANUP(); return (deliver_status); } /* * Report mail delivery loops. By definition, this requires * single-recipient delivery. Don't silently lose recipients. */ if (attr.flags & MAIL_COPY_DELIVERED) { DELIVERED_HDR_INFO *info; RECIPIENT *rcpt; int loop_found; if (request->rcpt_list.len > 1) msg_panic("%s: delivered-to enabled with multi-recipient request", myname); info = delivered_hdr_init(request->fp, request->data_offset, FOLD_ADDR_ALL); rcpt = request->rcpt_list.info; loop_found = delivered_hdr_find(info, rcpt->address); delivered_hdr_free(info); if (loop_found) { dsb_simple(why, "5.4.6", "mail forwarding loop for %s", rcpt->address); deliver_status = eval_command_status(PIPE_STAT_BOUNCE, service, request, &attr, why); DELIVER_MSG_CLEANUP(); return (deliver_status); } } /* * Deliver. Set the nexthop and sender variables, and expand the command * argument vector. Recipients will be expanded on the fly. XXX Rewrite * envelope and header addresses according to transport-specific * rewriting rules. */ if (vstream_fseek(request->fp, request->data_offset, SEEK_SET) < 0) msg_fatal("seek queue file %s: %m", VSTREAM_PATH(request->fp)); /* * A non-empty null sender replacement is subject to the 'q' flag. */ buf = vstring_alloc(10); sender = *request->sender ? request->sender : STR(attr.null_sender); if (*sender && (attr.flags & PIPE_OPT_QUOTE_LOCAL)) { quote_822_local(buf, sender); dict_update(PIPE_DICT_TABLE, PIPE_DICT_SENDER, STR(buf)); } else dict_update(PIPE_DICT_TABLE, PIPE_DICT_SENDER, sender); if (attr.flags & PIPE_OPT_FOLD_HOST) { vstring_strcpy(buf, request->nexthop); lowercase(STR(buf)); dict_update(PIPE_DICT_TABLE, PIPE_DICT_NEXTHOP, STR(buf)); } else dict_update(PIPE_DICT_TABLE, PIPE_DICT_NEXTHOP, request->nexthop); vstring_sprintf(buf, "%ld", (long) request->data_size); dict_update(PIPE_DICT_TABLE, PIPE_DICT_SIZE, STR(buf)); dict_update(PIPE_DICT_TABLE, PIPE_DICT_CLIENT_ADDR, request->client_addr); dict_update(PIPE_DICT_TABLE, PIPE_DICT_CLIENT_HELO, request->client_helo); dict_update(PIPE_DICT_TABLE, PIPE_DICT_CLIENT_NAME, request->client_name); dict_update(PIPE_DICT_TABLE, PIPE_DICT_CLIENT_PORT, request->client_port); dict_update(PIPE_DICT_TABLE, PIPE_DICT_CLIENT_PROTO, request->client_proto); dict_update(PIPE_DICT_TABLE, PIPE_DICT_SASL_METHOD, request->sasl_method); dict_update(PIPE_DICT_TABLE, PIPE_DICT_SASL_USERNAME, request->sasl_username); dict_update(PIPE_DICT_TABLE, PIPE_DICT_SASL_SENDER, request->sasl_sender); dict_update(PIPE_DICT_TABLE, PIPE_DICT_QUEUE_ID, request->queue_id); vstring_free(buf); if ((expanded_argv = expand_argv(service, attr.command, rcpt_list, attr.flags)) == 0) { dsb_simple(why, "4.3.5", "mail system configuration error"); deliver_status = eval_command_status(PIPE_STAT_DEFER, service, request, &attr, why); DELIVER_MSG_CLEANUP(); return (deliver_status); } export_env = argv_split(var_export_environ, ", \t\r\n"); command_status = pipe_command(request->fp, why, PIPE_CMD_UID, attr.uid, PIPE_CMD_GID, attr.gid, PIPE_CMD_SENDER, sender, PIPE_CMD_COPY_FLAGS, attr.flags, PIPE_CMD_ARGV, expanded_argv->argv, PIPE_CMD_TIME_LIMIT, conf.time_limit, PIPE_CMD_EOL, STR(attr.eol), PIPE_CMD_EXPORT, export_env->argv, PIPE_CMD_CWD, attr.exec_dir, PIPE_CMD_CHROOT, attr.chroot_dir, PIPE_CMD_ORIG_RCPT, rcpt_list->info[0].orig_addr, PIPE_CMD_DELIVERED, rcpt_list->info[0].address, PIPE_CMD_END); argv_free(export_env); deliver_status = eval_command_status(command_status, service, request, &attr, why); /* * Clean up. */ DELIVER_MSG_CLEANUP(); return (deliver_status); }
VSTREAM *tls_proxy_open(const char *service, int flags, VSTREAM *peer_stream, const char *peer_addr, const char *peer_port, int timeout) { VSTREAM *tlsproxy_stream; int status; int fd; static VSTRING *tlsproxy_service = 0; static VSTRING *remote_endpt = 0; /* * Initialize. */ if (tlsproxy_service == 0) { tlsproxy_service = vstring_alloc(20); remote_endpt = vstring_alloc(20); } /* * Connect to the tlsproxy(8) daemon. */ vstring_sprintf(tlsproxy_service, "%s/%s", MAIL_CLASS_PRIVATE, service); if ((fd = LOCAL_CONNECT(STR(tlsproxy_service), BLOCKING, TLSPROXY_INIT_TIMEOUT)) < 0) { msg_warn("connect to %s service: %m", STR(tlsproxy_service)); return (0); } /* * Initial handshake. Send the data attributes now, and send the client * file descriptor in a later transaction. * * XXX The formatted endpoint should be a state member. Then, we can * simplify all the format strings throughout the program. */ tlsproxy_stream = vstream_fdopen(fd, O_RDWR); vstring_sprintf(remote_endpt, "[%s]:%s", peer_addr, peer_port); attr_print(tlsproxy_stream, ATTR_FLAG_NONE, ATTR_TYPE_STR, MAIL_ATTR_REMOTE_ENDPT, STR(remote_endpt), ATTR_TYPE_INT, MAIL_ATTR_FLAGS, flags, ATTR_TYPE_INT, MAIL_ATTR_TIMEOUT, timeout, ATTR_TYPE_END); if (vstream_fflush(tlsproxy_stream) != 0) { msg_warn("error sending request to %s service: %m", STR(tlsproxy_service)); vstream_fclose(tlsproxy_stream); return (0); } /* * Receive the "TLS is available" indication. * * This may seem out of order, but we must have a read transaction between * sending the request attributes and sending the SMTP client file * descriptor. We can't assume UNIX-domain socket semantics here. */ if (attr_scan(tlsproxy_stream, ATTR_FLAG_STRICT, ATTR_TYPE_INT, MAIL_ATTR_STATUS, &status, ATTR_TYPE_END) != 1 || status == 0) { /* * The TLS proxy reports that the TLS engine is not available (due to * configuration error, or other causes). */ msg_warn("%s service role \"%s\" is not available", STR(tlsproxy_service), (flags & TLS_PROXY_FLAG_ROLE_SERVER) ? "server" : (flags & TLS_PROXY_FLAG_ROLE_CLIENT) ? "client" : "bogus role"); vstream_fclose(tlsproxy_stream); return (0); } /* * Send the remote SMTP client file descriptor. */ if (LOCAL_SEND_FD(vstream_fileno(tlsproxy_stream), vstream_fileno(peer_stream)) < 0) { /* * Some error: drop the TLS proxy stream. */ msg_warn("sending file handle to %s service: %m", STR(tlsproxy_service)); vstream_fclose(tlsproxy_stream); return (0); } return (tlsproxy_stream); }
MBOX *mbox_open(const char *path, int flags, mode_t mode, struct stat * st, uid_t chown_uid, gid_t chown_gid, int lock_style, const char *def_dsn, DSN_BUF *why) { struct stat local_statbuf; MBOX *mp; int locked = 0; VSTREAM *fp; if (st == 0) st = &local_statbuf; /* * If this is a regular file, create a dotlock file. This locking method * does not work well over NFS, but it is better than some alternatives. * With NFS, creating files atomically is a problem, and a successful * operation can fail with EEXIST. * * If filename.lock can't be created for reasons other than "file exists", * issue only a warning if the application says it is non-fatal. This is * for bass-awkward compatibility with existing installations that * deliver to files in non-writable directories. * * We dot-lock the file before opening, so we must avoid doing silly things * like dot-locking /dev/null. Fortunately, deliveries to non-mailbox * files execute with recipient privileges, so we don't have to worry * about creating dotlock files in places where the recipient would not * be able to write. * * Note: we use stat() to follow symlinks, because safe_open() allows the * target to be a root-owned symlink, and we don't want to create dotlock * files for /dev/null or other non-file objects. */ if ((lock_style & MBOX_DOT_LOCK) && (stat(path, st) < 0 || S_ISREG(st->st_mode))) { if (dot_lockfile(path, why->reason) == 0) { locked |= MBOX_DOT_LOCK; } else if (errno == EEXIST) { dsb_status(why, mbox_dsn(EAGAIN, def_dsn)); return (0); } else if (lock_style & MBOX_DOT_LOCK_MAY_FAIL) { msg_warn("%s", vstring_str(why->reason)); } else { dsb_status(why, mbox_dsn(errno, def_dsn)); return (0); } } /* * Open or create the target file. In case of a privileged open, the * privileged user may be attacked with hard/soft link tricks in an * unsafe parent directory. In case of an unprivileged open, the mail * system may be attacked by a malicious user-specified path, or the * unprivileged user may be attacked with hard/soft link tricks in an * unsafe parent directory. Open non-blocking to fend off attacks * involving non-file targets. */ if ((fp = safe_open(path, flags | O_NONBLOCK, mode, st, chown_uid, chown_gid, why->reason)) == 0) { dsb_status(why, mbox_dsn(errno, def_dsn)); if (locked & MBOX_DOT_LOCK) dot_unlockfile(path); return (0); } close_on_exec(vstream_fileno(fp), CLOSE_ON_EXEC); /* * If this is a regular file, acquire kernel locks. flock() locks are not * intended to work across a network; fcntl() locks are supposed to work * over NFS, but in the real world, NFS lock daemons often have serious * problems. */ #define HUNKY_DORY(lock_mask, myflock_style) ((lock_style & (lock_mask)) == 0 \ || deliver_flock(vstream_fileno(fp), (myflock_style), why->reason) == 0) if (S_ISREG(st->st_mode)) { if (HUNKY_DORY(MBOX_FLOCK_LOCK, MYFLOCK_STYLE_FLOCK) && HUNKY_DORY(MBOX_FCNTL_LOCK, MYFLOCK_STYLE_FCNTL)) { locked |= lock_style; } else { dsb_status(why, mbox_dsn(errno, def_dsn)); if (locked & MBOX_DOT_LOCK) dot_unlockfile(path); vstream_fclose(fp); return (0); } } /* * Sanity check: reportedly, GNU POP3D creates a new mailbox file and * deletes the old one. This does not play well with software that opens * the mailbox first and then locks it, such as software that that uses * FCNTL or FLOCK locks on open file descriptors (some UNIX systems don't * use dotlock files). * * To detect that GNU POP3D deletes the mailbox file we look at the target * file hard-link count. Note that safe_open() guarantees a hard-link * count of 1, so any change in this count is a sign of trouble. */ if (S_ISREG(st->st_mode) && (fstat(vstream_fileno(fp), st) < 0 || st->st_nlink != 1)) { vstring_sprintf(why->reason, "target file status changed unexpectedly"); dsb_status(why, mbox_dsn(EAGAIN, def_dsn)); msg_warn("%s: file status changed unexpectedly", path); if (locked & MBOX_DOT_LOCK) dot_unlockfile(path); vstream_fclose(fp); return (0); } mp = (MBOX *) mymalloc(sizeof(*mp)); mp->path = mystrdup(path); mp->fp = fp; mp->locked = locked; return (mp); }
void master_spawn(MASTER_SERV *serv) { const char *myname = "master_spawn"; MASTER_PROC *proc; MASTER_PID pid; int n; static unsigned master_generation = 0; static VSTRING *env_gen = 0; if (master_child_table == 0) master_child_table = binhash_create(0); if (env_gen == 0) env_gen = vstring_alloc(100); /* * Sanity checks. The master_avail module is supposed to know what it is * doing. */ if (!MASTER_LIMIT_OK(serv->max_proc, serv->total_proc)) msg_panic("%s: at process limit %d", myname, serv->total_proc); if (serv->avail_proc > 0) msg_panic("%s: processes available: %d", myname, serv->avail_proc); if (serv->flags & MASTER_FLAG_THROTTLE) msg_panic("%s: throttled service: %s", myname, serv->path); /* * Create a child process and connect parent and child via the status * pipe. */ master_generation += 1; switch (pid = fork()) { /* * Error. We're out of some essential resource. Best recourse is to * try again later. */ case -1: msg_warn("%s: fork: %m -- throttling", myname); master_throttle(serv); return; /* * Child process. Redirect child stdin/stdout to the parent-child * connection and run the requested command. Leave child stderr * alone. Disable exit handlers: they should be executed by the * parent only. * * When we reach the process limit on a public internet service, we * create stress-mode processes until the process count stays below * the limit for some amount of time. See master_avail_listen(). */ case 0: msg_cleanup((void (*) (void)) 0); /* disable exit handler */ closelog(); /* avoid filedes leak */ if (master_flow_pipe[0] <= MASTER_FLOW_READ) msg_fatal("%s: flow pipe read descriptor <= %d", myname, MASTER_FLOW_READ); if (DUP2(master_flow_pipe[0], MASTER_FLOW_READ) < 0) msg_fatal("%s: dup2: %m", myname); if (close(master_flow_pipe[0]) < 0) msg_fatal("close %d: %m", master_flow_pipe[0]); if (master_flow_pipe[1] <= MASTER_FLOW_WRITE) msg_fatal("%s: flow pipe read descriptor <= %d", myname, MASTER_FLOW_WRITE); if (DUP2(master_flow_pipe[1], MASTER_FLOW_WRITE) < 0) msg_fatal("%s: dup2: %m", myname); if (close(master_flow_pipe[1]) < 0) msg_fatal("close %d: %m", master_flow_pipe[1]); close(serv->status_fd[0]); /* status channel */ if (serv->status_fd[1] <= MASTER_STATUS_FD) msg_fatal("%s: status file descriptor collision", myname); if (DUP2(serv->status_fd[1], MASTER_STATUS_FD) < 0) msg_fatal("%s: dup2 status_fd: %m", myname); (void) close(serv->status_fd[1]); for (n = 0; n < serv->listen_fd_count; n++) { if (serv->listen_fd[n] <= MASTER_LISTEN_FD + n) msg_fatal("%s: listen file descriptor collision", myname); if (DUP2(serv->listen_fd[n], MASTER_LISTEN_FD + n) < 0) msg_fatal("%s: dup2 listen_fd %d: %m", myname, serv->listen_fd[n]); (void) close(serv->listen_fd[n]); } vstring_sprintf(env_gen, "%s=%o", MASTER_GEN_NAME, master_generation); if (putenv(vstring_str(env_gen)) < 0) msg_fatal("%s: putenv: %m", myname); if (serv->stress_param_val && serv->stress_expire_time > event_time()) serv->stress_param_val[0] = CONFIG_BOOL_YES[0]; execvp(serv->path, serv->args->argv); msg_fatal("%s: exec %s: %m", myname, serv->path); /* NOTREACHED */ /* * Parent. Fill in a process member data structure and set up links * between child and process. Say this process has become available. * If this service has a wakeup timer that is turned on only when the * service is actually used, turn on the wakeup timer. */ default: if (msg_verbose) msg_info("spawn command %s; pid %d", serv->path, pid); proc = (MASTER_PROC *) mymalloc(sizeof(MASTER_PROC)); proc->serv = serv; proc->pid = pid; proc->gen = master_generation; proc->use_count = 0; proc->avail = 0; binhash_enter(master_child_table, (void *) &pid, sizeof(pid), (void *) proc); serv->total_proc++; master_avail_more(serv, proc); if (serv->flags & MASTER_FLAG_CONDWAKE) { serv->flags &= ~MASTER_FLAG_CONDWAKE; master_wakeup_init(serv); if (msg_verbose) msg_info("start conditional timer for %s", serv->name); } return; } }
MASTER_SERV *get_master_ent() { VSTRING *buf = vstring_alloc(100); VSTRING *junk = vstring_alloc(100); MASTER_SERV *serv; char *cp; char *name; char *host = 0; char *port = 0; char *transport; int private; int unprivileged; /* passed on to child */ int chroot; /* passed on to child */ char *command; int n; char *bufp; char *atmp; const char *parse_err; static char *saved_interfaces = 0; char *err; if (master_fp == 0) msg_panic("get_master_ent: config file not open"); if (master_disable == 0) msg_panic("get_master_ent: no service disable list"); /* * XXX We cannot change the inet_interfaces setting for a running master * process. Listening sockets are inherited by child processes so that * closing and reopening those sockets in the master does not work. * * Another problem is that library routines still cache results that are * based on the old inet_interfaces setting. It is too much trouble to * recompute everything. * * In order to keep our data structures consistent we ignore changes in * inet_interfaces settings, and issue a warning instead. */ if (saved_interfaces == 0) saved_interfaces = mystrdup(var_inet_interfaces); /* * Skip blank lines and comment lines. */ for (;;) { if (readllines(buf, master_fp, &master_line_last, &master_line) == 0) { vstring_free(buf); vstring_free(junk); return (0); } bufp = vstring_str(buf); if ((cp = mystrtok(&bufp, master_blanks)) == 0) continue; name = cp; transport = get_str_ent(&bufp, "transport type", (char *) 0); vstring_sprintf(junk, "%s/%s", name, transport); if (match_service_match(master_disable, vstring_str(junk)) == 0) break; } /* * Parse one logical line from the configuration file. Initialize service * structure members in order. */ serv = (MASTER_SERV *) mymalloc(sizeof(MASTER_SERV)); serv->next = 0; /* * Flags member. */ serv->flags = 0; /* * All servers busy warning timer. */ serv->busy_warn_time = 0; /* * Service name. Syntax is transport-specific. */ serv->ext_name = mystrdup(name); /* * Transport type: inet (wild-card listen or virtual) or unix. */ #define STR_SAME !strcmp if (STR_SAME(transport, MASTER_XPORT_NAME_INET)) { if (!STR_SAME(saved_interfaces, var_inet_interfaces)) { msg_warn("service %s: ignoring %s change", serv->ext_name, VAR_INET_INTERFACES); msg_warn("to change %s, stop and start Postfix", VAR_INET_INTERFACES); } serv->type = MASTER_SERV_TYPE_INET; atmp = mystrdup(name); if ((parse_err = host_port(atmp, &host, "", &port, (char *) 0)) != 0) fatal_with_context("%s in \"%s\"", parse_err, name); if (*host) { serv->flags |= MASTER_FLAG_INETHOST;/* host:port */ MASTER_INET_ADDRLIST(serv) = (INET_ADDR_LIST *) mymalloc(sizeof(*MASTER_INET_ADDRLIST(serv))); inet_addr_list_init(MASTER_INET_ADDRLIST(serv)); if (inet_addr_host(MASTER_INET_ADDRLIST(serv), host) == 0) fatal_with_context("bad hostname or network address: %s", name); inet_addr_list_uniq(MASTER_INET_ADDRLIST(serv)); serv->listen_fd_count = MASTER_INET_ADDRLIST(serv)->used; } else { MASTER_INET_ADDRLIST(serv) = strcasecmp(saved_interfaces, INET_INTERFACES_ALL) ? own_inet_addr_list() : /* virtual */ wildcard_inet_addr_list(); /* wild-card */ inet_addr_list_uniq(MASTER_INET_ADDRLIST(serv)); serv->listen_fd_count = MASTER_INET_ADDRLIST(serv)->used; } MASTER_INET_PORT(serv) = mystrdup(port); for (n = 0; /* see below */ ; n++) { if (n >= MASTER_INET_ADDRLIST(serv)->used) { serv->flags |= MASTER_FLAG_LOCAL_ONLY; break; } if (!sock_addr_in_loopback(SOCK_ADDR_PTR(MASTER_INET_ADDRLIST(serv)->addrs + n))) break; } } else if (STR_SAME(transport, MASTER_XPORT_NAME_UNIX)) { serv->type = MASTER_SERV_TYPE_UNIX; serv->listen_fd_count = 1; serv->flags |= MASTER_FLAG_LOCAL_ONLY; } else if (STR_SAME(transport, MASTER_XPORT_NAME_UXDG)) { serv->type = MASTER_SERV_TYPE_UXDG; serv->listen_fd_count = 1; serv->flags |= MASTER_FLAG_LOCAL_ONLY; } else if (STR_SAME(transport, MASTER_XPORT_NAME_FIFO)) { serv->type = MASTER_SERV_TYPE_FIFO; serv->listen_fd_count = 1; serv->flags |= MASTER_FLAG_LOCAL_ONLY; #ifdef MASTER_SERV_TYPE_PASS } else if (STR_SAME(transport, MASTER_XPORT_NAME_PASS)) { serv->type = MASTER_SERV_TYPE_PASS; serv->listen_fd_count = 1; /* If this is a connection screener, remote clients are likely. */ #endif } else { fatal_with_context("bad transport type: %s", transport); } /* * Service class: public or private. */ private = get_bool_ent(&bufp, "private", "y");
static int erlang_query(DICT_ERLANG *dict_erlang, const char *key, ARGV *nodes, char *cookie, char *mod, char *fun, char **res) { int cur_node; int err, index, retries; int res_version, res_type, res_size; int fd; ei_cnode ec; ei_x_buff args; ei_x_buff resp; VSTRING *node_name; node_name = vstring_alloc(15); /* Get an "unique" name for the node */ vstring_sprintf(node_name, "dict_erlang%u", getpid() % 999); err = ei_connect_init(&ec, vstring_str(node_name), cookie, 0); if (err != 0) { msg_warn_erl("ei_connect_init"); return -1; } retries = 3; retry: cur_node = dict_erlang->active_node; do { fd = ei_connect(&ec, nodes->argv[cur_node]); if (fd >= 0) { dict_erlang->active_node = cur_node; if (msg_verbose) msg_info("connected to node %s", nodes->argv[cur_node]); break; } cur_node = (cur_node + 1) % nodes->argc; } while (cur_node != dict_erlang->active_node); if (fd < 0) { if (retries > 0 && erl_errno == EIO) { msg_warn_erl("no suitable nodes found, retrying"); retries--; goto retry; } msg_warn_erl("no suitable nodes found, failing"); return -1; } ei_x_new(&args); ei_x_new(&resp); ei_x_encode_list_header(&args, 1); ei_x_encode_binary(&args, key, strlen(key)); ei_x_encode_empty_list(&args); err = ei_rpc(&ec, fd, mod, fun, args.buff, args.index, &resp); if (err == -1) { msg_warn_erl("ei_rpc"); goto cleanup; } err = handle_response(dict_erlang, key, &resp, res); cleanup: close(fd); ei_x_free(&args); ei_x_free(&resp); return err; }
int bounce_one_intern(int flags, const char *queue, const char *id, const char *encoding, const char *sender, const char *dsn_envid, int dsn_ret, MSG_STATS *stats, RECIPIENT *rcpt, const char *relay, DSN *dsn) { DSN my_dsn = *dsn; int status; /* * MTA-requested address verification information is stored in the verify * service database. */ if (flags & DEL_REQ_FLAG_MTA_VRFY) { my_dsn.action = "undeliverable"; status = verify_append(id, stats, rcpt, relay, &my_dsn, DEL_RCPT_STAT_BOUNCE); return (status); } /* * User-requested address verification information is logged and mailed * to the requesting user. */ if (flags & DEL_REQ_FLAG_USR_VRFY) { my_dsn.action = "undeliverable"; status = trace_append(flags, id, stats, rcpt, relay, &my_dsn); return (status); } /* * When we're not bouncing, then use the standard multi-recipient logfile * based procedure. */ else if (var_soft_bounce) { return (bounce_append_intern(flags, id, stats, rcpt, relay, &my_dsn)); } /* * Normal mail delivery. May also send a delivery record to the user. * * XXX DSN We send all recipients regardless of DSN NOTIFY options, because * those options don't apply to postmaster notifications. */ else { /* * Supply default action. */ my_dsn.action = "failed"; if (mail_command_client(MAIL_CLASS_PRIVATE, var_bounce_service, ATTR_TYPE_INT, MAIL_ATTR_NREQ, BOUNCE_CMD_ONE, ATTR_TYPE_INT, MAIL_ATTR_FLAGS, flags, ATTR_TYPE_STR, MAIL_ATTR_QUEUE, queue, ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, id, ATTR_TYPE_STR, MAIL_ATTR_ENCODING, encoding, ATTR_TYPE_STR, MAIL_ATTR_SENDER, sender, ATTR_TYPE_STR, MAIL_ATTR_DSN_ENVID, dsn_envid, ATTR_TYPE_INT, MAIL_ATTR_DSN_RET, dsn_ret, ATTR_TYPE_FUNC, rcpt_print, (void *) rcpt, ATTR_TYPE_FUNC, dsn_print, (void *) &my_dsn, ATTR_TYPE_END) == 0 && ((flags & DEL_REQ_FLAG_RECORD) == 0 || trace_append(flags, id, stats, rcpt, relay, &my_dsn) == 0)) { log_adhoc(id, stats, rcpt, relay, &my_dsn, "bounced"); status = 0; } else if ((flags & BOUNCE_FLAG_CLEAN) == 0) { VSTRING *junk = vstring_alloc(100); my_dsn.status = "4.3.0"; vstring_sprintf(junk, "%s or %s service failure", var_bounce_service, var_trace_service); my_dsn.reason = vstring_str(junk); status = defer_append_intern(flags, id, stats, rcpt, relay, &my_dsn); vstring_free(junk); } else { status = -1; } return (status); } }
int bounce_append_intern(int flags, const char *id, MSG_STATS *stats, RECIPIENT *rcpt, const char *relay, DSN *dsn) { DSN my_dsn = *dsn; int status; /* * MTA-requested address verification information is stored in the verify * service database. */ if (flags & DEL_REQ_FLAG_MTA_VRFY) { my_dsn.action = "undeliverable"; status = verify_append(id, stats, rcpt, relay, &my_dsn, DEL_RCPT_STAT_BOUNCE); return (status); } /* * User-requested address verification information is logged and mailed * to the requesting user. */ if (flags & DEL_REQ_FLAG_USR_VRFY) { my_dsn.action = "undeliverable"; status = trace_append(flags, id, stats, rcpt, relay, &my_dsn); return (status); } /* * Normal (well almost) delivery. When we're pretending that we can't * bounce, don't create a defer log file when we wouldn't keep the bounce * log file. That's a lot of negatives in one sentence. */ else if (var_soft_bounce && (flags & BOUNCE_FLAG_CLEAN)) { return (-1); } /* * Normal mail delivery. May also send a delivery record to the user. * * XXX DSN We write all recipients to the bounce logfile regardless of DSN * NOTIFY options, because those options don't apply to postmaster * notifications. */ else { char *my_status = mystrdup(my_dsn.status); const char *log_status = var_soft_bounce ? "SOFTBOUNCE" : "bounced"; /* * Supply default action. */ my_dsn.status = my_status; if (var_soft_bounce) { my_status[0] = '4'; my_dsn.action = "delayed"; } else { my_dsn.action = "failed"; } if (mail_command_client(MAIL_CLASS_PRIVATE, var_soft_bounce ? var_defer_service : var_bounce_service, ATTR_TYPE_INT, MAIL_ATTR_NREQ, BOUNCE_CMD_APPEND, ATTR_TYPE_INT, MAIL_ATTR_FLAGS, flags, ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, id, ATTR_TYPE_FUNC, rcpt_print, (void *) rcpt, ATTR_TYPE_FUNC, dsn_print, (void *) &my_dsn, ATTR_TYPE_END) == 0 && ((flags & DEL_REQ_FLAG_RECORD) == 0 || trace_append(flags, id, stats, rcpt, relay, &my_dsn) == 0)) { log_adhoc(id, stats, rcpt, relay, &my_dsn, log_status); status = (var_soft_bounce ? -1 : 0); } else if ((flags & BOUNCE_FLAG_CLEAN) == 0) { VSTRING *junk = vstring_alloc(100); my_dsn.status = "4.3.0"; vstring_sprintf(junk, "%s or %s service failure", var_bounce_service, var_trace_service); my_dsn.reason = vstring_str(junk); status = defer_append_intern(flags, id, stats, rcpt, relay, &my_dsn); vstring_free(junk); } else { status = -1; } myfree(my_status); return (status); } }
static void pgsql_parse_config(DICT_PGSQL *dict_pgsql, const char *pgsqlcf) { const char *myname = "pgsql_parse_config"; CFG_PARSER *p = dict_pgsql->parser; char *hosts; VSTRING *query; char *select_function; dict_pgsql->username = cfg_get_str(p, "user", "", 0, 0); dict_pgsql->password = cfg_get_str(p, "password", "", 0, 0); dict_pgsql->dbname = cfg_get_str(p, "dbname", "", 1, 0); dict_pgsql->result_format = cfg_get_str(p, "result_format", "%s", 1, 0); /* * XXX: The default should be non-zero for safety, but that is not * backwards compatible. */ dict_pgsql->expansion_limit = cfg_get_int(dict_pgsql->parser, "expansion_limit", 0, 0, 0); if ((dict_pgsql->query = cfg_get_str(p, "query", 0, 0, 0)) == 0) { /* * No query specified -- fallback to building it from components ( * old style "select %s from %s where %s" ) */ query = vstring_alloc(64); select_function = cfg_get_str(p, "select_function", 0, 0, 0); if (select_function != 0) { vstring_sprintf(query, "SELECT %s('%%s')", select_function); myfree(select_function); } else db_common_sql_build_query(query, p); dict_pgsql->query = vstring_export(query); } /* * Must parse all templates before we can use db_common_expand() */ dict_pgsql->ctx = 0; (void) db_common_parse(&dict_pgsql->dict, &dict_pgsql->ctx, dict_pgsql->query, 1); (void) db_common_parse(0, &dict_pgsql->ctx, dict_pgsql->result_format, 0); db_common_parse_domain(p, dict_pgsql->ctx); /* * Maps that use substring keys should only be used with the full input * key. */ if (db_common_dict_partial(dict_pgsql->ctx)) dict_pgsql->dict.flags |= DICT_FLAG_PATTERN; else dict_pgsql->dict.flags |= DICT_FLAG_FIXED; if (dict_pgsql->dict.flags & DICT_FLAG_FOLD_FIX) dict_pgsql->dict.fold_buf = vstring_alloc(10); hosts = cfg_get_str(p, "hosts", "", 0, 0); dict_pgsql->hosts = argv_split(hosts, " ,\t\r\n"); if (dict_pgsql->hosts->argc == 0) { argv_add(dict_pgsql->hosts, "localhost", ARGV_END); argv_terminate(dict_pgsql->hosts); if (msg_verbose) msg_info("%s: %s: no hostnames specified, defaulting to '%s'", myname, pgsqlcf, dict_pgsql->hosts->argv[0]); } myfree(hosts); }
static const char *convert_long_parameter(char *ptr) { CONFIG_LONG_TABLE *clt = (CONFIG_LONG_TABLE *) ptr; return (STR(vstring_sprintf(param_string_buf, "%ld", clt->defval))); }
static void postalias(char *map_type, char *path_name, int postalias_flags, int open_flags, int dict_flags) { VSTREAM *NOCLOBBER source_fp; VSTRING *line_buffer; MKMAP *mkmap; int lineno; int last_line; VSTRING *key_buffer; VSTRING *value_buffer; TOK822 *tok_list; TOK822 *key_list; TOK822 *colon; TOK822 *value_list; struct stat st; mode_t saved_mask; /* * Initialize. */ line_buffer = vstring_alloc(100); key_buffer = vstring_alloc(100); value_buffer = vstring_alloc(100); if ((open_flags & O_TRUNC) == 0) { /* Incremental mode. */ source_fp = VSTREAM_IN; vstream_control(source_fp, CA_VSTREAM_CTL_PATH("stdin"), CA_VSTREAM_CTL_END); } else { /* Create database. */ if (strcmp(map_type, DICT_TYPE_PROXY) == 0) msg_fatal("can't create maps via the proxy service"); dict_flags |= DICT_FLAG_BULK_UPDATE; if ((source_fp = vstream_fopen(path_name, O_RDONLY, 0)) == 0) msg_fatal("open %s: %m", path_name); } if (fstat(vstream_fileno(source_fp), &st) < 0) msg_fatal("fstat %s: %m", path_name); /* * Turn off group/other read permissions as indicated in the source file. */ if ((postalias_flags & POSTALIAS_FLAG_SAVE_PERM) && S_ISREG(st.st_mode)) saved_mask = umask(022 | (~st.st_mode & 077)); /* * If running as root, run as the owner of the source file, so that the * result shows proper ownership, and so that a bug in postalias does not * allow privilege escalation. */ if ((postalias_flags & POSTALIAS_FLAG_AS_OWNER) && getuid() == 0 && (st.st_uid != geteuid() || st.st_gid != getegid())) set_eugid(st.st_uid, st.st_gid); /* * Open the database, create it when it does not exist, truncate it when * it does exist, and lock out any spectators. */ mkmap = mkmap_open(map_type, path_name, open_flags, dict_flags); /* * And restore the umask, in case it matters. */ if ((postalias_flags & POSTALIAS_FLAG_SAVE_PERM) && S_ISREG(st.st_mode)) umask(saved_mask); /* * Trap "exceptions" so that we can restart a bulk-mode update after a * recoverable error. */ for (;;) { if (dict_isjmp(mkmap->dict) != 0 && dict_setjmp(mkmap->dict) != 0 && vstream_fseek(source_fp, SEEK_SET, 0) < 0) msg_fatal("seek %s: %m", VSTREAM_PATH(source_fp)); /* * Add records to the database. */ last_line = 0; while (readllines(line_buffer, source_fp, &last_line, &lineno)) { /* * First some UTF-8 checks sans casefolding. */ if ((mkmap->dict->flags & DICT_FLAG_UTF8_ACTIVE) && !allascii(STR(line_buffer)) && !valid_utf8_string(STR(line_buffer), LEN(line_buffer))) { msg_warn("%s, line %d: non-UTF-8 input \"%s\"" " -- ignoring this line", VSTREAM_PATH(source_fp), lineno, STR(line_buffer)); continue; } /* * Tokenize the input, so that we do the right thing when a * quoted localpart contains special characters such as "@", ":" * and so on. */ if ((tok_list = tok822_scan(STR(line_buffer), (TOK822 **) 0)) == 0) continue; /* * Enforce the key:value format. Disallow missing keys, * multi-address keys, or missing values. In order to specify an * empty string or value, enclose it in double quotes. */ if ((colon = tok822_find_type(tok_list, ':')) == 0 || colon->prev == 0 || colon->next == 0 || tok822_rfind_type(colon, ',')) { msg_warn("%s, line %d: need name:value pair", VSTREAM_PATH(source_fp), lineno); tok822_free_tree(tok_list); continue; } /* * Key must be local. XXX We should use the Postfix rewriting and * resolving services to handle all address forms correctly. * However, we can't count on the mail system being up when the * alias database is being built, so we're guessing a bit. */ if (tok822_rfind_type(colon, '@') || tok822_rfind_type(colon, '%')) { msg_warn("%s, line %d: name must be local", VSTREAM_PATH(source_fp), lineno); tok822_free_tree(tok_list); continue; } /* * Split the input into key and value parts, and convert from * token representation back to string representation. Convert * the key to internal (unquoted) form, because the resolver * produces addresses in internal form. Convert the value to * external (quoted) form, because it will have to be re-parsed * upon lookup. Discard the token representation when done. */ key_list = tok_list; tok_list = 0; value_list = tok822_cut_after(colon); tok822_unlink(colon); tok822_free(colon); tok822_internalize(key_buffer, key_list, TOK822_STR_DEFL); tok822_free_tree(key_list); tok822_externalize(value_buffer, value_list, TOK822_STR_DEFL); tok822_free_tree(value_list); /* * Store the value under a case-insensitive key. */ mkmap_append(mkmap, STR(key_buffer), STR(value_buffer)); if (mkmap->dict->error) msg_fatal("table %s:%s: write error: %m", mkmap->dict->type, mkmap->dict->name); } break; } /* * Update or append sendmail and NIS signatures. */ if ((open_flags & O_TRUNC) == 0) mkmap->dict->flags |= DICT_FLAG_DUP_REPLACE; /* * Sendmail compatibility: add the @:@ signature to indicate that the * database is complete. This might be needed by NIS clients running * sendmail. */ mkmap_append(mkmap, "@", "@"); if (mkmap->dict->error) msg_fatal("table %s:%s: write error: %m", mkmap->dict->type, mkmap->dict->name); /* * NIS compatibility: add time and master info. Unlike other information, * this information MUST be written without a trailing null appended to * key or value. */ mkmap->dict->flags &= ~DICT_FLAG_TRY1NULL; mkmap->dict->flags |= DICT_FLAG_TRY0NULL; vstring_sprintf(value_buffer, "%010ld", (long) time((time_t *) 0)); #if (defined(HAS_NIS) || defined(HAS_NISPLUS)) mkmap->dict->flags &= ~DICT_FLAG_FOLD_FIX; mkmap_append(mkmap, "YP_LAST_MODIFIED", STR(value_buffer)); mkmap_append(mkmap, "YP_MASTER_NAME", var_myhostname); #endif /* * Close the alias database, and release the lock. */ mkmap_close(mkmap); /* * Cleanup. We're about to terminate, but it is a good sanity check. */ vstring_free(value_buffer); vstring_free(key_buffer); vstring_free(line_buffer); if (source_fp != VSTREAM_IN) vstream_fclose(source_fp); }
void register_dbms_parameters(const char *param_value, const char *(flag_parameter) (const char *, int, char *), PC_MASTER_ENT *local_scope) { const PC_DBMS_INFO *dp; char *bufp; char *db_type; char *prefix; static VSTRING *buffer = 0; static VSTRING *candidate = 0; const char **cpp; /* * Emulate Postfix parameter value expansion, prepending the appropriate * local (master.cf "-o name-value") namespace to the global (main.cf * "name=value") namespace. * * XXX This does not examine both sides of conditional macro expansion, and * may expand the "wrong" conditional macros. This is the best we can do * for legacy database configuration support. */ #define NO_SCAN_FILTER ((char *) 0) (void) mac_expand(buffer ? buffer : (buffer = vstring_alloc(100)), param_value, MAC_EXP_FLAG_RECURSE, NO_SCAN_FILTER, register_dbms_parameters_cb, (char *) local_scope); /* * Naive parsing. We don't really know if the parameter specifies free * text or a list of databases. */ bufp = STR(buffer); while ((db_type = mystrtok(&bufp, " ,\t\r\n")) != 0) { /* * Skip over "proxy:" indirections. */ while ((prefix = split_at(db_type, ':')) != 0 && strcmp(db_type, DICT_TYPE_PROXY) == 0) db_type = prefix; /* * Look for database:prefix where the prefix is not a pathname and * the database is a known type. Synthesize candidate parameter names * from the user-defined prefix and from the database-defined suffix * list, and see if those parameters have a "name=value" entry in the * local or global namespace. */ if (prefix != 0 && *prefix != '/' && *prefix != '.') { for (dp = dbms_info; dp->db_type != 0; dp++) { if (strcmp(db_type, dp->db_type) == 0) { for (cpp = dp->db_suffixes; *cpp; cpp++) { vstring_sprintf(candidate ? candidate : (candidate = vstring_alloc(30)), "%s_%s", prefix, *cpp); flag_parameter(STR(candidate), 0, (char *) local_scope); } break; } } } } }
int sent(int flags, const char *id, MSG_STATS *stats, RECIPIENT *recipient, const char *relay, DSN *dsn) { const char *myname="sent.c Sent"; if (msg_verbose) msg_info("HU--%s Starting",myname); DSN my_dsn = *dsn; DSN *dsn_res; int status; /* * Sanity check. */ if (my_dsn.status[0] != '2' || !dsn_valid(my_dsn.status)) { msg_warn("sent: ignoring dsn code \"%s\"", my_dsn.status); my_dsn.status = "2.0.0"; } /* * DSN filter (Postfix 3.0). */ if (delivery_status_filter != 0 && (dsn_res = dsn_filter_lookup(delivery_status_filter, &my_dsn)) != 0) my_dsn = *dsn_res; /* * MTA-requested address verification information is stored in the verify * service database. */ if (flags & DEL_REQ_FLAG_MTA_VRFY) { my_dsn.action = "deliverable"; status = verify_append(id, stats, recipient, relay, &my_dsn, DEL_RCPT_STAT_OK); return (status); } /* * User-requested address verification information is logged and mailed * to the requesting user. */ if (flags & DEL_REQ_FLAG_USR_VRFY) { my_dsn.action = "deliverable"; status = trace_append(flags, id, stats, recipient, relay, &my_dsn); return (status); } /* * Normal mail delivery. May also send a delivery record to the user. */ else { /* Readability macros: record all deliveries, or the delayed ones. */ #define REC_ALL_SENT(flags) (flags & DEL_REQ_FLAG_RECORD) #define REC_DLY_SENT(flags, rcpt) \ ((flags & DEL_REQ_FLAG_REC_DLY_SENT) \ && (rcpt->dsn_notify == 0 || (rcpt->dsn_notify & DSN_NOTIFY_DELAY))) if (my_dsn.action == 0 || my_dsn.action[0] == 0) my_dsn.action = "delivered"; if (((REC_ALL_SENT(flags) == 0 && REC_DLY_SENT(flags, recipient) == 0) || trace_append(flags, id, stats, recipient, relay, &my_dsn) == 0) && ((recipient->dsn_notify & DSN_NOTIFY_SUCCESS) == 0 || trace_append(flags, id, stats, recipient, relay, &my_dsn) == 0)) { log_adhoc(id, stats, recipient, relay, &my_dsn, "sent"); status = 0; } else { VSTRING *junk = vstring_alloc(100); vstring_sprintf(junk, "%s: %s service failed", id, var_trace_service); my_dsn.reason = vstring_str(junk); my_dsn.status = "4.3.0"; status = defer_append(flags, id, stats, recipient, relay, &my_dsn); vstring_free(junk); } return (status); } }
static void dict_cache_clean_event(int unused_event, char *cache_context) { const char *myname = "dict_cache_clean_event"; DICT_CACHE *cp = (DICT_CACHE *) cache_context; const char *cache_key; const char *cache_val; int next_interval; VSTRING *stamp_buf; int first_next; /* * We interleave cache cleanup with other processing, so that the * application's service remains available, with perhaps increased * latency. */ /* * Start a new cache cleanup run. */ if (cp->saved_curr_key == 0) { cp->retained = cp->dropped = 0; first_next = DICT_SEQ_FUN_FIRST; if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE) msg_info("%s: start %s cache cleanup", myname, cp->db->name); } /* * Continue a cache cleanup run in progress. */ else { first_next = DICT_SEQ_FUN_NEXT; } /* * Examine one cache entry. */ if (dict_cache_sequence(cp, first_next, &cache_key, &cache_val) == 0) { if (cp->exp_validator(cache_key, cache_val, cp->exp_context) == 0) { DC_SCHEDULE_FOR_DELETE_BEHIND(cp); cp->dropped++; if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE) msg_info("%s: drop %s cache entry for %s", myname, cp->db->name, cache_key); } else { cp->retained++; if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE) msg_info("%s: keep %s cache entry for %s", myname, cp->db->name, cache_key); } next_interval = 0; } /* * Cache cleanup completed. Report vital statistics. */ else { if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE) msg_info("%s: done %s cache cleanup scan", myname, cp->db->name); dict_cache_clean_stat_log_reset(cp, "full"); stamp_buf = vstring_alloc(100); vstring_sprintf(stamp_buf, "%ld", (long) event_time()); dict_put(cp->db, DC_LAST_CACHE_CLEANUP_COMPLETED, vstring_str(stamp_buf)); vstring_free(stamp_buf); next_interval = cp->exp_interval; } event_request_timer(dict_cache_clean_event, cache_context, next_interval); }
static void post_jail_init(char *unused_name, char **unused_argv) { const NAME_CODE actions[] = { PSC_NAME_ACT_DROP, PSC_ACT_DROP, PSC_NAME_ACT_ENFORCE, PSC_ACT_ENFORCE, PSC_NAME_ACT_IGNORE, PSC_ACT_IGNORE, PSC_NAME_ACT_CONT, PSC_ACT_IGNORE, /* compatibility */ 0, -1, }; int cache_flags; const char *tmp; /* * This routine runs after the skeleton code has entered the chroot jail. * Prevent automatic process suicide after a limited number of client * requests. It is OK to terminate after a limited amount of idle time. */ var_use_limit = 0; /* * Workaround for parameters whose values may contain "$", and that have * a default of "$parametername". Not sure if it would be a good idea to * always to this in the mail_conf_raw(3) module. */ if (*var_psc_rej_footer == '$' && mail_conf_lookup(var_psc_rej_footer + 1)) { tmp = mail_conf_eval_once(var_psc_rej_footer); myfree(var_psc_rej_footer); var_psc_rej_footer = mystrdup(tmp); } if (*var_psc_exp_filter == '$' && mail_conf_lookup(var_psc_exp_filter + 1)) { tmp = mail_conf_eval_once(var_psc_exp_filter); myfree(var_psc_exp_filter); var_psc_exp_filter = mystrdup(tmp); } /* * Other one-time initialization. */ psc_temp = vstring_alloc(10); vstring_sprintf(psc_temp, "%s/%s", MAIL_CLASS_PRIVATE, var_smtpd_service); psc_smtpd_service_name = mystrdup(STR(psc_temp)); psc_dnsbl_init(); psc_early_init(); psc_smtpd_init(); if ((psc_blist_action = name_code(actions, NAME_CODE_FLAG_NONE, var_psc_blist_action)) < 0) msg_fatal("bad %s value: %s", VAR_PSC_BLIST_ACTION, var_psc_blist_action); if ((psc_dnsbl_action = name_code(actions, NAME_CODE_FLAG_NONE, var_psc_dnsbl_action)) < 0) msg_fatal("bad %s value: %s", VAR_PSC_DNSBL_ACTION, var_psc_dnsbl_action); if ((psc_pregr_action = name_code(actions, NAME_CODE_FLAG_NONE, var_psc_pregr_action)) < 0) msg_fatal("bad %s value: %s", VAR_PSC_PREGR_ACTION, var_psc_pregr_action); if ((psc_pipel_action = name_code(actions, NAME_CODE_FLAG_NONE, var_psc_pipel_action)) < 0) msg_fatal("bad %s value: %s", VAR_PSC_PIPEL_ACTION, var_psc_pipel_action); if ((psc_nsmtp_action = name_code(actions, NAME_CODE_FLAG_NONE, var_psc_nsmtp_action)) < 0) msg_fatal("bad %s value: %s", VAR_PSC_NSMTP_ACTION, var_psc_nsmtp_action); if ((psc_barlf_action = name_code(actions, NAME_CODE_FLAG_NONE, var_psc_barlf_action)) < 0) msg_fatal("bad %s value: %s", VAR_PSC_BARLF_ACTION, var_psc_barlf_action); /* Fail "closed" on error. */ psc_wlist_if = addr_match_list_init(MATCH_FLAG_RETURN, var_psc_wlist_if); /* * Start the cache maintenance pseudo thread last. Early cleanup makes * verbose logging more informative (we get positive confirmation that * the cleanup thread runs). */ cache_flags = DICT_CACHE_FLAG_STATISTICS; if (msg_verbose > 1) cache_flags |= DICT_CACHE_FLAG_VERBOSE; if (psc_cache_map != 0 && var_psc_cache_scan > 0) dict_cache_control(psc_cache_map, DICT_CACHE_CTL_FLAGS, cache_flags, DICT_CACHE_CTL_INTERVAL, var_psc_cache_scan, DICT_CACHE_CTL_VALIDATOR, psc_cache_validator, DICT_CACHE_CTL_CONTEXT, (char *) 0, DICT_CACHE_CTL_END); /* * Pre-compute the minimal and maximal TTL. */ psc_min_ttl = PSC_MIN(PSC_MIN(var_psc_pregr_ttl, var_psc_dnsbl_ttl), PSC_MIN(PSC_MIN(var_psc_pipel_ttl, var_psc_nsmtp_ttl), var_psc_barlf_ttl)); psc_max_ttl = PSC_MAX(PSC_MAX(var_psc_pregr_ttl, var_psc_dnsbl_ttl), PSC_MAX(PSC_MAX(var_psc_pipel_ttl, var_psc_nsmtp_ttl), var_psc_barlf_ttl)); /* * Pre-compute the stress and normal command time limits. */ mail_conf_update(VAR_STRESS, "yes"); psc_stress_cmd_time_limit = get_mail_conf_time(VAR_PSC_CMD_TIME, DEF_PSC_CMD_TIME, 1, 0); psc_stress_greet_wait = get_mail_conf_time(VAR_PSC_GREET_WAIT, DEF_PSC_GREET_WAIT, 1, 0); mail_conf_update(VAR_STRESS, ""); psc_normal_cmd_time_limit = get_mail_conf_time(VAR_PSC_CMD_TIME, DEF_PSC_CMD_TIME, 1, 0); psc_normal_greet_wait = get_mail_conf_time(VAR_PSC_GREET_WAIT, DEF_PSC_GREET_WAIT, 1, 0); psc_lowat_check_queue_length = .7 * var_psc_pre_queue_limit; psc_hiwat_check_queue_length = .9 * var_psc_pre_queue_limit; if (msg_verbose) msg_info(VAR_PSC_CMD_TIME ": stress=%d normal=%d lowat=%d hiwat=%d", psc_stress_cmd_time_limit, psc_normal_cmd_time_limit, psc_lowat_check_queue_length, psc_hiwat_check_queue_length); if (psc_lowat_check_queue_length == 0) msg_panic("compiler error: 0.7 * %d = %d", var_psc_pre_queue_limit, psc_lowat_check_queue_length); if (psc_hiwat_check_queue_length == 0) msg_panic("compiler error: 0.9 * %d = %d", var_psc_pre_queue_limit, psc_hiwat_check_queue_length); /* * Per-client concurrency. */ psc_client_concurrency = htable_create(var_psc_pre_queue_limit); }
static VSTREAM *safe_open_exist(const char *path, int flags, struct stat * fstat_st, VSTRING *why) { struct stat local_statbuf; struct stat lstat_st; int saved_errno; VSTREAM *fp; /* * Open an existing file. */ if ((fp = vstream_fopen(path, flags & ~(O_CREAT | O_EXCL), 0)) == 0) { saved_errno = errno; vstring_sprintf(why, "cannot open file: %m"); errno = saved_errno; return (0); } /* * Examine the modes from the open file: it must have exactly one hard * link (so that someone can't lure us into clobbering a sensitive file * by making a hard link to it), and it must be a non-symlink file. */ if (fstat_st == 0) fstat_st = &local_statbuf; if (fstat(vstream_fileno(fp), fstat_st) < 0) { msg_fatal("%s: bad open file status: %m", path); } else if (fstat_st->st_nlink != 1) { vstring_sprintf(why, "file has %d hard links", (int) fstat_st->st_nlink); errno = EPERM; } else if (S_ISDIR(fstat_st->st_mode)) { vstring_sprintf(why, "file is a directory"); errno = EISDIR; } /* * Look up the file again, this time using lstat(). Compare the fstat() * (open file) modes with the lstat() modes. If there is any difference, * either we followed a symlink while opening an existing file, someone * quickly changed the number of hard links, or someone replaced the file * after the open() call. The link and mode tests aren't really necessary * in daemon processes. Set-uid programs, on the other hand, can be * slowed down by arbitrary amounts, and there it would make sense to * compare even more file attributes, such as the inode generation number * on systems that have one. * * Grr. Solaris /dev/whatever is a symlink. We'll have to make an exception * for symlinks owned by root. NEVER, NEVER, make exceptions for symlinks * owned by a non-root user. This would open a security hole when * delivering mail to a world-writable mailbox directory. */ else if (lstat(path, &lstat_st) < 0) { vstring_sprintf(why, "file status changed unexpectedly: %m"); errno = EPERM; } else if (S_ISLNK(lstat_st.st_mode)) { if (lstat_st.st_uid == 0) return (fp); vstring_sprintf(why, "file is a symbolic link"); errno = EPERM; } else if (fstat_st->st_dev != lstat_st.st_dev || fstat_st->st_ino != lstat_st.st_ino #ifdef HAS_ST_GEN || fstat_st->st_gen != lstat_st.st_gen #endif || fstat_st->st_nlink != lstat_st.st_nlink || fstat_st->st_mode != lstat_st.st_mode) { vstring_sprintf(why, "file status changed unexpectedly"); errno = EPERM; } /* * We are almost there... */ else { return (fp); } /* * End up here in case of fstat()/lstat() problems or inconsistencies. */ vstream_fclose(fp); return (0); }
static void enqueue(const int flags, const char *encoding, const char *dsn_envid, int dsn_ret, int dsn_notify, const char *rewrite_context, const char *sender, const char *full_name, char **recipients) { VSTRING *buf; VSTREAM *dst; char *saved_sender; char **cpp; int type; char *start; int skip_from_; TOK822 *tree; TOK822 *tp; int rcpt_count = 0; enum { STRIP_CR_DUNNO, STRIP_CR_DO, STRIP_CR_DONT, STRIP_CR_ERROR } strip_cr; MAIL_STREAM *handle; VSTRING *postdrop_command; uid_t uid = getuid(); int status; int naddr; int prev_type; MIME_STATE *mime_state = 0; SM_STATE state; int mime_errs; const char *errstr; int addr_count; int level; static NAME_CODE sm_fix_eol_table[] = { SM_FIX_EOL_ALWAYS, STRIP_CR_DO, SM_FIX_EOL_STRICT, STRIP_CR_DUNNO, SM_FIX_EOL_NEVER, STRIP_CR_DONT, 0, STRIP_CR_ERROR, }; /* * Access control is enforced in the postdrop command. The code here * merely produces a more user-friendly interface. */ if ((errstr = check_user_acl_byuid(VAR_SUBMIT_ACL, var_submit_acl, uid)) != 0) msg_fatal_status(EX_NOPERM, "User %s(%ld) is not allowed to submit mail", errstr, (long) uid); /* * Initialize. */ buf = vstring_alloc(100); /* * Stop run-away process accidents by limiting the queue file size. This * is not a defense against DOS attack. */ if (var_message_limit > 0 && get_file_limit() > var_message_limit) set_file_limit((off_t) var_message_limit); /* * The sender name is provided by the user. In principle, the mail pickup * service could deduce the sender name from queue file ownership, but: * pickup would not be able to run chrooted, and it may not be desirable * to use login names at all. */ if (sender != 0) { VSTRING_RESET(buf); VSTRING_TERMINATE(buf); tree = tok822_parse(sender); for (naddr = 0, tp = tree; tp != 0; tp = tp->next) if (tp->type == TOK822_ADDR && naddr++ == 0) tok822_internalize(buf, tp->head, TOK822_STR_DEFL); tok822_free_tree(tree); saved_sender = mystrdup(STR(buf)); if (naddr > 1) msg_warn("-f option specified malformed sender: %s", sender); } else { if ((sender = username()) == 0) msg_fatal_status(EX_OSERR, "no login name found for user ID %lu", (unsigned long) uid); saved_sender = mystrdup(sender); } /* * Let the postdrop command open the queue file for us, and sanity check * the content. XXX Make postdrop a manifest constant. */ errno = 0; postdrop_command = vstring_alloc(1000); vstring_sprintf(postdrop_command, "%s/postdrop -r", var_command_dir); for (level = 0; level < msg_verbose; level++) vstring_strcat(postdrop_command, " -v"); if ((handle = mail_stream_command(STR(postdrop_command))) == 0) msg_fatal_status(EX_UNAVAILABLE, "%s(%ld): unable to execute %s: %m", saved_sender, (long) uid, STR(postdrop_command)); vstring_free(postdrop_command); dst = handle->stream; /* * First, write envelope information to the output stream. * * For sendmail compatibility, parse each command-line recipient as if it * were an RFC 822 message header; some MUAs specify comma-separated * recipient lists; and some MUAs even specify "word word <address>". * * Sort-uniq-ing the recipient list is done after address canonicalization, * before recipients are written to queue file. That's cleaner than * having the queue manager nuke duplicate recipient status records. * * XXX Should limit the size of envelope records. * * With "sendmail -N", instead of a per-message NOTIFY record we store one * per recipient so that we can simplify the implementation somewhat. */ if (dsn_envid) rec_fprintf(dst, REC_TYPE_ATTR, "%s=%s", MAIL_ATTR_DSN_ENVID, dsn_envid); if (dsn_ret) rec_fprintf(dst, REC_TYPE_ATTR, "%s=%d", MAIL_ATTR_DSN_RET, dsn_ret); rec_fprintf(dst, REC_TYPE_ATTR, "%s=%s", MAIL_ATTR_RWR_CONTEXT, rewrite_context); if (full_name || (full_name = fullname()) != 0) rec_fputs(dst, REC_TYPE_FULL, full_name); rec_fputs(dst, REC_TYPE_FROM, saved_sender); if (verp_delims && *saved_sender == 0) msg_fatal_status(EX_USAGE, "%s(%ld): -V option requires non-null sender address", saved_sender, (long) uid); if (encoding) rec_fprintf(dst, REC_TYPE_ATTR, "%s=%s", MAIL_ATTR_ENCODING, encoding); if (DEL_REQ_TRACE_FLAGS(flags)) rec_fprintf(dst, REC_TYPE_ATTR, "%s=%d", MAIL_ATTR_TRACE_FLAGS, DEL_REQ_TRACE_FLAGS(flags)); if (verp_delims) rec_fputs(dst, REC_TYPE_VERP, verp_delims); if (recipients) { for (cpp = recipients; *cpp != 0; cpp++) { tree = tok822_parse(*cpp); for (addr_count = 0, tp = tree; tp != 0; tp = tp->next) { if (tp->type == TOK822_ADDR) { tok822_internalize(buf, tp->head, TOK822_STR_DEFL); if (dsn_notify) rec_fprintf(dst, REC_TYPE_ATTR, "%s=%d", MAIL_ATTR_DSN_NOTIFY, dsn_notify); if (REC_PUT_BUF(dst, REC_TYPE_RCPT, buf) < 0) msg_fatal_status(EX_TEMPFAIL, "%s(%ld): error writing queue file: %m", saved_sender, (long) uid); ++rcpt_count; ++addr_count; } } tok822_free_tree(tree); if (addr_count == 0) { if (rec_put(dst, REC_TYPE_RCPT, "", 0) < 0) msg_fatal_status(EX_TEMPFAIL, "%s(%ld): error writing queue file: %m", saved_sender, (long) uid); ++rcpt_count; } } } /* * Append the message contents to the queue file. Write chunks of at most * 1kbyte. Internally, we use different record types for data ending in * LF and for data that doesn't, so we can actually be binary transparent * for local mail. Unfortunately, SMTP has no record continuation * convention, so there is no guarantee that arbitrary data will be * delivered intact via SMTP. Strip leading From_ lines. For the benefit * of UUCP environments, also get rid of leading >>>From_ lines. */ rec_fputs(dst, REC_TYPE_MESG, ""); if (DEL_REQ_TRACE_ONLY(flags) != 0) { if (flags & SM_FLAG_XRCPT) msg_fatal_status(EX_USAGE, "%s(%ld): -t option cannot be used with -bv", saved_sender, (long) uid); if (*saved_sender) rec_fprintf(dst, REC_TYPE_NORM, "From: %s", saved_sender); rec_fprintf(dst, REC_TYPE_NORM, "Subject: probe"); if (recipients) { rec_fprintf(dst, REC_TYPE_CONT, "To:"); for (cpp = recipients; *cpp != 0; cpp++) { rec_fprintf(dst, REC_TYPE_NORM, " %s%s", *cpp, cpp[1] ? "," : ""); } } } else { /* * Initialize the MIME processor and set up the callback context. */ if (flags & SM_FLAG_XRCPT) { state.dst = dst; state.recipients = argv_alloc(2); state.resent_recip = argv_alloc(2); state.resent = 0; state.saved_sender = saved_sender; state.uid = uid; state.temp = vstring_alloc(10); mime_state = mime_state_alloc(MIME_OPT_DISABLE_MIME | MIME_OPT_REPORT_TRUNC_HEADER, output_header, (MIME_STATE_ANY_END) 0, output_text, (MIME_STATE_ANY_END) 0, (MIME_STATE_ERR_PRINT) 0, (void *) &state); } /* * Process header/body lines. */ skip_from_ = 1; strip_cr = name_code(sm_fix_eol_table, NAME_CODE_FLAG_STRICT_CASE, var_sm_fix_eol); if (strip_cr == STRIP_CR_ERROR) msg_fatal_status(EX_USAGE, "invalid %s value: %s", VAR_SM_FIX_EOL, var_sm_fix_eol); for (prev_type = 0; (type = rec_streamlf_get(VSTREAM_IN, buf, var_line_limit)) != REC_TYPE_EOF; prev_type = type) { if (strip_cr == STRIP_CR_DUNNO && type == REC_TYPE_NORM) { if (VSTRING_LEN(buf) > 0 && vstring_end(buf)[-1] == '\r') strip_cr = STRIP_CR_DO; else strip_cr = STRIP_CR_DONT; } if (skip_from_) { if (type == REC_TYPE_NORM) { start = STR(buf); if (strncmp(start + strspn(start, ">"), "From ", 5) == 0) continue; } skip_from_ = 0; } if (strip_cr == STRIP_CR_DO && type == REC_TYPE_NORM) while (VSTRING_LEN(buf) > 0 && vstring_end(buf)[-1] == '\r') vstring_truncate(buf, VSTRING_LEN(buf) - 1); if ((flags & SM_FLAG_AEOF) && prev_type != REC_TYPE_CONT && VSTRING_LEN(buf) == 1 && *STR(buf) == '.') break; if (mime_state) { mime_errs = mime_state_update(mime_state, type, STR(buf), VSTRING_LEN(buf)); if (mime_errs) msg_fatal_status(EX_DATAERR, "%s(%ld): unable to extract recipients: %s", saved_sender, (long) uid, mime_state_error(mime_errs)); } else { if (REC_PUT_BUF(dst, type, buf) < 0) msg_fatal_status(EX_TEMPFAIL, "%s(%ld): error writing queue file: %m", saved_sender, (long) uid); } } } /* * Finish MIME processing. We need a final mime_state_update() call in * order to flush text that is still buffered. That can happen when the * last line did not end in newline. */ if (mime_state) { mime_errs = mime_state_update(mime_state, REC_TYPE_EOF, "", 0); if (mime_errs) msg_fatal_status(EX_DATAERR, "%s(%ld): unable to extract recipients: %s", saved_sender, (long) uid, mime_state_error(mime_errs)); mime_state = mime_state_free(mime_state); } /* * Append recipient addresses that were extracted from message headers. */ rec_fputs(dst, REC_TYPE_XTRA, ""); if (flags & SM_FLAG_XRCPT) { for (cpp = state.resent ? state.resent_recip->argv : state.recipients->argv; *cpp; cpp++) { if (dsn_notify) rec_fprintf(dst, REC_TYPE_ATTR, "%s=%d", MAIL_ATTR_DSN_NOTIFY, dsn_notify); if (rec_put(dst, REC_TYPE_RCPT, *cpp, strlen(*cpp)) < 0) msg_fatal_status(EX_TEMPFAIL, "%s(%ld): error writing queue file: %m", saved_sender, (long) uid); ++rcpt_count; } argv_free(state.recipients); argv_free(state.resent_recip); vstring_free(state.temp); } if (rcpt_count == 0) msg_fatal_status(EX_USAGE, (flags & SM_FLAG_XRCPT) ? "%s(%ld): No recipient addresses found in message header" : "%s(%ld): Recipient addresses must be specified on" " the command line or via the -t option", saved_sender, (long) uid); /* * Identify the end of the queue file. */ rec_fputs(dst, REC_TYPE_END, ""); /* * Make sure that the message makes it to the file system. Once we have * terminated with successful exit status we cannot lose the message due * to "frivolous reasons". If all goes well, prevent the run-time error * handler from removing the file. */ if (vstream_ferror(VSTREAM_IN)) msg_fatal_status(EX_DATAERR, "%s(%ld): error reading input: %m", saved_sender, (long) uid); if ((status = mail_stream_finish(handle, (VSTRING *) 0)) != 0) msg_fatal_status((status & CLEANUP_STAT_BAD) ? EX_SOFTWARE : (status & CLEANUP_STAT_WRITE) ? EX_TEMPFAIL : EX_UNAVAILABLE, "%s(%ld): %s", saved_sender, (long) uid, cleanup_strerror(status)); /* * Don't leave them in the dark. */ if (DEL_REQ_TRACE_FLAGS(flags)) { vstream_printf("Mail Delivery Status Report will be mailed to <%s>.\n", saved_sender); vstream_fflush(VSTREAM_OUT); } /* * Cleanup. Not really necessary as we're about to exit, but good for * debugging purposes. */ vstring_free(buf); myfree(saved_sender); }
int sent(int flags, const char *id, MSG_STATS *stats, RECIPIENT *recipient, const char *relay, DSN *dsn) { DSN my_dsn = *dsn; int status; /* * Sanity check. */ if (my_dsn.status[0] != '2' || !dsn_valid(my_dsn.status)) { msg_warn("sent: ignoring dsn code \"%s\"", my_dsn.status); my_dsn.status = "2.0.0"; } /* * MTA-requested address verification information is stored in the verify * service database. */ if (flags & DEL_REQ_FLAG_MTA_VRFY) { my_dsn.action = "deliverable"; status = verify_append(id, stats, recipient, relay, &my_dsn, DEL_RCPT_STAT_OK); return (status); } /* * User-requested address verification information is logged and mailed * to the requesting user. */ if (flags & DEL_REQ_FLAG_USR_VRFY) { my_dsn.action = "deliverable"; status = trace_append(flags, id, stats, recipient, relay, &my_dsn); return (status); } /* * Normal mail delivery. May also send a delivery record to the user. */ else { if (my_dsn.action == 0 || my_dsn.action[0] == 0) my_dsn.action = "delivered"; if (((flags & DEL_REQ_FLAG_RECORD) == 0 || trace_append(flags, id, stats, recipient, relay, &my_dsn) == 0) && ((recipient->dsn_notify & DSN_NOTIFY_SUCCESS) == 0 || trace_append(flags, id, stats, recipient, relay, &my_dsn) == 0)) { log_adhoc(id, stats, recipient, relay, &my_dsn, "sent"); status = 0; } else { VSTRING *junk = vstring_alloc(100); vstring_sprintf(junk, "%s: %s service failed", id, var_trace_service); my_dsn.reason = vstring_str(junk); my_dsn.status ="4.3.0"; status = defer_append(flags, id, stats, recipient, relay, &my_dsn); vstring_free(junk); } return (status); } }
static int eval_command_status(int command_status, char *service, DELIVER_REQUEST *request, PIPE_ATTR *attr, DSN_BUF *why) { RECIPIENT *rcpt; int status; int result = 0; int n; char *saved_text; /* * Depending on the result, bounce or defer the message, and mark the * recipient as done where appropriate. */ switch (command_status) { case PIPE_STAT_OK: /* Save the command output before dsb_update() clobbers it. */ vstring_truncate(why->reason, trimblanks(STR(why->reason), VSTRING_LEN(why->reason)) - STR(why->reason)); if (VSTRING_LEN(why->reason) > 0) { VSTRING_TERMINATE(why->reason); saved_text = vstring_export(vstring_sprintf( vstring_alloc(VSTRING_LEN(why->reason)), " (%.100s)", STR(why->reason))); } else saved_text = mystrdup(""); /* uses shared R/O storage */ dsb_update(why, "2.0.0", (attr->flags & PIPE_OPT_FINAL_DELIVERY) ? "delivered" : "relayed", DSB_SKIP_RMTA, DSB_SKIP_REPLY, "delivered via %s service%s", service, saved_text); myfree(saved_text); (void) DSN_FROM_DSN_BUF(why); for (n = 0; n < request->rcpt_list.len; n++) { rcpt = request->rcpt_list.info + n; status = sent(DEL_REQ_TRACE_FLAGS(request->flags), request->queue_id, &request->msg_stats, rcpt, service, &why->dsn); if (status == 0 && (request->flags & DEL_REQ_FLAG_SUCCESS)) deliver_completed(request->fp, rcpt->offset); result |= status; } break; case PIPE_STAT_BOUNCE: case PIPE_STAT_DEFER: (void) DSN_FROM_DSN_BUF(why); for (n = 0; n < request->rcpt_list.len; n++) { rcpt = request->rcpt_list.info + n; /* XXX Maybe encapsulate this with ndr_append(). */ status = (STR(why->status)[0] != '4' ? bounce_append : defer_append) (DEL_REQ_TRACE_FLAGS(request->flags), request->queue_id, &request->msg_stats, rcpt, service, &why->dsn); if (status == 0) deliver_completed(request->fp, rcpt->offset); result |= status; } break; case PIPE_STAT_CORRUPT: /* XXX DSN should we send something? */ result |= DEL_STAT_DEFER; break; default: msg_panic("eval_command_status: bad status %d", command_status); /* NOTREACHED */ } return (result); }
/********************************************************************** * public interface dict_mysql_lookup * find database entry return 0 if no alias found, set dict_errno * on errors to DICT_ERRBO_RETRY and set dict_errno to 0 on success *********************************************************************/ static const char *dict_mysql_lookup(DICT *dict, const char *name) { MYSQL_RES *query_res; MYSQL_ROW row; DICT_MYSQL *dict_mysql; PLMYSQL *pldb; static VSTRING *result; static VSTRING *query = 0; int i, j, numrows; char *name_escaped = 0; dict_mysql = (DICT_MYSQL *) dict; pldb = dict_mysql->pldb; /* initialization for query */ query = vstring_alloc(24); vstring_strcpy(query, ""); if ((name_escaped = (char *) mymalloc((sizeof(char) * (strlen(name) * 2) +1))) == NULL) { msg_fatal("dict_mysql_lookup: out of memory."); } /* prepare the query */ mysql_escape_string(name_escaped, name, (unsigned int) strlen(name)); vstring_sprintf(query, "select %s from %s where %s = '%s' %s", dict_mysql->name->select_field, dict_mysql->name->table, dict_mysql->name->where_field, name_escaped, dict_mysql->name->additional_conditions); if (msg_verbose) msg_info("dict_mysql_lookup using sql query: %s", vstring_str(query)); /* free mem associated with preparing the query */ myfree(name_escaped); /* do the query - set dict_errno & cleanup if there's an error */ if ((query_res = plmysql_query(pldb, vstring_str(query), dict_mysql->name->dbname, dict_mysql->name->username, dict_mysql->name->password)) == 0) { dict_errno = DICT_ERR_RETRY; vstring_free(query); return 0; } dict_errno = 0; /* free the vstring query */ vstring_free(query); numrows = mysql_num_rows(query_res); if (msg_verbose) msg_info("dict_mysql_lookup: retrieved %d rows", numrows); if (numrows == 0) { mysql_free_result(query_res); return 0; } if (result == 0) result = vstring_alloc(10); vstring_strcpy(result, ""); for (i = 0; i < numrows; i++) { row = mysql_fetch_row(query_res); if (i > 0) vstring_strcat(result, ","); for (j = 0; j < mysql_num_fields(query_res); j++) { if (row[j] == 0) { if (msg_verbose > 1) msg_info("dict_mysql_lookup: null field #%d row #%d", j, i); mysql_free_result(query_res); return (0); } if (j > 0) vstring_strcat(result, ","); vstring_strcat(result, row[j]); if (msg_verbose > 1) msg_info("dict_mysql_lookup: retrieved field: %d: %s", j, row[j]); } } mysql_free_result(query_res); return vstring_str(result); }
static ARGV *match_list_parse(ARGV *list, char *string, int init_match) { const char *myname = "match_list_parse"; VSTRING *buf = vstring_alloc(10); VSTREAM *fp; const char *delim = " ,\t\r\n"; char *bp = string; char *start; char *item; char *map_type_name_flags; int match; #define OPEN_FLAGS O_RDONLY #define DICT_FLAGS (DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX) #define STR(x) vstring_str(x) /* * /filename contents are expanded in-line. To support !/filename we * prepend the negation operator to each item from the file. */ while ((start = mystrtokq(&bp, delim, "{}")) != 0) { if (*start == '#') { msg_warn("%s: comment at end of line is not supported: %s %s", myname, start, bp); break; } for (match = init_match, item = start; *item == '!'; item++) match = !match; if (*item == 0) msg_fatal("%s: no pattern after '!'", myname); if (*item == '/') { /* /file/name */ if ((fp = vstream_fopen(item, O_RDONLY, 0)) == 0) { vstring_sprintf(buf, "%s:%s", DICT_TYPE_NOFILE, item); /* XXX Should increment existing map refcount. */ if (dict_handle(STR(buf)) == 0) dict_register(STR(buf), dict_surrogate(DICT_TYPE_NOFILE, item, OPEN_FLAGS, DICT_FLAGS, "open file %s: %m", item)); argv_add(list, STR(buf), (char *) 0); } else { while (vstring_fgets(buf, fp)) if (vstring_str(buf)[0] != '#') list = match_list_parse(list, vstring_str(buf), match); if (vstream_fclose(fp)) msg_fatal("%s: read file %s: %m", myname, item); } } else if (MATCH_DICTIONARY(item)) { /* type:table */ vstring_sprintf(buf, "%s%s(%o,%s)", match ? "" : "!", item, OPEN_FLAGS, dict_flags_str(DICT_FLAGS)); map_type_name_flags = STR(buf) + (match == 0); /* XXX Should increment existing map refcount. */ if (dict_handle(map_type_name_flags) == 0) dict_register(map_type_name_flags, dict_open(item, OPEN_FLAGS, DICT_FLAGS)); argv_add(list, STR(buf), (char *) 0); } else { /* other pattern */ argv_add(list, match ? item : STR(vstring_sprintf(buf, "!%s", item)), (char *) 0); } } vstring_free(buf); return (list); }
static const char *pcf_convert_service_parameter(void *ptr) { return (STR(vstring_sprintf(pcf_param_string_buf, "$%s", (char *) ptr))); }
static const char *convert_int_parameter(char *ptr) { CONFIG_INT_TABLE *cit = (CONFIG_INT_TABLE *) ptr; return (STR(vstring_sprintf(param_string_buf, "%d", cit->defval))); }
int main(int argc, char **argv) { static VSTREAM *lock_fp; static VSTREAM *data_lock_fp; VSTRING *lock_path; VSTRING *data_lock_path; off_t inherited_limit; int debug_me = 0; int ch; int fd; int n; int test_lock = 0; VSTRING *why; WATCHDOG *watchdog; ARGV *import_env; int wait_flag = 0; int monitor_fd = -1; /* * Fingerprint executables and core dumps. */ MAIL_VERSION_STAMP_ALLOCATE; /* * Initialize. */ umask(077); /* never fails! */ /* * Process environment options as early as we can. */ if (getenv(CONF_ENV_VERB)) msg_verbose = 1; if (getenv(CONF_ENV_DEBUG)) debug_me = 1; /* * Don't die when a process goes away unexpectedly. */ signal(SIGPIPE, SIG_IGN); /* * Strip and save the process name for diagnostics etc. */ var_procname = mystrdup(basename(argv[0])); /* * When running a child process, don't leak any open files that were * leaked to us by our own (privileged) parent process. Descriptors 0-2 * are taken care of after we have initialized error logging. * * Some systems such as AIX have a huge per-process open file limit. In * those cases, limit the search for potential file descriptor leaks to * just the first couple hundred. * * The Debian post-installation script passes an open file descriptor into * the master process and waits forever for someone to close it. Because * of this we have to close descriptors > 2, and pray that doing so does * not break things. */ closefrom(3); /* * Initialize logging and exit handler. */ msg_syslog_init(mail_task(var_procname), LOG_PID, LOG_FACILITY); /* * Check the Postfix library version as soon as we enable logging. */ MAIL_VERSION_CHECK; /* * The mail system must be run by the superuser so it can revoke * privileges for selected operations. That's right - it takes privileges * to toss privileges. */ if (getuid() != 0) msg_fatal("the master command is reserved for the superuser"); if (unsafe() != 0) msg_fatal("the master command must not run as a set-uid process"); /* * Process JCL. */ while ((ch = GETOPT(argc, argv, "c:Dde:tvw")) > 0) { switch (ch) { case 'c': if (setenv(CONF_ENV_PATH, optarg, 1) < 0) msg_fatal("out of memory"); break; case 'd': master_detach = 0; break; case 'e': event_request_timer(master_exit_event, (char *) 0, atoi(optarg)); break; case 'D': debug_me = 1; break; case 't': test_lock = 1; break; case 'v': msg_verbose++; break; case 'w': wait_flag = 1; break; default: usage(argv[0]); /* NOTREACHED */ } } /* * This program takes no other arguments. */ if (argc > optind) usage(argv[0]); /* * Sanity check. */ if (test_lock && wait_flag) msg_fatal("the -t and -w options cannot be used together"); /* * Run a foreground monitor process that returns an exit status of 0 when * the child background process reports successful initialization as a * daemon process. We use a generous limit in case main/master.cf specify * symbolic hosts/ports and the naming service is slow. */ #define MASTER_INIT_TIMEOUT 100 /* keep this limit generous */ if (wait_flag) monitor_fd = master_monitor(MASTER_INIT_TIMEOUT); /* * If started from a terminal, get rid of any tty association. This also * means that all errors and warnings must go to the syslog daemon. */ if (master_detach) for (fd = 0; fd < 3; fd++) { (void) close(fd); if (open("/dev/null", O_RDWR, 0) != fd) msg_fatal("open /dev/null: %m"); } /* * Run in a separate process group, so that "postfix stop" can terminate * all MTA processes cleanly. Give up if we can't separate from our * parent process. We're not supposed to blow away the parent. */ if (debug_me == 0 && master_detach != 0 && setsid() == -1 && getsid(0) != getpid()) msg_fatal("unable to set session and process group ID: %m"); /* * Make some room for plumbing with file descriptors. XXX This breaks * when a service listens on many ports. In order to do this right we * must change the master-child interface so that descriptors do not need * to have fixed numbers. * * In a child we need two descriptors for the flow control pipe, one for * child->master status updates and at least one for listening. */ for (n = 0; n < 5; n++) { if (close_on_exec(dup(0), CLOSE_ON_EXEC) < 0) msg_fatal("dup(0): %m"); } /* * Final initializations. Unfortunately, we must read the global Postfix * configuration file after doing command-line processing, so that we get * consistent results when we SIGHUP the server to reload configuration * files. */ master_vars_init(); /* * In case of multi-protocol support. This needs to be done because * master does not invoke mail_params_init() (it was written before that * code existed). */ (void) inet_proto_init(VAR_INET_PROTOCOLS, var_inet_protocols); /* * Environment import filter, to enforce consistent behavior whether * Postfix is started by hand, or at system boot time. */ import_env = argv_split(var_import_environ, ", \t\r\n"); clean_env(import_env->argv); argv_free(import_env); if ((inherited_limit = get_file_limit()) < 0) set_file_limit(OFF_T_MAX); if (chdir(var_queue_dir)) msg_fatal("chdir %s: %m", var_queue_dir); /* * Lock down the master.pid file. In test mode, no file means that it * isn't locked. */ lock_path = vstring_alloc(10); data_lock_path = vstring_alloc(10); why = vstring_alloc(10); vstring_sprintf(lock_path, "%s/%s.pid", DEF_PID_DIR, var_procname); if (test_lock && access(vstring_str(lock_path), F_OK) < 0) exit(0); lock_fp = open_lock(vstring_str(lock_path), O_RDWR | O_CREAT, 0644, why); if (test_lock) exit(lock_fp ? 0 : 1); if (lock_fp == 0) msg_fatal("open lock file %s: %s", vstring_str(lock_path), vstring_str(why)); vstream_fprintf(lock_fp, "%*lu\n", (int) sizeof(unsigned long) * 4, (unsigned long) var_pid); if (vstream_fflush(lock_fp)) msg_fatal("cannot update lock file %s: %m", vstring_str(lock_path)); close_on_exec(vstream_fileno(lock_fp), CLOSE_ON_EXEC); /* * Lock down the Postfix-writable data directory. */ vstring_sprintf(data_lock_path, "%s/%s.lock", var_data_dir, var_procname); set_eugid(var_owner_uid, var_owner_gid); data_lock_fp = open_lock(vstring_str(data_lock_path), O_RDWR | O_CREAT, 0644, why); set_ugid(getuid(), getgid()); if (data_lock_fp == 0) msg_fatal("open lock file %s: %s", vstring_str(data_lock_path), vstring_str(why)); vstream_fprintf(data_lock_fp, "%*lu\n", (int) sizeof(unsigned long) * 4, (unsigned long) var_pid); if (vstream_fflush(data_lock_fp)) msg_fatal("cannot update lock file %s: %m", vstring_str(data_lock_path)); close_on_exec(vstream_fileno(data_lock_fp), CLOSE_ON_EXEC); /* * Clean up. */ vstring_free(why); vstring_free(lock_path); vstring_free(data_lock_path); /* * Optionally start the debugger on ourself. */ if (debug_me) debug_process(); /* * Finish initialization, last part. We must process configuration files * after processing command-line parameters, so that we get consistent * results when we SIGHUP the server to reload configuration files. */ master_config(); master_sigsetup(); master_flow_init(); msg_info("daemon started -- version %s, configuration %s", var_mail_version, var_config_dir); /* * Report successful initialization to the foreground monitor process. */ if (monitor_fd >= 0) { write(monitor_fd, "", 1); (void) close(monitor_fd); } /* * Process events. The event handler will execute the read/write/timer * action routines. Whenever something has happened, see if we received * any signal in the mean time. Although the master process appears to do * multiple things at the same time, it really is all a single thread, so * that there are no concurrency conflicts within the master process. */ #define MASTER_WATCHDOG_TIME 1000 watchdog = watchdog_create(MASTER_WATCHDOG_TIME, (WATCHDOG_FN) 0, (char *) 0); for (;;) { #ifdef HAS_VOLATILE_LOCKS if (myflock(vstream_fileno(lock_fp), INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) msg_fatal("refresh exclusive lock: %m"); if (myflock(vstream_fileno(data_lock_fp), INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) msg_fatal("refresh exclusive lock: %m"); #endif watchdog_start(watchdog); /* same as trigger servers */ event_loop(MASTER_WATCHDOG_TIME / 2); if (master_gotsighup) { msg_info("reload -- version %s, configuration %s", var_mail_version, var_config_dir); master_gotsighup = 0; /* this first */ master_vars_init(); /* then this */ master_refresh(); /* then this */ } if (master_gotsigchld) { if (msg_verbose) msg_info("got sigchld"); master_gotsigchld = 0; /* this first */ master_reap_child(); /* then this */ } } }
int main(int argc, char **argv) { SESSION *session; char *host; char *port; char *path; int path_len; int sessions = 1; int ch; ssize_t len; int n; int i; char *buf; const char *parse_err; struct addrinfo *res; int aierr; const char *protocols = INET_PROTO_NAME_ALL; INET_PROTO_INFO *proto_info; /* * Fingerprint executables and core dumps. */ MAIL_VERSION_STAMP_ALLOCATE; signal(SIGPIPE, SIG_IGN); msg_vstream_init(argv[0], VSTREAM_ERR); /* * Parse JCL. */ while ((ch = GETOPT(argc, argv, "46cC:f:l:m:M:r:R:s:t:vw:")) > 0) { switch (ch) { case '4': protocols = INET_PROTO_NAME_IPV4; break; case '6': protocols = INET_PROTO_NAME_IPV6; break; case 'c': count++; break; case 'C': if ((connect_count = atoi(optarg)) <= 0) usage(argv[0]); break; case 'f': sender = optarg; break; case 'l': if ((message_length = atoi(optarg)) <= 0) usage(argv[0]); break; case 'm': if ((message_count = atoi(optarg)) <= 0) usage(argv[0]); break; case 'M': if (*optarg == '[') { if (!valid_mailhost_literal(optarg, DO_GRIPE)) msg_fatal("bad address literal: %s", optarg); } else { if (!valid_hostname(optarg, DO_GRIPE)) msg_fatal("bad hostname: %s", optarg); } var_myhostname = optarg; break; case 'r': if ((recipients = atoi(optarg)) <= 0) usage(argv[0]); break; case 'R': if (fixed_delay > 0 || (random_delay = atoi(optarg)) <= 0) usage(argv[0]); break; case 's': if ((sessions = atoi(optarg)) <= 0) usage(argv[0]); break; case 't': recipient = optarg; break; case 'v': msg_verbose++; break; case 'w': if (random_delay > 0 || (fixed_delay = atoi(optarg)) <= 0) usage(argv[0]); break; default: usage(argv[0]); } } if (argc - optind != 1) usage(argv[0]); if (random_delay > 0) srand(getpid()); /* * Translate endpoint address to internal form. */ proto_info = inet_proto_init("protocols", protocols); if (strncmp(argv[optind], "unix:", 5) == 0) { path = argv[optind] + 5; path_len = strlen(path); if (path_len >= (int) sizeof(sun.sun_path)) msg_fatal("unix-domain name too long: %s", path); memset((char *) &sun, 0, sizeof(sun)); sun.sun_family = AF_UNIX; #ifdef HAS_SUN_LEN sun.sun_len = path_len + 1; #endif memcpy(sun.sun_path, path, path_len); sa = (struct sockaddr *) & sun; sa_length = sizeof(sun); } else { if (strncmp(argv[optind], "inet:", 5) == 0) argv[optind] += 5; buf = mystrdup(argv[optind]); if ((parse_err = host_port(buf, &host, (char *) 0, &port, "628")) != 0) msg_fatal("%s: %s", argv[optind], parse_err); if ((aierr = hostname_to_sockaddr(host, port, SOCK_STREAM, &res)) != 0) msg_fatal("%s: %s", argv[optind], MAI_STRERROR(aierr)); myfree(buf); sa = (struct sockaddr *) & ss; if (res->ai_addrlen > sizeof(ss)) msg_fatal("address length %d > buffer length %d", (int) res->ai_addrlen, (int) sizeof(ss)); memcpy((char *) sa, res->ai_addr, res->ai_addrlen); sa_length = res->ai_addrlen; #ifdef HAS_SA_LEN sa->sa_len = sa_length; #endif freeaddrinfo(res); } /* * Allocate space for temporary buffer. */ buffer = vstring_alloc(100); /* * Make sure we have sender and recipient addresses. */ if (var_myhostname == 0) var_myhostname = get_hostname(); if (sender == 0 || recipient == 0) { vstring_sprintf(buffer, "foo@%s", var_myhostname); defaddr = mystrdup(vstring_str(buffer)); if (sender == 0) sender = defaddr; if (recipient == 0) recipient = defaddr; } /* * Prepare some results that may be used multiple times: the message * content netstring, the sender netstring, and the recipient netstrings. */ mydate = mail_date(time((time_t *) 0)); mypid = getpid(); message_buffer = vstring_alloc(message_length + 200); vstring_sprintf(buffer, "From: <%s>\nTo: <%s>\nDate: %s\nMessage-Id: <%d@%s>\n\n", sender, recipient, mydate, mypid, var_myhostname); for (n = 1; LEN(buffer) < message_length; n++) { for (i = 0; i < n && i < 79; i++) VSTRING_ADDCH(buffer, 'X'); VSTRING_ADDCH(buffer, '\n'); } STR(buffer)[message_length - 1] = '\n'; netstring_memcpy(message_buffer, STR(buffer), message_length); len = strlen(sender); sender_buffer = vstring_alloc(len); netstring_memcpy(sender_buffer, sender, len); if (recipients == 1) { len = strlen(recipient); recipient_buffer = vstring_alloc(len); netstring_memcpy(recipient_buffer, recipient, len); } else { recipient_buffer = vstring_alloc(100); for (n = 0; n < recipients; n++) { vstring_sprintf(buffer, "%d%s", n, recipient); netstring_memcat(recipient_buffer, STR(buffer), LEN(buffer)); } } /* * Start sessions. */ while (sessions-- > 0) { session = (SESSION *) mymalloc(sizeof(*session)); session->stream = 0; session->xfer_count = 0; session->connect_count = connect_count; session->next = 0; session_count++; startup(session); } for (;;) { event_loop(-1); if (session_count <= 0 && message_count <= 0) { if (count) { VSTREAM_PUTC('\n', VSTREAM_OUT); vstream_fflush(VSTREAM_OUT); } exit(0); } } }