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); }
int bounce_notify_service(int flags, char *service, char *queue_name, char *queue_id, char *encoding, char *recipient, char *dsn_envid, int dsn_ret, BOUNCE_TEMPLATES *ts) { BOUNCE_INFO *bounce_info; int bounce_status = 1; int postmaster_status = 1; VSTREAM *bounce; int notify_mask = name_mask(VAR_NOTIFY_CLASSES, mail_error_masks, var_notify_classes); VSTRING *new_id = vstring_alloc(10); char *postmaster; int count; /* * Initialize. Open queue file, bounce log, etc. * * XXX DSN The bounce service produces RFC 3464-style "failed mail" reports * from information in two following types of logfile: * * 1 - bounce: this file is used for RFC 3464-style reports of permanent * delivery errors by the bounce(8) service. This reports to the sender * all recipients that have no DSN NOTIFY information (compatibility) and * all recipients that have DSN NOTIFY=FAILURE; this reports to * postmaster all recipients, if postmaster notification is enabled. * * 2 - defer: this file is used for three types of report: * * 2a) RFC 3464-style "mail is too old" reports by the bounce(8) service. * This reports to the sender all recipients that have no DSN NOTIFY * information (compatibility) and all recipients that have DSN * NOTIFY=FAILURE; this reports to postmaster all recipients, if * postmaster notification is enabled. * * Other reports that other servers produce from the defer logfile: * * 2b) On-demand reports of all delayed deliveries by the showq(8) service * and mailq(1) command. This reports all recipients that have a * transient delivery error. * * 2c) RFC 3464-style "delayed mail" notifications by the defer(8) service. * This reports to the sender all recipients that have no DSN NOTIFY * information (compatibility) and all recipients that have DSN * NOTIFY=DELAY; this reports to postmaster all recipients, if postmaster * notification is enabled. */ bounce_info = bounce_mail_init(service, queue_name, queue_id, encoding, dsn_envid, ts->failure); #define NULL_SENDER MAIL_ADDR_EMPTY /* special address */ #define NULL_TRACE_FLAGS 0 /* * The choice of sender address depends on the recipient address. For a * single bounce (a non-delivery notification to the message originator), * the sender address is the empty string. For a double bounce (typically * a failed single bounce, or a postmaster notification that was produced * by any of the mail processes) the sender address is defined by the * var_double_bounce_sender configuration variable. When a double bounce * cannot be delivered, the queue manager blackholes the resulting triple * bounce message. */ /* * Double bounce failed. Never send a triple bounce. * * However, this does not prevent double bounces from bouncing on other * systems. In order to cope with this, either the queue manager must * recognize the double-bounce recipient address and discard mail, or * every delivery agent must recognize the double-bounce sender address * and substitute something else so mail does not come back at us. */ if (strcasecmp(recipient, mail_addr_double_bounce()) == 0) { msg_warn("%s: undeliverable postmaster notification discarded", queue_id); bounce_status = 0; } /* * Single bounce failed. Optionally send a double bounce to postmaster, * subject to notify_classes restrictions. */ #define ANY_BOUNCE (MAIL_ERROR_2BOUNCE | MAIL_ERROR_BOUNCE) #define SEND_POSTMASTER_ANY_BOUNCE_NOTICE (notify_mask & ANY_BOUNCE) else if (*recipient == 0) { if (!SEND_POSTMASTER_ANY_BOUNCE_NOTICE) { bounce_status = 0; } else { postmaster = var_2bounce_rcpt; if ((bounce = post_mail_fopen_nowait(mail_addr_double_bounce(), postmaster, INT_FILT_MASK_BOUNCE, NULL_TRACE_FLAGS, new_id)) != 0) { /* * Double bounce to Postmaster. This is the last opportunity * for this message to be delivered. Send the text with * reason for the bounce, and the headers of the original * message. Don't bother sending the boiler-plate text. */ count = -1; if (bounce_header(bounce, bounce_info, postmaster, POSTMASTER_COPY) == 0 && (count = bounce_diagnostic_log(bounce, bounce_info, DSN_NOTIFY_OVERRIDE)) > 0 && bounce_header_dsn(bounce, bounce_info) == 0 && bounce_diagnostic_dsn(bounce, bounce_info, DSN_NOTIFY_OVERRIDE) > 0) { bounce_original(bounce, bounce_info, DSN_RET_FULL); bounce_status = post_mail_fclose(bounce); if (bounce_status == 0) msg_info("%s: postmaster non-delivery notification: %s", queue_id, STR(new_id)); } else { /* No applicable recipients found - cancel this notice. */ (void) vstream_fclose(bounce); if (count == 0) bounce_status = 0; } } } } /* * Non-bounce failed. Send a single bounce to the sender, subject to DSN * NOTIFY restrictions. */ else { if ((bounce = post_mail_fopen_nowait(NULL_SENDER, recipient, INT_FILT_MASK_BOUNCE, NULL_TRACE_FLAGS, new_id)) != 0) { /* * Send the bounce message header, some boilerplate text that * pretends that we are a polite mail system, the text with * reason for the bounce, and a copy of the original message. */ count = -1; if (bounce_header(bounce, bounce_info, recipient, NO_POSTMASTER_COPY) == 0 && bounce_boilerplate(bounce, bounce_info) == 0 && (count = bounce_diagnostic_log(bounce, bounce_info, DSN_NOTIFY_FAILURE)) > 0 && bounce_header_dsn(bounce, bounce_info) == 0 && bounce_diagnostic_dsn(bounce, bounce_info, DSN_NOTIFY_FAILURE) > 0) { bounce_original(bounce, bounce_info, dsn_ret ? dsn_ret : DSN_RET_FULL); bounce_status = post_mail_fclose(bounce); if (bounce_status == 0) msg_info("%s: sender non-delivery notification: %s", queue_id, STR(new_id)); } else { /* No applicable recipients found - cancel this notice. */ (void) vstream_fclose(bounce); if (count == 0) bounce_status = 0; } } /* * Optionally, send a postmaster notice, subject to notify_classes * restrictions. * * This postmaster notice is not critical, so if it fails don't * retransmit the bounce that we just generated, just log a warning. */ #define SEND_POSTMASTER_SINGLE_BOUNCE_NOTICE (notify_mask & MAIL_ERROR_BOUNCE) if (bounce_status == 0 && SEND_POSTMASTER_SINGLE_BOUNCE_NOTICE && strcasecmp(recipient, mail_addr_double_bounce()) != 0) { /* * Send the text with reason for the bounce, and the headers of * the original message. Don't bother sending the boiler-plate * text. This postmaster notice is not critical, so if it fails * don't retransmit the bounce that we just generated, just log a * warning. */ postmaster = var_bounce_rcpt; if ((bounce = post_mail_fopen_nowait(mail_addr_double_bounce(), postmaster, INT_FILT_MASK_BOUNCE, NULL_TRACE_FLAGS, new_id)) != 0) { count = -1; if (bounce_header(bounce, bounce_info, postmaster, POSTMASTER_COPY) == 0 && (count = bounce_diagnostic_log(bounce, bounce_info, DSN_NOTIFY_OVERRIDE)) > 0 && bounce_header_dsn(bounce, bounce_info) == 0 && bounce_diagnostic_dsn(bounce, bounce_info, DSN_NOTIFY_OVERRIDE) > 0) { bounce_original(bounce, bounce_info, DSN_RET_HDRS); postmaster_status = post_mail_fclose(bounce); if (postmaster_status == 0) msg_info("%s: postmaster non-delivery notification: %s", queue_id, STR(new_id)); } else { /* No applicable recipients found - cancel this notice. */ (void) vstream_fclose(bounce); if (count == 0) postmaster_status = 0; } } if (postmaster_status) msg_warn("%s: postmaster notice failed while bouncing to %s", queue_id, recipient); } } /* * Optionally, delete the recipients from the queue file. */ if (bounce_status == 0 && (flags & BOUNCE_FLAG_DELRCPT)) bounce_delrcpt(bounce_info); /* * Examine the completion status. Delete the bounce log file only when * the bounce was posted successfully, and only if we are bouncing for * real, not just warning. */ if (bounce_status == 0 && mail_queue_remove(service, queue_id) && errno != ENOENT) msg_fatal("remove %s %s: %m", service, queue_id); /* * Cleanup. */ bounce_mail_free(bounce_info); vstring_free(new_id); return (bounce_status); }
static int flush_send_path(const char *path, int how) { const char *myname = "flush_send_path"; VSTRING *queue_id; VSTRING *queue_file; VSTREAM *log; struct utimbuf tbuf; static char qmgr_flush_trigger[] = { QMGR_REQ_FLUSH_DEAD, /* flush dead site/transport cache */ }; static char qmgr_scan_trigger[] = { QMGR_REQ_SCAN_INCOMING, /* scan incoming queue */ }; HTABLE *dup_filter; int count; /* * Sanity check. */ if (!mail_queue_id_ok(path)) return (FLUSH_STAT_BAD); /* * Open the logfile. If the file does not exist, then there is no queued * mail for this destination. */ if ((log = mail_queue_open(MAIL_QUEUE_FLUSH, path, O_RDWR, 0600)) == 0) { if (errno != ENOENT) msg_fatal("%s: open fast flush logfile %s: %m", myname, path); return (FLUSH_STAT_OK); } /* * We must lock the logfile, so that we don't lose information when it is * truncated. Unfortunately, this means that the file can be locked for a * significant amount of time. If things really get stuck the Postfix * watchdog will take care of it. */ if (myflock(vstream_fileno(log), INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) msg_fatal("%s: lock fast flush logfile %s: %m", myname, path); /* * With the UNTHROTTLE_BEFORE strategy, we ask the queue manager to * unthrottle all transports and queues before we move a deferred queue * file to the incoming queue. This minimizes a race condition where the * queue manager seizes a queue file before it knows that we want to * flush that message. * * This reduces the race condition time window to a very small amount (the * flush server does not really know when the queue manager reads its * command fifo). But there is a worse race, where the queue manager * moves a deferred queue file to the active queue before we have a * chance to expedite its delivery. */ if (how & UNTHROTTLE_BEFORE) mail_trigger(MAIL_CLASS_PUBLIC, var_queue_service, qmgr_flush_trigger, sizeof(qmgr_flush_trigger)); /* * This is the part that dominates running time: schedule the listed * queue files for delivery by updating their file time stamps and by * moving them from the deferred queue to the incoming queue. This should * take no more than a couple seconds under normal conditions. Filter out * duplicate queue file names to avoid hammering the file system, with * some finite limit on the amount of memory that we are willing to * sacrifice for duplicate filtering. Graceful degradation. * * By moving selected queue files from the deferred queue to the incoming * queue we optimize for the case where most deferred mail is for other * sites. If that assumption does not hold, i.e. all deferred mail is for * the same site, then doing a "fast flush" will cost more disk I/O than * a "slow flush" that delivers the entire deferred queue. This penalty * is only temporary - it will go away after we unite the active queue * and the incoming queue. */ queue_id = vstring_alloc(10); queue_file = vstring_alloc(10); dup_filter = htable_create(10); tbuf.actime = tbuf.modtime = event_time(); for (count = 0; vstring_get_nonl(queue_id, log) != VSTREAM_EOF; count++) { if (!mail_queue_id_ok(STR(queue_id))) { msg_warn("bad queue id \"%.30s...\" in fast flush logfile %s", STR(queue_id), path); continue; } if (dup_filter->used >= FLUSH_DUP_FILTER_SIZE || htable_find(dup_filter, STR(queue_id)) == 0) { if (msg_verbose) msg_info("%s: logfile %s: update queue file %s time stamps", myname, path, STR(queue_id)); if (dup_filter->used <= FLUSH_DUP_FILTER_SIZE) htable_enter(dup_filter, STR(queue_id), 0); count += flush_one_file(STR(queue_id), queue_file, &tbuf, how); } else { if (msg_verbose) msg_info("%s: logfile %s: skip queue file %s as duplicate", myname, path, STR(queue_file)); } } htable_free(dup_filter, (void (*) (void *)) 0); vstring_free(queue_file); vstring_free(queue_id); /* * Truncate the fast flush log. */ if (count > 0 && ftruncate(vstream_fileno(log), (off_t) 0) < 0) msg_fatal("%s: truncate fast flush logfile %s: %m", myname, path); /* * Workaround for noatime mounts. Use futimes() if available. */ (void) utimes(VSTREAM_PATH(log), (struct timeval *) 0); /* * Request delivery and clean up. */ if (myflock(vstream_fileno(log), INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) msg_fatal("%s: unlock fast flush logfile %s: %m", myname, path); if (vstream_fclose(log) != 0) msg_warn("%s: read fast flush logfile %s: %m", myname, path); if (count > 0) { if (msg_verbose) msg_info("%s: requesting delivery for logfile %s", myname, path); mail_trigger(MAIL_CLASS_PUBLIC, var_queue_service, qmgr_scan_trigger, sizeof(qmgr_scan_trigger)); } return (FLUSH_STAT_OK); }
MILTERS *milter_receive(VSTREAM *stream, int count) { MILTERS *milters; MILTER *head = 0; MILTER *tail = 0; MILTER *milter = 0; if (msg_verbose) msg_info("receive %d milters", count); /* * XXX We must instantiate a MILTERS structure even when the sender has * no active filters, otherwise the cleanup server would try to use its * own non_smtpd_milters settings. */ #define NO_MILTERS ((char *) 0) #define NO_TIMEOUTS 0, 0, 0 #define NO_PROTOCOL ((char *) 0) #define NO_ACTION ((char *) 0) #define NO_MACROS ((MILTER_MACROS *) 0) milters = milter_new(NO_MILTERS, NO_TIMEOUTS, NO_PROTOCOL, NO_ACTION, NO_MACROS); /* * XXX Optimization: don't send or receive further information when there * aren't any active filters. */ if (count <= 0) return (milters); /* * Receive the global macro name lists. */ milters->macros = milter_macros_alloc(MILTER_MACROS_ALLOC_ZERO); if (attr_scan(stream, ATTR_FLAG_STRICT | ATTR_FLAG_MORE, ATTR_TYPE_FUNC, milter_macros_scan, (void *) milters->macros, ATTR_TYPE_END) != 1) { milter_free(milters); return (0); } /* * Receive the filters. */ for (; count > 0; count--) { if ((milter = milter8_receive(stream, milters)) == 0) { msg_warn("cannot receive milters via service %s socket", VSTREAM_PATH(stream)); milter_free(milters); return (0); } if (head == 0) { /* Coverity: milter_free() depends on milters->milter_list. */ milters->milter_list = head = milter; } else { tail->next = milter; } tail = milter; } /* * Over to you. */ (void) attr_print(stream, ATTR_FLAG_NONE, ATTR_TYPE_INT, MAIL_ATTR_STATUS, 0, ATTR_TYPE_END); return (milters); }
static int deliver_message(DELIVER_REQUEST *request, char *service, char **argv) { const char *myname = "deliver_message"; static PIPE_PARAMS conf; static PIPE_ATTR attr; RECIPIENT_LIST *rcpt_list = &request->rcpt_list; DSN_BUF *why = dsb_create(); VSTRING *buf; ARGV *expanded_argv = 0; int deliver_status; int command_status; ARGV *export_env; const char *sender; #define DELIVER_MSG_CLEANUP() { \ dsb_free(why); \ if (expanded_argv) argv_free(expanded_argv); \ } if (msg_verbose) msg_info("%s: from <%s>", myname, request->sender); /* * Sanity checks. The get_service_params() and get_service_attr() * routines also do some sanity checks. Look up service attributes and * config information only once. This is safe since the information comes * from a trusted source, not from the delivery request. */ if (request->nexthop[0] == 0) msg_fatal("empty nexthop hostname"); if (rcpt_list->len <= 0) msg_fatal("recipient count: %d", rcpt_list->len); if (attr.command == 0) { get_service_params(&conf, service); get_service_attr(&attr, argv); } /* * The D flag cannot be specified for multi-recipient deliveries. */ if ((attr.flags & MAIL_COPY_DELIVERED) && (rcpt_list->len > 1)) { dsb_simple(why, "4.3.5", "mail system configuration error"); deliver_status = eval_command_status(PIPE_STAT_DEFER, service, request, &attr, why); msg_warn("pipe flag `D' requires %s_destination_recipient_limit = 1", service); DELIVER_MSG_CLEANUP(); return (deliver_status); } /* * The O flag cannot be specified for multi-recipient deliveries. */ if ((attr.flags & MAIL_COPY_ORIG_RCPT) && (rcpt_list->len > 1)) { dsb_simple(why, "4.3.5", "mail system configuration error"); deliver_status = eval_command_status(PIPE_STAT_DEFER, service, request, &attr, why); msg_warn("pipe flag `O' requires %s_destination_recipient_limit = 1", service); DELIVER_MSG_CLEANUP(); return (deliver_status); } /* * Check that this agent accepts messages this large. */ if (attr.size_limit != 0 && request->data_size > attr.size_limit) { if (msg_verbose) msg_info("%s: too big: size_limit = %ld, request->data_size = %ld", myname, (long) attr.size_limit, request->data_size); dsb_simple(why, "5.2.3", "message too large"); deliver_status = eval_command_status(PIPE_STAT_BOUNCE, service, request, &attr, why); DELIVER_MSG_CLEANUP(); return (deliver_status); } /* * Don't deliver a trace-only request. */ if (DEL_REQ_TRACE_ONLY(request->flags)) { RECIPIENT *rcpt; int status; int n; deliver_status = 0; dsb_simple(why, "2.0.0", "delivers to command: %s", attr.command[0]); (void) DSN_FROM_DSN_BUF(why); for (n = 0; n < request->rcpt_list.len; n++) { rcpt = request->rcpt_list.info + n; status = sent(DEL_REQ_TRACE_FLAGS(request->flags), request->queue_id, &request->msg_stats, rcpt, service, &why->dsn); if (status == 0 && (request->flags & DEL_REQ_FLAG_SUCCESS)) deliver_completed(request->fp, rcpt->offset); deliver_status |= status; } DELIVER_MSG_CLEANUP(); return (deliver_status); } /* * Report mail delivery loops. By definition, this requires * single-recipient delivery. Don't silently lose recipients. */ if (attr.flags & MAIL_COPY_DELIVERED) { DELIVERED_HDR_INFO *info; RECIPIENT *rcpt; int loop_found; if (request->rcpt_list.len > 1) msg_panic("%s: delivered-to enabled with multi-recipient request", myname); info = delivered_hdr_init(request->fp, request->data_offset, FOLD_ADDR_ALL); rcpt = request->rcpt_list.info; loop_found = delivered_hdr_find(info, rcpt->address); delivered_hdr_free(info); if (loop_found) { dsb_simple(why, "5.4.6", "mail forwarding loop for %s", rcpt->address); deliver_status = eval_command_status(PIPE_STAT_BOUNCE, service, request, &attr, why); DELIVER_MSG_CLEANUP(); return (deliver_status); } } /* * Deliver. Set the nexthop and sender variables, and expand the command * argument vector. Recipients will be expanded on the fly. XXX Rewrite * envelope and header addresses according to transport-specific * rewriting rules. */ if (vstream_fseek(request->fp, request->data_offset, SEEK_SET) < 0) msg_fatal("seek queue file %s: %m", VSTREAM_PATH(request->fp)); /* * A non-empty null sender replacement is subject to the 'q' flag. */ buf = vstring_alloc(10); sender = *request->sender ? request->sender : STR(attr.null_sender); if (*sender && (attr.flags & PIPE_OPT_QUOTE_LOCAL)) { quote_822_local(buf, sender); dict_update(PIPE_DICT_TABLE, PIPE_DICT_SENDER, STR(buf)); } else dict_update(PIPE_DICT_TABLE, PIPE_DICT_SENDER, sender); if (attr.flags & PIPE_OPT_FOLD_HOST) { vstring_strcpy(buf, request->nexthop); lowercase(STR(buf)); dict_update(PIPE_DICT_TABLE, PIPE_DICT_NEXTHOP, STR(buf)); } else dict_update(PIPE_DICT_TABLE, PIPE_DICT_NEXTHOP, request->nexthop); vstring_sprintf(buf, "%ld", (long) request->data_size); dict_update(PIPE_DICT_TABLE, PIPE_DICT_SIZE, STR(buf)); dict_update(PIPE_DICT_TABLE, PIPE_DICT_CLIENT_ADDR, request->client_addr); dict_update(PIPE_DICT_TABLE, PIPE_DICT_CLIENT_HELO, request->client_helo); dict_update(PIPE_DICT_TABLE, PIPE_DICT_CLIENT_NAME, request->client_name); dict_update(PIPE_DICT_TABLE, PIPE_DICT_CLIENT_PORT, request->client_port); dict_update(PIPE_DICT_TABLE, PIPE_DICT_CLIENT_PROTO, request->client_proto); dict_update(PIPE_DICT_TABLE, PIPE_DICT_SASL_METHOD, request->sasl_method); dict_update(PIPE_DICT_TABLE, PIPE_DICT_SASL_USERNAME, request->sasl_username); dict_update(PIPE_DICT_TABLE, PIPE_DICT_SASL_SENDER, request->sasl_sender); dict_update(PIPE_DICT_TABLE, PIPE_DICT_QUEUE_ID, request->queue_id); vstring_free(buf); if ((expanded_argv = expand_argv(service, attr.command, rcpt_list, attr.flags)) == 0) { dsb_simple(why, "4.3.5", "mail system configuration error"); deliver_status = eval_command_status(PIPE_STAT_DEFER, service, request, &attr, why); DELIVER_MSG_CLEANUP(); return (deliver_status); } export_env = argv_split(var_export_environ, ", \t\r\n"); command_status = pipe_command(request->fp, why, PIPE_CMD_UID, attr.uid, PIPE_CMD_GID, attr.gid, PIPE_CMD_SENDER, sender, PIPE_CMD_COPY_FLAGS, attr.flags, PIPE_CMD_ARGV, expanded_argv->argv, PIPE_CMD_TIME_LIMIT, conf.time_limit, PIPE_CMD_EOL, STR(attr.eol), PIPE_CMD_EXPORT, export_env->argv, PIPE_CMD_CWD, attr.exec_dir, PIPE_CMD_CHROOT, attr.chroot_dir, PIPE_CMD_ORIG_RCPT, rcpt_list->info[0].orig_addr, PIPE_CMD_DELIVERED, rcpt_list->info[0].address, PIPE_CMD_END); argv_free(export_env); deliver_status = eval_command_status(command_status, service, request, &attr, why); /* * Clean up. */ DELIVER_MSG_CLEANUP(); return (deliver_status); }
void dict_test(int argc, char **argv) { VSTRING *keybuf = vstring_alloc(1); VSTRING *inbuf = vstring_alloc(1); DICT *dict; char *dict_name; int open_flags; char *bufp; char *cmd; const char *key; const char *value; int ch; int dict_flags = DICT_FLAG_LOCK | DICT_FLAG_DUP_REPLACE; int n; int rc; signal(SIGPIPE, SIG_IGN); msg_vstream_init(argv[0], VSTREAM_ERR); while ((ch = GETOPT(argc, argv, "v")) > 0) { switch (ch) { default: usage(argv[0]); case 'v': msg_verbose++; break; } } optind = OPTIND; if (argc - optind < 2) usage(argv[0]); if (strcasecmp(argv[optind + 1], "create") == 0) open_flags = O_CREAT | O_RDWR | O_TRUNC; else if (strcasecmp(argv[optind + 1], "write") == 0) open_flags = O_RDWR; else if (strcasecmp(argv[optind + 1], "read") == 0) open_flags = O_RDONLY; else msg_fatal("unknown access mode: %s", argv[2]); for (n = 2; argv[optind + n]; n++) { if (strcasecmp(argv[optind + 2], "fold") == 0) dict_flags |= DICT_FLAG_FOLD_ANY; else if (strcasecmp(argv[optind + 2], "sync") == 0) dict_flags |= DICT_FLAG_SYNC_UPDATE; else usage(argv[0]); } dict_name = argv[optind]; dict_allow_surrogate = 1; dict = dict_open(dict_name, open_flags, dict_flags); dict_register(dict_name, dict); while (vstring_fgets_nonl(inbuf, VSTREAM_IN)) { bufp = vstring_str(inbuf); if (!isatty(0)) { vstream_printf("> %s\n", bufp); vstream_fflush(VSTREAM_OUT); } if (*bufp == '#') continue; if ((cmd = mystrtok(&bufp, " ")) == 0) { vstream_printf("usage: verbose|del key|get key|put key=value|first|next|masks|flags\n"); vstream_fflush(VSTREAM_OUT); continue; } if (dict_changed_name()) msg_warn("dictionary has changed"); key = *bufp ? vstring_str(unescape(keybuf, mystrtok(&bufp, " ="))) : 0; value = mystrtok(&bufp, " ="); if (strcmp(cmd, "verbose") == 0 && !key) { msg_verbose++; } else if (strcmp(cmd, "del") == 0 && key && !value) { if ((rc = dict_del(dict, key)) > 0) vstream_printf("%s: not found\n", key); else if (rc < 0) vstream_printf("%s: error\n", key); else vstream_printf("%s: deleted\n", key); } else if (strcmp(cmd, "get") == 0 && key && !value) { if ((value = dict_get(dict, key)) == 0) { vstream_printf("%s: %s\n", key, dict->error ? "error" : "not found"); } else { vstream_printf("%s=%s\n", key, value); } } else if (strcmp(cmd, "put") == 0 && key && value) { if (dict_put(dict, key, value) != 0) vstream_printf("%s: %s\n", key, dict->error ? "error" : "not updated"); else vstream_printf("%s=%s\n", key, value); } else if (strcmp(cmd, "first") == 0 && !key && !value) { if (dict_seq(dict, DICT_SEQ_FUN_FIRST, &key, &value) == 0) vstream_printf("%s=%s\n", key, value); else vstream_printf("%s\n", dict->error ? "error" : "not found"); } else if (strcmp(cmd, "next") == 0 && !key && !value) { if (dict_seq(dict, DICT_SEQ_FUN_NEXT, &key, &value) == 0) vstream_printf("%s=%s\n", key, value); else vstream_printf("%s\n", dict->error ? "error" : "not found"); } else if (strcmp(cmd, "flags") == 0 && !key && !value) { vstream_printf("dict flags %s\n", dict_flags_str(dict->flags)); } else if (strcmp(cmd, "masks") == 0 && !key && !value) { vstream_printf("DICT_FLAG_IMPL_MASK %s\n", dict_flags_str(DICT_FLAG_IMPL_MASK)); vstream_printf("DICT_FLAG_PARANOID %s\n", dict_flags_str(DICT_FLAG_PARANOID)); vstream_printf("DICT_FLAG_RQST_MASK %s\n", dict_flags_str(DICT_FLAG_RQST_MASK)); vstream_printf("DICT_FLAG_INST_MASK %s\n", dict_flags_str(DICT_FLAG_INST_MASK)); } else { vstream_printf("usage: del key|get key|put key=value|first|next|masks|flags\n"); } vstream_fflush(VSTREAM_OUT); } vstring_free(keybuf); vstring_free(inbuf); dict_close(dict); }
int qmgr_active_feed(QMGR_SCAN *scan_info, const char *queue_id) { char *myname = "qmgr_active_feed"; QMGR_MESSAGE *message; struct stat st; const char *path; if (strcmp(scan_info->queue, MAIL_QUEUE_ACTIVE) == 0) msg_panic("%s: bad queue %s", myname, scan_info->queue); if (msg_verbose) msg_info("%s: queue %s", myname, scan_info->queue); /* * Make sure this is something we are willing to open. */ if (mail_open_ok(scan_info->queue, queue_id, &st, &path) == MAIL_OPEN_NO) return (0); if (msg_verbose) msg_info("%s: %s", myname, path); /* * Skip files that have time stamps into the future. They need to cool * down. Incoming and deferred files can have future time stamps. */ if ((scan_info->flags & QMGR_SCAN_ALL) == 0 && st.st_mtime > time((time_t *) 0) + 1) { if (msg_verbose) msg_info("%s: skip %s (%ld seconds)", myname, queue_id, (long) (st.st_mtime - event_time())); return (0); } /* * Move the message to the active queue. File access errors are fatal. */ if (mail_queue_rename(queue_id, scan_info->queue, MAIL_QUEUE_ACTIVE)) { if (errno != ENOENT) msg_fatal("%s: %s: rename from %s to %s: %m", myname, queue_id, scan_info->queue, MAIL_QUEUE_ACTIVE); msg_warn("%s: %s: rename from %s to %s: %m", myname, queue_id, scan_info->queue, MAIL_QUEUE_ACTIVE); return (0); } /* * Extract envelope information: sender and recipients. At this point, * mail addresses have been processed by the cleanup service so they * should be in canonical form. Generate requests to deliver this * message. * * Throwing away queue files seems bad, especially when they made it this * far into the mail system. Therefore we save bad files to a separate * directory for further inspection. * * After queue manager restart it is possible that a queue file is still * being delivered. In that case (the file is locked), defer delivery by * a minimal amount of time. */ if ((message = qmgr_message_alloc(MAIL_QUEUE_ACTIVE, queue_id, scan_info->flags)) == 0) { qmgr_active_corrupt(queue_id); return (0); } else if (message == QMGR_MESSAGE_LOCKED) { qmgr_active_defer(MAIL_QUEUE_ACTIVE, queue_id, MAIL_QUEUE_INCOMING, 60); return (0); } else { /* * Special case if all recipients were already delivered. Send any * bounces and clean up. */ if (message->refcount == 0) qmgr_active_done(message); return (1); } }
static void smtp_connect_local(SMTP_STATE *state, const char *path) { const char *myname = "smtp_connect_local"; SMTP_SESSION *session; DSN_BUF *why = state->why; /* * It's too painful to weave this code into the SMTP connection * management routine. * * Connection cache management is based on the UNIX-domain pathname, without * the "unix:" prefix. */ smtp_cache_policy(state, path); /* * XXX We assume that the session->addr member refers to a copy of the * UNIX-domain pathname, so that smtp_save_session() will cache the * connection using the pathname as the physical endpoint name. */ #define NO_PORT 0 /* * Opportunistic TLS for unix domain sockets does not make much sense, * since the channel is private, mere encryption without authentication * is just wasted cycles and opportunity for breakage. Since we are not * willing to retry after TLS handshake failures here, we downgrade "may" * no "none". Nothing is lost, and much waste is avoided. * * We don't know who is authenticating whom, so if a client cert is * available, "encrypt" may be a sensible policy. Otherwise, we also * downgrade "encrypt" to "none", this time just to avoid waste. */ if ((state->misc_flags & SMTP_MISC_FLAG_CONN_LOAD) == 0 || (session = smtp_reuse_addr(state, path, NO_PORT)) == 0) session = smtp_connect_unix(path, why, state->misc_flags); if ((state->session = session) != 0) { session->state = state; #ifdef USE_TLS session->tls_nexthop = var_myhostname; /* for TLS_LEV_SECURE */ if (session->tls_level == TLS_LEV_MAY) { msg_warn("%s: opportunistic TLS encryption is not appropriate " "for unix-domain destinations.", myname); session->tls_level = TLS_LEV_NONE; } #endif /* All delivery errors bounce or defer. */ state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER; /* * When a TLS handshake fails, the stream is marked "dead" to avoid * further I/O over a broken channel. */ if ((session->features & SMTP_FEATURE_FROM_CACHE) == 0 && smtp_helo(state) != 0) { if (!THIS_SESSION_IS_DEAD && vstream_ferror(session->stream) == 0 && vstream_feof(session->stream) == 0) smtp_quit(state); } else { smtp_xfer(state); } /* * With opportunistic TLS disabled we don't expect to be asked to * retry connections without TLS, and so we expect the final server * flag to stay on. */ if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_SERVER) == 0) msg_panic("%s: unix-domain destination not final!", myname); smtp_cleanup_session(state); } }
static void smtp_connect_inet(SMTP_STATE *state, const char *nexthop, char *def_service) { DELIVER_REQUEST *request = state->request; ARGV *sites; char *dest; char **cpp; int non_fallback_sites; int retry_plain = 0; DSN_BUF *why = state->why; /* * 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) { dsb_simple(why, "4.4.4", "all network protocols are disabled"); return; } /* * First try to deliver to the indicated destination, then try to deliver * to the optional fall-back relays. * * Future proofing: do a null destination sanity check in case we allow the * primary destination to be a list (it could be just separators). */ sites = argv_alloc(1); argv_add(sites, nexthop, (char *) 0); if (sites->argc == 0) msg_panic("null destination: \"%s\"", nexthop); non_fallback_sites = sites->argc; if ((state->misc_flags & SMTP_MISC_FLAG_USE_LMTP) == 0) argv_split_append(sites, var_fallback_relay, ", \t\r\n"); /* * Don't give up after a hard host lookup error until we have tried the * fallback relay servers. * * Don't bounce mail after a host lookup problem with a relayhost or with a * fallback relay. * * Don't give up after a qualifying soft error until we have tried all * qualifying backup mail servers. * * All this means that error handling and error reporting depends on whether * the error qualifies for trying to deliver to a backup mail server, or * whether we're looking up a relayhost or fallback relay. The challenge * then is to build this into the pre-existing SMTP client without * getting lost in the complexity. */ #define IS_FALLBACK_RELAY(cpp, sites, non_fallback_sites) \ (*(cpp) && (cpp) >= (sites)->argv + (non_fallback_sites)) for (cpp = sites->argv, (state->misc_flags |= SMTP_MISC_FLAG_FIRST_NEXTHOP); SMTP_RCPT_LEFT(state) > 0 && (dest = *cpp) != 0; cpp++, (state->misc_flags &= ~SMTP_MISC_FLAG_FIRST_NEXTHOP)) { char *dest_buf; char *domain; unsigned port; DNS_RR *addr_list; DNS_RR *addr; DNS_RR *next; int addr_count; int sess_count; SMTP_SESSION *session; int lookup_mx; unsigned domain_best_pref; MAI_HOSTADDR_STR hostaddr; if (cpp[1] == 0) state->misc_flags |= SMTP_MISC_FLAG_FINAL_NEXTHOP; /* * Parse the destination. Default is to use the SMTP port. Look up * the address instead of the mail exchanger when a quoted host is * specified, or when DNS lookups are disabled. */ dest_buf = smtp_parse_destination(dest, def_service, &domain, &port); if (var_helpful_warnings && ntohs(port) == 465) { msg_info("CLIENT wrappermode (port smtps/465) is unimplemented"); msg_info("instead, send to (port submission/587) with STARTTLS"); } /* * Resolve an SMTP server. Skip mail exchanger lookups when a quoted * host is specified, or when DNS lookups are disabled. */ if (msg_verbose) msg_info("connecting to %s port %d", domain, ntohs(port)); if ((state->misc_flags & SMTP_MISC_FLAG_USE_LMTP) == 0) { if (ntohs(port) == IPPORT_SMTP) state->misc_flags |= SMTP_MISC_FLAG_LOOP_DETECT; else state->misc_flags &= ~SMTP_MISC_FLAG_LOOP_DETECT; lookup_mx = (var_disable_dns == 0 && *dest != '['); } else lookup_mx = 0; if (!lookup_mx) { addr_list = smtp_host_addr(domain, state->misc_flags, why); /* XXX We could be an MX host for this destination... */ } else { int i_am_mx = 0; addr_list = smtp_domain_addr(domain, state->misc_flags, why, &i_am_mx); /* If we're MX host, don't connect to non-MX backups. */ if (i_am_mx) state->misc_flags |= SMTP_MISC_FLAG_FINAL_NEXTHOP; } /* * Don't try fall-back hosts if mail loops to myself. That would just * make the problem worse. */ if (addr_list == 0 && SMTP_HAS_LOOP_DSN(why)) state->misc_flags |= SMTP_MISC_FLAG_FINAL_NEXTHOP; /* * No early loop exit or we have a memory leak with dest_buf. */ if (addr_list) domain_best_pref = addr_list->pref; /* * When session caching is enabled, store the first good session for * this delivery request under the next-hop destination name. All * good sessions will be stored under their specific server IP * address. * * XXX Replace sites->argv by (lookup_mx, domain, port) triples so we * don't have to make clumsy ad-hoc copies and keep track of who * free()s the memory. * * XXX smtp_session_cache_destinations specifies domain names without * :port, because : is already used for maptype:mapname. Because of * this limitation we use the bare domain without the optional [] or * non-default TCP port. * * Opportunistic (a.k.a. on-demand) session caching on request by the * queue manager. This is turned temporarily when a destination has a * high volume of mail in the active queue. * * XXX Disable connection caching when sender-dependent authentication * is enabled. We must not send someone elses mail over an * authenticated connection, and we must not send mail that requires * authentication over a connection that wasn't authenticated. */ if (addr_list && (state->misc_flags & SMTP_MISC_FLAG_FIRST_NEXTHOP)) { smtp_cache_policy(state, domain); if (state->misc_flags & SMTP_MISC_FLAG_CONN_STORE) SET_NEXTHOP_STATE(state, lookup_mx, domain, port); } /* * Delete visited cached hosts from the address list. * * Optionally search the connection cache by domain name or by primary * MX address before we try to create new connections. * * Enforce the MX session and MX address counts per next-hop or * fall-back destination. smtp_reuse_session() will truncate the * address list when either limit is reached. */ if (addr_list && (state->misc_flags & SMTP_MISC_FLAG_CONN_LOAD)) { if (state->cache_used->used > 0) smtp_scrub_addr_list(state->cache_used, &addr_list); sess_count = addr_count = smtp_reuse_session(state, lookup_mx, domain, port, &addr_list, domain_best_pref); } else sess_count = addr_count = 0; /* * Connect to an SMTP server: create primary MX connections, and * reuse or create backup MX connections. * * At the start of an SMTP session, all recipients are unmarked. In the * course of an SMTP session, recipients are marked as KEEP (deliver * to alternate mail server) or DROP (remove from recipient list). At * the end of an SMTP session, weed out the recipient list. Unmark * any left-over recipients and try to deliver them to a backup mail * server. * * Cache the first good session under the next-hop destination name. * Cache all good sessions under their physical endpoint. * * Don't query the session cache for primary MX hosts. We already did * that in smtp_reuse_session(), and if any were found in the cache, * they were already deleted from the address list. */ for (addr = addr_list; SMTP_RCPT_LEFT(state) > 0 && addr; addr = next) { next = addr->next; if (++addr_count == var_smtp_mxaddr_limit) next = 0; if ((state->misc_flags & SMTP_MISC_FLAG_CONN_LOAD) == 0 || addr->pref == domain_best_pref || dns_rr_to_pa(addr, &hostaddr) == 0 || !(session = smtp_reuse_addr(state, hostaddr.buf, port))) session = smtp_connect_addr(dest, addr, port, why, state->misc_flags); if ((state->session = session) != 0) { session->state = state; if (addr->pref == domain_best_pref) session->features |= SMTP_FEATURE_BEST_MX; /* Don't count handshake errors towards the session limit. */ if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP) && next == 0) state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER; #ifdef USE_TLS /* Disable TLS when retrying after a handshake failure */ if (retry_plain) { if (session->tls_level >= TLS_LEV_ENCRYPT) msg_panic("Plain-text retry wrong for mandatory TLS"); session->tls_level = TLS_LEV_NONE; retry_plain = 0; } session->tls_nexthop = domain; /* for TLS_LEV_SECURE */ #endif if ((session->features & SMTP_FEATURE_FROM_CACHE) == 0 && smtp_helo(state) != 0) { #ifdef USE_TLS /* * When an opportunistic TLS handshake fails, try the * same address again, with TLS disabled. See also the * RETRY_AS_PLAINTEXT macro. */ if ((retry_plain = session->tls_retry_plain) != 0) { --addr_count; next = addr; } #endif /* * When a TLS handshake fails, the stream is marked * "dead" to avoid further I/O over a broken channel. */ if (!THIS_SESSION_IS_DEAD && vstream_ferror(session->stream) == 0 && vstream_feof(session->stream) == 0) smtp_quit(state); } else { /* Do count delivery errors towards the session limit. */ if (++sess_count == var_smtp_mxsess_limit) next = 0; if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP) && next == 0) state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER; smtp_xfer(state); } smtp_cleanup_session(state); } else { /* The reason already includes the IP address and TCP port. */ msg_info("%s", STR(why->reason)); } /* Insert: test if we must skip the remaining MX hosts. */ } dns_rr_free(addr_list); myfree(dest_buf); if (state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP) break; } /* * We still need to deliver, bounce or defer some left-over recipients: * either mail loops or some backup mail server was unavailable. */ if (SMTP_RCPT_LEFT(state) > 0) { /* * In case of a "no error" indication we make up an excuse: we did * find the host address, but we did not attempt to connect to it. * This can happen when the fall-back relay was already tried via a * cached connection, so that the address list scrubber left behind * an empty list. */ if (!SMTP_HAS_DSN(why)) { dsb_simple(why, "4.3.0", "server unavailable or unable to receive mail"); } /* * Pay attention to what could be configuration problems, and pretend * that these are recoverable rather than bouncing the mail. */ else if (!SMTP_HAS_SOFT_DSN(why) && (state->misc_flags & SMTP_MISC_FLAG_USE_LMTP) == 0) { /* * The fall-back destination did not resolve as expected, or it * is refusing to talk to us, or mail for it loops back to us. */ if (IS_FALLBACK_RELAY(cpp, sites, non_fallback_sites)) { msg_warn("%s configuration problem", VAR_SMTP_FALLBACK); vstring_strcpy(why->status, "4.3.5"); /* XXX Keep the diagnostic code and MTA. */ } /* * The next-hop relayhost did not resolve as expected, or it is * refusing to talk to us, or mail for it loops back to us. */ else if (strcmp(sites->argv[0], var_relayhost) == 0) { msg_warn("%s configuration problem", VAR_RELAYHOST); vstring_strcpy(why->status, "4.3.5"); /* XXX Keep the diagnostic code and MTA. */ } /* * Mail for the next-hop destination loops back to myself. Pass * the mail to the best_mx_transport or bounce it. */ else if (SMTP_HAS_LOOP_DSN(why) && *var_bestmx_transp) { dsb_reset(why); /* XXX */ state->status = deliver_pass_all(MAIL_CLASS_PRIVATE, var_bestmx_transp, request); SMTP_RCPT_LEFT(state) = 0; /* XXX */ } } } /* * Cleanup. */ if (HAVE_NEXTHOP_STATE(state)) FREE_NEXTHOP_STATE(state); argv_free(sites); }
static void qmgr_message_resolve(QMGR_MESSAGE *message) { static ARGV *defer_xport_argv; RECIPIENT_LIST list = message->rcpt_list; RECIPIENT *recipient; QMGR_TRANSPORT *transport = 0; QMGR_QUEUE *queue = 0; RESOLVE_REPLY reply; VSTRING *queue_name; char *at; char **cpp; char *nexthop; ssize_t len; int status; DSN dsn; MSG_STATS stats; DSN *saved_dsn; #define STREQ(x,y) (strcmp(x,y) == 0) #define STR vstring_str #define LEN VSTRING_LEN resolve_clnt_init(&reply); queue_name = vstring_alloc(1); for (recipient = list.info; recipient < list.info + list.len; recipient++) { /* * Redirect overrides all else. But only once (per entire message). * For consistency with the remainder of Postfix, rewrite the address * to canonical form before resolving it. */ if (message->redirect_addr) { if (recipient > list.info) { recipient->u.queue = 0; continue; } message->rcpt_offset = 0; rewrite_clnt_internal(REWRITE_CANON, message->redirect_addr, reply.recipient); RECIPIENT_UPDATE(recipient->address, STR(reply.recipient)); if (qmgr_resolve_one(message, recipient, recipient->address, &reply) < 0) continue; if (!STREQ(recipient->address, STR(reply.recipient))) RECIPIENT_UPDATE(recipient->address, STR(reply.recipient)); } /* * Content filtering overrides the address resolver. * * XXX Bypass content_filter inspection for user-generated probes * (sendmail -bv). MTA-generated probes never have the "please filter * me" bits turned on, but we handle them here anyway for the sake of * future proofing. */ #define FILTER_WITHOUT_NEXTHOP(filter, next) \ (((next) = split_at((filter), ':')) == 0 || *(next) == 0) #define RCPT_WITHOUT_DOMAIN(rcpt, next) \ ((next = strrchr(rcpt, '@')) == 0 || *++(next) == 0) else if (message->filter_xport && (message->tflags & DEL_REQ_TRACE_ONLY_MASK) == 0) { reply.flags = 0; vstring_strcpy(reply.transport, message->filter_xport); if (FILTER_WITHOUT_NEXTHOP(STR(reply.transport), nexthop) && *(nexthop = var_def_filter_nexthop) == 0 && RCPT_WITHOUT_DOMAIN(recipient->address, nexthop)) nexthop = var_myhostname; vstring_strcpy(reply.nexthop, nexthop); vstring_strcpy(reply.recipient, recipient->address); } /* * Resolve the destination to (transport, nexthop, address). The * result address may differ from the one specified by the sender. */ else { if (qmgr_resolve_one(message, recipient, recipient->address, &reply) < 0) continue; if (!STREQ(recipient->address, STR(reply.recipient))) RECIPIENT_UPDATE(recipient->address, STR(reply.recipient)); } /* * Bounce null recipients. This should never happen, but is most * likely the result of a fault in a different program, so aborting * the queue manager process does not help. */ if (recipient->address[0] == 0) { QMGR_REDIRECT(&reply, MAIL_SERVICE_ERROR, "5.1.3 null recipient address"); } /* * Discard mail to the local double bounce address here, so this * system can run without a local delivery agent. They'd still have * to configure something for mail directed to the local postmaster, * though, but that is an RFC requirement anyway. * * XXX This lookup should be done in the resolver, and the mail should * be directed to a general-purpose null delivery agent. */ if (reply.flags & RESOLVE_CLASS_LOCAL) { at = strrchr(STR(reply.recipient), '@'); len = (at ? (at - STR(reply.recipient)) : strlen(STR(reply.recipient))); if (strncasecmp(STR(reply.recipient), var_double_bounce_sender, len) == 0 && !var_double_bounce_sender[len]) { status = sent(message->tflags, message->queue_id, QMGR_MSG_STATS(&stats, message), recipient, "none", DSN_SIMPLE(&dsn, "2.0.0", "undeliverable postmaster notification discarded")); if (status == 0) { deliver_completed(message->fp, recipient->offset); #if 0 /* It's the default verification probe sender address. */ msg_warn("%s: undeliverable postmaster notification discarded", message->queue_id); #endif } else message->flags |= status; continue; } } /* * Optionally defer deliveries over specific transports, unless the * restriction is lifted temporarily. */ if (*var_defer_xports && (message->qflags & QMGR_FLUSH_DFXP) == 0) { if (defer_xport_argv == 0) defer_xport_argv = argv_split(var_defer_xports, " \t\r\n,"); for (cpp = defer_xport_argv->argv; *cpp; cpp++) if (strcmp(*cpp, STR(reply.transport)) == 0) break; if (*cpp) { QMGR_REDIRECT(&reply, MAIL_SERVICE_RETRY, "4.3.2 deferred transport"); } } /* * Look up or instantiate the proper transport. */ if (transport == 0 || !STREQ(transport->name, STR(reply.transport))) { if ((transport = qmgr_transport_find(STR(reply.transport))) == 0) transport = qmgr_transport_create(STR(reply.transport)); queue = 0; } /* * This message is being flushed. If need-be unthrottle the * transport. */ if ((message->qflags & QMGR_FLUSH_EACH) != 0 && QMGR_TRANSPORT_THROTTLED(transport)) qmgr_transport_unthrottle(transport); /* * This transport is dead. Defer delivery to this recipient. */ if (QMGR_TRANSPORT_THROTTLED(transport)) { saved_dsn = transport->dsn; if ((transport = qmgr_error_transport(MAIL_SERVICE_RETRY)) != 0) { nexthop = qmgr_error_nexthop(saved_dsn); vstring_strcpy(reply.nexthop, nexthop); myfree(nexthop); queue = 0; } else { qmgr_defer_recipient(message, recipient, saved_dsn); continue; } } /* * The nexthop destination provides the default name for the * per-destination queue. When the delivery agent accepts only one * recipient per delivery, give each recipient its own queue, so that * deliveries to different recipients of the same message can happen * in parallel, and so that we can enforce per-recipient concurrency * limits and prevent one recipient from tying up all the delivery * agent resources. We use recipient@nexthop as queue name rather * than the actual recipient domain name, so that one recipient in * multiple equivalent domains cannot evade the per-recipient * concurrency limit. Split the address on the recipient delimiter if * one is defined, so that extended addresses don't get extra * delivery slots. * * Fold the result to lower case so that we don't have multiple queues * for the same name. * * Important! All recipients in a queue must have the same nexthop * value. It is OK to have multiple queues with the same nexthop * value, but only when those queues are named after recipients. * * The single-recipient code below was written for local(8) like * delivery agents, and assumes that all domains that deliver to the * same (transport + nexthop) are aliases for $nexthop. Delivery * concurrency is changed from per-domain into per-recipient, by * changing the queue name from nexthop into localpart@nexthop. * * XXX This assumption is incorrect when different destinations share * the same (transport + nexthop). In reality, such transports are * rarely configured to use single-recipient deliveries. The fix is * to decouple the per-destination recipient limit from the * per-destination concurrency. */ vstring_strcpy(queue_name, STR(reply.nexthop)); if (strcmp(transport->name, MAIL_SERVICE_ERROR) != 0 && strcmp(transport->name, MAIL_SERVICE_RETRY) != 0 && transport->recipient_limit == 1) { /* Copy the recipient localpart. */ at = strrchr(STR(reply.recipient), '@'); len = (at ? (at - STR(reply.recipient)) : strlen(STR(reply.recipient))); vstring_strncpy(queue_name, STR(reply.recipient), len); /* Remove the address extension from the recipient localpart. */ if (*var_rcpt_delim && split_addr(STR(queue_name), *var_rcpt_delim)) vstring_truncate(queue_name, strlen(STR(queue_name))); /* Assume the recipient domain is equivalent to nexthop. */ vstring_sprintf_append(queue_name, "@%s", STR(reply.nexthop)); } lowercase(STR(queue_name)); /* * This transport is alive. Find or instantiate a queue for this * recipient. */ if (queue == 0 || !STREQ(queue->name, STR(queue_name))) { if ((queue = qmgr_queue_find(transport, STR(queue_name))) == 0) queue = qmgr_queue_create(transport, STR(queue_name), STR(reply.nexthop)); } /* * This message is being flushed. If need-be unthrottle the queue. */ if ((message->qflags & QMGR_FLUSH_EACH) != 0 && QMGR_QUEUE_THROTTLED(queue)) qmgr_queue_unthrottle(queue); /* * This queue is dead. Defer delivery to this recipient. */ if (QMGR_QUEUE_THROTTLED(queue)) { saved_dsn = queue->dsn; if ((queue = qmgr_error_queue(MAIL_SERVICE_RETRY, saved_dsn)) == 0) { qmgr_defer_recipient(message, recipient, saved_dsn); continue; } } /* * This queue is alive. Bind this recipient to this queue instance. */ recipient->u.queue = queue; } resolve_clnt_free(&reply); vstring_free(queue_name); }
static SMTP_SESSION *smtp_connect_addr(const char *destination, DNS_RR *addr, unsigned port, DSN_BUF *why, int sess_flags) { const char *myname = "smtp_connect_addr"; struct sockaddr_storage ss; /* remote */ struct sockaddr *sa = (struct sockaddr *) & ss; SOCKADDR_SIZE salen = sizeof(ss); MAI_HOSTADDR_STR hostaddr; int sock; char *bind_addr; char *bind_var; dsb_reset(why); /* Paranoia */ /* * Sanity checks. */ if (dns_rr_to_sa(addr, port, sa, &salen) != 0) { msg_warn("%s: skip address type %s: %m", myname, dns_strtype(addr->type)); dsb_simple(why, "4.4.0", "network address conversion failed: %m"); return (0); } /* * Initialize. */ if ((sock = socket(sa->sa_family, SOCK_STREAM, 0)) < 0) msg_fatal("%s: socket: %m", myname); if (inet_windowsize > 0) set_inet_windowsize(sock, inet_windowsize); /* * Allow the sysadmin to specify the source address, for example, as "-o * smtp_bind_address=x.x.x.x" in the master.cf file. */ #ifdef HAS_IPV6 if (sa->sa_family == AF_INET6) { bind_addr = var_smtp_bind_addr6; bind_var = VAR_SMTP_BIND_ADDR6; } else #endif if (sa->sa_family == AF_INET) { bind_addr = var_smtp_bind_addr; bind_var = VAR_SMTP_BIND_ADDR; } else bind_var = bind_addr = ""; if (*bind_addr) { int aierr; struct addrinfo *res0; if ((aierr = hostaddr_to_sockaddr(bind_addr, (char *) 0, 0, &res0)) != 0) msg_fatal("%s: bad %s parameter: %s: %s", myname, bind_var, bind_addr, MAI_STRERROR(aierr)); if (bind(sock, res0->ai_addr, res0->ai_addrlen) < 0) msg_warn("%s: bind %s: %m", myname, bind_addr); else if (msg_verbose) msg_info("%s: bind %s", myname, bind_addr); freeaddrinfo(res0); } /* * When running as a virtual host, bind to the virtual interface so that * the mail appears to come from the "right" machine address. * * XXX The IPv6 patch expands the null host (as client endpoint) and uses * the result as the loopback address list. */ else { int count = 0; struct sockaddr *own_addr = 0; INET_ADDR_LIST *addr_list = own_inet_addr_list(); struct sockaddr_storage *s; for (s = addr_list->addrs; s < addr_list->addrs + addr_list->used; s++) { if (SOCK_ADDR_FAMILY(s) == sa->sa_family) { if (count++ > 0) break; own_addr = SOCK_ADDR_PTR(s); } } if (count == 1 && !sock_addr_in_loopback(own_addr)) { if (bind(sock, own_addr, SOCK_ADDR_LEN(own_addr)) < 0) { SOCKADDR_TO_HOSTADDR(own_addr, SOCK_ADDR_LEN(own_addr), &hostaddr, (MAI_SERVPORT_STR *) 0, 0); msg_warn("%s: bind %s: %m", myname, hostaddr.buf); } else if (msg_verbose) { SOCKADDR_TO_HOSTADDR(own_addr, SOCK_ADDR_LEN(own_addr), &hostaddr, (MAI_SERVPORT_STR *) 0, 0); msg_info("%s: bind %s", myname, hostaddr.buf); } } } /* * Connect to the server. */ SOCKADDR_TO_HOSTADDR(sa, salen, &hostaddr, (MAI_SERVPORT_STR *) 0, 0); if (msg_verbose) msg_info("%s: trying: %s[%s] port %d...", myname, SMTP_HNAME(addr), hostaddr.buf, ntohs(port)); return (smtp_connect_sock(sock, sa, salen, SMTP_HNAME(addr), hostaddr.buf, port, destination, why, sess_flags)); }
static int qmgr_message_read(QMGR_MESSAGE *message) { VSTRING *buf; int rec_type; long curr_offset; long save_offset = message->rcpt_offset; /* save a flag */ char *start; int nrcpt = 0; const char *error_text; char *name; char *value; char *orig_rcpt = 0; int count; int dsn_notify = 0; char *dsn_orcpt = 0; int n; int have_log_client_attr = 0; /* * Initialize. No early returns or we have a memory leak. */ buf = vstring_alloc(100); /* * If we re-open this file, skip over on-file recipient records that we * already looked at, and refill the in-core recipient address list. */ if (message->rcpt_offset) { if (message->rcpt_list.len) msg_panic("%s: recipient list not empty on recipient reload", message->queue_id); if (vstream_fseek(message->fp, message->rcpt_offset, SEEK_SET) < 0) msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp)); message->rcpt_offset = 0; } /* * Read envelope records. XXX Rely on the front-end programs to enforce * record size limits. Read up to var_qmgr_rcpt_limit recipients from the * queue file, to protect against memory exhaustion. Recipient records * may appear before or after the message content, so we keep reading * from the queue file until we have enough recipients (rcpt_offset != 0) * and until we know all the non-recipient information. * * When reading recipients from queue file, stop reading when we reach a * per-message in-core recipient limit rather than a global in-core * recipient limit. Use the global recipient limit only in order to stop * opening queue files. The purpose is to achieve equal delay for * messages with recipient counts up to var_qmgr_rcpt_limit recipients. * * If we would read recipients up to a global recipient limit, the average * number of in-core recipients per message would asymptotically approach * (global recipient limit)/(active queue size limit), which gives equal * delay per recipient rather than equal delay per message. * * On the first open, we must examine all non-recipient records. * * Optimization: when we know that recipient records are not mixed with * non-recipient records, as is typical with mailing list mail, then we * can avoid having to examine all the queue file records before we can * start deliveries. This avoids some file system thrashing with huge * mailing lists. */ for (;;) { if ((curr_offset = vstream_ftell(message->fp)) < 0) msg_fatal("vstream_ftell %s: %m", VSTREAM_PATH(message->fp)); if (curr_offset == message->data_offset && curr_offset > 0) { if (vstream_fseek(message->fp, message->data_size, SEEK_CUR) < 0) msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp)); curr_offset += message->data_size; } rec_type = rec_get_raw(message->fp, buf, 0, REC_FLAG_NONE); start = vstring_str(buf); if (msg_verbose > 1) msg_info("record %c %s", rec_type, start); if (rec_type == REC_TYPE_PTR) { if ((rec_type = rec_goto(message->fp, start)) == REC_TYPE_ERROR) break; /* Need to update curr_offset after pointer jump. */ continue; } if (rec_type <= 0) { msg_warn("%s: message rejected: missing end record", message->queue_id); break; } if (rec_type == REC_TYPE_END) { message->rflags |= QMGR_READ_FLAG_SEEN_ALL_NON_RCPT; break; } /* * Map named attributes to pseudo record types, so that we don't have * to pollute the queue file with records that are incompatible with * past Postfix versions. Preferably, people should be able to back * out from an upgrade without losing mail. */ if (rec_type == REC_TYPE_ATTR) { if ((error_text = split_nameval(start, &name, &value)) != 0) { msg_warn("%s: ignoring bad attribute: %s: %.200s", message->queue_id, error_text, start); rec_type = REC_TYPE_ERROR; break; } if ((n = rec_attr_map(name)) != 0) { start = value; rec_type = n; } } /* * Process recipient records. */ if (rec_type == REC_TYPE_RCPT) { /* See also below for code setting orig_rcpt etc. */ #define FUDGE(x) ((x) * (var_qmgr_fudge / 100.0)) if (message->rcpt_offset == 0) { recipient_list_add(&message->rcpt_list, curr_offset, dsn_orcpt ? dsn_orcpt : "", dsn_notify ? dsn_notify : 0, orig_rcpt ? orig_rcpt : "", start); if (dsn_orcpt) { myfree(dsn_orcpt); dsn_orcpt = 0; } if (orig_rcpt) { myfree(orig_rcpt); orig_rcpt = 0; } if (dsn_notify) dsn_notify = 0; if (message->rcpt_list.len >= FUDGE(var_qmgr_rcpt_limit)) { if ((message->rcpt_offset = vstream_ftell(message->fp)) < 0) msg_fatal("vstream_ftell %s: %m", VSTREAM_PATH(message->fp)); if (message->rflags & QMGR_READ_FLAG_SEEN_ALL_NON_RCPT) /* We already examined all non-recipient records. */ break; if (message->rflags & QMGR_READ_FLAG_MIXED_RCPT_OTHER) /* Examine all remaining non-recipient records. */ continue; /* Optimizations for "pure recipient" record sections. */ if (curr_offset > message->data_offset) { /* We already examined all non-recipient records. */ message->rflags |= QMGR_READ_FLAG_SEEN_ALL_NON_RCPT; break; } /* Examine non-recipient records in extracted segment. */ if (vstream_fseek(message->fp, message->data_offset + message->data_size, SEEK_SET) < 0) msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp)); continue; } } continue; } if (rec_type == REC_TYPE_DONE || rec_type == REC_TYPE_DRCP) { if (message->rcpt_offset == 0) { if (dsn_orcpt) { myfree(dsn_orcpt); dsn_orcpt = 0; } if (orig_rcpt) { myfree(orig_rcpt); orig_rcpt = 0; } if (dsn_notify) dsn_notify = 0; } continue; } if (rec_type == REC_TYPE_DSN_ORCPT) { /* See also above for code clearing dsn_orcpt. */ if (dsn_orcpt != 0) { msg_warn("%s: ignoring out-of-order DSN original recipient address <%.200s>", message->queue_id, dsn_orcpt); myfree(dsn_orcpt); dsn_orcpt = 0; } if (message->rcpt_offset == 0) dsn_orcpt = mystrdup(start); continue; } if (rec_type == REC_TYPE_DSN_NOTIFY) { /* See also above for code clearing dsn_notify. */ if (dsn_notify != 0) { msg_warn("%s: ignoring out-of-order DSN notify flags <%d>", message->queue_id, dsn_notify); dsn_notify = 0; } if (message->rcpt_offset == 0) { if (!alldig(start) || (n = atoi(start)) == 0 || !DSN_NOTIFY_OK(n)) msg_warn("%s: ignoring malformed DSN notify flags <%.200s>", message->queue_id, start); else dsn_notify = n; continue; } } if (rec_type == REC_TYPE_ORCP) { /* See also above for code clearing orig_rcpt. */ if (orig_rcpt != 0) { msg_warn("%s: ignoring out-of-order original recipient <%.200s>", message->queue_id, orig_rcpt); myfree(orig_rcpt); orig_rcpt = 0; } if (message->rcpt_offset == 0) orig_rcpt = mystrdup(start); continue; } /* * Process non-recipient records. */ if (message->rflags & QMGR_READ_FLAG_SEEN_ALL_NON_RCPT) /* We already examined all non-recipient records. */ continue; if (rec_type == REC_TYPE_SIZE) { if (message->data_offset == 0) { if ((count = sscanf(start, "%ld %ld %d %d %ld", &message->data_size, &message->data_offset, &nrcpt, &message->rflags, &message->cont_length)) >= 3) { /* Postfix >= 1.0 (a.k.a. 20010228). */ if (message->data_offset <= 0 || message->data_size <= 0) { msg_warn("%s: invalid size record: %.100s", message->queue_id, start); rec_type = REC_TYPE_ERROR; break; } if (message->rflags & ~QMGR_READ_FLAG_USER) { msg_warn("%s: invalid flags in size record: %.100s", message->queue_id, start); rec_type = REC_TYPE_ERROR; break; } } else if (count == 1) { /* Postfix < 1.0 (a.k.a. 20010228). */ qmgr_message_oldstyle_scan(message); } else { /* Can't happen. */ msg_warn("%s: message rejected: weird size record", message->queue_id); rec_type = REC_TYPE_ERROR; break; } } /* Postfix < 2.4 compatibility. */ if (message->cont_length == 0) { message->cont_length = message->data_size; } else if (message->cont_length < 0) { msg_warn("%s: invalid size record: %.100s", message->queue_id, start); rec_type = REC_TYPE_ERROR; break; } continue; } if (rec_type == REC_TYPE_TIME) { if (message->arrival_time.tv_sec == 0) REC_TYPE_TIME_SCAN(start, message->arrival_time); continue; } if (rec_type == REC_TYPE_CTIME) { if (message->create_time == 0) message->create_time = atol(start); continue; } if (rec_type == REC_TYPE_FILT) { if (message->filter_xport != 0) myfree(message->filter_xport); message->filter_xport = mystrdup(start); continue; } if (rec_type == REC_TYPE_INSP) { if (message->inspect_xport != 0) myfree(message->inspect_xport); message->inspect_xport = mystrdup(start); continue; } if (rec_type == REC_TYPE_RDR) { if (message->redirect_addr != 0) myfree(message->redirect_addr); message->redirect_addr = mystrdup(start); continue; } if (rec_type == REC_TYPE_FROM) { if (message->sender == 0) { message->sender = mystrdup(start); opened(message->queue_id, message->sender, message->cont_length, nrcpt, "queue %s", message->queue_name); } continue; } if (rec_type == REC_TYPE_DSN_ENVID) { if (message->dsn_envid == 0) message->dsn_envid = mystrdup(start); } if (rec_type == REC_TYPE_DSN_RET) { if (message->dsn_ret == 0) { if (!alldig(start) || (n = atoi(start)) == 0 || !DSN_RET_OK(n)) msg_warn("%s: ignoring malformed DSN RET flags in queue file record:%.100s", message->queue_id, start); else message->dsn_ret = n; } } if (rec_type == REC_TYPE_ATTR) { /* Allow extra segment to override envelope segment info. */ if (strcmp(name, MAIL_ATTR_ENCODING) == 0) { if (message->encoding != 0) myfree(message->encoding); message->encoding = mystrdup(value); } /* * Backwards compatibility. Before Postfix 2.3, the logging * attributes were called client_name, etc. Now they are called * log_client_name. etc., and client_name is used for the actual * client information. To support old queue files, we accept both * names for the purpose of logging; the new name overrides the * old one. * * XXX Do not use the "legacy" client_name etc. attribute values for * initializing the logging attributes, when this file already * contains the "modern" log_client_name etc. logging attributes. * Otherwise, logging attributes that are not present in the * queue file would be set with information from the real client. */ else if (strcmp(name, MAIL_ATTR_ACT_CLIENT_NAME) == 0) { if (have_log_client_attr == 0 && message->client_name == 0) message->client_name = mystrdup(value); } else if (strcmp(name, MAIL_ATTR_ACT_CLIENT_ADDR) == 0) { if (have_log_client_attr == 0 && message->client_addr == 0) message->client_addr = mystrdup(value); } else if (strcmp(name, MAIL_ATTR_ACT_CLIENT_PORT) == 0) { if (have_log_client_attr == 0 && message->client_port == 0) message->client_port = mystrdup(value); } else if (strcmp(name, MAIL_ATTR_ACT_PROTO_NAME) == 0) { if (have_log_client_attr == 0 && message->client_proto == 0) message->client_proto = mystrdup(value); } else if (strcmp(name, MAIL_ATTR_ACT_HELO_NAME) == 0) { if (have_log_client_attr == 0 && message->client_helo == 0) message->client_helo = mystrdup(value); } /* Original client attributes. */ else if (strcmp(name, MAIL_ATTR_LOG_CLIENT_NAME) == 0) { if (message->client_name != 0) myfree(message->client_name); message->client_name = mystrdup(value); have_log_client_attr = 1; } else if (strcmp(name, MAIL_ATTR_LOG_CLIENT_ADDR) == 0) { if (message->client_addr != 0) myfree(message->client_addr); message->client_addr = mystrdup(value); have_log_client_attr = 1; } else if (strcmp(name, MAIL_ATTR_LOG_CLIENT_PORT) == 0) { if (message->client_port != 0) myfree(message->client_port); message->client_port = mystrdup(value); have_log_client_attr = 1; } else if (strcmp(name, MAIL_ATTR_LOG_PROTO_NAME) == 0) { if (message->client_proto != 0) myfree(message->client_proto); message->client_proto = mystrdup(value); have_log_client_attr = 1; } else if (strcmp(name, MAIL_ATTR_LOG_HELO_NAME) == 0) { if (message->client_helo != 0) myfree(message->client_helo); message->client_helo = mystrdup(value); have_log_client_attr = 1; } else if (strcmp(name, MAIL_ATTR_SASL_METHOD) == 0) { if (message->sasl_method == 0) message->sasl_method = mystrdup(value); else msg_warn("%s: ignoring multiple %s attribute: %s", message->queue_id, MAIL_ATTR_SASL_METHOD, value); } else if (strcmp(name, MAIL_ATTR_SASL_USERNAME) == 0) { if (message->sasl_username == 0) message->sasl_username = mystrdup(value); else msg_warn("%s: ignoring multiple %s attribute: %s", message->queue_id, MAIL_ATTR_SASL_USERNAME, value); } else if (strcmp(name, MAIL_ATTR_SASL_SENDER) == 0) { if (message->sasl_sender == 0) message->sasl_sender = mystrdup(value); else msg_warn("%s: ignoring multiple %s attribute: %s", message->queue_id, MAIL_ATTR_SASL_SENDER, value); } else if (strcmp(name, MAIL_ATTR_LOG_IDENT) == 0) { if (message->log_ident == 0) message->log_ident = mystrdup(value); else msg_warn("%s: ignoring multiple %s attribute: %s", message->queue_id, MAIL_ATTR_LOG_IDENT, value); } else if (strcmp(name, MAIL_ATTR_RWR_CONTEXT) == 0) { if (message->rewrite_context == 0) message->rewrite_context = mystrdup(value); else msg_warn("%s: ignoring multiple %s attribute: %s", message->queue_id, MAIL_ATTR_RWR_CONTEXT, value); } /* * Optional tracing flags (verify, sendmail -v, sendmail -bv). * This record is killed after a trace logfile report is sent and * after the logfile is deleted. */ else if (strcmp(name, MAIL_ATTR_TRACE_FLAGS) == 0) { message->tflags = DEL_REQ_TRACE_FLAGS(atoi(value)); if (message->tflags == DEL_REQ_FLAG_RECORD) message->tflags_offset = curr_offset; else message->tflags_offset = 0; } continue; } if (rec_type == REC_TYPE_WARN) { if (message->warn_offset == 0) { message->warn_offset = curr_offset; REC_TYPE_WARN_SCAN(start, message->warn_time); } continue; } if (rec_type == REC_TYPE_VERP) { if (message->verp_delims == 0) { if (message->sender == 0 || message->sender[0] == 0) { msg_warn("%s: ignoring VERP request for null sender", message->queue_id); } else if (verp_delims_verify(start) != 0) { msg_warn("%s: ignoring bad VERP request: \"%.100s\"", message->queue_id, start); } else { if (msg_verbose) msg_info("%s: enabling VERP for sender \"%.100s\"", message->queue_id, message->sender); message->single_rcpt = 1; message->verp_delims = mystrdup(start); } } continue; } } /* * Grr. */ if (dsn_orcpt != 0) { if (rec_type > 0) msg_warn("%s: ignoring out-of-order DSN original recipient <%.200s>", message->queue_id, dsn_orcpt); myfree(dsn_orcpt); } if (orig_rcpt != 0) { if (rec_type > 0) msg_warn("%s: ignoring out-of-order original recipient <%.200s>", message->queue_id, orig_rcpt); myfree(orig_rcpt); } /* * Avoid clumsiness elsewhere in the program. When sending data across an * IPC channel, sending an empty string is more convenient than sending a * null pointer. */ if (message->dsn_envid == 0) message->dsn_envid = mystrdup(""); if (message->encoding == 0) message->encoding = mystrdup(MAIL_ATTR_ENC_NONE); if (message->client_name == 0) message->client_name = mystrdup(""); if (message->client_addr == 0) message->client_addr = mystrdup(""); if (message->client_port == 0) message->client_port = mystrdup(""); if (message->client_proto == 0) message->client_proto = mystrdup(""); if (message->client_helo == 0) message->client_helo = mystrdup(""); if (message->sasl_method == 0) message->sasl_method = mystrdup(""); if (message->sasl_username == 0) message->sasl_username = mystrdup(""); if (message->sasl_sender == 0) message->sasl_sender = mystrdup(""); if (message->log_ident == 0) message->log_ident = mystrdup(""); if (message->rewrite_context == 0) message->rewrite_context = mystrdup(MAIL_ATTR_RWR_LOCAL); /* Postfix < 2.3 compatibility. */ if (message->create_time == 0) message->create_time = message->arrival_time.tv_sec; /* * Clean up. */ vstring_free(buf); /* * Sanity checks. Verify that all required information was found, * including the queue file end marker. */ if (rec_type <= 0) { /* Already logged warning. */ } else if (message->arrival_time.tv_sec == 0) { msg_warn("%s: message rejected: missing arrival time record", message->queue_id); } else if (message->sender == 0) { msg_warn("%s: message rejected: missing sender record", message->queue_id); } else if (message->data_offset == 0) { msg_warn("%s: message rejected: missing size record", message->queue_id); } else { return (0); } message->rcpt_offset = save_offset; /* restore flag */ return (-1); }
static int xsasl_dovecot_server_connect(XSASL_DOVECOT_SERVER_IMPL *xp) { const char *myname = "xsasl_dovecot_server_connect"; VSTRING *line_str; VSTREAM *sasl_stream; char *line, *cmd, *mech_name; unsigned int major_version, minor_version; int fd, success; int sec_props; const char *path; if (msg_verbose) msg_info("%s: Connecting", myname); /* * Not documented, but necessary for testing. */ path = xp->socket_path; if (strncmp(path, "inet:", 5) == 0) { fd = inet_connect(path + 5, BLOCKING, AUTH_TIMEOUT); } else { if (strncmp(path, "unix:", 5) == 0) path += 5; fd = unix_connect(path, BLOCKING, AUTH_TIMEOUT); } if (fd < 0) { msg_warn("SASL: Connect to %s failed: %m", xp->socket_path); return (-1); } sasl_stream = vstream_fdopen(fd, O_RDWR); vstream_control(sasl_stream, VSTREAM_CTL_PATH, xp->socket_path, VSTREAM_CTL_TIMEOUT, AUTH_TIMEOUT, VSTREAM_CTL_END); /* XXX Encapsulate for logging. */ vstream_fprintf(sasl_stream, "VERSION\t%u\t%u\n" "CPID\t%u\n", AUTH_PROTOCOL_MAJOR_VERSION, AUTH_PROTOCOL_MINOR_VERSION, (unsigned int) getpid()); if (vstream_fflush(sasl_stream) == VSTREAM_EOF) { msg_warn("SASL: Couldn't send handshake: %m"); return (-1); } success = 0; line_str = vstring_alloc(256); /* XXX Encapsulate for logging. */ while (vstring_get_nonl(line_str, sasl_stream) != VSTREAM_EOF) { line = vstring_str(line_str); if (msg_verbose) msg_info("%s: auth reply: %s", myname, line); cmd = line; line = split_at(line, '\t'); if (strcmp(cmd, "VERSION") == 0) { if (sscanf(line, "%u\t%u", &major_version, &minor_version) != 2) { msg_warn("SASL: Protocol version error"); break; } if (major_version != AUTH_PROTOCOL_MAJOR_VERSION) { /* Major version is different from ours. */ msg_warn("SASL: Protocol version mismatch (%d vs. %d)", major_version, AUTH_PROTOCOL_MAJOR_VERSION); break; } } else if (strcmp(cmd, "MECH") == 0 && line != NULL) { mech_name = line; line = split_at(line, '\t'); if (line != 0) { sec_props = name_mask_delim_opt(myname, xsasl_dovecot_serv_sec_props, line, "\t", NAME_MASK_ANY_CASE | NAME_MASK_IGNORE); if ((sec_props & SEC_PROPS_PRIVATE) != 0) continue; } else sec_props = 0; xsasl_dovecot_server_mech_append(&xp->mechanism_list, mech_name, sec_props); } else if (strcmp(cmd, "DONE") == 0) { /* Handshake finished. */ success = 1; break; } else { /* ignore any unknown commands */ } } vstring_free(line_str); if (!success) { /* handshake failed */ (void) vstream_fclose(sasl_stream); return (-1); } xp->sasl_stream = sasl_stream; return (0); }
SMTP_SESSION *smtp_session_activate(int fd, SMTP_ITERATOR *iter, VSTRING *dest_prop, VSTRING *endp_prop) { const char *myname = "smtp_session_activate"; VSTREAM *mp; SMTP_SESSION *session; int endp_features; /* server features */ int dest_features; /* server features */ long expire_time; /* session re-use expiration time */ int reuse_count; /* # times reused */ #ifdef USE_TLS TLS_SESS_STATE *tls_context = 0; SMTP_TLS_POLICY *tls = iter->parent->tls; #define TLS_PROXY_CONTEXT_FREE() do { \ if (tls_context) \ tls_proxy_context_free(tls_context); \ } while (0) #else #define TLS_PROXY_CONTEXT_FREE() /* nothing */ #endif #define SMTP_SESSION_ACTIVATE_ERR_RETURN() do { \ TLS_PROXY_CONTEXT_FREE(); \ return (0); \ } while (0) /* * Sanity check: if TLS is required, the cached properties must contain a * TLS context. */ if ((mp = vstream_memopen(endp_prop, O_RDONLY)) == 0 || attr_scan_plain(mp, ATTR_FLAG_NONE, #ifdef USE_TLS RECV_ATTR_INT(SESS_ATTR_TLS_LEVEL, &tls->level), #endif RECV_ATTR_INT(SESS_ATTR_REUSE_COUNT, &reuse_count), RECV_ATTR_INT(SESS_ATTR_ENDP_FEATURES, &endp_features), RECV_ATTR_LONG(SESS_ATTR_EXPIRE_TIME, &expire_time), ATTR_TYPE_END) != 4 #ifdef USE_TLS || ((tls->level > TLS_LEV_MAY || (tls->level == TLS_LEV_MAY && vstream_peek(mp) > 0)) && attr_scan_plain(mp, ATTR_FLAG_NONE, RECV_ATTR_FUNC(tls_proxy_context_scan, (void *) &tls_context), ATTR_TYPE_END) != 1) #endif || vstream_fclose(mp) != 0) { msg_warn("smtp_session_activate: bad cached endp properties"); SMTP_SESSION_ACTIVATE_ERR_RETURN(); } /* * Clobber the iterator's current nexthop, host and address fields with * cached-connection information. This is done when a session is looked * up by delivery request nexthop instead of address and port. It is the * caller's responsibility to save and restore the delivery request * nexthop with SMTP_ITER_SAVE_DEST() and SMTP_ITER_RESTORE_DEST(). * * TODO: Eliminate the duplication between SMTP_ITERATOR and SMTP_SESSION. * * TODO: restore SASL username and password information so that we can * correctly save a reused authenticated connection. */ if (dest_prop && VSTRING_LEN(dest_prop)) { if ((mp = vstream_memopen(dest_prop, O_RDONLY)) == 0 || attr_scan_plain(mp, ATTR_FLAG_NONE, RECV_ATTR_STR(SESS_ATTR_DEST, iter->dest), RECV_ATTR_STR(SESS_ATTR_HOST, iter->host), RECV_ATTR_STR(SESS_ATTR_ADDR, iter->addr), RECV_ATTR_INT(SESS_ATTR_DEST_FEATURES, &dest_features), ATTR_TYPE_END) != 4 || vstream_fclose(mp) != 0) { msg_warn("smtp_session_passivate: bad cached dest properties"); SMTP_SESSION_ACTIVATE_ERR_RETURN(); } } else { dest_features = 0; } #ifdef USE_TLS if (msg_verbose) msg_info("%s: tls_level=%d", myname, tls->level); #endif /* * Allright, bundle up what we have sofar. */ #define NO_FLAGS 0 session = smtp_session_alloc(vstream_fdopen(fd, O_RDWR), iter, (time_t) 0, NO_FLAGS); session->features = (endp_features | dest_features | SMTP_FEATURE_FROM_CACHE); #ifdef USE_TLS session->tls_context = tls_context; #endif CACHE_THIS_SESSION_UNTIL(expire_time); session->reuse_count = ++reuse_count; if (msg_verbose) msg_info("%s: dest=%s host=%s addr=%s port=%u features=0x%x, " "ttl=%ld, reuse=%d", myname, STR(iter->dest), STR(iter->host), STR(iter->addr), ntohs(iter->port), endp_features | dest_features, (long) (expire_time - time((time_t *) 0)), reuse_count); #if USE_TLS if (tls_context) tls_log_summary(TLS_ROLE_CLIENT, TLS_USAGE_USED, session->tls_context); #endif return (session); }
int main(int argc, char **argv) { struct stat st; int fd; int c; VSTRING *buf; int status; MAIL_STREAM *dst; int rec_type; static char *segment_info[] = { REC_TYPE_POST_ENVELOPE, REC_TYPE_POST_CONTENT, REC_TYPE_POST_EXTRACT, "" }; char **expected; uid_t uid = getuid(); ARGV *import_env; const char *error_text; char *attr_name; char *attr_value; const char *errstr; char *junk; struct timeval start; int saved_errno; int from_count = 0; int rcpt_count = 0; int validate_input = 1; /* * Fingerprint executables and core dumps. */ MAIL_VERSION_STAMP_ALLOCATE; /* * Be consistent with file permissions. */ umask(022); /* * To minimize confusion, make sure that the standard file descriptors * are open before opening anything else. XXX Work around for 44BSD where * fstat can return EBADF on an open file descriptor. */ for (fd = 0; fd < 3; fd++) if (fstat(fd, &st) == -1 && (close(fd), open("/dev/null", O_RDWR, 0)) != fd) msg_fatal("open /dev/null: %m"); /* * Set up logging. Censor the process name: it is provided by the user. */ argv[0] = "postdrop"; msg_vstream_init(argv[0], VSTREAM_ERR); msg_syslog_init(mail_task("postdrop"), LOG_PID, LOG_FACILITY); set_mail_conf_str(VAR_PROCNAME, var_procname = mystrdup(argv[0])); /* * Check the Postfix library version as soon as we enable logging. */ MAIL_VERSION_CHECK; /* * Parse JCL. This program is set-gid and must sanitize all command-line * arguments. The configuration directory argument is validated by the * mail configuration read routine. Don't do complex things until we have * completed initializations. */ while ((c = GETOPT(argc, argv, "c:rv")) > 0) { switch (c) { case 'c': if (setenv(CONF_ENV_PATH, optarg, 1) < 0) msg_fatal("out of memory"); break; case 'r': /* forward compatibility */ break; case 'v': if (geteuid() == 0) msg_verbose++; break; default: msg_fatal("usage: %s [-c config_dir] [-v]", argv[0]); } } /* * Read the global configuration file and extract configuration * information. Some claim that the user should supply the working * directory instead. That might be OK, given that this command needs * write permission in a subdirectory called "maildrop". However we still * need to reliably detect incomplete input, and so we must perform * record-level I/O. With that, we should also take the opportunity to * perform some sanity checks on the input. */ mail_conf_read(); if (strcmp(var_syslog_name, DEF_SYSLOG_NAME) != 0) msg_syslog_init(mail_task("postdrop"), LOG_PID, LOG_FACILITY); get_mail_conf_str_table(str_table); /* * Mail submission access control. Should this be in the user-land gate, * or in the daemon process? */ if ((errstr = check_user_acl_byuid(var_submit_acl, uid)) != 0) msg_fatal("User %s(%ld) is not allowed to submit mail", errstr, (long) uid); /* * Stop run-away process accidents by limiting the queue file size. This * is not a defense against DOS attack. */ if (var_message_limit > 0 && get_file_limit() > var_message_limit) set_file_limit((off_t) var_message_limit); /* * This program is installed with setgid privileges. Strip the process * environment so that we don't have to trust the C library. */ import_env = argv_split(var_import_environ, ", \t\r\n"); clean_env(import_env->argv); argv_free(import_env); if (chdir(var_queue_dir)) msg_fatal("chdir %s: %m", var_queue_dir); if (msg_verbose) msg_info("chdir %s", var_queue_dir); /* * Set up signal handlers and a runtime error handler so that we can * clean up incomplete output. * * postdrop_sig() uses the in-kernel SIGINT handler address as an atomic * variable to prevent nested postdrop_sig() calls. For this reason, the * SIGINT handler must be configured before other signal handlers are * allowed to invoke postdrop_sig(). */ signal(SIGPIPE, SIG_IGN); signal(SIGXFSZ, SIG_IGN); signal(SIGINT, postdrop_sig); signal(SIGQUIT, postdrop_sig); if (signal(SIGTERM, SIG_IGN) == SIG_DFL) signal(SIGTERM, postdrop_sig); if (signal(SIGHUP, SIG_IGN) == SIG_DFL) signal(SIGHUP, postdrop_sig); msg_cleanup(postdrop_cleanup); /* End of initializations. */ /* * Don't trust the caller's time information. */ GETTIMEOFDAY(&start); /* * Create queue file. mail_stream_file() never fails. Send the queue ID * to the caller. Stash away a copy of the queue file name so we can * clean up in case of a fatal error or an interrupt. */ dst = mail_stream_file(MAIL_QUEUE_MAILDROP, MAIL_CLASS_PUBLIC, var_pickup_service, 0444); attr_print(VSTREAM_OUT, ATTR_FLAG_NONE, ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, dst->id, ATTR_TYPE_END); vstream_fflush(VSTREAM_OUT); postdrop_path = mystrdup(VSTREAM_PATH(dst->stream)); /* * Copy stdin to file. The format is checked so that we can recognize * incomplete input and cancel the operation. With the sanity checks * applied here, the pickup daemon could skip format checks and pass a * file descriptor to the cleanup daemon. These are by no means all * sanity checks - the cleanup service and queue manager services will * reject messages that lack required information. * * If something goes wrong, slurp up the input before responding to the * client, otherwise the client will give up after detecting SIGPIPE. * * Allow attribute records if the attribute specifies the MIME body type * (sendmail -B). */ vstream_control(VSTREAM_IN, VSTREAM_CTL_PATH, "stdin", VSTREAM_CTL_END); buf = vstring_alloc(100); expected = segment_info; /* Override time information from the untrusted caller. */ rec_fprintf(dst->stream, REC_TYPE_TIME, REC_TYPE_TIME_FORMAT, REC_TYPE_TIME_ARG(start)); for (;;) { /* Don't allow PTR records. */ rec_type = rec_get_raw(VSTREAM_IN, buf, var_line_limit, REC_FLAG_NONE); if (rec_type == REC_TYPE_EOF) { /* request cancelled */ mail_stream_cleanup(dst); if (remove(postdrop_path)) msg_warn("uid=%ld: remove %s: %m", (long) uid, postdrop_path); else if (msg_verbose) msg_info("remove %s", postdrop_path); myfree(postdrop_path); postdrop_path = 0; exit(0); } if (rec_type == REC_TYPE_ERROR) msg_fatal("uid=%ld: malformed input", (long) uid); if (strchr(*expected, rec_type) == 0) msg_fatal("uid=%ld: unexpected record type: %d", (long) uid, rec_type); if (rec_type == **expected) expected++; /* Override time information from the untrusted caller. */ if (rec_type == REC_TYPE_TIME) continue; /* Check these at submission time instead of pickup time. */ if (rec_type == REC_TYPE_FROM) from_count++; if (rec_type == REC_TYPE_RCPT) rcpt_count++; /* Limit the attribute types that users may specify. */ if (rec_type == REC_TYPE_ATTR) { if ((error_text = split_nameval(vstring_str(buf), &attr_name, &attr_value)) != 0) { msg_warn("uid=%ld: ignoring malformed record: %s: %.200s", (long) uid, error_text, vstring_str(buf)); continue; } #define STREQ(x,y) (strcmp(x,y) == 0) if ((STREQ(attr_name, MAIL_ATTR_ENCODING) && (STREQ(attr_value, MAIL_ATTR_ENC_7BIT) || STREQ(attr_value, MAIL_ATTR_ENC_8BIT) || STREQ(attr_value, MAIL_ATTR_ENC_NONE))) || STREQ(attr_name, MAIL_ATTR_DSN_ENVID) || STREQ(attr_name, MAIL_ATTR_DSN_NOTIFY) || rec_attr_map(attr_name) || (STREQ(attr_name, MAIL_ATTR_RWR_CONTEXT) && (STREQ(attr_value, MAIL_ATTR_RWR_LOCAL) || STREQ(attr_value, MAIL_ATTR_RWR_REMOTE))) || STREQ(attr_name, MAIL_ATTR_TRACE_FLAGS)) { /* XXX */ rec_fprintf(dst->stream, REC_TYPE_ATTR, "%s=%s", attr_name, attr_value); } else { msg_warn("uid=%ld: ignoring attribute record: %.200s=%.200s", (long) uid, attr_name, attr_value); } continue; } if (REC_PUT_BUF(dst->stream, rec_type, buf) < 0) { /* rec_get() errors must not clobber errno. */ saved_errno = errno; while ((rec_type = rec_get_raw(VSTREAM_IN, buf, var_line_limit, REC_FLAG_NONE)) != REC_TYPE_END && rec_type != REC_TYPE_EOF) if (rec_type == REC_TYPE_ERROR) msg_fatal("uid=%ld: malformed input", (long) uid); validate_input = 0; errno = saved_errno; break; } if (rec_type == REC_TYPE_END) break; } vstring_free(buf); /* * As of Postfix 2.7 the pickup daemon discards mail without recipients. * Such mail may enter the maildrop queue when "postsuper -r" is invoked * before the queue manager deletes an already delivered message. Looking * at file ownership is not a good way to make decisions on what mail to * discard. Instead, the pickup server now requires that new submissions * always have at least one recipient record. * * The Postfix sendmail command already rejects mail without recipients. * However, in the future postdrop may receive mail via other programs, * so we add a redundant recipient check here for future proofing. * * The test for the sender address is just for consistency of error * reporting (report at submission time instead of pickup time). Besides * the segment terminator records, there aren't any other mandatory * records in a Postfix submission queue file. */ if (validate_input && (from_count == 0 || rcpt_count == 0)) { status = CLEANUP_STAT_BAD; mail_stream_cleanup(dst); } /* * Finish the file. */ else if ((status = mail_stream_finish(dst, (VSTRING *) 0)) != 0) { msg_warn("uid=%ld: %m", (long) uid); postdrop_cleanup(); } /* * Disable deletion on fatal error before reporting success, so the file * will not be deleted after we have taken responsibility for delivery. */ if (postdrop_path) { junk = postdrop_path; postdrop_path = 0; myfree(junk); } /* * Send the completion status to the caller and terminate. */ attr_print(VSTREAM_OUT, ATTR_FLAG_NONE, ATTR_TYPE_INT, MAIL_ATTR_STATUS, status, ATTR_TYPE_STR, MAIL_ATTR_WHY, "", ATTR_TYPE_END); vstream_fflush(VSTREAM_OUT); exit(status); }
int cleanup_bounce(CLEANUP_STATE *state) { const char *myname = "cleanup_bounce"; VSTRING *buf = vstring_alloc(100); const CLEANUP_STAT_DETAIL *detail; DSN_SPLIT dp; const char *dsn_status; const char *dsn_text; char *rcpt = 0; RECIPIENT recipient; DSN dsn; char *attr_name; char *attr_value; char *dsn_orcpt = 0; int dsn_notify = 0; char *orig_rcpt = 0; char *start; int rec_type; int junk; long curr_offset; const char *encoding; const char *dsn_envid; int dsn_ret; int bounce_err; /* * Parse the failure reason if one was given, otherwise use a generic * mapping from cleanup-internal error code to (DSN + text). */ if (state->reason) { dsn_split(&dp, "5.0.0", state->reason); dsn_status = DSN_STATUS(dp.dsn); dsn_text = dp.text; } else { detail = cleanup_stat_detail(state->errs); dsn_status = detail->dsn; dsn_text = detail->text; } /* * Create a bounce logfile with one entry for each final recipient. * Degrade gracefully in case of no recipients or no queue file. * * Victor Duchovni observes that the number of recipients in the queue file * can potentially be very large due to virtual alias expansion. This can * expand the recipient count by virtual_alias_expansion_limit (default: * 1000) times. * * After a queue file write error, purge any unwritten data (so that * vstream_fseek() won't fail while trying to flush it) and reset the * stream error flags to avoid false alarms. */ if (vstream_ferror(state->dst) || vstream_fflush(state->dst)) { (void) vstream_fpurge(state->dst, VSTREAM_PURGE_BOTH); vstream_clearerr(state->dst); } if (vstream_fseek(state->dst, 0L, SEEK_SET) < 0) msg_fatal("%s: seek %s: %m", myname, cleanup_path); while ((state->errs & CLEANUP_STAT_WRITE) == 0) { if ((curr_offset = vstream_ftell(state->dst)) < 0) msg_fatal("%s: vstream_ftell %s: %m", myname, cleanup_path); if ((rec_type = rec_get(state->dst, buf, 0)) <= 0 || rec_type == REC_TYPE_END) break; start = STR(buf); if (rec_type == REC_TYPE_ATTR) { if (split_nameval(STR(buf), &attr_name, &attr_value) != 0 || *attr_value == 0) continue; /* Map attribute names to pseudo record type. */ if ((junk = rec_attr_map(attr_name)) != 0) { start = attr_value; rec_type = junk; } } switch (rec_type) { case REC_TYPE_DSN_ORCPT: /* RCPT TO ORCPT parameter */ if (dsn_orcpt != 0) /* can't happen */ myfree(dsn_orcpt); dsn_orcpt = mystrdup(start); break; case REC_TYPE_DSN_NOTIFY: /* RCPT TO NOTIFY parameter */ if (alldig(start) && (junk = atoi(start)) > 0 && DSN_NOTIFY_OK(junk)) dsn_notify = junk; else dsn_notify = 0; break; case REC_TYPE_ORCP: /* unmodified RCPT TO address */ if (orig_rcpt != 0) /* can't happen */ myfree(orig_rcpt); orig_rcpt = mystrdup(start); break; case REC_TYPE_RCPT: /* rewritten RCPT TO address */ rcpt = start; RECIPIENT_ASSIGN(&recipient, curr_offset, dsn_orcpt ? dsn_orcpt : "", dsn_notify, orig_rcpt ? orig_rcpt : rcpt, rcpt); (void) DSN_SIMPLE(&dsn, dsn_status, dsn_text); cleanup_bounce_append(state, &recipient, &dsn); /* FALLTHROUGH */ case REC_TYPE_DRCP: /* canceled recipient */ case REC_TYPE_DONE: /* can't happen */ if (orig_rcpt != 0) { myfree(orig_rcpt); orig_rcpt = 0; } if (dsn_orcpt != 0) { myfree(dsn_orcpt); dsn_orcpt = 0; } dsn_notify = 0; break; } } if (orig_rcpt != 0) /* can't happen */ myfree(orig_rcpt); if (dsn_orcpt != 0) /* can't happen */ myfree(dsn_orcpt); /* * No recipients. Yes, this can happen. */ if ((state->errs & CLEANUP_STAT_WRITE) == 0 && rcpt == 0) { RECIPIENT_ASSIGN(&recipient, 0, "", 0, "", "unknown"); (void) DSN_SIMPLE(&dsn, dsn_status, dsn_text); cleanup_bounce_append(state, &recipient, &dsn); } vstring_free(buf); /* * Flush the bounce logfile to the sender. See also qmgr_active.c. */ if ((state->errs & CLEANUP_STAT_WRITE) == 0) { if ((encoding = nvtable_find(state->attr, MAIL_ATTR_ENCODING)) == 0) encoding = MAIL_ATTR_ENC_NONE; dsn_envid = state->dsn_envid ? state->dsn_envid : ""; /* Do not send unfiltered (body) content. */ dsn_ret = (state->errs & (CLEANUP_STAT_CONT | CLEANUP_STAT_SIZE)) ? DSN_RET_HDRS : state->dsn_ret; if (state->verp_delims == 0 || var_verp_bounce_off) { bounce_err = bounce_flush(BOUNCE_FLAG_CLEAN, state->queue_name, state->queue_id, encoding, state->sender, dsn_envid, dsn_ret); } else { bounce_err = bounce_flush_verp(BOUNCE_FLAG_CLEAN, state->queue_name, state->queue_id, encoding, state->sender, dsn_envid, dsn_ret, state->verp_delims); } if (bounce_err != 0) { msg_warn("%s: bounce message failure", state->queue_id); state->errs |= CLEANUP_STAT_WRITE; } } /* * Schedule this message (and trace logfile) for deletion when all is * well. When all is not well these files would be deleted too, but the * client would get a different completion status so we have to carefully * maintain the bits anyway. */ if ((state->errs &= CLEANUP_STAT_WRITE) == 0) state->flags |= CLEANUP_FLAG_DISCARD; return (state->errs); }
void psc_send_socket(PSC_STATE *state) { const char *myname = "psc_send_socket"; int server_fd; if (msg_verbose > 1) msg_info("%s: sq=%d cq=%d send socket %d from [%s]:%s", myname, psc_post_queue_length, psc_check_queue_length, vstream_fileno(state->smtp_client_stream), state->smtp_client_addr, state->smtp_client_port); /* * Connect to the real SMTP service over a local IPC channel, send the * file descriptor, and close the file descriptor to save resources. * Experience has shown that some systems will discard information when * we close a channel immediately after writing. Thus, we waste resources * waiting for the remote side to close the local IPC channel first. The * good side of waiting is that we learn when the real SMTP server is * falling behind. * * This is where we would forward the connection to an SMTP server that * provides an appropriate level of service for this client class. For * example, a server that is more forgiving, or one that is more * suspicious. Alternatively, we could send attributes along with the * socket with client reputation information, making everything even more * Postfix-specific. */ if ((server_fd = PASS_CONNECT(psc_smtpd_service_name, NON_BLOCKING, PSC_SEND_SOCK_CONNECT_TIMEOUT)) < 0) { msg_warn("cannot connect to service %s: %m", psc_smtpd_service_name); if (state->flags & PSC_STATE_FLAG_PREGR_TODO) { PSC_SMTPD_X21(state, "421 4.3.2 No system resources\r\n"); } else { PSC_SEND_REPLY(state, "421 4.3.2 All server ports are busy\r\n"); psc_free_session_state(state); } return; } if (LOCAL_SEND_FD(server_fd, vstream_fileno(state->smtp_client_stream)) < 0) { msg_warn("cannot pass connection to service %s: %m", psc_smtpd_service_name); (void) close(server_fd); if (state->flags & PSC_STATE_FLAG_PREGR_TODO) { PSC_SMTPD_X21(state, "421 4.3.2 No system resources\r\n"); } else { PSC_SEND_REPLY(state, "421 4.3.2 No system resources\r\n"); psc_free_session_state(state); } return; } else { /* * Closing the smtp_client_fd here triggers a FreeBSD 7.1 kernel bug * where smtp-source sometimes sees the connection being closed after * it has already received the real SMTP server's 220 greeting! */ #if 0 PSC_DEL_CLIENT_STATE(state); #endif PSC_ADD_SERVER_STATE(state, server_fd); PSC_READ_EVENT_REQUEST(state->smtp_server_fd, psc_send_socket_close_event, (char *) state, PSC_SEND_SOCK_NOTIFY_TIMEOUT); return; } }
static void pre_jail_init(char *unused_name, char **unused_argv) { VSTRING *redirect; /* * Open read-only maps before dropping privilege, for consistency with * other Postfix daemons. */ psc_acl_pre_jail_init(var_mynetworks, VAR_PSC_ACL); if (*var_psc_acl) psc_acl = psc_acl_parse(var_psc_acl, VAR_PSC_ACL); /* Ignore smtpd_forbid_cmds lookup errors. Non-critical feature. */ if (*var_psc_forbid_cmds) psc_forbid_cmds = string_list_init(VAR_PSC_FORBID_CMDS, MATCH_FLAG_RETURN, var_psc_forbid_cmds); if (*var_psc_dnsbl_reply) psc_dnsbl_reply = dict_open(var_psc_dnsbl_reply, O_RDONLY, DICT_FLAG_DUP_WARN); /* * Never, ever, get killed by a master signal, as that would corrupt the * database when we're in the middle of an update. */ if (setsid() < 0) msg_warn("setsid: %m"); /* * Security: don't create root-owned files that contain untrusted data. * And don't create Postfix-owned files in root-owned directories, * either. We want a correct relationship between (file or directory) * ownership and (file or directory) content. To open files before going * to jail, temporarily drop root privileges. */ SAVE_AND_SET_EUGID(var_owner_uid, var_owner_gid); redirect = vstring_alloc(100); /* * Keep state in persistent external map. As a safety measure we sync the * database on each update. This hurts on LINUX file systems that sync * all dirty disk blocks whenever any application invokes fsync(). * * Start the cache maintenance pseudo thread after dropping privileges. */ #define PSC_DICT_OPEN_FLAGS (DICT_FLAG_DUP_REPLACE | DICT_FLAG_SYNC_UPDATE | \ DICT_FLAG_OPEN_LOCK) if (*var_psc_cache_map) psc_cache_map = dict_cache_open(data_redirect_map(redirect, var_psc_cache_map), O_CREAT | O_RDWR, PSC_DICT_OPEN_FLAGS); /* * Clean up and restore privilege. */ vstring_free(redirect); RESTORE_SAVED_EUGID(); /* * Initialize the dummy SMTP engine. */ psc_smtpd_pre_jail_init(); }
static void psc_endpt_haproxy_event(int event, char *context) { const char *myname = "psc_endpt_haproxy_event"; PSC_HAPROXY_STATE *state = (PSC_HAPROXY_STATE *) context; int status = 0; 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 last_char = 0; const char *err; VSTRING *escape_buf; char read_buf[HAPROXY_MAX_LEN]; ssize_t read_len; char *cp; /* * We must not read(2) past the <CR><LF> that terminates the haproxy * line. For efficiency reasons we read the entire haproxy line in one * read(2) call when we know that the line is unfragmented. In the rare * case that the line is fragmented, we fall back and read(2) it one * character at a time. */ switch (event) { case EVENT_TIME: msg_warn("haproxy read: time limit exceeded"); status = -1; break; case EVENT_READ: /* Determine the initial VSTREAM read(2) buffer size. */ if (VSTRING_LEN(state->buffer) == 0) { if ((read_len = recv(vstream_fileno(state->stream), read_buf, sizeof(read_buf) - 1, MSG_PEEK)) > 0 && ((cp = memchr(read_buf, '\n', read_len)) != 0)) { read_len = cp - read_buf + 1; } else { read_len = 1; } vstream_control(state->stream, VSTREAM_CTL_BUFSIZE, read_len, VSTREAM_CTL_END); } /* Drain the VSTREAM buffer, otherwise this pseudo-thread will hang. */ do { if ((last_char = VSTREAM_GETC(state->stream)) == VSTREAM_EOF) { if (vstream_ferror(state->stream)) msg_warn("haproxy read: %m"); else msg_warn("haproxy read: lost connection"); status = -1; break; } if (VSTRING_LEN(state->buffer) >= HAPROXY_MAX_LEN) { msg_warn("haproxy read: line too long"); status = -1; break; } VSTRING_ADDCH(state->buffer, last_char); } while (vstream_peek(state->stream) > 0); break; } /* * Parse the haproxy line. Note: the haproxy_srvr_parse() routine * performs address protocol checks, address and port syntax checks, and * converts IPv4-in-IPv6 address string syntax (:ffff::1.2.3.4) to IPv4 * syntax where permitted by the main.cf:inet_protocols setting. */ if (status == 0 && last_char == '\n') { VSTRING_TERMINATE(state->buffer); if ((err = haproxy_srvr_parse(vstring_str(state->buffer), &smtp_client_addr, &smtp_client_port, &smtp_server_addr, &smtp_server_port)) != 0) { escape_buf = vstring_alloc(HAPROXY_MAX_LEN + 2); escape(escape_buf, vstring_str(state->buffer), VSTRING_LEN(state->buffer)); msg_warn("haproxy read: %s: %s", err, vstring_str(escape_buf)); status = -1; vstring_free(escape_buf); } } /* * Are we done yet? */ if (status < 0 || last_char == '\n') { PSC_CLEAR_EVENT_REQUEST(vstream_fileno(state->stream), psc_endpt_haproxy_event, context); vstream_control(state->stream, VSTREAM_CTL_BUFSIZE, (ssize_t) VSTREAM_BUFSIZE, VSTREAM_CTL_END); state->notify(status, state->stream, &smtp_client_addr, &smtp_client_port, &smtp_server_addr, &smtp_server_port); /* Note: the stream may be closed at this point. */ vstring_free(state->buffer); myfree((char *) state); } }
static struct rspamd_osb_tokenizer_config * rspamd_tokenizer_osb_config_from_ucl (rspamd_mempool_t * pool, const ucl_object_t *obj) { const ucl_object_t *elt; struct rspamd_osb_tokenizer_config *cf, *def; guchar *key = NULL; gsize keylen; if (pool != NULL) { cf = rspamd_mempool_alloc (pool, sizeof (*cf)); } else { cf = g_slice_alloc (sizeof (*cf)); } /* Use default config */ def = rspamd_tokenizer_osb_default_config (); memcpy (cf, def, sizeof (*cf)); elt = ucl_object_find_key (obj, "hash"); if (elt != NULL && ucl_object_type (elt) == UCL_STRING) { if (g_ascii_strncasecmp (ucl_object_tostring (elt), "xxh", 3) == 0) { cf->ht = RSPAMD_OSB_HASH_XXHASH; elt = ucl_object_find_key (obj, "seed"); if (elt != NULL && ucl_object_type (elt) == UCL_INT) { cf->seed = ucl_object_toint (elt); } } else if (g_ascii_strncasecmp (ucl_object_tostring (elt), "sip", 3) == 0) { cf->ht = RSPAMD_OSB_HASH_SIPHASH; elt = ucl_object_find_key (obj, "key"); if (elt != NULL && ucl_object_type (elt) == UCL_STRING) { key = rspamd_decode_base32 (ucl_object_tostring (elt), 0, &keylen); if (keylen < sizeof (rspamd_sipkey_t)) { msg_warn ("siphash key is too short: %s", keylen); g_free (key); } else { memcpy (cf->sk, key, sizeof (cf->sk)); g_free (key); } } else { msg_warn_pool ("siphash cannot be used without key"); } } } else { elt = ucl_object_find_key (obj, "compat"); if (elt != NULL && ucl_object_toboolean (elt)) { cf->ht = RSPAMD_OSB_HASH_COMPAT; } } elt = ucl_object_find_key (obj, "window"); if (elt != NULL && ucl_object_type (elt) == UCL_INT) { cf->window_size = ucl_object_toint (elt); if (cf->window_size > DEFAULT_FEATURE_WINDOW_SIZE * 4) { msg_err_pool ("too large window size: %d", cf->window_size); cf->window_size = DEFAULT_FEATURE_WINDOW_SIZE; } } return cf; }
int milter_send(MILTERS *milters, VSTREAM *stream) { MILTER *m; int status = 0; int count = 0; /* * XXX Optimization: send only the filters that are actually used in the * remote process. No point sending a filter that looks at HELO commands * to a cleanup server. For now we skip only the filters that are known * to be disabled (either in global error state or in global accept * state). * * XXX We must send *some* information, even when there are no active * filters, otherwise the cleanup server would try to apply its own * non_smtpd_milters settings. */ if (milters != 0) for (m = milters->milter_list; m != 0; m = m->next) if (m->active(m)) count++; (void) rec_fprintf(stream, REC_TYPE_MILT_COUNT, "%d", count); if (msg_verbose) msg_info("send %d milters", count); /* * XXX Optimization: don't send or receive further information when there * aren't any active filters. */ if (count <= 0) return (0); /* * Send the filter macro name lists. */ (void) attr_print(stream, ATTR_FLAG_MORE, ATTR_TYPE_FUNC, milter_macros_print, (void *) milters->macros, ATTR_TYPE_END); /* * Send the filter instances. */ for (m = milters->milter_list; m != 0; m = m->next) if (m->active(m) && (status = m->send(m, stream)) != 0) break; /* * Over to you. */ if (status != 0 || attr_scan(stream, ATTR_FLAG_STRICT, ATTR_TYPE_INT, MAIL_ATTR_STATUS, &status, ATTR_TYPE_END) != 1 || status != 0) { msg_warn("cannot send milters to service %s", VSTREAM_PATH(stream)); return (-1); } return (0); }
static void enqueue(const int flags, const char *encoding, const char *dsn_envid, int dsn_notify, const char *rewrite_context, const char *sender, const char *full_name, char **recipients) { VSTRING *buf; VSTREAM *dst; char *saved_sender; char **cpp; int type; char *start; int skip_from_; TOK822 *tree; TOK822 *tp; int rcpt_count = 0; enum { STRIP_CR_DUNNO, STRIP_CR_DO, STRIP_CR_DONT } strip_cr; MAIL_STREAM *handle; VSTRING *postdrop_command; uid_t uid = getuid(); int status; int naddr; int prev_type; MIME_STATE *mime_state = 0; SM_STATE state; int mime_errs; const char *errstr; int addr_count; int level; /* * Access control is enforced in the postdrop command. The code here * merely produces a more user-friendly interface. */ if ((errstr = check_user_acl_byuid(var_submit_acl, uid)) != 0) msg_fatal_status(EX_NOPERM, "User %s(%ld) is not allowed to submit mail", errstr, (long) uid); /* * Initialize. */ buf = vstring_alloc(100); /* * Stop run-away process accidents by limiting the queue file size. This * is not a defense against DOS attack. */ if (var_message_limit > 0 && get_file_limit() > var_message_limit) set_file_limit((off_t) var_message_limit); /* * The sender name is provided by the user. In principle, the mail pickup * service could deduce the sender name from queue file ownership, but: * pickup would not be able to run chrooted, and it may not be desirable * to use login names at all. */ if (sender != 0) { VSTRING_RESET(buf); VSTRING_TERMINATE(buf); tree = tok822_parse(sender); for (naddr = 0, tp = tree; tp != 0; tp = tp->next) if (tp->type == TOK822_ADDR && naddr++ == 0) tok822_internalize(buf, tp->head, TOK822_STR_DEFL); tok822_free_tree(tree); saved_sender = mystrdup(STR(buf)); if (naddr > 1) msg_warn("-f option specified malformed sender: %s", sender); } else { if ((sender = username()) == 0) msg_fatal_status(EX_OSERR, "no login name found for user ID %lu", (unsigned long) uid); saved_sender = mystrdup(sender); } /* * Let the postdrop command open the queue file for us, and sanity check * the content. XXX Make postdrop a manifest constant. */ errno = 0; postdrop_command = vstring_alloc(1000); vstring_sprintf(postdrop_command, "%s/postdrop -r", var_command_dir); for (level = 0; level < msg_verbose; level++) vstring_strcat(postdrop_command, " -v"); if ((handle = mail_stream_command(STR(postdrop_command))) == 0) msg_fatal_status(EX_UNAVAILABLE, "%s(%ld): unable to execute %s: %m", saved_sender, (long) uid, STR(postdrop_command)); vstring_free(postdrop_command); dst = handle->stream; /* * First, write envelope information to the output stream. * * For sendmail compatibility, parse each command-line recipient as if it * were an RFC 822 message header; some MUAs specify comma-separated * recipient lists; and some MUAs even specify "word word <address>". * * Sort-uniq-ing the recipient list is done after address canonicalization, * before recipients are written to queue file. That's cleaner than * having the queue manager nuke duplicate recipient status records. * * XXX Should limit the size of envelope records. * * With "sendmail -N", instead of a per-message NOTIFY record we store one * per recipient so that we can simplify the implementation somewhat. */ if (dsn_envid) rec_fprintf(dst, REC_TYPE_ATTR, "%s=%s", MAIL_ATTR_DSN_ENVID, dsn_envid); rec_fprintf(dst, REC_TYPE_ATTR, "%s=%s", MAIL_ATTR_RWR_CONTEXT, rewrite_context); if (full_name || (full_name = fullname()) != 0) rec_fputs(dst, REC_TYPE_FULL, full_name); rec_fputs(dst, REC_TYPE_FROM, saved_sender); if (verp_delims && *saved_sender == 0) msg_fatal_status(EX_USAGE, "%s(%ld): -V option requires non-null sender address", saved_sender, (long) uid); if (encoding) rec_fprintf(dst, REC_TYPE_ATTR, "%s=%s", MAIL_ATTR_ENCODING, encoding); if (DEL_REQ_TRACE_FLAGS(flags)) rec_fprintf(dst, REC_TYPE_ATTR, "%s=%d", MAIL_ATTR_TRACE_FLAGS, DEL_REQ_TRACE_FLAGS(flags)); if (verp_delims) rec_fputs(dst, REC_TYPE_VERP, verp_delims); if (recipients) { for (cpp = recipients; *cpp != 0; cpp++) { tree = tok822_parse(*cpp); for (addr_count = 0, tp = tree; tp != 0; tp = tp->next) { if (tp->type == TOK822_ADDR) { tok822_internalize(buf, tp->head, TOK822_STR_DEFL); if (dsn_notify) rec_fprintf(dst, REC_TYPE_ATTR, "%s=%d", MAIL_ATTR_DSN_NOTIFY, dsn_notify); if (REC_PUT_BUF(dst, REC_TYPE_RCPT, buf) < 0) msg_fatal_status(EX_TEMPFAIL, "%s(%ld): error writing queue file: %m", saved_sender, (long) uid); ++rcpt_count; ++addr_count; } } tok822_free_tree(tree); if (addr_count == 0) { if (rec_put(dst, REC_TYPE_RCPT, "", 0) < 0) msg_fatal_status(EX_TEMPFAIL, "%s(%ld): error writing queue file: %m", saved_sender, (long) uid); ++rcpt_count; } } } /* * Append the message contents to the queue file. Write chunks of at most * 1kbyte. Internally, we use different record types for data ending in * LF and for data that doesn't, so we can actually be binary transparent * for local mail. Unfortunately, SMTP has no record continuation * convention, so there is no guarantee that arbitrary data will be * delivered intact via SMTP. Strip leading From_ lines. For the benefit * of UUCP environments, also get rid of leading >>>From_ lines. */ rec_fputs(dst, REC_TYPE_MESG, ""); if (DEL_REQ_TRACE_ONLY(flags) != 0) { if (flags & SM_FLAG_XRCPT) msg_fatal_status(EX_USAGE, "%s(%ld): -t option cannot be used with -bv", saved_sender, (long) uid); if (*saved_sender) rec_fprintf(dst, REC_TYPE_NORM, "From: %s", saved_sender); rec_fprintf(dst, REC_TYPE_NORM, "Subject: probe"); if (recipients) { rec_fprintf(dst, REC_TYPE_CONT, "To:"); for (cpp = recipients; *cpp != 0; cpp++) { rec_fprintf(dst, REC_TYPE_NORM, " %s%s", *cpp, cpp[1] ? "," : ""); } } } else { /* * Initialize the MIME processor and set up the callback context. */ if (flags & SM_FLAG_XRCPT) { state.dst = dst; state.recipients = argv_alloc(2); state.resent_recip = argv_alloc(2); state.resent = 0; state.saved_sender = saved_sender; state.uid = uid; state.temp = vstring_alloc(10); mime_state = mime_state_alloc(MIME_OPT_DISABLE_MIME | MIME_OPT_REPORT_TRUNC_HEADER, output_header, (MIME_STATE_ANY_END) 0, output_text, (MIME_STATE_ANY_END) 0, (MIME_STATE_ERR_PRINT) 0, (void *) &state); } /* * Process header/body lines. */ skip_from_ = 1; strip_cr = STRIP_CR_DUNNO; for (prev_type = 0; (type = rec_streamlf_get(VSTREAM_IN, buf, var_line_limit)) != REC_TYPE_EOF; prev_type = type) { if (strip_cr == STRIP_CR_DUNNO && type == REC_TYPE_NORM) { if (VSTRING_LEN(buf) > 0 && vstring_end(buf)[-1] == '\r') strip_cr = STRIP_CR_DO; else strip_cr = STRIP_CR_DONT; } if (skip_from_) { if (type == REC_TYPE_NORM) { start = STR(buf); if (strncmp(start + strspn(start, ">"), "From ", 5) == 0) continue; } skip_from_ = 0; } if (strip_cr == STRIP_CR_DO && type == REC_TYPE_NORM) if (VSTRING_LEN(buf) > 0 && vstring_end(buf)[-1] == '\r') vstring_truncate(buf, VSTRING_LEN(buf) - 1); if ((flags & SM_FLAG_AEOF) && prev_type != REC_TYPE_CONT && VSTRING_LEN(buf) == 1 && *STR(buf) == '.') break; if (mime_state) { mime_errs = mime_state_update(mime_state, type, STR(buf), VSTRING_LEN(buf)); if (mime_errs) msg_fatal_status(EX_DATAERR, "%s(%ld): unable to extract recipients: %s", saved_sender, (long) uid, mime_state_error(mime_errs)); } else { if (REC_PUT_BUF(dst, type, buf) < 0) msg_fatal_status(EX_TEMPFAIL, "%s(%ld): error writing queue file: %m", saved_sender, (long) uid); } } } /* * Finish MIME processing. We need a final mime_state_update() call in * order to flush text that is still buffered. That can happen when the * last line did not end in newline. */ if (mime_state) { mime_errs = mime_state_update(mime_state, REC_TYPE_EOF, "", 0); if (mime_errs) msg_fatal_status(EX_DATAERR, "%s(%ld): unable to extract recipients: %s", saved_sender, (long) uid, mime_state_error(mime_errs)); mime_state = mime_state_free(mime_state); } /* * Append recipient addresses that were extracted from message headers. */ rec_fputs(dst, REC_TYPE_XTRA, ""); if (flags & SM_FLAG_XRCPT) { for (cpp = state.resent ? state.resent_recip->argv : state.recipients->argv; *cpp; cpp++) { if (dsn_notify) rec_fprintf(dst, REC_TYPE_ATTR, "%s=%d", MAIL_ATTR_DSN_NOTIFY, dsn_notify); if (rec_put(dst, REC_TYPE_RCPT, *cpp, strlen(*cpp)) < 0) msg_fatal_status(EX_TEMPFAIL, "%s(%ld): error writing queue file: %m", saved_sender, (long) uid); ++rcpt_count; } argv_free(state.recipients); argv_free(state.resent_recip); vstring_free(state.temp); } if (rcpt_count == 0) msg_fatal_status(EX_USAGE, (flags & SM_FLAG_XRCPT) ? "%s(%ld): No recipient addresses found in message header" : "Recipient addresses must be specified on" " the command line or via the -t option", saved_sender, (long) uid); /* * Identify the end of the queue file. */ rec_fputs(dst, REC_TYPE_END, ""); /* * Make sure that the message makes it to the file system. Once we have * terminated with successful exit status we cannot lose the message due * to "frivolous reasons". If all goes well, prevent the run-time error * handler from removing the file. */ if (vstream_ferror(VSTREAM_IN)) msg_fatal_status(EX_DATAERR, "%s(%ld): error reading input: %m", saved_sender, (long) uid); if ((status = mail_stream_finish(handle, (VSTRING *) 0)) != 0) msg_fatal_status((status & CLEANUP_STAT_BAD) ? EX_SOFTWARE : (status & CLEANUP_STAT_WRITE) ? EX_TEMPFAIL : EX_UNAVAILABLE, "%s(%ld): %s", saved_sender, (long) uid, cleanup_strerror(status)); /* * Don't leave them in the dark. */ if (DEL_REQ_TRACE_FLAGS(flags)) { vstream_printf("Mail Delivery Status Report will be mailed to <%s>.\n", saved_sender); vstream_fflush(VSTREAM_OUT); } /* * Cleanup. Not really necessary as we're about to exit, but good for * debugging purposes. */ vstring_free(buf); myfree(saved_sender); }
int main(int argc, char **argv) { MILTERS *milters = 0; char *conn_macros, *helo_macros, *mail_macros, *rcpt_macros; char *data_macros, *eoh_macros, *eod_macros, *unk_macros; VSTRING *inbuf = vstring_alloc(100); char *bufp; char *cmd; int ch; int istty = isatty(vstream_fileno(VSTREAM_IN)); conn_macros = helo_macros = mail_macros = rcpt_macros = data_macros = eoh_macros = eod_macros = unk_macros = ""; msg_vstream_init(argv[0], VSTREAM_ERR); while ((ch = GETOPT(argc, argv, "V:v")) > 0) { switch (ch) { default: msg_fatal("usage: %s [-a action] [-p protocol] [-v]", argv[0]); case 'a': var_milt_def_action = optarg; break; case 'p': var_milt_protocol = optarg; break; case 'v': msg_verbose++; break; } } optind = OPTIND; for (;;) { const char *resp = 0; ARGV *argv; char **args; if (istty) { vstream_printf("- "); vstream_fflush(VSTREAM_OUT); } if (vstring_fgets_nonl(inbuf, VSTREAM_IN) <= 0) break; bufp = vstring_str(inbuf); if (!istty) { vstream_printf("> %s\n", bufp); vstream_fflush(VSTREAM_OUT); } if (*bufp == '#') continue; cmd = mystrtok(&bufp, " "); if (cmd == 0) { usage(); continue; } argv = argv_split(bufp, " "); args = argv->argv; if (strcmp(cmd, "create") == 0 && argv->argc == 1) { if (milters != 0) { msg_warn("deleting existing milters"); milter_free(milters); } milters = milter_create(args[0], var_milt_conn_time, var_milt_cmd_time, var_milt_msg_time, var_milt_protocol, var_milt_def_action, conn_macros, helo_macros, mail_macros, rcpt_macros, data_macros, eoh_macros, eod_macros, unk_macros); } else if (strcmp(cmd, "free") == 0 && argv->argc == 0) { if (milters == 0) { msg_warn("no milters"); continue; } milter_free(milters); milters = 0; } else if (strcmp(cmd, "connect") == 0 && argv->argc == 4) { if (milters == 0) { msg_warn("no milters"); continue; } resp = milter_conn_event(milters, args[0], args[1], args[2], strcmp(args[3], "AF_INET") == 0 ? AF_INET : strcmp(args[3], "AF_INET6") == 0 ? AF_INET6 : strcmp(args[3], "AF_UNIX") == 0 ? AF_UNIX : AF_UNSPEC); } else if (strcmp(cmd, "helo") == 0 && argv->argc == 1) { if (milters == 0) { msg_warn("no milters"); continue; } resp = milter_helo_event(milters, args[0], 0); } else if (strcmp(cmd, "ehlo") == 0 && argv->argc == 1) { if (milters == 0) { msg_warn("no milters"); continue; } resp = milter_helo_event(milters, args[0], 1); } else if (strcmp(cmd, "mail") == 0 && argv->argc > 0) { if (milters == 0) { msg_warn("no milters"); continue; } resp = milter_mail_event(milters, (const char **) args); } else if (strcmp(cmd, "rcpt") == 0 && argv->argc > 0) { if (milters == 0) { msg_warn("no milters"); continue; } resp = milter_rcpt_event(milters, 0, (const char **) args); } else if (strcmp(cmd, "unknown") == 0 && argv->argc > 0) { if (milters == 0) { msg_warn("no milters"); continue; } resp = milter_unknown_event(milters, args[0]); } else if (strcmp(cmd, "data") == 0 && argv->argc == 0) { if (milters == 0) { msg_warn("no milters"); continue; } resp = milter_data_event(milters); } else if (strcmp(cmd, "disconnect") == 0 && argv->argc == 0) { if (milters == 0) { msg_warn("no milters"); continue; } milter_disc_event(milters); } else { usage(); } if (resp != 0) msg_info("%s", resp); argv_free(argv); } if (milters != 0) milter_free(milters); vstring_free(inbuf); return (0); }
int main(int argc, char **argv) { static char *full_name = 0; /* sendmail -F */ struct stat st; char *slash; char *sender = 0; /* sendmail -f */ int c; int fd; int mode; ARGV *ext_argv; int debug_me = 0; int err; int n; int flags = SM_FLAG_DEFAULT; char *site_to_flush = 0; char *id_to_flush = 0; char *encoding = 0; char *qtime = 0; const char *errstr; uid_t uid; const char *rewrite_context = MAIL_ATTR_RWR_LOCAL; int dsn_notify = 0; const char *dsn_envid = 0; int saved_optind; /* * Fingerprint executables and core dumps. */ MAIL_VERSION_STAMP_ALLOCATE; /* * Be consistent with file permissions. */ umask(022); /* * To minimize confusion, make sure that the standard file descriptors * are open before opening anything else. XXX Work around for 44BSD where * fstat can return EBADF on an open file descriptor. */ for (fd = 0; fd < 3; fd++) if (fstat(fd, &st) == -1 && (close(fd), open("/dev/null", O_RDWR, 0)) != fd) msg_fatal_status(EX_OSERR, "open /dev/null: %m"); /* * The CDE desktop calendar manager leaks a parent file descriptor into * the child process. For the sake of sendmail compatibility we have to * close the file descriptor otherwise mail notification will hang. */ for ( /* void */ ; fd < 100; fd++) (void) close(fd); /* * Process environment options as early as we can. We might be called * from a set-uid (set-gid) program, so be careful with importing * environment variables. */ if (safe_getenv(CONF_ENV_VERB)) msg_verbose = 1; if (safe_getenv(CONF_ENV_DEBUG)) debug_me = 1; /* * Initialize. Set up logging, read the global configuration file and * extract configuration information. Set up signal handlers so that we * can clean up incomplete output. */ if ((slash = strrchr(argv[0], '/')) != 0 && slash[1]) argv[0] = slash + 1; msg_vstream_init(argv[0], VSTREAM_ERR); msg_cleanup(tempfail); msg_syslog_init(mail_task("sendmail"), LOG_PID, LOG_FACILITY); set_mail_conf_str(VAR_PROCNAME, var_procname = mystrdup(argv[0])); /* * Some sites mistakenly install Postfix sendmail as set-uid root. Drop * set-uid privileges only when root, otherwise some systems will not * reset the saved set-userid, which would be a security vulnerability. */ if (geteuid() == 0 && getuid() != 0) { msg_warn("the Postfix sendmail command has set-uid root file permissions"); msg_warn("or the command is run from a set-uid root process"); msg_warn("the Postfix sendmail command must be installed without set-uid root file permissions"); set_ugid(getuid(), getgid()); } /* * Further initialization. Load main.cf first, so that command-line * options can override main.cf settings. Pre-scan the argument list so * that we load the right main.cf file. */ #define GETOPT_LIST "A:B:C:F:GIL:N:O:R:UV:X:b:ce:f:h:imno:p:r:q:tvx" saved_optind = optind; while (argv[OPTIND] != 0) { if (strcmp(argv[OPTIND], "-q") == 0) { /* not getopt compatible */ optind++; continue; } if ((c = GETOPT(argc, argv, GETOPT_LIST)) <= 0) break; if (c == 'C') { VSTRING *buf = vstring_alloc(1); if (setenv(CONF_ENV_PATH, strcmp(sane_basename(buf, optarg), MAIN_CONF_FILE) == 0 ? sane_dirname(buf, optarg) : optarg, 1) < 0) msg_fatal_status(EX_UNAVAILABLE, "out of memory"); vstring_free(buf); } } optind = saved_optind; mail_conf_read(); if (strcmp(var_syslog_name, DEF_SYSLOG_NAME) != 0) msg_syslog_init(mail_task("sendmail"), LOG_PID, LOG_FACILITY); get_mail_conf_str_table(str_table); if (chdir(var_queue_dir)) msg_fatal_status(EX_UNAVAILABLE, "chdir %s: %m", var_queue_dir); signal(SIGPIPE, SIG_IGN); /* * Optionally start the debugger on ourself. This must be done after * reading the global configuration file, because that file specifies * what debugger command to execute. */ if (debug_me) debug_process(); /* * The default mode of operation is determined by the process name. It * can, however, be changed via command-line options (for example, * "newaliases -bp" will show the mail queue). */ if (strcmp(argv[0], "mailq") == 0) { mode = SM_MODE_MAILQ; } else if (strcmp(argv[0], "newaliases") == 0) { mode = SM_MODE_NEWALIAS; } else if (strcmp(argv[0], "smtpd") == 0) { mode = SM_MODE_DAEMON; } else { mode = SM_MODE_ENQUEUE; } /* * Parse JCL. Sendmail has been around for a long time, and has acquired * a large number of options in the course of time. Some options such as * -q are not parsable with GETOPT() and get special treatment. */ #define OPTIND (optind > 0 ? optind : 1) while (argv[OPTIND] != 0) { if (strcmp(argv[OPTIND], "-q") == 0) { if (mode == SM_MODE_DAEMON) msg_warn("ignoring -q option in daemon mode"); else mode = SM_MODE_FLUSHQ; optind++; continue; } if (strcmp(argv[OPTIND], "-V") == 0) { msg_warn("option -V is deprecated with Postfix 2.3; " "specify -XV instead"); argv[OPTIND] = "-XV"; } if (strncmp(argv[OPTIND], "-V", 2) == 0 && strlen(argv[OPTIND]) == 4) { msg_warn("option %s is deprecated with Postfix 2.3; " "specify -X%s instead", argv[OPTIND], argv[OPTIND] + 1); argv[OPTIND] = concatenate("-X", argv[OPTIND] + 1, (char *) 0); } if (strcmp(argv[OPTIND], "-XV") == 0) { verp_delims = var_verp_delims; optind++; continue; } if ((c = GETOPT(argc, argv, GETOPT_LIST)) <= 0) break; switch (c) { default: if (msg_verbose) msg_info("-%c option ignored", c); break; case 'n': msg_fatal_status(EX_USAGE, "-%c option not supported", c); case 'B': if (strcmp(optarg, "8BITMIME") == 0)/* RFC 1652 */ encoding = MAIL_ATTR_ENC_8BIT; else if (strcmp(optarg, "7BIT") == 0) /* RFC 1652 */ encoding = MAIL_ATTR_ENC_7BIT; else msg_fatal_status(EX_USAGE, "-B option needs 8BITMIME or 7BIT"); break; case 'F': /* full name */ full_name = optarg; break; case 'G': /* gateway submission */ rewrite_context = MAIL_ATTR_RWR_REMOTE; break; case 'I': /* newaliases */ mode = SM_MODE_NEWALIAS; break; case 'N': if ((dsn_notify = dsn_notify_mask(optarg)) == 0) msg_warn("bad -N option value -- ignored"); break; case 'V': /* DSN, was: VERP */ if (strlen(optarg) > 100) msg_warn("too long -V option value -- ignored"); else if (!allprint(optarg)) msg_warn("bad syntax in -V option value -- ignored"); else dsn_envid = optarg; break; case 'X': switch (*optarg) { default: msg_fatal_status(EX_USAGE, "unsupported: -%c%c", c, *optarg); case 'V': /* VERP */ if (verp_delims_verify(optarg + 1) != 0) msg_fatal_status(EX_USAGE, "-V requires two characters from %s", var_verp_filter); verp_delims = optarg + 1; break; } break; case 'b': switch (*optarg) { default: msg_fatal_status(EX_USAGE, "unsupported: -%c%c", c, *optarg); case 'd': /* daemon mode */ if (mode == SM_MODE_FLUSHQ) msg_warn("ignoring -q option in daemon mode"); mode = SM_MODE_DAEMON; break; case 'h': /* print host status */ case 'H': /* flush host status */ mode = SM_MODE_IGNORE; break; case 'i': /* newaliases */ mode = SM_MODE_NEWALIAS; break; case 'm': /* deliver mail */ mode = SM_MODE_ENQUEUE; break; case 'p': /* mailq */ mode = SM_MODE_MAILQ; break; case 's': /* stand-alone mode */ mode = SM_MODE_USER; break; case 'v': /* expand recipients */ flags |= DEL_REQ_FLAG_USR_VRFY; break; } break; case 'f': sender = optarg; break; case 'i': flags &= ~SM_FLAG_AEOF; break; case 'o': switch (*optarg) { default: if (msg_verbose) msg_info("-%c%c option ignored", c, *optarg); break; case 'A': if (optarg[1] == 0) msg_fatal_status(EX_USAGE, "-oA requires pathname"); myfree(var_alias_db_map); var_alias_db_map = mystrdup(optarg + 1); set_mail_conf_str(VAR_ALIAS_DB_MAP, var_alias_db_map); break; case '7': case '8': break; case 'i': flags &= ~SM_FLAG_AEOF; break; case 'm': break; } break; case 'r': /* obsoleted by -f */ sender = optarg; break; case 'q': if (ISDIGIT(optarg[0])) { qtime = optarg; } else if (optarg[0] == 'R') { site_to_flush = optarg + 1; if (*site_to_flush == 0) msg_fatal_status(EX_USAGE, "specify: -qRsitename"); } else if (optarg[0] == 'I') { id_to_flush = optarg + 1; if (*id_to_flush == 0) msg_fatal_status(EX_USAGE, "specify: -qIqueueid"); } else { msg_fatal_status(EX_USAGE, "-q%c is not implemented", optarg[0]); } break; case 't': flags |= SM_FLAG_XRCPT; break; case 'v': msg_verbose++; break; case '?': msg_fatal_status(EX_USAGE, "usage: %s [options]", argv[0]); } } /* * Look for conflicting options and arguments. */ if ((flags & SM_FLAG_XRCPT) && mode != SM_MODE_ENQUEUE) msg_fatal_status(EX_USAGE, "-t can be used only in delivery mode"); if (site_to_flush && mode != SM_MODE_ENQUEUE) msg_fatal_status(EX_USAGE, "-qR can be used only in delivery mode"); if (id_to_flush && mode != SM_MODE_ENQUEUE) msg_fatal_status(EX_USAGE, "-qI can be used only in delivery mode"); if (flags & DEL_REQ_FLAG_USR_VRFY) { if (flags & SM_FLAG_XRCPT) msg_fatal_status(EX_USAGE, "-t option cannot be used with -bv"); if (dsn_notify) msg_fatal_status(EX_USAGE, "-N option cannot be used with -bv"); if (msg_verbose == 1) msg_fatal_status(EX_USAGE, "-v option cannot be used with -bv"); } /* * The -v option plays double duty. One requests verbose delivery, more * than one requests verbose logging. */ if (msg_verbose == 1 && mode == SM_MODE_ENQUEUE) { msg_verbose = 0; flags |= DEL_REQ_FLAG_RECORD; } /* * Start processing. Everything is delegated to external commands. */ if (qtime && mode != SM_MODE_DAEMON) exit(0); switch (mode) { default: msg_panic("unknown operation mode: %d", mode); /* NOTREACHED */ case SM_MODE_ENQUEUE: if (site_to_flush) { if (argv[OPTIND]) msg_fatal_status(EX_USAGE, "flush site requires no recipient"); ext_argv = argv_alloc(2); argv_add(ext_argv, "postqueue", "-s", site_to_flush, (char *) 0); for (n = 0; n < msg_verbose; n++) argv_add(ext_argv, "-v", (char *) 0); argv_terminate(ext_argv); mail_run_replace(var_command_dir, ext_argv->argv); /* NOTREACHED */ } else if (id_to_flush) { if (argv[OPTIND]) msg_fatal_status(EX_USAGE, "flush queue_id requires no recipient"); ext_argv = argv_alloc(2); argv_add(ext_argv, "postqueue", "-i", id_to_flush, (char *) 0); for (n = 0; n < msg_verbose; n++) argv_add(ext_argv, "-v", (char *) 0); argv_terminate(ext_argv); mail_run_replace(var_command_dir, ext_argv->argv); /* NOTREACHED */ } else { enqueue(flags, encoding, dsn_envid, dsn_notify, rewrite_context, sender, full_name, argv + OPTIND); exit(0); /* NOTREACHED */ } break; case SM_MODE_MAILQ: if (argv[OPTIND]) msg_fatal_status(EX_USAGE, "display queue mode requires no recipient"); ext_argv = argv_alloc(2); argv_add(ext_argv, "postqueue", "-p", (char *) 0); for (n = 0; n < msg_verbose; n++) argv_add(ext_argv, "-v", (char *) 0); argv_terminate(ext_argv); mail_run_replace(var_command_dir, ext_argv->argv); /* NOTREACHED */ case SM_MODE_FLUSHQ: if (argv[OPTIND]) msg_fatal_status(EX_USAGE, "flush queue mode requires no recipient"); ext_argv = argv_alloc(2); argv_add(ext_argv, "postqueue", "-f", (char *) 0); for (n = 0; n < msg_verbose; n++) argv_add(ext_argv, "-v", (char *) 0); argv_terminate(ext_argv); mail_run_replace(var_command_dir, ext_argv->argv); /* NOTREACHED */ case SM_MODE_DAEMON: if (argv[OPTIND]) msg_fatal_status(EX_USAGE, "daemon mode requires no recipient"); ext_argv = argv_alloc(2); argv_add(ext_argv, "postfix", (char *) 0); for (n = 0; n < msg_verbose; n++) argv_add(ext_argv, "-v", (char *) 0); argv_add(ext_argv, "start", (char *) 0); argv_terminate(ext_argv); err = (mail_run_background(var_command_dir, ext_argv->argv) < 0); argv_free(ext_argv); exit(err); break; case SM_MODE_NEWALIAS: if (argv[OPTIND]) msg_fatal_status(EX_USAGE, "alias initialization mode requires no recipient"); if (*var_alias_db_map == 0) return (0); ext_argv = argv_alloc(2); argv_add(ext_argv, "postalias", (char *) 0); for (n = 0; n < msg_verbose; n++) argv_add(ext_argv, "-v", (char *) 0); argv_split_append(ext_argv, var_alias_db_map, ", \t\r\n"); argv_terminate(ext_argv); mail_run_replace(var_command_dir, ext_argv->argv); /* NOTREACHED */ case SM_MODE_USER: if (argv[OPTIND]) msg_fatal_status(EX_USAGE, "stand-alone mode requires no recipient"); /* The actual enforcement happens in the postdrop command. */ if ((errstr = check_user_acl_byuid(var_submit_acl, uid = getuid())) != 0) msg_fatal_status(EX_NOPERM, "User %s(%ld) is not allowed to submit mail", errstr, (long) uid); ext_argv = argv_alloc(2); argv_add(ext_argv, "smtpd", "-S", (char *) 0); for (n = 0; n < msg_verbose; n++) argv_add(ext_argv, "-v", (char *) 0); argv_terminate(ext_argv); mail_run_replace(var_daemon_dir, ext_argv->argv); /* NOTREACHED */ case SM_MODE_IGNORE: exit(0); /* NOTREACHED */ } }
static ARGV *expand_argv(const char *service, char **argv, RECIPIENT_LIST *rcpt_list, int flags) { VSTRING *buf = vstring_alloc(100); ARGV *result; char **cpp; PIPE_STATE state; int i; char *ext; char *dom; /* * This appears to be simple operation (replace $name by its expansion). * However, it becomes complex because a command-line argument that * references $recipient must expand to as many command-line arguments as * there are recipients (that's wat programs called by sendmail expect). * So we parse each command-line argument, and depending on what we find, * we either expand the argument just once, or we expand it once for each * recipient. In either case we end up parsing the command-line argument * twice. The amount of CPU time wasted will be negligible. * * Note: we can't use recursive macro expansion here, because recursion * would screw up mail addresses that contain $ characters. */ #define NO 0 #define EARLY_RETURN(x) { argv_free(result); vstring_free(buf); return (x); } result = argv_alloc(1); for (cpp = argv; *cpp; cpp++) { state.service = service; state.expand_flag = 0; if (mac_parse(*cpp, parse_callback, (char *) &state) & MAC_PARSE_ERROR) EARLY_RETURN(0); if (state.expand_flag == 0) { /* no $recipient etc. */ argv_add(result, dict_eval(PIPE_DICT_TABLE, *cpp, NO), ARGV_END); } else { /* contains $recipient etc. */ for (i = 0; i < rcpt_list->len; i++) { /* * This argument contains $recipient. */ if (state.expand_flag & PIPE_FLAG_RCPT) { morph_recipient(buf, rcpt_list->info[i].address, flags); dict_update(PIPE_DICT_TABLE, PIPE_DICT_RCPT, STR(buf)); } /* * This argument contains $original_recipient. */ if (state.expand_flag & PIPE_FLAG_ORIG_RCPT) { morph_recipient(buf, rcpt_list->info[i].orig_addr, flags); dict_update(PIPE_DICT_TABLE, PIPE_DICT_ORIG_RCPT, STR(buf)); } /* * This argument contains $user. Extract the plain user name. * Either anything to the left of the extension delimiter or, * in absence of the latter, anything to the left of the * rightmost @. * * Beware: if the user name is blank (e.g. +user@host), the * argument is suppressed. This is necessary to allow for * cyrus bulletin-board (global mailbox) delivery. XXX But, * skipping empty user parts will also prevent other * expansions of this specific command-line argument. */ if (state.expand_flag & PIPE_FLAG_USER) { morph_recipient(buf, rcpt_list->info[i].address, flags & PIPE_OPT_FOLD_ALL); if (split_at_right(STR(buf), '@') == 0) msg_warn("no @ in recipient address: %s", rcpt_list->info[i].address); if (*var_rcpt_delim) split_addr(STR(buf), var_rcpt_delim); if (*STR(buf) == 0) continue; dict_update(PIPE_DICT_TABLE, PIPE_DICT_USER, STR(buf)); } /* * This argument contains $extension. Extract the recipient * extension: anything between the leftmost extension * delimiter and the rightmost @. The extension may be blank. */ if (state.expand_flag & PIPE_FLAG_EXTENSION) { morph_recipient(buf, rcpt_list->info[i].address, flags & PIPE_OPT_FOLD_ALL); if (split_at_right(STR(buf), '@') == 0) msg_warn("no @ in recipient address: %s", rcpt_list->info[i].address); if (*var_rcpt_delim == 0 || (ext = split_addr(STR(buf), var_rcpt_delim)) == 0) ext = ""; /* insert null arg */ dict_update(PIPE_DICT_TABLE, PIPE_DICT_EXTENSION, ext); } /* * This argument contains $mailbox. Extract the mailbox name: * anything to the left of the rightmost @. */ if (state.expand_flag & PIPE_FLAG_MAILBOX) { morph_recipient(buf, rcpt_list->info[i].address, flags & PIPE_OPT_FOLD_ALL); if (split_at_right(STR(buf), '@') == 0) msg_warn("no @ in recipient address: %s", rcpt_list->info[i].address); dict_update(PIPE_DICT_TABLE, PIPE_DICT_MAILBOX, STR(buf)); } /* * This argument contains $domain. Extract the domain name: * anything to the right of the rightmost @. */ if (state.expand_flag & PIPE_FLAG_DOMAIN) { morph_recipient(buf, rcpt_list->info[i].address, flags & PIPE_OPT_FOLD_ALL); dom = split_at_right(STR(buf), '@'); if (dom == 0) { msg_warn("no @ in recipient address: %s", rcpt_list->info[i].address); dom = ""; /* insert null arg */ } dict_update(PIPE_DICT_TABLE, PIPE_DICT_DOMAIN, dom); } /* * Done. */ argv_add(result, dict_eval(PIPE_DICT_TABLE, *cpp, NO), ARGV_END); } } } argv_terminate(result); vstring_free(buf); return (result); }
static void cleanup_service(VSTREAM *src, char *unused_service, char **argv) { VSTRING *buf = vstring_alloc(100); CLEANUP_STATE *state; int flags; int type = 0; /* * 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(); /* * 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, ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, state->queue_id, ATTR_TYPE_END); if (attr_scan(src, ATTR_FLAG_STRICT, ATTR_TYPE_NUM, 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(src, buf, 0)) < 0) { 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(src, buf, 0)) > 0) /* void */ ; } /* * 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. */ attr_print(src, ATTR_FLAG_NONE, ATTR_TYPE_NUM, MAIL_ATTR_STATUS, cleanup_flush(state), ATTR_TYPE_STR, MAIL_ATTR_WHY, state->reason ? state->reason : "", ATTR_TYPE_END); cleanup_free(state); /* * Cleanup. */ vstring_free(buf); }
static int flush_one_file(const char *queue_id, VSTRING *queue_file, struct utimbuf * tbuf, int how) { const char *myname = "flush_one_file"; const char *queue_name; const char *path; /* * Some other instance of this program may flush some logfile and may * just have moved this queue file to the incoming queue. */ for (queue_name = MAIL_QUEUE_DEFERRED; /* see below */ ; queue_name = MAIL_QUEUE_INCOMING) { path = mail_queue_path(queue_file, queue_name, queue_id); if (utime(path, tbuf) == 0) break; if (errno != ENOENT) msg_warn("%s: update %s time stamps: %m", myname, path); if (STREQ(queue_name, MAIL_QUEUE_INCOMING)) return (0); } /* * With the UNTHROTTLE_AFTER strategy, we leave it up to the queue * manager to unthrottle transports and queues as it reads recipients * from a queue file. We request this unthrottle operation by setting the * group read permission bit. * * Note: we must avoid using chmod(). It is not only slower than fchmod() * but it is also less secure. With chmod(), an attacker could repeatedly * send requests to the flush server and trick it into changing * permissions of non-queue files, by exploiting a race condition. * * We use safe_open() because we don't validate the file content before * modifying the file status. */ if (how & UNTHROTTLE_AFTER) { VSTRING *why; struct stat st; VSTREAM *fp; for (why = vstring_alloc(1); /* see below */ ; queue_name = MAIL_QUEUE_INCOMING, path = mail_queue_path(queue_file, queue_name, queue_id)) { if ((fp = safe_open(path, O_RDWR, 0, &st, -1, -1, why)) != 0) break; if (errno != ENOENT) msg_warn("%s: open %s: %s", myname, path, STR(why)); if (errno != ENOENT || STREQ(queue_name, MAIL_QUEUE_INCOMING)) { vstring_free(why); return (0); } } vstring_free(why); if ((st.st_mode & MAIL_QUEUE_STAT_READY) != MAIL_QUEUE_STAT_READY) { (void) vstream_fclose(fp); return (0); } if (fchmod(vstream_fileno(fp), st.st_mode | MAIL_QUEUE_STAT_UNTHROTTLE) < 0) msg_warn("%s: fchmod %s: %m", myname, path); (void) vstream_fclose(fp); } /* * Move the file to the incoming queue, if it isn't already there. */ if (STREQ(queue_name, MAIL_QUEUE_INCOMING) == 0 && mail_queue_rename(queue_id, queue_name, MAIL_QUEUE_INCOMING) < 0 && errno != ENOENT) msg_warn("%s: rename from %s to %s: %m", path, queue_name, MAIL_QUEUE_INCOMING); /* * If we got here, we achieved something, so let's claim succes. */ return (1); }
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; int auths; 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_AUTH, cmd_len) == 0) { if (anvil_clnt_auth(anvil, service, addr, &auths) != ANVIL_STAT_OK) msg_warn("error!"); else vstream_printf("rate=%d\n", auths); } 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, &auths) != ANVIL_STAT_OK) msg_warn("error!"); else vstream_printf("count=%d, rate=%d msgs=%d rcpts=%d newtls=%d " "auths=%d\n", count, rate, msgs, rcpts, newtls, auths); } else { vstream_printf("bad command: \"%s\"\n", cmd); usage(); } vstream_fflush(VSTREAM_OUT); } vstring_free(inbuf); anvil_clnt_free(anvil); return (0); }
TLS_APPL_STATE *tls_client_init(const TLS_CLIENT_INIT_PROPS *props) { long off = 0; int cachable; SSL_CTX *client_ctx; TLS_APPL_STATE *app_ctx; const EVP_MD *md_alg; unsigned int md_len; int log_mask; /* * Convert user loglevel to internal logmask. */ log_mask = tls_log_mask(props->log_param, props->log_level); if (log_mask & TLS_LOG_VERBOSE) msg_info("initializing the client-side TLS engine"); /* * Load (mostly cipher related) TLS-library internal main.cf parameters. */ tls_param_init(); /* * Detect mismatch between compile-time headers and run-time library. */ tls_check_version(); /* * Initialize the OpenSSL library by the book! To start with, we must * initialize the algorithms. We want cleartext error messages instead of * just error codes, so we load the error_strings. */ SSL_load_error_strings(); OpenSSL_add_ssl_algorithms(); /* * Create an application data index for SSL objects, so that we can * attach TLScontext information; this information is needed inside * tls_verify_certificate_callback(). */ if (TLScontext_index < 0) { if ((TLScontext_index = SSL_get_ex_new_index(0, 0, 0, 0, 0)) < 0) { msg_warn("Cannot allocate SSL application data index: " "disabling TLS support"); return (0); } } /* * If the administrator specifies an unsupported digest algorithm, fail * now, rather than in the middle of a TLS handshake. */ if ((md_alg = EVP_get_digestbyname(props->fpt_dgst)) == 0) { msg_warn("Digest algorithm \"%s\" not found: disabling TLS support", props->fpt_dgst); return (0); } /* * Sanity check: Newer shared libraries may use larger digests. */ if ((md_len = EVP_MD_size(md_alg)) > EVP_MAX_MD_SIZE) { msg_warn("Digest algorithm \"%s\" output size %u too large:" " disabling TLS support", props->fpt_dgst, md_len); return (0); } /* * Initialize the PRNG (Pseudo Random Number Generator) with some seed * from external and internal sources. Don't enable TLS without some real * entropy. */ if (tls_ext_seed(var_tls_daemon_rand_bytes) < 0) { msg_warn("no entropy for TLS key generation: disabling TLS support"); return (0); } tls_int_seed(); /* * The SSL/TLS specifications require the client to send a message in the * oldest specification it understands with the highest level it * understands in the message. RFC2487 is only specified for TLSv1, but * we want to be as compatible as possible, so we will start off with a * SSLv2 greeting allowing the best we can offer: TLSv1. We can restrict * this with the options setting later, anyhow. */ ERR_clear_error(); if ((client_ctx = SSL_CTX_new(SSLv23_client_method())) == 0) { msg_warn("cannot allocate client SSL_CTX: disabling TLS support"); tls_print_errors(); return (0); } /* * See the verify callback in tls_verify.c */ SSL_CTX_set_verify_depth(client_ctx, props->verifydepth + 1); /* * Protocol selection is destination dependent, so we delay the protocol * selection options to the per-session SSL object. */ off |= tls_bug_bits(); SSL_CTX_set_options(client_ctx, off); /* * Set the call-back routine for verbose logging. */ if (log_mask & TLS_LOG_DEBUG) SSL_CTX_set_info_callback(client_ctx, tls_info_callback); /* * Load the CA public key certificates for both the client cert and for * the verification of server certificates. As provided by OpenSSL we * support two types of CA certificate handling: One possibility is to * add all CA certificates to one large CAfile, the other possibility is * a directory pointed to by CApath, containing separate files for each * CA with softlinks named after the hash values of the certificate. The * first alternative has the advantage that the file is opened and read * at startup time, so that you don't have the hassle to maintain another * copy of the CApath directory for chroot-jail. */ if (tls_set_ca_certificate_info(client_ctx, props->CAfile, props->CApath) < 0) { /* tls_set_ca_certificate_info() already logs a warning. */ SSL_CTX_free(client_ctx); /* 200411 */ return (0); } /* * We do not need a client certificate, so the certificates are only * loaded (and checked) if supplied. A clever client would handle * multiple client certificates and decide based on the list of * acceptable CAs, sent by the server, which certificate to submit. * OpenSSL does however not do this and also has no call-back hooks to * easily implement it. * * Load the client public key certificate and private key from file and * check whether the cert matches the key. We can use RSA certificates * ("cert") DSA certificates ("dcert") or ECDSA certificates ("eccert"). * All three can be made available at the same time. The CA certificates * for all three are handled in the same setup already finished. Which * one is used depends on the cipher negotiated (that is: the first * cipher listed by the client which does match the server). The client * certificate is presented after the server chooses the session cipher, * so we will just present the right cert for the chosen cipher (if it * uses certificates). */ if (tls_set_my_certificate_key_info(client_ctx, props->cert_file, props->key_file, props->dcert_file, props->dkey_file, props->eccert_file, props->eckey_file) < 0) { /* tls_set_my_certificate_key_info() already logs a warning. */ SSL_CTX_free(client_ctx); /* 200411 */ return (0); } /* * According to the OpenSSL documentation, temporary RSA key is needed * export ciphers are in use. We have to provide one, so well, we just do * it. */ SSL_CTX_set_tmp_rsa_callback(client_ctx, tls_tmp_rsa_cb); /* * Finally, the setup for the server certificate checking, done "by the * book". */ SSL_CTX_set_verify(client_ctx, SSL_VERIFY_NONE, tls_verify_certificate_callback); /* * Initialize the session cache. * * Since the client does not search an internal cache, we simply disable it. * It is only useful for expiring old sessions, but we do that in the * tlsmgr(8). * * This makes SSL_CTX_remove_session() not useful for flushing broken * sessions from the external cache, so we must delete them directly (not * via a callback). */ if (tls_mgr_policy(props->cache_type, &cachable) != TLS_MGR_STAT_OK) cachable = 0; /* * Allocate an application context, and populate with mandatory protocol * and cipher data. */ app_ctx = tls_alloc_app_context(client_ctx, log_mask); /* * The external session cache is implemented by the tlsmgr(8) process. */ if (cachable) { app_ctx->cache_type = mystrdup(props->cache_type); /* * OpenSSL does not use callbacks to load sessions from a client * cache, so we must invoke that function directly. Apparently, * OpenSSL does not provide a way to pass session names from here to * call-back routines that do session lookup. * * OpenSSL can, however, automatically save newly created sessions for * us by callback (we create the session name in the call-back * function). * * XXX gcc 2.95 can't compile #ifdef .. #endif in the expansion of * SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_INTERNAL_STORE | * SSL_SESS_CACHE_NO_AUTO_CLEAR. */ #ifndef SSL_SESS_CACHE_NO_INTERNAL_STORE #define SSL_SESS_CACHE_NO_INTERNAL_STORE 0 #endif SSL_CTX_set_session_cache_mode(client_ctx, SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_INTERNAL_STORE | SSL_SESS_CACHE_NO_AUTO_CLEAR); SSL_CTX_sess_set_new_cb(client_ctx, new_client_session_cb); } return (app_ctx); }
static void tlsp_get_request_event(int event, char *context) { const char *myname = "tlsp_get_request_event"; TLSP_STATE *state = (TLSP_STATE *) context; VSTREAM *plaintext_stream = state->plaintext_stream; int plaintext_fd = vstream_fileno(plaintext_stream); static VSTRING *remote_endpt; static VSTRING *server_id; int req_flags; int timeout; int ready; /* * One-time initialization. */ if (remote_endpt == 0) { remote_endpt = vstring_alloc(10); server_id = vstring_alloc(10); } /* * At this point we still manually manage plaintext read/write/timeout * events. Turn off timer events. Below we disable read events on error, * and redefine read events on success. */ if (event != EVENT_TIME) event_cancel_timer(tlsp_get_request_event, (char *) state); /* * We must send some data, after receiving the request attributes and * before receiving the remote file descriptor. We can't assume * UNIX-domain socket semantics here. */ if (event != EVENT_READ || attr_scan(plaintext_stream, ATTR_FLAG_STRICT, ATTR_TYPE_STR, MAIL_ATTR_REMOTE_ENDPT, remote_endpt, ATTR_TYPE_INT, MAIL_ATTR_FLAGS, &req_flags, ATTR_TYPE_INT, MAIL_ATTR_TIMEOUT, &timeout, ATTR_TYPE_STR, MAIL_ATTR_SERVER_ID, server_id, ATTR_TYPE_END) != 4) { msg_warn("%s: receive request attributes: %m", myname); event_disable_readwrite(plaintext_fd); tlsp_state_free(state); return; } /* * If the requested TLS engine is unavailable, hang up after making sure * that the plaintext peer has received our "sorry" indication. */ ready = ((req_flags & TLS_PROXY_FLAG_ROLE_SERVER) != 0 && tlsp_server_ctx != 0); if (attr_print(plaintext_stream, ATTR_FLAG_NONE, ATTR_TYPE_INT, MAIL_ATTR_STATUS, ready, ATTR_TYPE_END) != 0 || vstream_fflush(plaintext_stream) != 0 || ready == 0) { read_wait(plaintext_fd, TLSP_INIT_TIMEOUT); /* XXX */ event_disable_readwrite(plaintext_fd); tlsp_state_free(state); return; } /* * XXX We use the same fixed timeout throughout the entire session for * both plaintext and ciphertext communication. This timeout is just a * safety feature; the real timeout will be enforced by our plaintext * peer. */ else { state->remote_endpt = mystrdup(STR(remote_endpt)); state->server_id = mystrdup(STR(server_id)); msg_info("CONNECT %s %s", (req_flags & TLS_PROXY_FLAG_ROLE_SERVER) ? "from" : (req_flags & TLS_PROXY_FLAG_ROLE_CLIENT) ? "to" : "(bogus_direction)", state->remote_endpt); state->req_flags = req_flags; state->timeout = timeout + 10; /* XXX */ event_enable_read(plaintext_fd, tlsp_get_fd_event, (char *) state); event_request_timer(tlsp_get_fd_event, (char *) state, TLSP_INIT_TIMEOUT); return; } }