int mail_copy(const char *sender, const char *orig_rcpt, const char *delivered, VSTREAM *src, VSTREAM *dst, int flags, const char *eol, DSN_BUF *why) { const char *myname = "mail_copy"; VSTRING *buf; char *bp; off_t orig_length; int read_error; int write_error; int corrupt_error = 0; time_t now; int type; int prev_type; struct stat st; off_t size_limit; /* * Workaround 20090114. This will hopefully get someone's attention. The * problem with file_size_limit < message_size_limit is that mail will be * delivered again and again until someone removes it from the queue by * hand, because Postfix cannot mark a recipient record as "completed". */ if (fstat(vstream_fileno(src), &st) < 0) msg_fatal("fstat: %m"); if ((size_limit = get_file_limit()) < st.st_size) msg_panic("file size limit %lu < message size %lu. This " "causes large messages to be delivered repeatedly " "after they were submitted with \"sendmail -t\" " "or after recipients were added with the Milter " "SMFIR_ADDRCPT request", (unsigned long) size_limit, (unsigned long) st.st_size); /* * Initialize. */ #ifndef NO_TRUNCATE if ((flags & MAIL_COPY_TOFILE) != 0) if ((orig_length = vstream_fseek(dst, (off_t) 0, SEEK_END)) < 0) msg_fatal("seek file %s: %m", VSTREAM_PATH(dst)); #endif buf = vstring_alloc(100); /* * Prepend a bunch of headers to the message. */ if (flags & (MAIL_COPY_FROM | MAIL_COPY_RETURN_PATH)) { if (sender == 0) msg_panic("%s: null sender", myname); quote_822_local(buf, sender); if (flags & MAIL_COPY_FROM) { time(&now); vstream_fprintf(dst, "From %s %.24s%s", *sender == 0 ? MAIL_ADDR_MAIL_DAEMON : vstring_str(buf), asctime(localtime(&now)), eol); } if (flags & MAIL_COPY_RETURN_PATH) { vstream_fprintf(dst, "Return-Path: <%s>%s", *sender ? vstring_str(buf) : "", eol); } } if (flags & MAIL_COPY_ORIG_RCPT) { if (orig_rcpt == 0) msg_panic("%s: null orig_rcpt", myname); /* * An empty original recipient record almost certainly means that * original recipient processing was disabled. */ if (*orig_rcpt) { quote_822_local(buf, orig_rcpt); vstream_fprintf(dst, "X-Original-To: %s%s", vstring_str(buf), eol); } } if (flags & MAIL_COPY_DELIVERED) { if (delivered == 0) msg_panic("%s: null delivered", myname); quote_822_local(buf, delivered); vstream_fprintf(dst, "Delivered-To: %s%s", vstring_str(buf), eol); } /* * Copy the message. Escape lines that could be confused with the ugly * From_ line. Make sure that there is a blank line at the end of the * message so that the next ugly From_ can be found by mail reading * software. * * XXX Rely on the front-end services to enforce record size limits. */ #define VSTREAM_FWRITE_BUF(s,b) \ vstream_fwrite((s),vstring_str(b),VSTRING_LEN(b)) prev_type = REC_TYPE_NORM; while ((type = rec_get(src, buf, 0)) > 0) { if (type != REC_TYPE_NORM && type != REC_TYPE_CONT) break; bp = vstring_str(buf); if (prev_type == REC_TYPE_NORM) { if ((flags & MAIL_COPY_QUOTE) && *bp == 'F' && !strncmp(bp, "From ", 5)) VSTREAM_PUTC('>', dst); if ((flags & MAIL_COPY_DOT) && *bp == '.') VSTREAM_PUTC('.', dst); } if (VSTRING_LEN(buf) && VSTREAM_FWRITE_BUF(dst, buf) != VSTRING_LEN(buf)) break; if (type == REC_TYPE_NORM && vstream_fputs(eol, dst) == VSTREAM_EOF) break; prev_type = type; } if (vstream_ferror(dst) == 0) { if (var_fault_inj_code == 1) type = 0; if (type != REC_TYPE_XTRA) { /* XXX Where is the queue ID? */ msg_warn("bad record type: %d in message content", type); corrupt_error = mark_corrupt(src); } if (prev_type != REC_TYPE_NORM) vstream_fputs(eol, dst); if (flags & MAIL_COPY_BLANK) vstream_fputs(eol, dst); } vstring_free(buf); /* * Make sure we read and wrote all. Truncate the file to its original * length when the delivery failed. POSIX does not require ftruncate(), * so we may have a portability problem. Note that fclose() may fail even * while fflush and fsync() succeed. Think of remote file systems such as * AFS that copy the file back to the server upon close. Oh well, no * point optimizing the error case. XXX On systems that use flock() * locking, we must truncate the file file before closing it (and losing * the exclusive lock). */ read_error = vstream_ferror(src); write_error = vstream_fflush(dst); #ifdef HAS_FSYNC if ((flags & MAIL_COPY_TOFILE) != 0) write_error |= fsync(vstream_fileno(dst)); #endif if (var_fault_inj_code == 2) { read_error = 1; errno = ENOENT; } if (var_fault_inj_code == 3) { write_error = 1; errno = ENOENT; } #ifndef NO_TRUNCATE if ((flags & MAIL_COPY_TOFILE) != 0) if (corrupt_error || read_error || write_error) /* Complain about ignored "undo" errors? So sue me. */ (void) ftruncate(vstream_fileno(dst), orig_length); #endif write_error |= vstream_fclose(dst); /* * Return the optional verbose error description. */ #define TRY_AGAIN_ERROR(errno) \ (errno == EAGAIN || errno == ESTALE) if (why && read_error) dsb_unix(why, TRY_AGAIN_ERROR(errno) ? "4.3.0" : "5.3.0", sys_exits_detail(EX_IOERR)->text, "error reading message: %m"); if (why && write_error) dsb_unix(why, mbox_dsn(errno, "5.3.0"), sys_exits_detail(EX_IOERR)->text, "error writing message: %m"); /* * Use flag+errno description when the optional verbose description is * not desired. */ return ((corrupt_error ? MAIL_COPY_STAT_CORRUPT : 0) | (read_error ? MAIL_COPY_STAT_READ : 0) | (write_error ? MAIL_COPY_STAT_WRITE : 0)); }
static void cleanup_service(VSTREAM *src, char *unused_service, char **argv) { VSTRING *buf = vstring_alloc(100); CLEANUP_STATE *state; int flags; int type = 0; int status; /* * Sanity check. This service takes no command-line arguments. */ if (argv[0]) msg_fatal("unexpected command-line argument: %s", argv[0]); /* * Open a queue file and initialize state. */ state = cleanup_open(src); /* * Send the queue id to the client. Read client processing options. If we * can't read the client processing options we can pretty much forget * about the whole operation. */ attr_print(src, ATTR_FLAG_NONE, SEND_ATTR_STR(MAIL_ATTR_QUEUEID, state->queue_id), ATTR_TYPE_END); if (attr_scan(src, ATTR_FLAG_STRICT, RECV_ATTR_INT(MAIL_ATTR_FLAGS, &flags), ATTR_TYPE_END) != 1) { state->errs |= CLEANUP_STAT_BAD; flags = 0; } cleanup_control(state, flags); /* * XXX Rely on the front-end programs to enforce record size limits. * * First, copy the envelope records to the queue file. Then, copy the * message content (headers and body). Finally, attach any information * extracted from message headers. */ while (CLEANUP_OUT_OK(state)) { if ((type = rec_get_raw(src, buf, 0, REC_FLAG_NONE)) < 0) { state->errs |= CLEANUP_STAT_BAD; break; } if (REC_GET_HIDDEN_TYPE(type)) { msg_warn("%s: record type %d not allowed - discarding this message", state->queue_id, type); state->errs |= CLEANUP_STAT_BAD; break; } CLEANUP_RECORD(state, type, vstring_str(buf), VSTRING_LEN(buf)); if (type == REC_TYPE_END) break; } /* * Keep reading in case of problems, until the sender is ready to receive * our status report. */ if (CLEANUP_OUT_OK(state) == 0 && type > 0) { while (type != REC_TYPE_END && (type = rec_get_raw(src, buf, 0, REC_FLAG_NONE)) > 0) { if (type == REC_TYPE_MILT_COUNT) { int milter_count = atoi(vstring_str(buf)); /* Avoid deadlock. */ if (milter_count >= 0) cleanup_milter_receive(state, milter_count); } } } /* * Log something to make timeout errors easier to debug. */ if (vstream_ftimeout(src)) msg_warn("%s: read timeout on %s", state->queue_id, VSTREAM_PATH(src)); /* * Finish this message, and report the result status to the client. */ status = cleanup_flush(state); /* in case state is modified */ attr_print(src, ATTR_FLAG_NONE, SEND_ATTR_INT(MAIL_ATTR_STATUS, status), SEND_ATTR_STR(MAIL_ATTR_WHY, (state->flags & CLEANUP_FLAG_SMTP_REPLY) && state->smtp_reply ? state->smtp_reply : state->reason ? state->reason : ""), ATTR_TYPE_END); cleanup_free(state); /* * Cleanup. */ vstring_free(buf); }
static int tls_policy_lookup_one(SMTP_SESSION *session, int *site_level, const char *site_name, const char *site_class) { const char *lookup; char *policy; char *saved_policy; char *tok; const char *err; char *name; char *val; static VSTRING *cbuf; #undef FREE_RETURN #define FREE_RETURN(x) do { myfree(saved_policy); return (x); } while (0) if ((lookup = maps_find(tls_policy, site_name, 0)) == 0) { if (tls_policy->error) { msg_fatal("%s: %s lookup error for %s", session->state->request->queue_id, tls_policy->title, site_name); /* XXX session->stream has no longjmp context yet. */ } return (0); } if (cbuf == 0) cbuf = vstring_alloc(10); #define WHERE \ vstring_str(vstring_sprintf(cbuf, "TLS policy table, %s \"%s\"", \ site_class, site_name)) saved_policy = policy = mystrdup(lookup); if ((tok = mystrtok(&policy, "\t\n\r ,")) == 0) { msg_warn("%s: invalid empty policy", WHERE); *site_level = TLS_LEV_INVALID; FREE_RETURN(1); /* No further lookups */ } *site_level = tls_level_lookup(tok); if (*site_level == TLS_LEV_INVALID) { /* tls_level_lookup() logs no warning. */ msg_warn("%s: invalid security level \"%s\"", WHERE, tok); FREE_RETURN(1); /* No further lookups */ } /* * Warn about ignored attributes when TLS is disabled. */ if (*site_level < TLS_LEV_MAY) { while ((tok = mystrtok(&policy, "\t\n\r ,")) != 0) msg_warn("%s: ignoring attribute \"%s\" with TLS disabled", WHERE, tok); FREE_RETURN(1); } /* * Errors in attributes may have security consequences, don't ignore * errors that can degrade security. */ while ((tok = mystrtok(&policy, "\t\n\r ,")) != 0) { if ((err = split_nameval(tok, &name, &val)) != 0) { *site_level = TLS_LEV_INVALID; msg_warn("%s: malformed attribute/value pair \"%s\": %s", WHERE, tok, err); break; } /* Only one instance per policy. */ if (!strcasecmp(name, "ciphers")) { if (*val == 0) { msg_warn("%s: attribute \"%s\" has empty value", WHERE, name); *site_level = TLS_LEV_INVALID; break; } if (session->tls_grade) { msg_warn("%s: attribute \"%s\" is specified multiple times", WHERE, name); *site_level = TLS_LEV_INVALID; break; } session->tls_grade = mystrdup(val); continue; } /* Only one instance per policy. */ if (!strcasecmp(name, "protocols")) { if (session->tls_protocols) { msg_warn("%s: attribute \"%s\" is specified multiple times", WHERE, name); *site_level = TLS_LEV_INVALID; break; } session->tls_protocols = mystrdup(val); continue; } /* Multiple instance(s) per policy. */ if (!strcasecmp(name, "match")) { char *delim = *site_level == TLS_LEV_FPRINT ? "|" : ":"; if (*site_level <= TLS_LEV_ENCRYPT) { msg_warn("%s: attribute \"%s\" invalid at security level \"%s\"", WHERE, name, policy_name(*site_level)); *site_level = TLS_LEV_INVALID; break; } if (*val == 0) { msg_warn("%s: attribute \"%s\" has empty value", WHERE, name); *site_level = TLS_LEV_INVALID; break; } if (session->tls_matchargv == 0) session->tls_matchargv = argv_split(val, delim); else argv_split_append(session->tls_matchargv, val, delim); continue; } /* Only one instance per policy. */ if (!strcasecmp(name, "exclude")) { if (session->tls_exclusions) { msg_warn("%s: attribute \"%s\" is specified multiple times", WHERE, name); *site_level = TLS_LEV_INVALID; break; } session->tls_exclusions = vstring_strcpy(vstring_alloc(10), val); continue; } else { msg_warn("%s: invalid attribute name: \"%s\"", WHERE, name); *site_level = TLS_LEV_INVALID; break; } } FREE_RETURN(1); }
static NORETURN usage(const char *me) { msg_fatal("usage: %s [-c config_dir] [-D (debug)] [-d (don't detach from terminal)] [-e exit_time] [-t (test)] [-v] [-w (wait for initialization)]", me); }
static void psc_service(VSTREAM *smtp_client_stream, char *unused_service, char **unused_argv) { const char *myname = "psc_service"; PSC_STATE *state; struct sockaddr_storage addr_storage; SOCKADDR_SIZE addr_storage_len = sizeof(addr_storage); MAI_HOSTADDR_STR smtp_client_addr; MAI_SERVPORT_STR smtp_client_port; MAI_HOSTADDR_STR smtp_server_addr; MAI_SERVPORT_STR smtp_server_port; int aierr; const char *stamp_str; int saved_flags; /* * For sanity, require that at least one of INET or INET6 is enabled. * Otherwise, we can't look up interface information, and we can't * convert names or addresses. */ if (inet_proto_info()->ai_family_list[0] == 0) msg_fatal("all network protocols are disabled (%s = %s)", VAR_INET_PROTOCOLS, var_inet_protocols); /* * This program handles all incoming connections, so it must not block. * We use event-driven code for all operations that introduce latency. * * Note: instead of using VSTREAM-level timeouts, we enforce limits on the * total amount of time to receive a complete SMTP command line. */ non_blocking(vstream_fileno(smtp_client_stream), NON_BLOCKING); /* * We use the event_server framework. This means we get already-accepted * connections so we have to invoke getpeername() to find out the remote * address and port. */ /* Best effort - if this non-blocking write(2) fails, so be it. */ #define PSC_SERVICE_DISCONNECT_AND_RETURN(stream) do { \ (void) write(vstream_fileno(stream), \ "421 4.3.2 No system resources\r\n", \ sizeof("421 4.3.2 No system resources\r\n") - 1); \ event_server_disconnect(stream); \ return; \ } while (0); /* * Look up the remote SMTP client address and port. */ if (getpeername(vstream_fileno(smtp_client_stream), (struct sockaddr *) & addr_storage, &addr_storage_len) < 0) { msg_warn("getpeername: %m -- dropping this connection"); PSC_SERVICE_DISCONNECT_AND_RETURN(smtp_client_stream); } /* * Convert the remote SMTP client address and port to printable form for * logging and access control. */ if ((aierr = sockaddr_to_hostaddr((struct sockaddr *) & addr_storage, addr_storage_len, &smtp_client_addr, &smtp_client_port, 0)) != 0) { msg_warn("cannot convert client address/port to string: %s" " -- dropping this connection", MAI_STRERROR(aierr)); PSC_SERVICE_DISCONNECT_AND_RETURN(smtp_client_stream); } if (strncasecmp("::ffff:", smtp_client_addr.buf, 7) == 0) memmove(smtp_client_addr.buf, smtp_client_addr.buf + 7, sizeof(smtp_client_addr.buf) - 7); if (msg_verbose > 1) msg_info("%s: sq=%d cq=%d connect from [%s]:%s", myname, psc_post_queue_length, psc_check_queue_length, smtp_client_addr.buf, smtp_client_port.buf); /* * Look up the local SMTP server address and port. */ if (getsockname(vstream_fileno(smtp_client_stream), (struct sockaddr *) & addr_storage, &addr_storage_len) < 0) { msg_warn("getsockname: %m -- dropping this connection"); PSC_SERVICE_DISCONNECT_AND_RETURN(smtp_client_stream); } /* * Convert the local SMTP server address and port to printable form for * logging and access control. */ if ((aierr = sockaddr_to_hostaddr((struct sockaddr *) & addr_storage, addr_storage_len, &smtp_server_addr, &smtp_server_port, 0)) != 0) { msg_warn("cannot convert server address/port to string: %s" " -- dropping this connection", MAI_STRERROR(aierr)); PSC_SERVICE_DISCONNECT_AND_RETURN(smtp_client_stream); } if (strncasecmp("::ffff:", smtp_server_addr.buf, 7) == 0) memmove(smtp_server_addr.buf, smtp_server_addr.buf + 7, sizeof(smtp_server_addr.buf) - 7); msg_info("CONNECT from [%s]:%s to [%s]:%s", smtp_client_addr.buf, smtp_client_port.buf, smtp_server_addr.buf, smtp_server_port.buf); /* * Bundle up all the loose session pieces. This zeroes all flags and time * stamps. */ state = psc_new_session_state(smtp_client_stream, smtp_client_addr.buf, smtp_client_port.buf); /* * Reply with 421 when the client has too many open connections. */ if (var_psc_cconn_limit > 0 && state->client_concurrency > var_psc_cconn_limit) { msg_info("NOQUEUE: reject: CONNECT from [%s]:%s: too many connections", state->smtp_client_addr, state->smtp_client_port); PSC_DROP_SESSION_STATE(state, "421 4.7.0 Error: too many connections\r\n"); return; } /* * Reply with 421 when we can't forward more connections. */ if (var_psc_post_queue_limit > 0 && psc_post_queue_length >= var_psc_post_queue_limit) { msg_info("NOQUEUE: reject: CONNECT from [%s]:%s: all server ports busy", state->smtp_client_addr, state->smtp_client_port); PSC_DROP_SESSION_STATE(state, "421 4.3.2 All server ports are busy\r\n"); return; } /* * The permanent white/blacklist has highest precedence. */ if (psc_acl != 0) { switch (psc_acl_eval(state, psc_acl, VAR_PSC_ACL)) { /* * Permanently blacklisted. */ case PSC_ACL_ACT_BLACKLIST: msg_info("BLACKLISTED [%s]:%s", PSC_CLIENT_ADDR_PORT(state)); PSC_FAIL_SESSION_STATE(state, PSC_STATE_FLAG_BLIST_FAIL); switch (psc_blist_action) { case PSC_ACT_DROP: PSC_DROP_SESSION_STATE(state, "521 5.3.2 Service currently unavailable\r\n"); return; case PSC_ACT_ENFORCE: PSC_ENFORCE_SESSION_STATE(state, "550 5.3.2 Service currently unavailable\r\n"); break; case PSC_ACT_IGNORE: PSC_UNFAIL_SESSION_STATE(state, PSC_STATE_FLAG_BLIST_FAIL); /* * Not: PSC_PASS_SESSION_STATE. Repeat this test the next * time. */ break; default: msg_panic("%s: unknown blacklist action value %d", myname, psc_blist_action); } break; /* * Permanently whitelisted. */ case PSC_ACL_ACT_WHITELIST: msg_info("WHITELISTED [%s]:%s", PSC_CLIENT_ADDR_PORT(state)); psc_conclude(state); return; /* * Other: dunno (don't know) or error. */ default: break; } } /* * The temporary whitelist (i.e. the postscreen cache) has the lowest * precedence. This cache contains information about the results of prior * tests. Whitelist the client when all enabled test results are still * valid. */ if ((state->flags & PSC_STATE_MASK_ANY_FAIL) == 0 && psc_cache_map != 0 && (stamp_str = psc_cache_lookup(psc_cache_map, state->smtp_client_addr)) != 0) { saved_flags = state->flags; psc_parse_tests(state, stamp_str, event_time()); state->flags |= saved_flags; if (msg_verbose) msg_info("%s: cached + recent flags: %s", myname, psc_print_state_flags(state->flags, myname)); if ((state->flags & PSC_STATE_MASK_ANY_TODO_FAIL) == 0) { msg_info("PASS OLD [%s]:%s", PSC_CLIENT_ADDR_PORT(state)); psc_conclude(state); return; } } else { saved_flags = state->flags; psc_new_tests(state); state->flags |= saved_flags; if (msg_verbose) msg_info("%s: new + recent flags: %s", myname, psc_print_state_flags(state->flags, myname)); } /* * Don't whitelist clients that connect to backup MX addresses. Fail * "closed" on error. */ if (addr_match_list_match(psc_wlist_if, smtp_server_addr.buf) == 0) { state->flags |= (PSC_STATE_FLAG_WLIST_FAIL | PSC_STATE_FLAG_NOFORWARD); msg_info("WHITELIST VETO [%s]:%s", PSC_CLIENT_ADDR_PORT(state)); } /* * Reply with 421 when we can't analyze more connections. That also means * no deep protocol tests when the noforward flag is raised. */ if (var_psc_pre_queue_limit > 0 && psc_check_queue_length - psc_post_queue_length >= var_psc_pre_queue_limit) { msg_info("reject: connect from [%s]:%s: all screening ports busy", state->smtp_client_addr, state->smtp_client_port); PSC_DROP_SESSION_STATE(state, "421 4.3.2 All screening ports are busy\r\n"); return; } /* * If the client has no up-to-date results for some tests, do those tests * first. Otherwise, skip the tests and hand off the connection. */ if (state->flags & PSC_STATE_MASK_EARLY_TODO) psc_early_tests(state); else if (state->flags & (PSC_STATE_MASK_SMTPD_TODO | PSC_STATE_FLAG_NOFORWARD)) psc_smtpd_tests(state); else psc_conclude(state); }
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 show_queue(void) { char buf[VSTREAM_BUFSIZE]; VSTREAM *showq; int n; /* * Connect to the show queue service. Terminate silently when piping into * a program that terminates early. */ if ((showq = mail_connect(MAIL_CLASS_PUBLIC, var_showq_service, BLOCKING)) != 0) { while ((n = vstream_fread(showq, buf, sizeof(buf))) > 0) { if (vstream_fwrite(VSTREAM_OUT, buf, n) != n || vstream_fflush(VSTREAM_OUT) != 0) { if (errno == EPIPE) break; msg_fatal("write error: %m"); } } if (vstream_fclose(showq) && errno != EPIPE) msg_warn("close: %m"); } /* * Don't assume that the mail system is down when the user has * insufficient permission to access the showq socket. */ else if (errno == EACCES) { msg_fatal_status(EX_SOFTWARE, "Connect to the %s %s service: %m", var_mail_name, var_showq_service); } /* * When the mail system is down, the superuser can still access the queue * directly. Just run the showq program in stand-alone mode. */ else if (geteuid() == 0) { ARGV *argv; int stat; msg_warn("Mail system is down -- accessing queue directly"); argv = argv_alloc(6); argv_add(argv, var_showq_service, "-u", "-S", (char *) 0); for (n = 0; n < msg_verbose; n++) argv_add(argv, "-v", (char *) 0); argv_terminate(argv); stat = mail_run_foreground(var_daemon_dir, argv->argv); argv_free(argv); } /* * When the mail system is down, unprivileged users are stuck, because by * design the mail system contains no set_uid programs. The only way for * an unprivileged user to cross protection boundaries is to talk to the * showq daemon. */ else { msg_fatal_status(EX_UNAVAILABLE, "Queue report unavailable - mail system is down"); } }
DICT *dict_pcre_open(const char *mapname, int open_flags, int dict_flags) { DICT_PCRE *dict_pcre; VSTREAM *map_fp; struct stat st; VSTRING *line_buffer; DICT_PCRE_RULE *last_rule = 0; DICT_PCRE_RULE *rule; int lineno = 0; int nesting = 0; char *p; /* * Sanity checks. */ if (open_flags != O_RDONLY) return (dict_surrogate(DICT_TYPE_PCRE, mapname, open_flags, dict_flags, "%s:%s map requires O_RDONLY access mode", DICT_TYPE_PCRE, mapname)); /* * Open the configuration file. */ if ((map_fp = vstream_fopen(mapname, O_RDONLY, 0)) == 0) return (dict_surrogate(DICT_TYPE_PCRE, mapname, open_flags, dict_flags, "open %s: %m", mapname)); if (fstat(vstream_fileno(map_fp), &st) < 0) msg_fatal("fstat %s: %m", mapname); line_buffer = vstring_alloc(100); dict_pcre = (DICT_PCRE *) dict_alloc(DICT_TYPE_PCRE, mapname, sizeof(*dict_pcre)); dict_pcre->dict.lookup = dict_pcre_lookup; dict_pcre->dict.close = dict_pcre_close; dict_pcre->dict.flags = dict_flags | DICT_FLAG_PATTERN; if (dict_flags & DICT_FLAG_FOLD_MUL) dict_pcre->dict.fold_buf = vstring_alloc(10); dict_pcre->head = 0; dict_pcre->expansion_buf = 0; if (dict_pcre_init == 0) { pcre_malloc = (void *(*) (size_t)) mymalloc; pcre_free = (void (*) (void *)) myfree; dict_pcre_init = 1; } dict_pcre->dict.owner.uid = st.st_uid; dict_pcre->dict.owner.status = (st.st_uid != 0); /* * Parse the pcre table. */ while (readlline(line_buffer, map_fp, &lineno)) { p = vstring_str(line_buffer); trimblanks(p, 0)[0] = 0; /* Trim space at end */ if (*p == 0) continue; rule = dict_pcre_parse_rule(mapname, lineno, p, nesting, dict_flags); if (rule == 0) continue; if (rule->op == DICT_PCRE_OP_IF) { nesting++; } else if (rule->op == DICT_PCRE_OP_ENDIF) { nesting--; } if (last_rule == 0) dict_pcre->head = rule; else last_rule->next = rule; last_rule = rule; } if (nesting) msg_warn("pcre map %s, line %d: more IFs than ENDIFs", mapname, lineno); vstring_free(line_buffer); vstream_fclose(map_fp); return (DICT_DEBUG (&dict_pcre->dict)); }
static void bounce_service(VSTREAM *client, char *service_name, char **argv) { int command; int status; /* * Sanity check. This service takes no command-line arguments. The * service name should be usable as a subdirectory name. */ if (argv[0]) msg_fatal("unexpected command-line argument: %s", argv[0]); if (mail_queue_name_ok(service_name) == 0) msg_fatal("malformed service name: %s", service_name); /* * Read and validate the first parameter of the client request. Let the * request-specific protocol routines take care of the remainder. */ if (attr_scan(client, ATTR_FLAG_STRICT | ATTR_FLAG_MORE, ATTR_TYPE_INT, MAIL_ATTR_NREQ, &command, 0) != 1) { msg_warn("malformed request"); status = -1; } else if (command == BOUNCE_CMD_VERP) { status = bounce_verp_proto(service_name, client); } else if (command == BOUNCE_CMD_FLUSH) { status = bounce_notify_proto(service_name, client, bounce_notify_service); } else if (command == BOUNCE_CMD_WARN) { status = bounce_notify_proto(service_name, client, bounce_warn_service); } else if (command == BOUNCE_CMD_TRACE) { status = bounce_notify_proto(service_name, client, bounce_trace_service); } else if (command == BOUNCE_CMD_APPEND) { status = bounce_append_proto(service_name, client); } else if (command == BOUNCE_CMD_ONE) { status = bounce_one_proto(service_name, client); } else { msg_warn("unknown command: %d", command); status = -1; } /* * When the request has completed, send the completion status to the * client. */ attr_print(client, ATTR_FLAG_NONE, ATTR_TYPE_INT, MAIL_ATTR_STATUS, status, ATTR_TYPE_END); vstream_fflush(client); /* * When a cleanup trap was set, delete the log file in case of error. * This includes errors while sending the completion status to the * client. */ if (bounce_cleanup_path) { if (status || vstream_ferror(client)) bounce_cleanup_log(); bounce_cleanup_unregister(); } }
static void post_init(char *unused_name, char **unused_argv) { static const NAME_MASK lookup_masks[] = { SMTP_HOST_LOOKUP_DNS, SMTP_HOST_FLAG_DNS, SMTP_HOST_LOOKUP_NATIVE, SMTP_HOST_FLAG_NATIVE, 0, }; static const NAME_MASK dns_res_opt_masks[] = { SMTP_DNS_RES_OPT_DEFNAMES, RES_DEFNAMES, SMTP_DNS_RES_OPT_DNSRCH, RES_DNSRCH, 0, }; static const NAME_CODE dns_support[] = { SMTP_DNS_SUPPORT_DISABLED, SMTP_DNS_DISABLED, SMTP_DNS_SUPPORT_ENABLED, SMTP_DNS_ENABLED, #if (RES_USE_DNSSEC != 0) && (RES_USE_EDNS0 != 0) SMTP_DNS_SUPPORT_DNSSEC, SMTP_DNS_DNSSEC, #endif 0, SMTP_DNS_INVALID, }; if (*var_smtp_dns_support == 0) { /* Backwards compatible empty setting */ smtp_dns_support = var_disable_dns ? SMTP_DNS_DISABLED : SMTP_DNS_ENABLED; } else { smtp_dns_support = name_code(dns_support, NAME_CODE_FLAG_NONE, var_smtp_dns_support); if (smtp_dns_support == SMTP_DNS_INVALID) msg_fatal("invalid %s: \"%s\"", SMTP_X(DNS_SUPPORT), var_smtp_dns_support); var_disable_dns = (smtp_dns_support == SMTP_DNS_DISABLED); } /* * Select hostname lookup mechanisms. */ if (smtp_dns_support == SMTP_DNS_DISABLED) smtp_host_lookup_mask = SMTP_HOST_FLAG_NATIVE; else smtp_host_lookup_mask = name_mask(SMTP_X(HOST_LOOKUP), lookup_masks, var_smtp_host_lookup); if (msg_verbose) msg_info("host name lookup methods: %s", str_name_mask(SMTP_X(HOST_LOOKUP), lookup_masks, smtp_host_lookup_mask)); /* * Session cache instance. */ if (*var_smtp_cache_dest || var_smtp_cache_demand) #if 0 smtp_scache = scache_multi_create(); #else smtp_scache = scache_clnt_create(var_scache_service, var_scache_proto_tmout, var_ipc_idle_limit, var_ipc_ttl_limit); #endif /* * Select DNS query flags. */ smtp_dns_res_opt = name_mask(SMTP_X(DNS_RES_OPT), dns_res_opt_masks, var_smtp_dns_res_opt); }
static const char *dict_pcre_lookup(DICT *dict, const char *lookup_string) { DICT_PCRE *dict_pcre = (DICT_PCRE *) dict; DICT_PCRE_RULE *rule; DICT_PCRE_IF_RULE *if_rule; DICT_PCRE_MATCH_RULE *match_rule; int lookup_len = strlen(lookup_string); DICT_PCRE_EXPAND_CONTEXT ctxt; int nesting = 0; dict->error = 0; if (msg_verbose) msg_info("dict_pcre_lookup: %s: %s", dict->name, lookup_string); /* * Optionally fold the key. */ if (dict->flags & DICT_FLAG_FOLD_MUL) { if (dict->fold_buf == 0) dict->fold_buf = vstring_alloc(10); vstring_strcpy(dict->fold_buf, lookup_string); lookup_string = lowercase(vstring_str(dict->fold_buf)); } for (rule = dict_pcre->head; rule; rule = rule->next) { /* * Skip rules inside failed IF/ENDIF. */ if (nesting < rule->nesting) continue; switch (rule->op) { /* * Search for a matching expression. */ case DICT_PCRE_OP_MATCH: match_rule = (DICT_PCRE_MATCH_RULE *) rule; ctxt.matches = pcre_exec(match_rule->pattern, match_rule->hints, lookup_string, lookup_len, NULL_STARTOFFSET, NULL_EXEC_OPTIONS, ctxt.offsets, PCRE_MAX_CAPTURE * 3); if (ctxt.matches > 0) { if (!match_rule->match) continue; /* Negative rule matched */ } else if (ctxt.matches == PCRE_ERROR_NOMATCH) { if (match_rule->match) continue; /* Positive rule did not * match */ } else { dict_pcre_exec_error(dict->name, rule->lineno, ctxt.matches); continue; /* pcre_exec failed */ } /* * Skip $number substitutions when the replacement text contains * no $number strings, as learned during the compile time * pre-scan. The pre-scan already replaced $$ by $. */ if (match_rule->max_sub == 0) return match_rule->replacement; /* * We've got a match. Perform substitution on replacement string. */ if (dict_pcre->expansion_buf == 0) dict_pcre->expansion_buf = vstring_alloc(10); VSTRING_RESET(dict_pcre->expansion_buf); ctxt.dict_pcre = dict_pcre; ctxt.match_rule = match_rule; ctxt.lookup_string = lookup_string; if (mac_parse(match_rule->replacement, dict_pcre_expand, (char *) &ctxt) & MAC_PARSE_ERROR) msg_fatal("pcre map %s, line %d: bad replacement syntax", dict->name, rule->lineno); VSTRING_TERMINATE(dict_pcre->expansion_buf); return (vstring_str(dict_pcre->expansion_buf)); /* * Conditional. XXX We provide space for matched substring info * because PCRE uses part of it as workspace for backtracking. * PCRE will allocate memory if it runs out of backtracking * storage. */ case DICT_PCRE_OP_IF: if_rule = (DICT_PCRE_IF_RULE *) rule; ctxt.matches = pcre_exec(if_rule->pattern, if_rule->hints, lookup_string, lookup_len, NULL_STARTOFFSET, NULL_EXEC_OPTIONS, ctxt.offsets, PCRE_MAX_CAPTURE * 3); if (ctxt.matches > 0) { if (!if_rule->match) continue; /* Negative rule matched */ } else if (ctxt.matches == PCRE_ERROR_NOMATCH) { if (if_rule->match) continue; /* Positive rule did not * match */ } else { dict_pcre_exec_error(dict->name, rule->lineno, ctxt.matches); continue; /* pcre_exec failed */ } nesting++; continue; /* * ENDIF after successful IF. */ case DICT_PCRE_OP_ENDIF: nesting--; continue; default: msg_panic("dict_pcre_lookup: impossible operation %d", rule->op); } } return (0); }
static void pre_init(char *unused_name, char **unused_argv) { int use_tls; static const NAME_CODE addr_pref_map[] = { INET_PROTO_NAME_IPV6, SMTP_MISC_FLAG_PREF_IPV6, INET_PROTO_NAME_IPV4, SMTP_MISC_FLAG_PREF_IPV4, INET_PROTO_NAME_ANY, 0, 0, -1, }; /* * Turn on per-peer debugging. */ debug_peer_init(); /* * SASL initialization. */ if (var_smtp_sasl_enable) #ifdef USE_SASL_AUTH smtp_sasl_initialize(); #else msg_warn("%s is true, but SASL support is not compiled in", SMTP_X(SASL_ENABLE)); #endif if (*var_smtp_tls_level != 0) #ifdef USE_TLS switch (tls_level_lookup(var_smtp_tls_level)) { case TLS_LEV_SECURE: case TLS_LEV_VERIFY: case TLS_LEV_DANE_ONLY: case TLS_LEV_FPRINT: case TLS_LEV_ENCRYPT: var_smtp_use_tls = var_smtp_enforce_tls = 1; break; case TLS_LEV_DANE: case TLS_LEV_MAY: var_smtp_use_tls = 1; var_smtp_enforce_tls = 0; break; case TLS_LEV_NONE: var_smtp_use_tls = var_smtp_enforce_tls = 0; break; default: /* tls_level_lookup() logs no warning. */ /* session_tls_init() assumes that var_smtp_tls_level is sane. */ msg_fatal("Invalid TLS level \"%s\"", var_smtp_tls_level); } #endif use_tls = (var_smtp_use_tls || var_smtp_enforce_tls); /* * Initialize the TLS data before entering the chroot jail */ if (use_tls || var_smtp_tls_per_site[0] || var_smtp_tls_policy[0]) { #ifdef USE_TLS TLS_CLIENT_INIT_PROPS props; /* * We get stronger type safety and a cleaner interface by combining * the various parameters into a single tls_client_props structure. * * Large parameter lists are error-prone, so we emulate a language * feature that C does not have natively: named parameter lists. */ smtp_tls_ctx = TLS_CLIENT_INIT(&props, log_param = SMTP_X(TLS_LOGLEVEL), log_level = var_smtp_tls_loglevel, verifydepth = var_smtp_tls_scert_vd, cache_type = X_SMTP(TLS_MGR_SCACHE), cert_file = var_smtp_tls_cert_file, key_file = var_smtp_tls_key_file, dcert_file = var_smtp_tls_dcert_file, dkey_file = var_smtp_tls_dkey_file, eccert_file = var_smtp_tls_eccert_file, eckey_file = var_smtp_tls_eckey_file, CAfile = var_smtp_tls_CAfile, CApath = var_smtp_tls_CApath, mdalg = var_smtp_tls_fpt_dgst); smtp_tls_list_init(); #else msg_warn("TLS has been selected, but TLS support is not compiled in"); #endif } /* * Flush client. */ flush_init(); /* * Session cache domain list. */ if (*var_smtp_cache_dest) smtp_cache_dest = string_list_init(MATCH_FLAG_RETURN, var_smtp_cache_dest); /* * EHLO keyword filter. */ if (*var_smtp_ehlo_dis_maps) smtp_ehlo_dis_maps = maps_create(SMTP_X(EHLO_DIS_MAPS), var_smtp_ehlo_dis_maps, DICT_FLAG_LOCK); /* * PIX bug workarounds. */ if (*var_smtp_pix_bug_maps) smtp_pix_bug_maps = maps_create(SMTP_X(PIX_BUG_MAPS), var_smtp_pix_bug_maps, DICT_FLAG_LOCK); /* * Generic maps. */ if (*var_prop_extension) smtp_ext_prop_mask = ext_prop_mask(VAR_PROP_EXTENSION, var_prop_extension); if (*var_smtp_generic_maps) smtp_generic_maps = maps_create(SMTP_X(GENERIC_MAPS), var_smtp_generic_maps, DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX); /* * Header/body checks. */ smtp_header_checks = hbc_header_checks_create( SMTP_X(HEAD_CHKS), var_smtp_head_chks, SMTP_X(MIME_CHKS), var_smtp_mime_chks, SMTP_X(NEST_CHKS), var_smtp_nest_chks, smtp_hbc_callbacks); smtp_body_checks = hbc_body_checks_create( SMTP_X(BODY_CHKS), var_smtp_body_chks, smtp_hbc_callbacks); /* * Server reply filter. */ if (*var_smtp_resp_filter) smtp_chat_resp_filter = dict_open(var_smtp_resp_filter, O_RDONLY, DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX); /* * Address family preference. */ if (*var_smtp_addr_pref) { smtp_addr_pref = name_code(addr_pref_map, NAME_CODE_FLAG_NONE, var_smtp_addr_pref); if (smtp_addr_pref < 0) msg_fatal("bad %s value: %s", SMTP_X(ADDR_PREF), var_smtp_addr_pref); } }
void smtpd_peer_init(SMTPD_STATE *state) { const char *myname = "smtpd_peer_init"; SOCKADDR_SIZE sa_length; struct sockaddr *sa; INET_PROTO_INFO *proto_info = inet_proto_info(); sa = (struct sockaddr *) & (state->sockaddr); sa_length = sizeof(state->sockaddr); /* * Look up the peer address information. * * XXX If we make local endpoint (getsockname) information available to * Milter applications as {if_name} and {if_addr}, then we also must be * able to provide this via the XCLIENT command for Milter testing. * * XXX If we make local or remote port information available to policy * servers or Milter applications, then we must also make this testable * with the XCLIENT command, otherwise there will be confusion. * * XXX If we make local or remote port information available via logging, * then we must also support these attributes with the XFORWARD command. * * XXX If support were to be added for Milter applications in down-stream * MTAs, then consistency demands that we propagate a lot of Sendmail * macro information via the XFORWARD command. Otherwise we could end up * with a very confusing situation. */ if (getpeername(vstream_fileno(state->client), sa, &sa_length) >= 0) { errno = 0; } /* * If peer went away, give up. */ if (errno != 0 && errno != ENOTSOCK) { state->name = mystrdup(CLIENT_NAME_UNKNOWN); state->reverse_name = mystrdup(CLIENT_NAME_UNKNOWN); state->addr = mystrdup(CLIENT_ADDR_UNKNOWN); state->rfc_addr = mystrdup(CLIENT_ADDR_UNKNOWN); state->addr_family = AF_UNSPEC; state->name_status = SMTPD_PEER_CODE_PERM; state->reverse_name_status = SMTPD_PEER_CODE_PERM; state->port = mystrdup(CLIENT_PORT_UNKNOWN); } /* * Convert the client address to printable address and hostname. * * XXX If we're given an IPv6 (or IPv4) connection from, e.g., inetd, while * Postfix IPv6 (or IPv4) support is turned off, don't (skip to the final * else clause, pretend the origin is localhost[127.0.0.1], and become an * open relay). */ else if (errno == 0 && (sa->sa_family == AF_INET #ifdef AF_INET6 || sa->sa_family == AF_INET6 #endif )) { MAI_HOSTNAME_STR client_name; MAI_HOSTADDR_STR client_addr; MAI_SERVPORT_STR client_port; int aierr; char *colonp; /* * Sanity check: we can't use sockets that we're not configured for. */ if (strchr((char *) proto_info->sa_family_list, sa->sa_family) == 0) msg_fatal("cannot handle socket type %s with \"%s = %s\"", #ifdef AF_INET6 sa->sa_family == AF_INET6 ? "AF_INET6" : #endif sa->sa_family == AF_INET ? "AF_INET" : "other", VAR_INET_PROTOCOLS, var_inet_protocols); /* * Sorry, but there are some things that we just cannot do while * connected to the network. */ if (geteuid() != var_owner_uid || getuid() != var_owner_uid) { msg_error("incorrect SMTP server privileges: uid=%lu euid=%lu", (unsigned long) getuid(), (unsigned long) geteuid()); msg_fatal("the Postfix SMTP server must run with $%s privileges", VAR_MAIL_OWNER); } /* * Convert the client address to printable form. */ if ((aierr = sockaddr_to_hostaddr(sa, sa_length, &client_addr, &client_port, 0)) != 0) msg_fatal("%s: cannot convert client address/port to string: %s", myname, MAI_STRERROR(aierr)); state->port = mystrdup(client_port.buf); /* * XXX Strip off the IPv6 datalink suffix to avoid false alarms with * strict address syntax checks. */ #ifdef HAS_IPV6 (void) split_at(client_addr.buf, '%'); #endif /* * We convert IPv4-in-IPv6 address to 'true' IPv4 address early on, * but only if IPv4 support is enabled (why would anyone want to turn * it off)? With IPv4 support enabled we have no need for the IPv6 * form in logging, hostname verification and access checks. */ #ifdef HAS_IPV6 if (sa->sa_family == AF_INET6) { if (strchr((char *) proto_info->sa_family_list, AF_INET) != 0 && IN6_IS_ADDR_V4MAPPED(&SOCK_ADDR_IN6_ADDR(sa)) && (colonp = strrchr(client_addr.buf, ':')) != 0) { struct addrinfo *res0; if (msg_verbose > 1) msg_info("%s: rewriting V4-mapped address \"%s\" to \"%s\"", myname, client_addr.buf, colonp + 1); state->addr = mystrdup(colonp + 1); state->rfc_addr = mystrdup(colonp + 1); state->addr_family = AF_INET; aierr = hostaddr_to_sockaddr(state->addr, (char *) 0, 0, &res0); if (aierr) msg_fatal("%s: cannot convert %s from string to binary: %s", myname, state->addr, MAI_STRERROR(aierr)); sa_length = res0->ai_addrlen; if (sa_length > sizeof(state->sockaddr)) sa_length = sizeof(state->sockaddr); memcpy((char *) sa, res0->ai_addr, sa_length); freeaddrinfo(res0); /* 200412 */ } /* * Following RFC 2821 section 4.1.3, an IPv6 address literal gets * a prefix of 'IPv6:'. We do this consistently for all IPv6 * addresses that that appear in headers or envelopes. The fact * that valid_mailhost_addr() enforces the form helps of course. * We use the form without IPV6: prefix when doing access * control, or when accessing the connection cache. */ else { state->addr = mystrdup(client_addr.buf); state->rfc_addr = concatenate(IPV6_COL, client_addr.buf, (char *) 0); state->addr_family = sa->sa_family; } } /* * An IPv4 address is in dotted quad decimal form. */ else #endif { state->addr = mystrdup(client_addr.buf); state->rfc_addr = mystrdup(client_addr.buf); state->addr_family = sa->sa_family; } /* * Look up and sanity check the client hostname. * * It is unsafe to allow numeric hostnames, especially because there * exists pressure to turn off the name->addr double check. In that * case an attacker could trivally bypass access restrictions. * * sockaddr_to_hostname() already rejects malformed or numeric names. */ #define TEMP_AI_ERROR(e) \ ((e) == EAI_AGAIN || (e) == EAI_MEMORY || (e) == EAI_SYSTEM) #define REJECT_PEER_NAME(state, code) { \ myfree(state->name); \ state->name = mystrdup(CLIENT_NAME_UNKNOWN); \ state->name_status = code; \ } if (var_smtpd_peername_lookup == 0) { state->name = mystrdup(CLIENT_NAME_UNKNOWN); state->reverse_name = mystrdup(CLIENT_NAME_UNKNOWN); state->name_status = SMTPD_PEER_CODE_PERM; state->reverse_name_status = SMTPD_PEER_CODE_PERM; } else if ((aierr = sockaddr_to_hostname(sa, sa_length, &client_name, (MAI_SERVNAME_STR *) 0, 0)) != 0) { state->name = mystrdup(CLIENT_NAME_UNKNOWN); state->reverse_name = mystrdup(CLIENT_NAME_UNKNOWN); state->name_status = (TEMP_AI_ERROR(aierr) ? SMTPD_PEER_CODE_TEMP : SMTPD_PEER_CODE_PERM); state->reverse_name_status = (TEMP_AI_ERROR(aierr) ? SMTPD_PEER_CODE_TEMP : SMTPD_PEER_CODE_PERM); } else { struct addrinfo *res0; struct addrinfo *res; state->name = mystrdup(client_name.buf); state->reverse_name = mystrdup(client_name.buf); state->name_status = SMTPD_PEER_CODE_OK; state->reverse_name_status = SMTPD_PEER_CODE_OK; /* * Reject the hostname if it does not list the peer address. * Without further validation or qualification, such information * must not be allowed to enter the audit trail, as people would * draw false conclusions. */ aierr = hostname_to_sockaddr_pf(state->name, state->addr_family, (char *) 0, 0, &res0); if (aierr) { msg_warn("hostname %s does not resolve to address %s: %s", state->name, state->addr, MAI_STRERROR(aierr)); REJECT_PEER_NAME(state, (TEMP_AI_ERROR(aierr) ? SMTPD_PEER_CODE_TEMP : SMTPD_PEER_CODE_FORGED)); } else { for (res = res0; /* void */ ; res = res->ai_next) { if (res == 0) { msg_warn("hostname %s does not resolve to address %s", state->name, state->addr); REJECT_PEER_NAME(state, SMTPD_PEER_CODE_FORGED); break; } if (strchr((char *) proto_info->sa_family_list, res->ai_family) == 0) { msg_info("skipping address family %d for host %s", res->ai_family, state->name); continue; } if (sock_addr_cmp_addr(res->ai_addr, sa) == 0) break; /* keep peer name */ } freeaddrinfo(res0); } } } /* * If it's not Internet, assume the client is local, and avoid using the * naming service because that can hang when the machine is disconnected. */ else { state->name = mystrdup("localhost"); state->reverse_name = mystrdup("localhost"); if (proto_info->sa_family_list[0] == PF_INET6) { state->addr = mystrdup("::1"); /* XXX bogus. */ state->rfc_addr = mystrdup(IPV6_COL "::1"); /* XXX bogus. */ } else { state->addr = mystrdup("127.0.0.1");/* XXX bogus. */ state->rfc_addr = mystrdup("127.0.0.1"); /* XXX bogus. */ } state->addr_family = AF_UNSPEC; state->name_status = SMTPD_PEER_CODE_OK; state->reverse_name_status = SMTPD_PEER_CODE_OK; state->port = mystrdup("0"); /* XXX bogus. */ } /* * Do the name[addr]:port formatting for pretty reports. */ state->namaddr = SMTPD_BUILD_NAMADDRPORT(state->name, state->addr, state->port); }
static int smtpd_peer_sockaddr_to_hostaddr(SMTPD_STATE *state) { const char *myname = "smtpd_peer_sockaddr_to_hostaddr"; struct sockaddr *sa = (struct sockaddr *) &(state->sockaddr); SOCKADDR_SIZE sa_length = state->sockaddr_len; /* * XXX If we're given an IPv6 (or IPv4) connection from, e.g., inetd, * while Postfix IPv6 (or IPv4) support is turned off, don't (skip to the * final else clause, pretend the origin is localhost[127.0.0.1], and * become an open relay). */ if (sa->sa_family == AF_INET #ifdef AF_INET6 || sa->sa_family == AF_INET6 #endif ) { MAI_HOSTADDR_STR client_addr; MAI_SERVPORT_STR client_port; int aierr; char *colonp; /* * Sanity check: we can't use sockets that we're not configured for. */ if (strchr((char *) proto_info->sa_family_list, sa->sa_family) == 0) msg_fatal("cannot handle socket type %s with \"%s = %s\"", #ifdef AF_INET6 sa->sa_family == AF_INET6 ? "AF_INET6" : #endif sa->sa_family == AF_INET ? "AF_INET" : "other", VAR_INET_PROTOCOLS, var_inet_protocols); /* * Sorry, but there are some things that we just cannot do while * connected to the network. */ if (geteuid() != var_owner_uid || getuid() != var_owner_uid) { msg_error("incorrect SMTP server privileges: uid=%lu euid=%lu", (unsigned long) getuid(), (unsigned long) geteuid()); msg_fatal("the Postfix SMTP server must run with $%s privileges", VAR_MAIL_OWNER); } /* * Convert the client address to printable form. */ if ((aierr = sockaddr_to_hostaddr(sa, sa_length, &client_addr, &client_port, 0)) != 0) msg_fatal("%s: cannot convert client address/port to string: %s", myname, MAI_STRERROR(aierr)); state->port = mystrdup(client_port.buf); /* * XXX Require that the infrastructure strips off the IPv6 datalink * suffix to avoid false alarms with strict address syntax checks. */ #ifdef HAS_IPV6 if (strchr(client_addr.buf, '%') != 0) msg_panic("%s: address %s has datalink suffix", myname, client_addr.buf); #endif /* * We convert IPv4-in-IPv6 address to 'true' IPv4 address early on, * but only if IPv4 support is enabled (why would anyone want to turn * it off)? With IPv4 support enabled we have no need for the IPv6 * form in logging, hostname verification and access checks. */ #ifdef HAS_IPV6 if (sa->sa_family == AF_INET6) { if (strchr((char *) proto_info->sa_family_list, AF_INET) != 0 && IN6_IS_ADDR_V4MAPPED(&SOCK_ADDR_IN6_ADDR(sa)) && (colonp = strrchr(client_addr.buf, ':')) != 0) { struct addrinfo *res0; if (msg_verbose > 1) msg_info("%s: rewriting V4-mapped address \"%s\" to \"%s\"", myname, client_addr.buf, colonp + 1); state->addr = mystrdup(colonp + 1); state->rfc_addr = mystrdup(colonp + 1); state->addr_family = AF_INET; aierr = hostaddr_to_sockaddr(state->addr, (char *) 0, 0, &res0); if (aierr) msg_fatal("%s: cannot convert %s from string to binary: %s", myname, state->addr, MAI_STRERROR(aierr)); sa_length = res0->ai_addrlen; if (sa_length > sizeof(state->sockaddr)) sa_length = sizeof(state->sockaddr); memcpy((void *) sa, res0->ai_addr, sa_length); freeaddrinfo(res0); /* 200412 */ } /* * Following RFC 2821 section 4.1.3, an IPv6 address literal gets * a prefix of 'IPv6:'. We do this consistently for all IPv6 * addresses that that appear in headers or envelopes. The fact * that valid_mailhost_addr() enforces the form helps of course. * We use the form without IPV6: prefix when doing access * control, or when accessing the connection cache. */ else { state->addr = mystrdup(client_addr.buf); state->rfc_addr = concatenate(IPV6_COL, client_addr.buf, (char *) 0); state->addr_family = sa->sa_family; } } /* * An IPv4 address is in dotted quad decimal form. */ else #endif { state->addr = mystrdup(client_addr.buf); state->rfc_addr = mystrdup(client_addr.buf); state->addr_family = sa->sa_family; } return (0); } /* * It's not Internet. */ else { return (-1); } }
int deliver_file(LOCAL_STATE state, USER_ATTR usr_attr, char *path) { const char *myname = "deliver_file"; struct stat st; MBOX *mp; DSN_BUF *why = state.msg_attr.why; int mail_copy_status = MAIL_COPY_STAT_WRITE; int deliver_status; int copy_flags; /* * Make verbose logging easier to understand. */ state.level++; if (msg_verbose) MSG_LOG_STATE(myname, state); /* * DUPLICATE ELIMINATION * * Skip this file if it was already delivered to as this user. */ if (been_here(state.dup_filter, "file %ld %s", (long) usr_attr.uid, path)) return (0); /* * DELIVERY POLICY * * Do we allow delivery to files? */ if ((local_file_deliver_mask & state.msg_attr.exp_type) == 0) { dsb_simple(why, "5.7.1", "mail to file is restricted"); /* Account for possible owner- sender address override. */ return (bounce_workaround(state)); } /* * Don't deliver trace-only requests. */ if (DEL_REQ_TRACE_ONLY(state.request->flags)) { dsb_simple(why, "2.0.0", "delivers to file: %s", path); return (sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr))); } /* * DELIVERY RIGHTS * * Use a default uid/gid when none are given. */ if (usr_attr.uid == 0 && (usr_attr.uid = var_default_uid) == 0) msg_panic("privileged default user id"); if (usr_attr.gid == 0 && (usr_attr.gid = var_default_gid) == 0) msg_panic("privileged default group id"); /* * If the name ends in /, use maildir-style delivery instead. */ if (path[strlen(path) - 1] == '/') return (deliver_maildir(state, usr_attr, path)); /* * Deliver. From here on, no early returns or we have a memory leak. */ if (msg_verbose) msg_info("deliver_file (%ld,%ld): %s", (long) usr_attr.uid, (long) usr_attr.gid, path); if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0) msg_fatal("seek queue file %s: %m", state.msg_attr.queue_id); /* * As the specified user, open or create the file, lock it, and append * the message. */ copy_flags = MAIL_COPY_MBOX; if ((local_deliver_hdr_mask & DELIVER_HDR_FILE) == 0) copy_flags &= ~MAIL_COPY_DELIVERED; set_eugid(usr_attr.uid, usr_attr.gid); mp = mbox_open(path, O_APPEND | O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR, &st, -1, -1, local_mbox_lock_mask | MBOX_DOT_LOCK_MAY_FAIL, "5.2.0", why); if (mp != 0) { if (S_ISREG(st.st_mode) && st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) { vstream_fclose(mp->fp); dsb_simple(why, "5.7.1", "file is executable"); } else { mail_copy_status = mail_copy(COPY_ATTR(state.msg_attr), mp->fp, S_ISREG(st.st_mode) ? copy_flags : (copy_flags & ~MAIL_COPY_TOFILE), "\n", why); } 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 append message to file %s: ", path); if (STR(why->status)[0] == '4') deliver_status = defer_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); else /* Account for possible owner- sender address override. */ deliver_status = bounce_workaround(state); } else { dsb_simple(why, "2.0.0", "delivered to file: %s", path); deliver_status = sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr)); } return (deliver_status); }
/********************************************************************** * 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); }
char *tls_serverid_digest(const TLS_CLIENT_START_PROPS *props, long protomask, const char *ciphers) { EVP_MD_CTX *mdctx; const EVP_MD *md; const char *mdalg; unsigned char md_buf[EVP_MAX_MD_SIZE]; unsigned int md_len; int ok = 1; int i; long sslversion; VSTRING *result; /* * Try to use sha256: our serverid choice should be strong enough to * resist 2nd-preimage attacks with a difficulty comparable to that of * DANE TLSA digests. Failing that, we compute serverid digests with the * default digest, but DANE requires sha256 and sha512, so if we must * fall back to our default digest, DANE support won't be available. We * panic if the fallback algorithm is not available, as it was verified * available in tls_client_init() and must not simply vanish. */ if ((md = EVP_get_digestbyname(mdalg = "sha256")) == 0 && (md = EVP_get_digestbyname(mdalg = props->mdalg)) == 0) msg_panic("digest algorithm \"%s\" not found", mdalg); /* Salt the session lookup key with the OpenSSL runtime version. */ sslversion = SSLeay(); mdctx = EVP_MD_CTX_create(); checkok(EVP_DigestInit_ex(mdctx, md, NULL)); digest_string(props->helo ? props->helo : ""); digest_object(&sslversion); digest_object(&protomask); digest_string(ciphers); /* * All we get from the session cache is a single bit telling us whether * the certificate is trusted or not, but we need to know whether the * trust is CA-based (in that case we must do name checks) or whether it * is a direct end-point match. We mustn't confuse the two, so it is * best to process only TA trust in the verify callback and check the EE * trust after. This works since re-used sessions always have access to * the leaf certificate, while only the original session has the leaf and * the full trust chain. * * Only the trust anchor matchlist is hashed into the session key. The end * entity certs are not used to determine whether a certificate is * trusted or not, rather these are rechecked against the leaf cert * outside the verification callback, each time a session is created or * reused. * * Therefore, the security context of the session does not depend on the EE * matching data, which is checked separately each time. So we exclude * the EE part of the DANE structure from the serverid digest. * * If the security level is "dane", we send SNI information to the peer. * This may cause it to respond with a non-default certificate. Since * certificates for sessions with no or different SNI data may not match, * we must include the SNI name in the session id. */ if (props->dane) { int mixed = (props->dane->flags & TLS_DANE_FLAG_MIXED); digest_object(&mixed); digest_dane(props->dane, ta); #if 0 digest_dane(props->dane, ee); /* See above */ #endif digest_string(props->tls_level == TLS_LEV_DANE ? props->host : ""); } checkok(EVP_DigestFinal_ex(mdctx, md_buf, &md_len)); EVP_MD_CTX_destroy(mdctx); if (!ok) msg_fatal("error computing %s message digest", mdalg); /* Check for OpenSSL contract violation */ if (md_len > EVP_MAX_MD_SIZE) msg_panic("unexpectedly large %s digest size: %u", mdalg, md_len); /* * Append the digest to the serverid. We don't compare this digest to * any user-specified fingerprints. Therefore, we don't need to use a * colon-separated format, which saves space in the TLS session cache and * makes logging of session cache lookup keys more readable. * * This does however duplicate a few lines of code from the digest encoder * for colon-separated cert and pkey fingerprints. If that is a * compelling reason to consolidate, we could use that and append the * result. */ result = vstring_alloc(strlen(props->serverid) + 1 + 2 * md_len); vstring_strcpy(result, props->serverid); VSTRING_ADDCH(result, '&'); for (i = 0; i < md_len; i++) { VSTRING_ADDCH(result, hexcodes[(md_buf[i] & 0xf0) >> 4U]); VSTRING_ADDCH(result, hexcodes[(md_buf[i] & 0x0f)]); } VSTRING_TERMINATE(result); return (vstring_export(result)); }
static int ial_getifaddrs(INET_ADDR_LIST *addr_list, INET_ADDR_LIST *mask_list, int af) { const char *myname = "inet_addr_local[getifaddrs]"; struct ifaddrs *ifap, *ifa; struct sockaddr *sa, *sam; if (getifaddrs(&ifap) < 0) msg_fatal("%s: getifaddrs: %m", myname); /* * Get the address of each IP network interface. According to BIND we * must include interfaces that are down because the machine may still * receive packets for that address (yes, via some other interface). * Having no way to verify this claim on every machine, I will give them * the benefit of the doubt. * * FIX 200501: The IPv6 patch did not report NetBSD loopback interfaces; * fixed by replacing IFF_RUNNING by IFF_UP. * * FIX 200501: The IPV6 patch did not skip wild-card interface addresses * (tested on FreeBSD). */ for (ifa = ifap; ifa; ifa = ifa->ifa_next) { if (!(ifa->ifa_flags & IFF_UP) || ifa->ifa_addr == 0) continue; sa = ifa->ifa_addr; if (af != AF_UNSPEC && sa->sa_family != af) continue; sam = ifa->ifa_netmask; if (sam == 0) { /* XXX In mynetworks, a null netmask would match everyone. */ msg_warn("ignoring interface with null netmask, address family %d", sa->sa_family); continue; } switch (sa->sa_family) { case AF_INET: if (SOCK_ADDR_IN_ADDR(sa).s_addr == INADDR_ANY) continue; break; #ifdef HAS_IPV6 case AF_INET6: if (IN6_IS_ADDR_UNSPECIFIED(&SOCK_ADDR_IN6_ADDR(sa))) continue; break; #endif default: continue; } inet_addr_list_append(addr_list, sa); if (mask_list != 0) { /* * Unfortunately, sa_len/sa_family may be broken in the netmask * sockaddr structure. We must fix this manually to have correct * addresses. --dcs */ #ifdef HAS_SA_LEN sam->sa_len = sa->sa_family == AF_INET6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in); #endif sam->sa_family = sa->sa_family; inet_addr_list_append(mask_list, sam); } } freeifaddrs(ifap); return (0); }
NORETURN single_server_main(int argc, char **argv, SINGLE_SERVER_FN service,...) { const char *myname = "single_server_main"; VSTREAM *stream = 0; char *root_dir = 0; char *user_name = 0; int debug_me = 0; int daemon_mode = 1; char *service_name = basename(argv[0]); int delay; int c; int socket_count = 1; int fd; va_list ap; MAIL_SERVER_INIT_FN pre_init = 0; MAIL_SERVER_INIT_FN post_init = 0; MAIL_SERVER_LOOP_FN loop = 0; int key; char *transport = 0; char *lock_path; VSTRING *why; int alone = 0; int zerolimit = 0; WATCHDOG *watchdog; char *oname_val; char *oname; char *oval; const char *err; char *generation; int msg_vstream_needed = 0; int redo_syslog_init = 0; const char *dsn_filter_title; const char **dsn_filter_maps; /* * 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); /* * Don't die for frivolous reasons. */ #ifdef SIGXFSZ signal(SIGXFSZ, SIG_IGN); #endif /* * May need this every now and then. */ var_procname = mystrdup(basename(argv[0])); set_mail_conf_str(VAR_PROCNAME, var_procname); /* * Initialize logging and exit handler. Do the syslog first, so that its * initialization completes before we enter the optional chroot jail. */ msg_syslog_init(mail_task(var_procname), LOG_PID, LOG_FACILITY); if (msg_verbose) msg_info("daemon started"); /* * Check the Postfix library version as soon as we enable logging. */ MAIL_VERSION_CHECK; /* * Initialize from the configuration file. Allow command-line options to * override compiled-in defaults or configured parameter values. */ mail_conf_suck(); /* * After database open error, continue execution with reduced * functionality. */ dict_allow_surrogate = 1; /* * Pick up policy settings from master process. Shut up error messages to * stderr, because no-one is going to see them. */ opterr = 0; while ((c = GETOPT(argc, argv, "cdDi:lm:n:o:s:St:uvVz")) > 0) { switch (c) { case 'c': root_dir = "setme"; break; case 'd': daemon_mode = 0; break; case 'D': debug_me = 1; break; case 'i': mail_conf_update(VAR_MAX_IDLE, optarg); break; case 'l': alone = 1; break; case 'm': mail_conf_update(VAR_MAX_USE, optarg); break; case 'n': service_name = optarg; break; case 'o': oname_val = mystrdup(optarg); if ((err = split_nameval(oname_val, &oname, &oval)) != 0) msg_fatal("invalid \"-o %s\" option value: %s", optarg, err); mail_conf_update(oname, oval); if (strcmp(oname, VAR_SYSLOG_NAME) == 0) redo_syslog_init = 1; myfree(oname_val); break; case 's': if ((socket_count = atoi(optarg)) <= 0) msg_fatal("invalid socket_count: %s", optarg); break; case 'S': stream = VSTREAM_IN; break; case 'u': user_name = "setme"; break; case 't': transport = optarg; break; case 'v': msg_verbose++; break; case 'V': if (++msg_vstream_needed == 1) msg_vstream_init(mail_task(var_procname), VSTREAM_ERR); break; case 'z': zerolimit = 1; break; default: msg_fatal("invalid option: %c", c); break; } } /* * Initialize generic parameters. */ mail_params_init(); if (redo_syslog_init) msg_syslog_init(mail_task(var_procname), LOG_PID, LOG_FACILITY); /* * Register higher-level dictionaries and initialize the support for * dynamically-loaded dictionarles. */ mail_dict_init(); /* * If not connected to stdin, stdin must not be a terminal. */ if (daemon_mode && stream == 0 && isatty(STDIN_FILENO)) { msg_vstream_init(var_procname, VSTREAM_ERR); msg_fatal("do not run this command by hand-3"); } /* * Application-specific initialization. */ va_start(ap, service); while ((key = va_arg(ap, int)) != 0) { switch (key) { case MAIL_SERVER_INT_TABLE: get_mail_conf_int_table(va_arg(ap, CONFIG_INT_TABLE *)); break; case MAIL_SERVER_LONG_TABLE: get_mail_conf_long_table(va_arg(ap, CONFIG_LONG_TABLE *)); break; case MAIL_SERVER_STR_TABLE: get_mail_conf_str_table(va_arg(ap, CONFIG_STR_TABLE *)); break; case MAIL_SERVER_BOOL_TABLE: get_mail_conf_bool_table(va_arg(ap, CONFIG_BOOL_TABLE *)); break; case MAIL_SERVER_TIME_TABLE: get_mail_conf_time_table(va_arg(ap, CONFIG_TIME_TABLE *)); break; case MAIL_SERVER_RAW_TABLE: get_mail_conf_raw_table(va_arg(ap, CONFIG_RAW_TABLE *)); break; case MAIL_SERVER_NINT_TABLE: get_mail_conf_nint_table(va_arg(ap, CONFIG_NINT_TABLE *)); break; case MAIL_SERVER_NBOOL_TABLE: get_mail_conf_nbool_table(va_arg(ap, CONFIG_NBOOL_TABLE *)); break; case MAIL_SERVER_PRE_INIT: pre_init = va_arg(ap, MAIL_SERVER_INIT_FN); break; case MAIL_SERVER_POST_INIT: post_init = va_arg(ap, MAIL_SERVER_INIT_FN); break; case MAIL_SERVER_LOOP: loop = va_arg(ap, MAIL_SERVER_LOOP_FN); break; case MAIL_SERVER_EXIT: single_server_onexit = va_arg(ap, MAIL_SERVER_EXIT_FN); break; case MAIL_SERVER_PRE_ACCEPT: single_server_pre_accept = va_arg(ap, MAIL_SERVER_ACCEPT_FN); break; case MAIL_SERVER_IN_FLOW_DELAY: single_server_in_flow_delay = 1; break; case MAIL_SERVER_SOLITARY: if (stream == 0 && !alone) msg_fatal("service %s requires a process limit of 1", service_name); break; case MAIL_SERVER_UNLIMITED: if (stream == 0 && !zerolimit) msg_fatal("service %s requires a process limit of 0", service_name); break; case MAIL_SERVER_PRIVILEGED: if (user_name) msg_fatal("service %s requires privileged operation", service_name); break; case MAIL_SERVER_BOUNCE_INIT: dsn_filter_title = va_arg(ap, const char *); dsn_filter_maps = va_arg(ap, const char **); bounce_client_init(dsn_filter_title, *dsn_filter_maps); break; default: msg_panic("%s: unknown argument type: %d", myname, key); } } va_end(ap); if (root_dir) root_dir = var_queue_dir; if (user_name) user_name = var_mail_owner; /* * Can options be required? */ if (stream == 0) { if (transport == 0) msg_fatal("no transport type specified"); if (strcasecmp(transport, MASTER_XPORT_NAME_INET) == 0) single_server_accept = single_server_accept_inet; else if (strcasecmp(transport, MASTER_XPORT_NAME_UNIX) == 0) single_server_accept = single_server_accept_local; #ifdef MASTER_XPORT_NAME_PASS else if (strcasecmp(transport, MASTER_XPORT_NAME_PASS) == 0) single_server_accept = single_server_accept_pass; #endif else msg_fatal("unsupported transport type: %s", transport); } /* * Retrieve process generation from environment. */ if ((generation = getenv(MASTER_GEN_NAME)) != 0) { if (!alldig(generation)) msg_fatal("bad generation: %s", generation); OCTAL_TO_UNSIGNED(single_server_generation, generation); if (msg_verbose) msg_info("process generation: %s (%o)", generation, single_server_generation); } /* * Optionally start the debugger on ourself. */ if (debug_me) debug_process(); /* * Traditionally, BSD select() can't handle multiple processes selecting * on the same socket, and wakes up every process in select(). See TCP/IP * Illustrated volume 2 page 532. We avoid select() collisions with an * external lock file. */ if (stream == 0 && !alone) { lock_path = concatenate(DEF_PID_DIR, "/", transport, ".", service_name, (void *) 0); why = vstring_alloc(1); if ((single_server_lock = safe_open(lock_path, O_CREAT | O_RDWR, 0600, (struct stat *) 0, -1, -1, why)) == 0) msg_fatal("open lock file %s: %s", lock_path, vstring_str(why)); close_on_exec(vstream_fileno(single_server_lock), CLOSE_ON_EXEC); myfree(lock_path); vstring_free(why); } /* * Set up call-back info. */ single_server_service = service; single_server_name = service_name; single_server_argv = argv + optind; /* * Run pre-jail initialization. */ if (chdir(var_queue_dir) < 0) msg_fatal("chdir(\"%s\"): %m", var_queue_dir); if (pre_init) pre_init(single_server_name, single_server_argv); /* * Optionally, restrict the damage that this process can do. */ resolve_local_init(); tzset(); chroot_uid(root_dir, user_name); /* * Run post-jail initialization. */ if (post_init) post_init(single_server_name, single_server_argv); /* * Are we running as a one-shot server with the client connection on * standard input? If so, make sure the output is written to stdout so as * to satisfy common expectation. */ if (stream != 0) { vstream_control(stream, CA_VSTREAM_CTL_DOUBLE, CA_VSTREAM_CTL_WRITE_FD(STDOUT_FILENO), CA_VSTREAM_CTL_END); service(stream, single_server_name, single_server_argv); vstream_fflush(stream); single_server_exit(); } /* * Running as a semi-resident server. Service connection requests. * Terminate when we have serviced a sufficient number of clients, when * no-one has been talking to us for a configurable amount of time, or * when the master process terminated abnormally. */ if (var_idle_limit > 0) event_request_timer(single_server_timeout, (void *) 0, var_idle_limit); for (fd = MASTER_LISTEN_FD; fd < MASTER_LISTEN_FD + socket_count; fd++) { event_enable_read(fd, single_server_accept, CAST_INT_TO_VOID_PTR(fd)); close_on_exec(fd, CLOSE_ON_EXEC); } event_enable_read(MASTER_STATUS_FD, single_server_abort, (void *) 0); close_on_exec(MASTER_STATUS_FD, CLOSE_ON_EXEC); close_on_exec(MASTER_FLOW_READ, CLOSE_ON_EXEC); close_on_exec(MASTER_FLOW_WRITE, CLOSE_ON_EXEC); watchdog = watchdog_create(var_daemon_timeout, (WATCHDOG_FN) 0, (void *) 0); /* * The event loop, at last. */ while (var_use_limit == 0 || use_count < var_use_limit) { if (single_server_lock != 0) { watchdog_stop(watchdog); if (myflock(vstream_fileno(single_server_lock), INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) msg_fatal("select lock: %m"); } watchdog_start(watchdog); delay = loop ? loop(single_server_name, single_server_argv) : -1; event_loop(delay); } single_server_exit(); }
static int ial_siocglif(INET_ADDR_LIST *addr_list, INET_ADDR_LIST *mask_list, int af) { const char *myname = "inet_addr_local[siocglif]"; struct lifconf lifc; struct lifreq *lifr; struct lifreq *lifr_mask; struct lifreq *the_end; struct sockaddr *sa; int sock; VSTRING *buf; /* * See also comments in ial_siocgif() */ if (af != AF_INET && af != AF_INET6) msg_fatal("%s: address family was %d, must be AF_INET (%d) or " "AF_INET6 (%d)", myname, af, AF_INET, AF_INET6); sock = ial_socket(af); if (sock < 0) return (0); buf = vstring_alloc(1024); for (;;) { memset(&lifc, 0, sizeof(lifc)); lifc.lifc_family = AF_UNSPEC; /* XXX Why??? */ lifc.lifc_len = vstring_avail(buf); lifc.lifc_buf = vstring_str(buf); if (ioctl(sock, SIOCGLIFCONF, (char *) &lifc) < 0) { if (errno != EINVAL) msg_fatal("%s: ioctl SIOCGLIFCONF: %m", myname); } else if (lifc.lifc_len < vstring_avail(buf) / 2) break; VSTRING_SPACE(buf, vstring_avail(buf) * 2); } the_end = (struct lifreq *) (lifc.lifc_buf + lifc.lifc_len); for (lifr = lifc.lifc_req; lifr < the_end;) { sa = (struct sockaddr *) &lifr->lifr_addr; if (sa->sa_family != af) { lifr = NEXT_INTERFACE(lifr); continue; } if (af == AF_INET) { if (SOCK_ADDR_IN_ADDR(sa).s_addr == INADDR_ANY) { lifr = NEXT_INTERFACE(lifr); continue; } #ifdef HAS_IPV6 } else if (af == AF_INET6) { if (IN6_IS_ADDR_UNSPECIFIED(&SOCK_ADDR_IN6_ADDR(sa))) { lifr = NEXT_INTERFACE(lifr); continue; } } #endif inet_addr_list_append(addr_list, sa); if (mask_list) { lifr_mask = (struct lifreq *) mymalloc(sizeof(struct lifreq)); memcpy((void *) lifr_mask, (void *) lifr, sizeof(struct lifreq)); if (ioctl(sock, SIOCGLIFNETMASK, lifr_mask) < 0) msg_fatal("%s: ioctl(SIOCGLIFNETMASK): %m", myname); /* XXX: Check whether sa_len/family are honoured --dcs */ inet_addr_list_append(mask_list, (struct sockaddr *) &lifr_mask->lifr_addr); myfree((void *) lifr_mask); } lifr = NEXT_INTERFACE(lifr); } vstring_free(buf); (void) close(sock); return (0); }
const char *smtpd_expand_lookup(const char *name, int unused_mode, char *context) { SMTPD_STATE *state = (SMTPD_STATE *) context; time_t now; struct tm *lt; if (state->expand_buf == 0) state->expand_buf = vstring_alloc(10); if (msg_verbose > 1) msg_info("smtpd_expand_lookup: ${%s}", name); #define STREQN(x,y,n) (*(x) == *(y) && strncmp((x), (y), (n)) == 0) #define CONST_LEN(x) (sizeof(x) - 1) /* * Don't query main.cf parameters, as the result of expansion could * reveal system-internal information in server replies. * * XXX: This said, multiple servers may be behind a single client-visible * name or IP address, and each may generate its own logs. Therefore, it * may be useful to expose the replying MTA id (myhostname) in the * contact footer, to identify the right logs. So while we don't expose * the raw configuration dictionary, we do expose "$myhostname" as * expanded in var_myhostname. * * Return NULL only for non-existent names. */ if (STREQ(name, MAIL_ATTR_SERVER_NAME)) { return (var_myhostname); } else if (STREQ(name, MAIL_ATTR_ACT_CLIENT)) { return (state->namaddr); } else if (STREQ(name, MAIL_ATTR_ACT_CLIENT_PORT)) { return (state->port); } else if (STREQ(name, MAIL_ATTR_ACT_CLIENT_ADDR)) { return (state->addr); } else if (STREQ(name, MAIL_ATTR_ACT_CLIENT_NAME)) { return (state->name); } else if (STREQ(name, MAIL_ATTR_ACT_REVERSE_CLIENT_NAME)) { return (state->reverse_name); } else if (STREQ(name, MAIL_ATTR_ACT_HELO_NAME)) { return (state->helo_name ? state->helo_name : ""); } else if (STREQN(name, MAIL_ATTR_SENDER, CONST_LEN(MAIL_ATTR_SENDER))) { return (smtpd_expand_addr(state->expand_buf, state->sender, name, CONST_LEN(MAIL_ATTR_SENDER))); } else if (STREQN(name, MAIL_ATTR_RECIP, CONST_LEN(MAIL_ATTR_RECIP))) { return (smtpd_expand_addr(state->expand_buf, state->recipient, name, CONST_LEN(MAIL_ATTR_RECIP))); } if (STREQ(name, MAIL_ATTR_LOCALTIME)) { if (time(&now) == (time_t) - 1) msg_fatal("time lookup failed: %m"); lt = localtime(&now); VSTRING_RESET(state->expand_buf); do { VSTRING_SPACE(state->expand_buf, 100); } while (strftime(STR(state->expand_buf), vstring_avail(state->expand_buf), "%b %d %H:%M:%S", lt) == 0); return (STR(state->expand_buf)); } else { smtpd_expand_unknown(name); return (0); } }
static int ial_siocgif(INET_ADDR_LIST *addr_list, INET_ADDR_LIST *mask_list, int af) { const char *myname = "inet_addr_local[siocgif]"; struct in_addr addr; struct ifconf ifc; struct ifreq *ifr; struct ifreq *ifr_mask; struct ifreq *the_end; int sock; VSTRING *buf; /* * Get the network interface list. XXX The socket API appears to have no * function that returns the number of network interfaces, so we have to * guess how much space is needed to store the result. * * On BSD-derived systems, ioctl SIOCGIFCONF returns as much information as * possible, leaving it up to the application to repeat the request with * a larger buffer if the result caused a tight fit. * * Other systems, such as Solaris 2.5, generate an EINVAL error when the * buffer is too small for the entire result. Workaround: ignore EINVAL * errors and repeat the request with a larger buffer. The downside is * that the program can run out of memory due to a non-memory problem, * making it more difficult than necessary to diagnose the real problem. */ sock = ial_socket(af); if (sock < 0) return (0); buf = vstring_alloc(1024); for (;;) { ifc.ifc_len = vstring_avail(buf); ifc.ifc_buf = vstring_str(buf); if (ioctl(sock, SIOCGIFCONF, (char *) &ifc) < 0) { if (errno != EINVAL) msg_fatal("%s: ioctl SIOCGIFCONF: %m", myname); } else if (ifc.ifc_len < vstring_avail(buf) / 2) break; VSTRING_SPACE(buf, vstring_avail(buf) * 2); } the_end = (struct ifreq *) (ifc.ifc_buf + ifc.ifc_len); for (ifr = ifc.ifc_req; ifr < the_end;) { if (ifr->ifr_addr.sa_family != af) { ifr = NEXT_INTERFACE(ifr); continue; } if (af == AF_INET) { addr = ((struct sockaddr_in *) & ifr->ifr_addr)->sin_addr; if (addr.s_addr != INADDR_ANY) { inet_addr_list_append(addr_list, &ifr->ifr_addr); if (mask_list) { ifr_mask = (struct ifreq *) mymalloc(IFREQ_SIZE(ifr)); memcpy((void *) ifr_mask, (void *) ifr, IFREQ_SIZE(ifr)); if (ioctl(sock, SIOCGIFNETMASK, ifr_mask) < 0) msg_fatal("%s: ioctl SIOCGIFNETMASK: %m", myname); /* * Note that this SIOCGIFNETMASK has truly screwed up the * contents of sa_len/sa_family. We must fix this * manually to have correct addresses. --dcs */ ifr_mask->ifr_addr.sa_family = af; #ifdef HAS_SA_LEN ifr_mask->ifr_addr.sa_len = sizeof(struct sockaddr_in); #endif inet_addr_list_append(mask_list, &ifr_mask->ifr_addr); myfree((void *) ifr_mask); } } } #ifdef HAS_IPV6 else if (af == AF_INET6) { struct sockaddr *sa; sa = SOCK_ADDR_PTR(&ifr->ifr_addr); if (!(IN6_IS_ADDR_UNSPECIFIED(&SOCK_ADDR_IN6_ADDR(sa)))) { inet_addr_list_append(addr_list, sa); if (mask_list) { /* XXX Assume /128 for everything */ struct sockaddr_in6 mask6; mask6 = *SOCK_ADDR_IN6_PTR(sa); memset((void *) &mask6.sin6_addr, ~0, sizeof(mask6.sin6_addr)); inet_addr_list_append(mask_list, SOCK_ADDR_PTR(&mask6)); } } } #endif ifr = NEXT_INTERFACE(ifr); } vstring_free(buf); (void) close(sock); return (0); }
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 */ } } }
static void pre_jail_init(char *unused_name, char **unused_argv) { TLS_SERVER_INIT_PROPS props; const char *cert_file; int have_server_cert; int no_server_cert_ok; int require_server_cert; /* * The code in this routine is pasted literally from smtpd(8). I am not * going to sanitize this because doing so surely will break things in * unexpected ways. */ if (*var_tlsp_tls_level) { switch (tls_level_lookup(var_tlsp_tls_level)) { default: msg_fatal("Invalid TLS level \"%s\"", var_tlsp_tls_level); /* NOTREACHED */ break; case TLS_LEV_SECURE: case TLS_LEV_VERIFY: case TLS_LEV_FPRINT: msg_warn("%s: unsupported TLS level \"%s\", using \"encrypt\"", VAR_TLSP_TLS_LEVEL, var_tlsp_tls_level); /* FALLTHROUGH */ case TLS_LEV_ENCRYPT: var_tlsp_enforce_tls = var_tlsp_use_tls = 1; break; case TLS_LEV_MAY: var_tlsp_enforce_tls = 0; var_tlsp_use_tls = 1; break; case TLS_LEV_NONE: var_tlsp_enforce_tls = var_tlsp_use_tls = 0; break; } } var_tlsp_use_tls = var_tlsp_use_tls || var_tlsp_enforce_tls; if (!var_tlsp_use_tls) { msg_warn("TLS service is requested, but disabled with %s or %s", VAR_TLSP_TLS_LEVEL, VAR_TLSP_USE_TLS); return; } /* * Load TLS keys before dropping privileges. * * Can't use anonymous ciphers if we want client certificates. Must use * anonymous ciphers if we have no certificates. */ ask_client_cert = require_server_cert = (var_tlsp_tls_ask_ccert || (var_tlsp_enforce_tls && var_tlsp_tls_req_ccert)); if (strcasecmp(var_tlsp_tls_cert_file, "none") == 0) { no_server_cert_ok = 1; cert_file = ""; } else { no_server_cert_ok = 0; cert_file = var_tlsp_tls_cert_file; } have_server_cert = (*cert_file || *var_tlsp_tls_dcert_file || *var_tlsp_tls_eccert_file); /* Some TLS configuration errors are not show stoppers. */ if (!have_server_cert && require_server_cert) msg_warn("Need a server cert to request client certs"); if (!var_tlsp_enforce_tls && var_tlsp_tls_req_ccert) msg_warn("Can't require client certs unless TLS is required"); /* After a show-stopper error, log a warning. */ if (have_server_cert || (no_server_cert_ok && !require_server_cert)) /* * Large parameter lists are error-prone, so we emulate a language * feature that C does not have natively: named parameter lists. */ tlsp_server_ctx = TLS_SERVER_INIT(&props, log_param = VAR_TLSP_TLS_LOGLEVEL, log_level = var_tlsp_tls_loglevel, verifydepth = var_tlsp_tls_ccert_vd, cache_type = TLS_MGR_SCACHE_SMTPD, set_sessid = var_tlsp_tls_set_sessid, cert_file = cert_file, key_file = var_tlsp_tls_key_file, dcert_file = var_tlsp_tls_dcert_file, dkey_file = var_tlsp_tls_dkey_file, eccert_file = var_tlsp_tls_eccert_file, eckey_file = var_tlsp_tls_eckey_file, CAfile = var_tlsp_tls_CAfile, CApath = var_tlsp_tls_CApath, dh1024_param_file = var_tlsp_tls_dh1024_param_file, dh512_param_file = var_tlsp_tls_dh512_param_file, eecdh_grade = var_tlsp_tls_eecdh, protocols = var_tlsp_enforce_tls ? var_tlsp_tls_mand_proto : var_tlsp_tls_proto, ask_ccert = ask_client_cert, mdalg = var_tlsp_tls_fpt_dgst); else msg_warn("No server certs available. TLS can't be enabled"); /* * To maintain sanity, allow partial SSL_write() operations, and allow * SSL_write() buffer pointers to change after a WANT_READ or WANT_WRITE * result. This is based on OpenSSL developers talking on a mailing list, * but is not supported by documentation. If this code stops working then * no-one can be held responsible. */ if (tlsp_server_ctx) SSL_CTX_set_mode(tlsp_server_ctx->ssl_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); }
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); }
int main(int unused_argc, char **argv) { VSTRING *inbuf = vstring_alloc(1); char *bufp; char *cmd; ssize_t cmd_len; char *service; char *addr; int count; int rate; int msgs; int rcpts; int newtls; ANVIL_CLNT *anvil; msg_vstream_init(argv[0], VSTREAM_ERR); mail_conf_read(); msg_info("using config files in %s", var_config_dir); if (chdir(var_queue_dir) < 0) msg_fatal("chdir %s: %m", var_queue_dir); msg_verbose++; anvil = anvil_clnt_create(); while (vstring_fgets_nonl(inbuf, VSTREAM_IN)) { bufp = vstring_str(inbuf); if ((cmd = mystrtok(&bufp, " ")) == 0 || *bufp == 0 || (service = mystrtok(&bufp, " ")) == 0 || *service == 0 || (addr = mystrtok(&bufp, " ")) == 0 || *addr == 0 || mystrtok(&bufp, " ") != 0) { vstream_printf("bad command syntax\n"); usage(); vstream_fflush(VSTREAM_OUT); continue; } cmd_len = strlen(cmd); if (strncmp(cmd, ANVIL_REQ_CONN, cmd_len) == 0) { if (anvil_clnt_connect(anvil, service, addr, &count, &rate) != ANVIL_STAT_OK) msg_warn("error!"); else vstream_printf("count=%d, rate=%d\n", count, rate); } else if (strncmp(cmd, ANVIL_REQ_MAIL, cmd_len) == 0) { if (anvil_clnt_mail(anvil, service, addr, &msgs) != ANVIL_STAT_OK) msg_warn("error!"); else vstream_printf("rate=%d\n", msgs); } else if (strncmp(cmd, ANVIL_REQ_RCPT, cmd_len) == 0) { if (anvil_clnt_rcpt(anvil, service, addr, &rcpts) != ANVIL_STAT_OK) msg_warn("error!"); else vstream_printf("rate=%d\n", rcpts); } else if (strncmp(cmd, ANVIL_REQ_NTLS, cmd_len) == 0) { if (anvil_clnt_newtls(anvil, service, addr, &newtls) != ANVIL_STAT_OK) msg_warn("error!"); else vstream_printf("rate=%d\n", newtls); } else if (strncmp(cmd, ANVIL_REQ_NTLS_STAT, cmd_len) == 0) { if (anvil_clnt_newtls_stat(anvil, service, addr, &newtls) != ANVIL_STAT_OK) msg_warn("error!"); else vstream_printf("rate=%d\n", newtls); } else if (strncmp(cmd, ANVIL_REQ_DISC, cmd_len) == 0) { if (anvil_clnt_disconnect(anvil, service, addr) != ANVIL_STAT_OK) msg_warn("error!"); else vstream_printf("OK\n"); } else if (strncmp(cmd, ANVIL_REQ_LOOKUP, cmd_len) == 0) { if (anvil_clnt_lookup(anvil, service, addr, &count, &rate, &msgs, &rcpts, &newtls) != ANVIL_STAT_OK) msg_warn("error!"); else vstream_printf("count=%d, rate=%d msgs=%d rcpts=%d newtls=%d\n", count, rate, msgs, rcpts, newtls); } else { vstream_printf("bad command: \"%s\"\n", cmd); usage(); } vstream_fflush(VSTREAM_OUT); } vstring_free(inbuf); anvil_clnt_free(anvil); return (0); }
static void qmgr_transport_abort(int unused_event, void *context) { QMGR_TRANSPORT_ALLOC *alloc = (QMGR_TRANSPORT_ALLOC *) context; msg_fatal("timeout connecting to transport: %s", alloc->transport->name); }
static int deliver_message(DELIVER_REQUEST *request, const char *def_dsn, int (*append) (int, const char *, MSG_STATS *, RECIPIENT *, const char *, DSN *)) { const char *myname = "deliver_message"; VSTREAM *src; int result = 0; int status; RECIPIENT *rcpt; int nrcpt; DSN_SPLIT dp; DSN dsn; if (msg_verbose) msg_info("deliver_message: from 2-%s", request->sender); /* * Sanity checks. */ if (request->nexthop[0] == 0) msg_fatal("empty nexthop hostname"); if (request->rcpt_list.len <= 0) msg_fatal("recipient count: %d", request->rcpt_list.len); /* * Open the queue file. Opening the file can fail for a variety of * reasons, such as the system running out of resources. Instead of * throwing away mail, we're raising a fatal error which forces the mail * system to back off, and retry later. */ src = mail_queue_open(request->queue_name, request->queue_id, O_RDWR, 0); if (src == 0) msg_fatal("%s: open %s %s: %m", myname, request->queue_name, request->queue_id); if (msg_verbose) msg_info("%s: file %s", myname, VSTREAM_PATH(src)); /* * Bounce/defer/whatever all recipients. */ #define BOUNCE_FLAGS(request) DEL_REQ_TRACE_FLAGS(request->flags) dsn_split(&dp, def_dsn, request->nexthop); (void) DSN_SIMPLE(&dsn, DSN_STATUS(dp.dsn), dp.text); for (nrcpt = 0; nrcpt < request->rcpt_list.len; nrcpt++) { rcpt = request->rcpt_list.info + nrcpt; status = append(BOUNCE_FLAGS(request), request->queue_id, &request->msg_stats, rcpt, "none", &dsn); if (status == 0) deliver_completed(src, rcpt->offset); result |= status; } /* * Clean up. */ if (vstream_fclose(src)) msg_warn("close %s %s: %m", request->queue_name, request->queue_id); return (result); }
static void postcat(VSTREAM *fp, VSTRING *buffer, int flags) { int prev_type = 0; int rec_type; struct timeval tv; time_t time; int ch; off_t offset; const char *error_text; char *attr_name; char *attr_value; int rec_flags = (msg_verbose ? REC_FLAG_NONE : REC_FLAG_DEFAULT); int state; /* state machine, input type */ int do_print; /* state machine, output control */ long data_offset; /* state machine, read optimization */ long data_size; /* state machine, read optimization */ #define TEXT_RECORD(rec_type) \ (rec_type == REC_TYPE_CONT || rec_type == REC_TYPE_NORM) /* * See if this is a plausible file. */ if ((ch = VSTREAM_GETC(fp)) != VSTREAM_EOF) { if (!strchr(REC_TYPE_ENVELOPE, ch)) { msg_warn("%s: input is not a valid queue file", VSTREAM_PATH(fp)); return; } vstream_ungetc(fp, ch); } /* * Other preliminaries. */ if (flags & PC_FLAG_PRINT_ENV) vstream_printf("*** ENVELOPE RECORDS %s ***\n", VSTREAM_PATH(fp)); state = PC_STATE_ENV; do_print = (flags & PC_FLAG_PRINT_ENV); data_offset = data_size = -1; /* * Now look at the rest. */ for (;;) { if (flags & PC_FLAG_PRINT_OFFSET) offset = vstream_ftell(fp); rec_type = rec_get_raw(fp, buffer, 0, rec_flags); if (rec_type == REC_TYPE_ERROR) msg_fatal("record read error"); if (rec_type == REC_TYPE_EOF) break; /* * First inspect records that have side effects on the (envelope, * header, body) state machine or on the record reading order. * * XXX Comments marked "Optimization:" identify subtle code that will * likely need to be revised when the queue file organization is * changed. */ #define PRINT_MARKER(flags, fp, offset, type, text) do { \ if ((flags) & PC_FLAG_PRINT_OFFSET) \ vstream_printf("%9lu ", (unsigned long) (offset)); \ if (flags & PC_FLAG_PRINT_RTYPE_DEC) \ vstream_printf("%3d ", (type)); \ vstream_printf("*** %s %s ***\n", (text), VSTREAM_PATH(fp)); \ vstream_fflush(VSTREAM_OUT); \ } while (0) #define PRINT_RECORD(flags, offset, type, value) do { \ if ((flags) & PC_FLAG_PRINT_OFFSET) \ vstream_printf("%9lu ", (unsigned long) (offset)); \ if (flags & PC_FLAG_PRINT_RTYPE_DEC) \ vstream_printf("%3d ", (type)); \ vstream_printf("%s: %s\n", rec_type_name(rec_type), (value)); \ vstream_fflush(VSTREAM_OUT); \ } while (0) if (TEXT_RECORD(rec_type)) { /* This is wrong when the message starts with whitespace. */ if (state == PC_STATE_HEADER && (flags & (PC_MASK_PRINT_TEXT)) && prev_type != REC_TYPE_CONT && TEXT_RECORD(rec_type) && !(is_header(STR(buffer)) || IS_SPACE_TAB(STR(buffer)[0]))) { /* Update the state machine. */ state = PC_STATE_BODY; do_print = (flags & PC_FLAG_PRINT_BODY); /* Optimization: terminate if nothing left to print. */ if (do_print == 0 && (flags & PC_FLAG_PRINT_ENV) == 0) break; /* Optimization: skip to extracted segment marker. */ if (do_print == 0 && (flags & PC_FLAG_PRINT_ENV) && data_offset >= 0 && data_size >= 0 && vstream_fseek(fp, data_offset + data_size, SEEK_SET) < 0) msg_fatal("seek error: %m"); } /* Optional output happens further down below. */ } else if (rec_type == REC_TYPE_MESG) { /* Sanity check. */ if (state != PC_STATE_ENV) msg_warn("%s: out-of-order message content marker", VSTREAM_PATH(fp)); /* Optional output. */ if (flags & PC_FLAG_PRINT_ENV) PRINT_MARKER(flags, fp, offset, rec_type, "MESSAGE CONTENTS"); /* Optimization: skip to extracted segment marker. */ if ((flags & PC_MASK_PRINT_TEXT) == 0 && data_offset >= 0 && data_size >= 0 && vstream_fseek(fp, data_offset + data_size, SEEK_SET) < 0) msg_fatal("seek error: %m"); /* Update the state machine, even when skipping. */ state = PC_STATE_HEADER; do_print = (flags & PC_FLAG_PRINT_HEADER); continue; } else if (rec_type == REC_TYPE_XTRA) { /* Sanity check. */ if (state != PC_STATE_HEADER && state != PC_STATE_BODY) msg_warn("%s: out-of-order extracted segment marker", VSTREAM_PATH(fp)); /* Optional output (terminate preceding header/body line). */ if (do_print && prev_type == REC_TYPE_CONT) VSTREAM_PUTCHAR('\n'); if (flags & PC_FLAG_PRINT_ENV) PRINT_MARKER(flags, fp, offset, rec_type, "HEADER EXTRACTED"); /* Update the state machine. */ state = PC_STATE_ENV; do_print = (flags & PC_FLAG_PRINT_ENV); /* Optimization: terminate if nothing left to print. */ if (do_print == 0) break; continue; } else if (rec_type == REC_TYPE_END) { /* Sanity check. */ if (state != PC_STATE_ENV) msg_warn("%s: out-of-order message end marker", VSTREAM_PATH(fp)); /* Optional output. */ if (flags & PC_FLAG_PRINT_ENV) PRINT_MARKER(flags, fp, offset, rec_type, "MESSAGE FILE END"); /* Terminate the state machine. */ break; } else if (rec_type == REC_TYPE_PTR) { /* Optional output. */ /* This record type is exposed only with '-v'. */ if (do_print) PRINT_RECORD(flags, offset, rec_type, STR(buffer)); /* Skip to the pointer's target record. */ if (rec_goto(fp, STR(buffer)) == REC_TYPE_ERROR) msg_fatal("bad pointer record, or input is not seekable"); continue; } else if (rec_type == REC_TYPE_SIZE) { /* Optional output (here before we update the state machine). */ if (do_print) PRINT_RECORD(flags, offset, rec_type, STR(buffer)); /* Read the message size/offset for the state machine optimizer. */ if (data_size >= 0 || data_offset >= 0) { msg_warn("file contains multiple size records"); } else { if (sscanf(STR(buffer), "%ld %ld", &data_size, &data_offset) != 2 || data_offset <= 0 || data_size <= 0) msg_fatal("invalid size record: %.100s", STR(buffer)); /* Optimization: skip to the message header. */ if ((flags & PC_FLAG_PRINT_ENV) == 0) { if (vstream_fseek(fp, data_offset, SEEK_SET) < 0) msg_fatal("seek error: %m"); /* Update the state machine. */ state = PC_STATE_HEADER; do_print = (flags & PC_FLAG_PRINT_HEADER); } } continue; } /* * Don't inspect side-effect-free records that aren't printed. */ if (do_print == 0) continue; if (flags & PC_FLAG_PRINT_OFFSET) vstream_printf("%9lu ", (unsigned long) offset); if (flags & PC_FLAG_PRINT_RTYPE_DEC) vstream_printf("%3d ", rec_type); switch (rec_type) { case REC_TYPE_TIME: REC_TYPE_TIME_SCAN(STR(buffer), tv); time = tv.tv_sec; vstream_printf("%s: %s", rec_type_name(rec_type), asctime(localtime(&time))); break; case REC_TYPE_WARN: REC_TYPE_WARN_SCAN(STR(buffer), time); vstream_printf("%s: %s", rec_type_name(rec_type), asctime(localtime(&time))); break; case REC_TYPE_CONT: /* REC_TYPE_FILT collision */ if (state == PC_STATE_ENV) vstream_printf("%s: ", rec_type_name(rec_type)); else if (msg_verbose) vstream_printf("unterminated_text: "); vstream_fwrite(VSTREAM_OUT, STR(buffer), LEN(buffer)); if (state == PC_STATE_ENV || msg_verbose || (flags & PC_FLAG_PRINT_OFFSET) != 0) { rec_type = 0; VSTREAM_PUTCHAR('\n'); } break; case REC_TYPE_NORM: if (msg_verbose) vstream_printf("%s: ", rec_type_name(rec_type)); vstream_fwrite(VSTREAM_OUT, STR(buffer), LEN(buffer)); VSTREAM_PUTCHAR('\n'); break; case REC_TYPE_DTXT: /* This record type is exposed only with '-v'. */ vstream_printf("%s: ", rec_type_name(rec_type)); vstream_fwrite(VSTREAM_OUT, STR(buffer), LEN(buffer)); VSTREAM_PUTCHAR('\n'); break; case REC_TYPE_ATTR: error_text = split_nameval(STR(buffer), &attr_name, &attr_value); if (error_text != 0) { msg_warn("%s: malformed attribute: %s: %.100s", VSTREAM_PATH(fp), error_text, STR(buffer)); break; } if (strcmp(attr_name, MAIL_ATTR_CREATE_TIME) == 0) { time = atol(attr_value); vstream_printf("%s: %s", MAIL_ATTR_CREATE_TIME, asctime(localtime(&time))); } else { vstream_printf("%s: %s=%s\n", rec_type_name(rec_type), attr_name, attr_value); } break; default: vstream_printf("%s: %s\n", rec_type_name(rec_type), STR(buffer)); break; } prev_type = rec_type; /* * In case the next record is broken. */ vstream_fflush(VSTREAM_OUT); } }
int main(int argc, char **argv) { int c, ndx; int readonly; int fd; const char *file; const char *runpath; Elf *elf; GElf_Ehdr ehdr; size_t shstrndx, shnum; Elf_Scn *scn; Elf_Data *data; char *shnames = NULL; Cache *cache, *_cache; Cache *dynsec, *strsec; GElf_Word numdyn; dyn_elt_t rpath_elt; dyn_elt_t runpath_elt; dyn_elt_t strpad_elt; dyn_elt_t flags_1_elt; dyn_elt_t null_elt; int changed = 0; opterr = 0; while ((c = getopt(argc, argv, "dr")) != EOF) { switch (c) { case 'd': d_flg = 1; break; case 'r': r_flg = 1; break; case '?': msg_usage(); } } /* * The first plain argument is the file name, and is required. * The second plain argument is the runpath, and is optional. * If no runpath is given, we print the current runpath to stdout * and exit. If it is present, we modify the ELF file to use it. */ argc = argc - optind; argv += optind; if ((argc < 1) || (argc > 2)) msg_usage(); if ((argc == 2) && r_flg) msg_usage(); readonly = (argc == 1) && !r_flg; file = argv[0]; if (!readonly) runpath = argv[1]; if ((fd = open(file, readonly ? O_RDONLY : O_RDWR)) == -1) msg_fatal("unable to open file: %s: %s\n", file, strerror(errno)); (void) elf_version(EV_CURRENT); elf = elf_begin(fd, readonly ? ELF_C_READ : ELF_C_RDWR, NULL); if (elf == NULL) msg_elf("elf_begin"); /* We only handle standalone ELF files */ switch (elf_kind(elf)) { case ELF_K_AR: msg_fatal("unable to edit ELF archive: %s\n", file); break; case ELF_K_ELF: break; default: msg_fatal("unable to edit non-ELF file: %s\n", file); break; } if (gelf_getehdr(elf, &ehdr) == NULL) msg_elf("gelf_getehdr"); if (elf_getshnum(elf, &shnum) == 0) msg_elf("elf_getshnum"); if (elf_getshstrndx(elf, &shstrndx) == 0) msg_elf("elf_getshstrndx"); /* * Obtain the .shstrtab data buffer to provide the required section * name strings. */ if ((scn = elf_getscn(elf, shstrndx)) == NULL) msg_elf("elf_getscn"); if ((data = elf_getdata(scn, NULL)) == NULL) msg_elf("elf_getdata"); shnames = data->d_buf; /* * Allocate a cache to maintain a descriptor for each section. */ if ((cache = malloc(shnum * sizeof (Cache))) == NULL) msg_fatal("unable to allocate section cache: %s\n", strerror(errno)); bzero(cache, sizeof (cache[0])); cache->c_name = ""; _cache = cache + 1; /* * Fill in cache with information for each section, and * locate the dynamic section. */ dynsec = strsec = NULL; for (ndx = 1, scn = NULL; scn = elf_nextscn(elf, scn); ndx++, _cache++) { _cache->c_ndx = ndx; if (gelf_getshdr(scn, &_cache->c_shdr) == NULL) msg_elf("gelf_getshdr"); _cache->c_data = elf_getdata(scn, NULL); _cache->c_name = shnames + _cache->c_shdr.sh_name; if (_cache->c_shdr.sh_type == SHT_DYNAMIC) { dynsec = _cache; numdyn = dynsec->c_shdr.sh_size / dynsec->c_shdr.sh_entsize; msg_debug("[%d]%s: dynamic section\n", ndx, _cache->c_name); } } /* * If we got a dynamic section, locate the string table. * If not, we can't continue. */ if (dynsec == NULL) msg_fatal("file lacks a dynamic section: %s\n", file); strsec = &cache[dynsec->c_shdr.sh_link]; msg_debug("[%d]%s: dynamic string table section\n", strsec->c_ndx, strsec->c_name); /* * History Lesson And Strategy: * * This routine handles both DT_RPATH and DT_RUNPATH entries, altering * either or both if they are present. * * The original SYSV ABI only had DT_RPATH, and the runtime loader used * it to search for things in the following order: * * DT_RPATH, LD_LIBRARY_PATH, defaults * * Solaris did not follow this rule. Environment variables should * supersede everything else, so we have always deviated from the * ABI and and instead search in the order * * LD_LIBRARY_PATH, DT_RPATH, defaults * * Other Unix variants initially followed the ABI, but in recent years * realized that it was a mistake. Hence, DT_RUNPATH was invented, * with the search order: * * LD_LIBRARY_PATH, DT_RUNPATH, defaults * * So for Solaris, DT_RPATH and DT_RUNPATH mean the same thing. If both * are present (which does happen), we set them both to the new * value. If either one is present, we set that one. If neither is * present, and we have a spare DT_NULL slot, we create a DT_RUNPATH. */ /* * Examine the dynamic section to determine the index for * - DT_RPATH * - DT_RUNPATH * - DT_SUNW_STRPAD * - DT_NULL, and whether there are any extra DT_NULL slots */ dyn_elt_init(&rpath_elt); dyn_elt_init(&runpath_elt); dyn_elt_init(&strpad_elt); dyn_elt_init(&flags_1_elt); dyn_elt_init(&null_elt); for (ndx = 0; ndx < numdyn; ndx++) { GElf_Dyn dyn; if (gelf_getdyn(dynsec->c_data, ndx, &dyn) == NULL) msg_elf("gelf_getdyn"); switch (dyn.d_tag) { case DT_NULL: /* * Remember the state of the first DT_NULL. If there * are more than one (i.e. the first one is not * in the final spot), and there is no runpath, then * we will turn the first one into a DT_RUNPATH. */ if (!null_elt.seen) { dyn_elt_save(&null_elt, ndx, &dyn); msg_debug("[%d]%s[%d]: DT_NULL\n", dynsec->c_ndx, dynsec->c_name, ndx); } break; case DT_RPATH: dyn_elt_save(&rpath_elt, ndx, &dyn); msg_debug("[%d]%s[%d]: DT_RPATH: %s\n", dynsec->c_ndx, dynsec->c_name, ndx, DYN_ELT_STRING(&rpath_elt, strsec)); break; case DT_RUNPATH: dyn_elt_save(&runpath_elt, ndx, &dyn); msg_debug("[%d]%s[%d]: DT_RUNPATH: %s\n", dynsec->c_ndx, dynsec->c_name, ndx, DYN_ELT_STRING(&runpath_elt, strsec)); break; case DT_SUNW_STRPAD: dyn_elt_save(&strpad_elt, ndx, &dyn); msg_debug("[%d]%s[%d]: DT_STRPAD: %d\n", dynsec->c_ndx, dynsec->c_name, ndx, (int)strpad_elt.dyn.d_un.d_val); break; case DT_FLAGS_1: dyn_elt_save(&flags_1_elt, ndx, &dyn); break; } } /* * If this is a readonly session, then print the existing * runpath and exit. DT_RPATH and DT_RUNPATH should have * the same value, so we arbitrarily favor DT_RUNPATH. */ if (readonly) { if (runpath_elt.seen) (void) printf("%s\n", DYN_ELT_STRING(&runpath_elt, strsec)); else if (rpath_elt.seen) (void) printf("%s\n", DYN_ELT_STRING(&rpath_elt, strsec)); else msg_debug("ELF file does not have a runpath: %s\n", file); return (0); } /* Edit the file, either to remove the runpath or to add/modify it */ if (r_flg) { if (!(runpath_elt.seen || rpath_elt.seen)) msg_debug("[%d]%s: no runpath found\n", dynsec->c_ndx, dynsec->c_name); else changed = remove_runpath(dynsec, numdyn); } else { changed = new_runpath(runpath, dynsec, numdyn, strsec, &rpath_elt, &runpath_elt, &strpad_elt, &null_elt); } if (changed) { /* * If possible, set the DF_1_EDITED flag, indicating that * this file has been edited after the fact. */ if (flags_1_elt.seen) { flags_1_elt.dyn.d_un.d_val |= DF_1_EDITED; } else if (null_elt.seen && (null_elt.ndx < (numdyn - 1))) { msg_debug("[%d]%s: No existing flags_1 entry to " "modify. Will use extra DT_NULL in slot [%d] \n", dynsec->c_ndx, dynsec->c_name, null_elt.ndx); flags_1_elt.seen = 1; flags_1_elt.ndx = null_elt.ndx; flags_1_elt.dyn.d_tag = DT_FLAGS_1; flags_1_elt.dyn.d_un.d_val = DF_1_EDITED; } if (flags_1_elt.seen) { msg_debug("[%d]%s[%d]: Set DF_1_EDITED flag\n", dynsec->c_ndx, dynsec->c_name, flags_1_elt.ndx); if (gelf_update_dyn(dynsec->c_data, flags_1_elt.ndx, &flags_1_elt.dyn) == 0) msg_elf("gelf_update_dyn"); } /* * Mark the data area as dirty so libelf will flush our * changes to the dynamic section data. */ (void) elf_flagdata(dynsec->c_data, ELF_C_SET, ELF_F_DIRTY); /* Flush the file to disk */ if (elf_update(elf, ELF_C_WRITE) == -1) msg_elf("elf_update"); (void) close(fd); (void) elf_end(elf); } return (0); }