void dict_load_fp(const char *dict_name, VSTREAM *fp) { const char *myname = "dict_load_fp"; VSTRING *buf; char *member; char *val; int lineno; const char *err; struct stat st; DICT *dict; /* * Instantiate the dictionary even if the file is empty. */ DICT_FIND_FOR_UPDATE(dict, dict_name); buf = vstring_alloc(100); lineno = 0; if (fstat(vstream_fileno(fp), &st) < 0) msg_fatal("fstat %s: %m", VSTREAM_PATH(fp)); while (readlline(buf, fp, &lineno)) { if ((err = split_nameval(STR(buf), &member, &val)) != 0) msg_fatal("%s, line %d: %s: \"%s\"", VSTREAM_PATH(fp), lineno, err, STR(buf)); if (msg_verbose > 1) msg_info("%s: %s = %s", myname, member, val); if (dict->update(dict, member, val) != 0) msg_fatal("%s, line %d: unable to update %s:%s", VSTREAM_PATH(fp), lineno, dict->type, dict->name); } vstring_free(buf); dict->owner.uid = st.st_uid; dict->owner.status = (st.st_uid != 0); }
void pcf_set_parameters(char **name_val_array) { char *name, *value, *junk; const char *err; char **cpp; for (cpp = name_val_array; *cpp; cpp++) { junk = mystrdup(*cpp); if ((err = split_nameval(junk, &name, &value)) != 0) msg_fatal("invalid parameter override: %s: %s", *cpp, err); mail_conf_update(name, value); myfree(junk); } }
static void pcf_normalize_nameval(ARGV *argv, int field) { char *arg = argv->argv[field]; char *name; char *value; const char *err; char *normalized; if ((err = split_nameval(arg, &name, &value)) != 0) { msg_warn("%s: %s: \"%s\"", MASTER_CONF_FILE, err, arg); } else { normalized = concatenate(name, "=", value, (char *) 0); argv_replace_one(argv, field, normalized); myfree(normalized); } }
void dict_load_fp(const char *dict_name, VSTREAM *fp) { const char *myname = "dict_load_fp"; VSTRING *buf; char *member; char *val; const char *old; int old_lineno; int lineno; const char *err; struct stat st; DICT *dict; /* * Instantiate the dictionary even if the file is empty. */ DICT_FIND_FOR_UPDATE(dict, dict_name); buf = vstring_alloc(100); old_lineno = lineno = 0; if (fstat(vstream_fileno(fp), &st) < 0) msg_fatal("fstat %s: %m", VSTREAM_PATH(fp)); for ( /* void */ ; readlline(buf, fp, &lineno); old_lineno = lineno) { if ((err = split_nameval(STR(buf), &member, &val)) != 0) msg_fatal("%s, line %s: %s: \"%s\"", VSTREAM_PATH(fp), format_line_number((VSTRING *) 0, old_lineno + 1, lineno), err, STR(buf)); if (msg_verbose > 1) msg_info("%s: %s = %s", myname, member, val); if ((old = dict->lookup(dict, member)) != 0 && strcmp(old, val) != 0) msg_warn("%s, line %d: overriding earlier entry: %s=%s", VSTREAM_PATH(fp), lineno, member, old); if (dict->update(dict, member, val) != 0) msg_fatal("%s, line %d: unable to update %s:%s", VSTREAM_PATH(fp), lineno, dict->type, dict->name); } vstring_free(buf); dict->owner.uid = st.st_uid; dict->owner.status = (st.st_uid != 0); }
void mail_conf_checkdir(const char *config_dir) { VSTRING *buf; VSTREAM *fp; char *path; char *name; char *value; char *cp; int found = 0; /* * If running set-[ug]id, require that a non-default configuration * directory name is blessed as a bona fide configuration directory in * the default main.cf file. */ path = concatenate(DEF_CONFIG_DIR, "/", "main.cf", (char *) 0); if ((fp = vstream_fopen(path, O_RDONLY, 0)) == 0) msg_fatal("open file %s: %m", path); buf = vstring_alloc(1); while (found == 0 && readlline(buf, fp, (int *) 0)) { if (split_nameval(vstring_str(buf), &name, &value) == 0 && (strcmp(name, VAR_CONFIG_DIRS) == 0 || strcmp(name, VAR_MULTI_CONF_DIRS) == 0)) { while (found == 0 && (cp = mystrtok(&value, CHARS_COMMA_SP)) != 0) if (strcmp(cp, config_dir) == 0) found = 1; } } if (vstream_fclose(fp)) msg_fatal("read file %s: %m", path); vstring_free(buf); if (found == 0) { msg_error("unauthorized configuration directory name: %s", config_dir); msg_fatal("specify \"%s = %s\" or \"%s = %s\" in %s", VAR_CONFIG_DIRS, config_dir, VAR_MULTI_CONF_DIRS, config_dir, path); } myfree(path); }
void cleanup_extracted_process(CLEANUP_STATE *state, int type, const char *buf, ssize_t len) { const char *myname = "cleanup_extracted_process"; const char *encoding; char *attr_name; char *attr_value; const char *error_text; int extra_opts; int junk; #ifdef DELAY_ACTION int defer_delay; #endif if (msg_verbose) msg_info("extracted envelope %c %.*s", type, (int) len, buf); if (type == REC_TYPE_FLGS) { /* Not part of queue file format. */ extra_opts = atoi(buf); if (extra_opts & ~CLEANUP_FLAG_MASK_EXTRA) msg_warn("%s: ignoring bad extra flags: 0x%x", state->queue_id, extra_opts); else state->flags |= extra_opts; return; } #ifdef DELAY_ACTION if (type == REC_TYPE_DELAY) { /* Not part of queue file format. */ defer_delay = atoi(buf); if (defer_delay <= 0) msg_warn("%s: ignoring bad delay time: %s", state->queue_id, buf); else state->defer_delay = defer_delay; return; } #endif if (strchr(REC_TYPE_EXTRACT, type) == 0) { msg_warn("%s: message rejected: " "unexpected record type %d in extracted envelope", state->queue_id, type); state->errs |= CLEANUP_STAT_BAD; return; } /* * Map DSN attribute name to pseudo record type 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 (type == REC_TYPE_ATTR) { vstring_strcpy(state->attr_buf, buf); error_text = split_nameval(STR(state->attr_buf), &attr_name, &attr_value); if (error_text != 0) { msg_warn("%s: message rejected: malformed attribute: %s: %.100s", state->queue_id, error_text, buf); state->errs |= CLEANUP_STAT_BAD; return; } /* Zero-length values are place holders for unavailable values. */ if (*attr_value == 0) { msg_warn("%s: spurious null attribute value for \"%s\" -- ignored", state->queue_id, attr_name); return; } if ((junk = rec_attr_map(attr_name)) != 0) { buf = attr_value; type = junk; } } /* * On the transition from non-recipient records to recipient records, * emit optional information from header/body content. */ if ((state->flags & CLEANUP_FLAG_INRCPT) == 0 && strchr(REC_TYPE_EXT_RECIPIENT, type) != 0) { if (state->filter != 0) cleanup_out_string(state, REC_TYPE_FILT, state->filter); if (state->redirect != 0) cleanup_out_string(state, REC_TYPE_RDR, state->redirect); if ((encoding = nvtable_find(state->attr, MAIL_ATTR_ENCODING)) != 0) cleanup_out_format(state, REC_TYPE_ATTR, "%s=%s", MAIL_ATTR_ENCODING, encoding); state->flags |= CLEANUP_FLAG_INRCPT; /* Make room to append more meta records. */ if (state->milters || cleanup_milters) { if ((state->append_meta_pt_offset = vstream_ftell(state->dst)) < 0) msg_fatal("%s: vstream_ftell %s: %m:", myname, cleanup_path); cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, 0L); if ((state->append_meta_pt_target = vstream_ftell(state->dst)) < 0) msg_fatal("%s: vstream_ftell %s: %m:", myname, cleanup_path); } } /* * Extracted envelope recipient record processing. */ if (type == REC_TYPE_RCPT) { if (state->sender == 0) { /* protect showq */ msg_warn("%s: message rejected: envelope recipient precedes sender", state->queue_id); state->errs |= CLEANUP_STAT_BAD; return; } if (state->orig_rcpt == 0) state->orig_rcpt = mystrdup(buf); cleanup_addr_recipient(state, buf); if (cleanup_milters != 0 && state->milters == 0 && CLEANUP_MILTER_OK(state)) cleanup_milter_emul_rcpt(state, cleanup_milters, state->recip); myfree(state->orig_rcpt); state->orig_rcpt = 0; if (state->dsn_orcpt != 0) { myfree(state->dsn_orcpt); state->dsn_orcpt = 0; } state->dsn_notify = 0; return; } if (type == REC_TYPE_DONE || type == REC_TYPE_DRCP) { if (state->orig_rcpt != 0) { myfree(state->orig_rcpt); state->orig_rcpt = 0; } if (state->dsn_orcpt != 0) { myfree(state->dsn_orcpt); state->dsn_orcpt = 0; } state->dsn_notify = 0; return; } if (type == REC_TYPE_DSN_ORCPT) { if (state->dsn_orcpt) { msg_warn("%s: ignoring out-of-order DSN original recipient record <%.200s>", state->queue_id, state->dsn_orcpt); myfree(state->dsn_orcpt); } state->dsn_orcpt = mystrdup(buf); return; } if (type == REC_TYPE_DSN_NOTIFY) { if (state->dsn_notify) { msg_warn("%s: ignoring out-of-order DSN notify record <%d>", state->queue_id, state->dsn_notify); state->dsn_notify = 0; } if (!alldig(buf) || (junk = atoi(buf)) == 0 || DSN_NOTIFY_OK(junk) == 0) msg_warn("%s: ignoring malformed dsn notify record <%.200s>", state->queue_id, buf); else state->qmgr_opts |= QMGR_READ_FLAG_FROM_DSN(state->dsn_notify = junk); return; } if (type == REC_TYPE_ORCP) { if (state->orig_rcpt != 0) { msg_warn("%s: ignoring out-of-order original recipient record <%.200s>", state->queue_id, buf); myfree(state->orig_rcpt); } state->orig_rcpt = mystrdup(buf); return; } if (type == REC_TYPE_END) { /* Make room to append recipient. */ if ((state->milters || cleanup_milters) && state->append_rcpt_pt_offset < 0) { if ((state->append_rcpt_pt_offset = vstream_ftell(state->dst)) < 0) msg_fatal("%s: vstream_ftell %s: %m:", myname, cleanup_path); cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, 0L); if ((state->append_rcpt_pt_target = vstream_ftell(state->dst)) < 0) msg_fatal("%s: vstream_ftell %s: %m:", myname, cleanup_path); } state->flags &= ~CLEANUP_FLAG_INRCPT; state->flags |= CLEANUP_FLAG_END_SEEN; cleanup_extracted_finish(state); return; } /* * Extracted envelope non-recipient record processing. */ if (state->flags & CLEANUP_FLAG_INRCPT) /* Tell qmgr that recipient records are mixed with other information. */ state->qmgr_opts |= QMGR_READ_FLAG_MIXED_RCPT_OTHER; cleanup_out(state, type, buf, len); return; }
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 */ int save_unread = message->rcpt_unread; /* save a count */ char *start; int recipient_limit; 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. * * For the first time, the message recipient limit is calculated from the * global recipient limit. This is to avoid reading little recipients * when the active queue is near empty. When the queue becomes full, only * the necessary amount is read in core. Such priming is necessary * because there are no message jobs yet. * * For the next time, the recipient limit is based solely on the message * jobs' positions in the job lists and/or job stacks. */ 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; recipient_limit = message->rcpt_limit - message->rcpt_count; } else { recipient_limit = var_qmgr_rcpt_limit - qmgr_recipient_count; if (recipient_limit < message->rcpt_limit) recipient_limit = message->rcpt_limit; } /* Keep interrupt latency in check. */ if (recipient_limit > 5000) recipient_limit = 5000; if (recipient_limit <= 0) msg_panic("%s: no recipient slots available", message->queue_id); if (msg_verbose) msg_info("%s: recipient limit %d", message->queue_id, recipient_limit); /* * Read envelope records. XXX Rely on the front-end programs to enforce * record size limits. Read up to recipient_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. * * Note that the total recipient count record is accurate only for fresh * queue files. After some of the recipients are marked as done and the * queue file is deferred, it can be used as upper bound estimate only. * Fortunately, this poses no major problem on the scheduling algorithm, * as the only impact is that the already deferred messages are not * chosen by qmgr_job_candidate() as often as they could. * * 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. */ if (message->rcpt_offset == 0) { message->rcpt_unread--; 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 >= recipient_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) { message->rcpt_unread--; 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 %d", &message->data_size, &message->data_offset, &message->rcpt_unread, &message->rflags, &message->cont_length, &message->smtputf8)) >= 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, message->rcpt_unread, "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); } /* * After sending a "delayed" warning, request sender notification when * message delivery is completed. While "mail delayed" notifications are * bad enough because they multiply the amount of email traffic, "delay * cleared" notifications are even worse because they come in a sudden * burst when the queue drains after a network outage. */ if (var_dsn_delay_cleared && message->warn_time < 0) message->tflags |= DEL_REQ_FLAG_REC_DLY_SENT; /* * Remember when we have read the last recipient batch. Note that we do * it here after reading as reading might have used considerable amount * of time. */ message->refill_time = sane_time(); /* * 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 (message->rcpt_unread < 0 || (message->rcpt_offset == 0 && message->rcpt_unread != 0)) { msg_warn("%s: rcpt count mismatch (%d)", message->queue_id, message->rcpt_unread); message->rcpt_unread = 0; } 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 */ message->rcpt_unread = save_unread; /* restore count */ recipient_list_free(&message->rcpt_list); recipient_list_init(&message->rcpt_list, RCPT_LIST_INIT_QUEUE); return (-1); }
static void postcat(VSTREAM *fp, VSTRING *buffer, int flags) { int prev_type = 0; int rec_type; struct timeval tv; time_t time; int ch; off_t offset; const char *error_text; char *attr_name; char *attr_value; int rec_flags = (msg_verbose ? REC_FLAG_NONE : REC_FLAG_DEFAULT); int state; /* state machine, input type */ int do_print; /* state machine, output control */ long data_offset; /* state machine, read optimization */ long data_size; /* state machine, read optimization */ #define TEXT_RECORD(rec_type) \ (rec_type == REC_TYPE_CONT || rec_type == REC_TYPE_NORM) /* * See if this is a plausible file. */ if ((ch = VSTREAM_GETC(fp)) != VSTREAM_EOF) { if (!strchr(REC_TYPE_ENVELOPE, ch)) { msg_warn("%s: input is not a valid queue file", VSTREAM_PATH(fp)); return; } vstream_ungetc(fp, ch); } /* * Other preliminaries. */ if (flags & PC_FLAG_PRINT_ENV) vstream_printf("*** ENVELOPE RECORDS %s ***\n", VSTREAM_PATH(fp)); state = PC_STATE_ENV; do_print = (flags & PC_FLAG_PRINT_ENV); data_offset = data_size = -1; /* * Now look at the rest. */ for (;;) { if (flags & PC_FLAG_PRINT_OFFSET) offset = vstream_ftell(fp); rec_type = rec_get_raw(fp, buffer, 0, rec_flags); if (rec_type == REC_TYPE_ERROR) msg_fatal("record read error"); if (rec_type == REC_TYPE_EOF) break; /* * First inspect records that have side effects on the (envelope, * header, body) state machine or on the record reading order. * * XXX Comments marked "Optimization:" identify subtle code that will * likely need to be revised when the queue file organization is * changed. */ #define PRINT_MARKER(flags, fp, offset, type, text) do { \ if ((flags) & PC_FLAG_PRINT_OFFSET) \ vstream_printf("%9lu ", (unsigned long) (offset)); \ if (flags & PC_FLAG_PRINT_RTYPE_DEC) \ vstream_printf("%3d ", (type)); \ vstream_printf("*** %s %s ***\n", (text), VSTREAM_PATH(fp)); \ vstream_fflush(VSTREAM_OUT); \ } while (0) #define PRINT_RECORD(flags, offset, type, value) do { \ if ((flags) & PC_FLAG_PRINT_OFFSET) \ vstream_printf("%9lu ", (unsigned long) (offset)); \ if (flags & PC_FLAG_PRINT_RTYPE_DEC) \ vstream_printf("%3d ", (type)); \ vstream_printf("%s: %s\n", rec_type_name(rec_type), (value)); \ vstream_fflush(VSTREAM_OUT); \ } while (0) if (TEXT_RECORD(rec_type)) { /* This is wrong when the message starts with whitespace. */ if (state == PC_STATE_HEADER && (flags & (PC_MASK_PRINT_TEXT)) && prev_type != REC_TYPE_CONT && TEXT_RECORD(rec_type) && !(is_header(STR(buffer)) || IS_SPACE_TAB(STR(buffer)[0]))) { /* Update the state machine. */ state = PC_STATE_BODY; do_print = (flags & PC_FLAG_PRINT_BODY); /* Optimization: terminate if nothing left to print. */ if (do_print == 0 && (flags & PC_FLAG_PRINT_ENV) == 0) break; /* Optimization: skip to extracted segment marker. */ if (do_print == 0 && (flags & PC_FLAG_PRINT_ENV) && data_offset >= 0 && data_size >= 0 && vstream_fseek(fp, data_offset + data_size, SEEK_SET) < 0) msg_fatal("seek error: %m"); } /* Optional output happens further down below. */ } else if (rec_type == REC_TYPE_MESG) { /* Sanity check. */ if (state != PC_STATE_ENV) msg_warn("%s: out-of-order message content marker", VSTREAM_PATH(fp)); /* Optional output. */ if (flags & PC_FLAG_PRINT_ENV) PRINT_MARKER(flags, fp, offset, rec_type, "MESSAGE CONTENTS"); /* Optimization: skip to extracted segment marker. */ if ((flags & PC_MASK_PRINT_TEXT) == 0 && data_offset >= 0 && data_size >= 0 && vstream_fseek(fp, data_offset + data_size, SEEK_SET) < 0) msg_fatal("seek error: %m"); /* Update the state machine, even when skipping. */ state = PC_STATE_HEADER; do_print = (flags & PC_FLAG_PRINT_HEADER); continue; } else if (rec_type == REC_TYPE_XTRA) { /* Sanity check. */ if (state != PC_STATE_HEADER && state != PC_STATE_BODY) msg_warn("%s: out-of-order extracted segment marker", VSTREAM_PATH(fp)); /* Optional output (terminate preceding header/body line). */ if (do_print && prev_type == REC_TYPE_CONT) VSTREAM_PUTCHAR('\n'); if (flags & PC_FLAG_PRINT_ENV) PRINT_MARKER(flags, fp, offset, rec_type, "HEADER EXTRACTED"); /* Update the state machine. */ state = PC_STATE_ENV; do_print = (flags & PC_FLAG_PRINT_ENV); /* Optimization: terminate if nothing left to print. */ if (do_print == 0) break; continue; } else if (rec_type == REC_TYPE_END) { /* Sanity check. */ if (state != PC_STATE_ENV) msg_warn("%s: out-of-order message end marker", VSTREAM_PATH(fp)); /* Optional output. */ if (flags & PC_FLAG_PRINT_ENV) PRINT_MARKER(flags, fp, offset, rec_type, "MESSAGE FILE END"); /* Terminate the state machine. */ break; } else if (rec_type == REC_TYPE_PTR) { /* Optional output. */ /* This record type is exposed only with '-v'. */ if (do_print) PRINT_RECORD(flags, offset, rec_type, STR(buffer)); /* Skip to the pointer's target record. */ if (rec_goto(fp, STR(buffer)) == REC_TYPE_ERROR) msg_fatal("bad pointer record, or input is not seekable"); continue; } else if (rec_type == REC_TYPE_SIZE) { /* Optional output (here before we update the state machine). */ if (do_print) PRINT_RECORD(flags, offset, rec_type, STR(buffer)); /* Read the message size/offset for the state machine optimizer. */ if (data_size >= 0 || data_offset >= 0) { msg_warn("file contains multiple size records"); } else { if (sscanf(STR(buffer), "%ld %ld", &data_size, &data_offset) != 2 || data_offset <= 0 || data_size <= 0) msg_fatal("invalid size record: %.100s", STR(buffer)); /* Optimization: skip to the message header. */ if ((flags & PC_FLAG_PRINT_ENV) == 0) { if (vstream_fseek(fp, data_offset, SEEK_SET) < 0) msg_fatal("seek error: %m"); /* Update the state machine. */ state = PC_STATE_HEADER; do_print = (flags & PC_FLAG_PRINT_HEADER); } } continue; } /* * Don't inspect side-effect-free records that aren't printed. */ if (do_print == 0) continue; if (flags & PC_FLAG_PRINT_OFFSET) vstream_printf("%9lu ", (unsigned long) offset); if (flags & PC_FLAG_PRINT_RTYPE_DEC) vstream_printf("%3d ", rec_type); switch (rec_type) { case REC_TYPE_TIME: REC_TYPE_TIME_SCAN(STR(buffer), tv); time = tv.tv_sec; vstream_printf("%s: %s", rec_type_name(rec_type), asctime(localtime(&time))); break; case REC_TYPE_WARN: REC_TYPE_WARN_SCAN(STR(buffer), time); vstream_printf("%s: %s", rec_type_name(rec_type), asctime(localtime(&time))); break; case REC_TYPE_CONT: /* REC_TYPE_FILT collision */ if (state == PC_STATE_ENV) vstream_printf("%s: ", rec_type_name(rec_type)); else if (msg_verbose) vstream_printf("unterminated_text: "); vstream_fwrite(VSTREAM_OUT, STR(buffer), LEN(buffer)); if (state == PC_STATE_ENV || msg_verbose || (flags & PC_FLAG_PRINT_OFFSET) != 0) { rec_type = 0; VSTREAM_PUTCHAR('\n'); } break; case REC_TYPE_NORM: if (msg_verbose) vstream_printf("%s: ", rec_type_name(rec_type)); vstream_fwrite(VSTREAM_OUT, STR(buffer), LEN(buffer)); VSTREAM_PUTCHAR('\n'); break; case REC_TYPE_DTXT: /* This record type is exposed only with '-v'. */ vstream_printf("%s: ", rec_type_name(rec_type)); vstream_fwrite(VSTREAM_OUT, STR(buffer), LEN(buffer)); VSTREAM_PUTCHAR('\n'); break; case REC_TYPE_ATTR: error_text = split_nameval(STR(buffer), &attr_name, &attr_value); if (error_text != 0) { msg_warn("%s: malformed attribute: %s: %.100s", VSTREAM_PATH(fp), error_text, STR(buffer)); break; } if (strcmp(attr_name, MAIL_ATTR_CREATE_TIME) == 0) { time = atol(attr_value); vstream_printf("%s: %s", MAIL_ATTR_CREATE_TIME, asctime(localtime(&time))); } else { vstream_printf("%s: %s=%s\n", rec_type_name(rec_type), attr_name, attr_value); } break; default: vstream_printf("%s: %s\n", rec_type_name(rec_type), STR(buffer)); break; } prev_type = rec_type; /* * In case the next record is broken. */ vstream_fflush(VSTREAM_OUT); } }
static int tls_policy_lookup_one(SMTP_SESSION *session, int *site_level, const char *site_name, const char *site_class) { const char *lookup; char *policy; char *saved_policy; char *tok; const char *err; char *name; char *val; static VSTRING *cbuf; #undef FREE_RETURN #define FREE_RETURN(x) do { myfree(saved_policy); return (x); } while (0) if ((lookup = maps_find(tls_policy, site_name, 0)) == 0) { if (tls_policy->error) { msg_fatal("%s: %s lookup error for %s", session->state->request->queue_id, tls_policy->title, site_name); /* XXX session->stream has no longjmp context yet. */ } return (0); } if (cbuf == 0) cbuf = vstring_alloc(10); #define WHERE \ vstring_str(vstring_sprintf(cbuf, "TLS policy table, %s \"%s\"", \ site_class, site_name)) saved_policy = policy = mystrdup(lookup); if ((tok = mystrtok(&policy, "\t\n\r ,")) == 0) { msg_warn("%s: invalid empty policy", WHERE); *site_level = TLS_LEV_INVALID; FREE_RETURN(1); /* No further lookups */ } *site_level = tls_level_lookup(tok); if (*site_level == TLS_LEV_INVALID) { /* tls_level_lookup() logs no warning. */ msg_warn("%s: invalid security level \"%s\"", WHERE, tok); FREE_RETURN(1); /* No further lookups */ } /* * Warn about ignored attributes when TLS is disabled. */ if (*site_level < TLS_LEV_MAY) { while ((tok = mystrtok(&policy, "\t\n\r ,")) != 0) msg_warn("%s: ignoring attribute \"%s\" with TLS disabled", WHERE, tok); FREE_RETURN(1); } /* * Errors in attributes may have security consequences, don't ignore * errors that can degrade security. */ while ((tok = mystrtok(&policy, "\t\n\r ,")) != 0) { if ((err = split_nameval(tok, &name, &val)) != 0) { *site_level = TLS_LEV_INVALID; msg_warn("%s: malformed attribute/value pair \"%s\": %s", WHERE, tok, err); break; } /* Only one instance per policy. */ if (!strcasecmp(name, "ciphers")) { if (*val == 0) { msg_warn("%s: attribute \"%s\" has empty value", WHERE, name); *site_level = TLS_LEV_INVALID; break; } if (session->tls_grade) { msg_warn("%s: attribute \"%s\" is specified multiple times", WHERE, name); *site_level = TLS_LEV_INVALID; break; } session->tls_grade = mystrdup(val); continue; } /* Only one instance per policy. */ if (!strcasecmp(name, "protocols")) { if (session->tls_protocols) { msg_warn("%s: attribute \"%s\" is specified multiple times", WHERE, name); *site_level = TLS_LEV_INVALID; break; } session->tls_protocols = mystrdup(val); continue; } /* Multiple instance(s) per policy. */ if (!strcasecmp(name, "match")) { char *delim = *site_level == TLS_LEV_FPRINT ? "|" : ":"; if (*site_level <= TLS_LEV_ENCRYPT) { msg_warn("%s: attribute \"%s\" invalid at security level \"%s\"", WHERE, name, policy_name(*site_level)); *site_level = TLS_LEV_INVALID; break; } if (*val == 0) { msg_warn("%s: attribute \"%s\" has empty value", WHERE, name); *site_level = TLS_LEV_INVALID; break; } if (session->tls_matchargv == 0) session->tls_matchargv = argv_split(val, delim); else argv_split_append(session->tls_matchargv, val, delim); continue; } /* Only one instance per policy. */ if (!strcasecmp(name, "exclude")) { if (session->tls_exclusions) { msg_warn("%s: attribute \"%s\" is specified multiple times", WHERE, name); *site_level = TLS_LEV_INVALID; break; } session->tls_exclusions = vstring_strcpy(vstring_alloc(10), val); continue; } else { msg_warn("%s: invalid attribute name: \"%s\"", WHERE, name); *site_level = TLS_LEV_INVALID; break; } } FREE_RETURN(1); }
static void tls_policy_lookup_one(SMTP_TLS_POLICY *tls, int *site_level, const char *site_name, const char *site_class) { const char *lookup; char *policy; char *saved_policy; char *tok; const char *err; char *name; char *val; static VSTRING *cbuf; #undef FREE_RETURN #define FREE_RETURN do { myfree(saved_policy); return; } while (0) #define INVALID_RETURN(why, levelp) do { \ MARK_INVALID((why), (levelp)); FREE_RETURN; } while (0) #define WHERE \ STR(vstring_sprintf(cbuf, "%s, %s \"%s\"", \ tls_policy->title, site_class, site_name)) if (cbuf == 0) cbuf = vstring_alloc(10); if ((lookup = maps_find(tls_policy, site_name, 0)) == 0) { if (tls_policy->error) { msg_warn("%s: policy table lookup error", WHERE); MARK_INVALID(tls->why, site_level); } return; } saved_policy = policy = mystrdup(lookup); if ((tok = mystrtok(&policy, "\t\n\r ,")) == 0) { msg_warn("%s: invalid empty policy", WHERE); INVALID_RETURN(tls->why, site_level); } *site_level = tls_level_lookup(tok); if (*site_level == TLS_LEV_INVALID) { /* tls_level_lookup() logs no warning. */ msg_warn("%s: invalid security level \"%s\"", WHERE, tok); INVALID_RETURN(tls->why, site_level); } /* * Warn about ignored attributes when TLS is disabled. */ if (*site_level < TLS_LEV_MAY) { while ((tok = mystrtok(&policy, "\t\n\r ,")) != 0) msg_warn("%s: ignoring attribute \"%s\" with TLS disabled", WHERE, tok); FREE_RETURN; } /* * Errors in attributes may have security consequences, don't ignore * errors that can degrade security. */ while ((tok = mystrtok(&policy, "\t\n\r ,")) != 0) { if ((err = split_nameval(tok, &name, &val)) != 0) { msg_warn("%s: malformed attribute/value pair \"%s\": %s", WHERE, tok, err); INVALID_RETURN(tls->why, site_level); } /* Only one instance per policy. */ if (!strcasecmp(name, "ciphers")) { if (*val == 0) { msg_warn("%s: attribute \"%s\" has empty value", WHERE, name); INVALID_RETURN(tls->why, site_level); } if (tls->grade) { msg_warn("%s: attribute \"%s\" is specified multiple times", WHERE, name); INVALID_RETURN(tls->why, site_level); } tls->grade = mystrdup(val); continue; } /* Only one instance per policy. */ if (!strcasecmp(name, "protocols")) { if (tls->protocols) { msg_warn("%s: attribute \"%s\" is specified multiple times", WHERE, name); INVALID_RETURN(tls->why, site_level); } tls->protocols = mystrdup(val); continue; } /* Multiple instances per policy. */ if (!strcasecmp(name, "match")) { if (*val == 0) { msg_warn("%s: attribute \"%s\" has empty value", WHERE, name); INVALID_RETURN(tls->why, site_level); } switch (*site_level) { default: msg_warn("%s: attribute \"%s\" invalid at security level " "\"%s\"", WHERE, name, policy_name(*site_level)); INVALID_RETURN(tls->why, site_level); break; case TLS_LEV_FPRINT: if (!tls->dane) tls->dane = tls_dane_alloc(); tls_dane_add_ee_digests(tls->dane, var_smtp_tls_fpt_dgst, val, "|"); break; case TLS_LEV_VERIFY: case TLS_LEV_SECURE: if (tls->matchargv == 0) tls->matchargv = argv_split(val, ":"); else argv_split_append(tls->matchargv, val, ":"); break; } continue; } /* Only one instance per policy. */ if (!strcasecmp(name, "exclude")) { if (tls->exclusions) { msg_warn("%s: attribute \"%s\" is specified multiple times", WHERE, name); INVALID_RETURN(tls->why, site_level); } tls->exclusions = vstring_strcpy(vstring_alloc(10), val); continue; } /* Multiple instances per policy. */ if (!strcasecmp(name, "tafile")) { /* Only makes sense if we're using CA-based trust */ if (!TLS_MUST_PKIX(*site_level)) { msg_warn("%s: attribute \"%s\" invalid at security level" " \"%s\"", WHERE, name, policy_name(*site_level)); INVALID_RETURN(tls->why, site_level); } if (*val == 0) { msg_warn("%s: attribute \"%s\" has empty value", WHERE, name); INVALID_RETURN(tls->why, site_level); } if (!tls->dane) tls->dane = tls_dane_alloc(); if (!tls_dane_load_trustfile(tls->dane, val)) { INVALID_RETURN(tls->why, site_level); } continue; } msg_warn("%s: invalid attribute name: \"%s\"", WHERE, name); INVALID_RETURN(tls->why, site_level); } FREE_RETURN; }
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(); /* Re-evaluate mail_task() after reading main.cf. */ 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? */ mail_dict_init(); if ((errstr = check_user_acl_byuid(VAR_SUBMIT_ACL, 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 = mail_parm_split(VAR_IMPORT_ENVIRON, var_import_environ); 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, SEND_ATTR_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, CA_VSTREAM_CTL_PATH("stdin"), CA_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, SEND_ATTR_INT(MAIL_ATTR_STATUS, status), SEND_ATTR_STR(MAIL_ATTR_WHY, ""), ATTR_TYPE_END); vstream_fflush(VSTREAM_OUT); exit(status); }
static int copy_segment(VSTREAM *qfile, VSTREAM *cleanup, PICKUP_INFO *info, VSTRING *buf, char *expected) { int type; int check_first = (*expected == REC_TYPE_CONTENT[0]); int time_seen = 0; char *attr_name; char *attr_value; char *saved_attr; int skip_attr; /* * Limit the input record size. All front-end programs should protect the * mail system against unreasonable inputs. This also requires that we * limit the size of envelope records written by the local posting agent. * * Records with named attributes are filtered by postdrop(1). * * We must allow PTR records here because of "postsuper -r". */ for (;;) { if ((type = rec_get(qfile, buf, var_line_limit)) < 0 || strchr(expected, type) == 0) return (file_read_error(info, type)); if (msg_verbose) msg_info("%s: read %c %s", info->id, type, vstring_str(buf)); if (type == *expected) break; if (type == REC_TYPE_FROM) { if (info->sender == 0) info->sender = mystrdup(vstring_str(buf)); /* Compatibility with Postfix < 2.3. */ if (time_seen == 0) rec_fprintf(cleanup, REC_TYPE_TIME, "%ld", (long) info->st.st_mtime); } if (type == REC_TYPE_TIME) time_seen = 1; /* * XXX Workaround: REC_TYPE_FILT (used in envelopes) == REC_TYPE_CONT * (used in message content). * * As documented in postsuper(1), ignore content filter record. */ if (*expected != REC_TYPE_CONTENT[0]) { if (type == REC_TYPE_FILT) /* Discard FILTER record after "postsuper -r". */ continue; if (type == REC_TYPE_RDR) /* Discard REDIRECT record after "postsuper -r". */ continue; } if (*expected == REC_TYPE_EXTRACT[0]) { if (type == REC_TYPE_RRTO) /* Discard return-receipt record after "postsuper -r". */ continue; if (type == REC_TYPE_ERTO) /* Discard errors-to record after "postsuper -r". */ continue; if (type == REC_TYPE_ATTR) { saved_attr = mystrdup(vstring_str(buf)); skip_attr = (split_nameval(saved_attr, &attr_name, &attr_value) == 0 && rec_attr_map(attr_name) == 0); myfree(saved_attr); /* Discard other/header/body action after "postsuper -r". */ if (skip_attr) continue; } } /* * XXX Force an empty record when the queue file content begins with * whitespace, so that it won't be considered as being part of our * own Received: header. What an ugly Kluge. */ if (check_first && (type == REC_TYPE_NORM || type == REC_TYPE_CONT)) { check_first = 0; if (VSTRING_LEN(buf) > 0 && IS_SPACE_TAB(vstring_str(buf)[0])) rec_put(cleanup, REC_TYPE_NORM, "", 0); } if ((REC_PUT_BUF(cleanup, type, buf)) < 0) return (cleanup_service_error(info, CLEANUP_STAT_WRITE)); } return (0); }
void pcf_edit_master(int mode, int argc, char **argv) { const char *myname = "pcf_edit_master"; char *path; EDIT_FILE *ep; VSTREAM *src; VSTREAM *dst; VSTRING *line_buf = vstring_alloc(100); VSTRING *parse_buf = vstring_alloc(100); int lineno; PCF_MASTER_ENT *new_entry; VSTRING *full_entry_buf = vstring_alloc(100); char *cp; char *pattern; int service_name_type_matched; const char *err; PCF_MASTER_EDIT_REQ *edit_reqs; PCF_MASTER_EDIT_REQ *req; int num_reqs = argc; const char *edit_opts = "-Me, -Fe, -Pe, -X, or -#"; char *service_name; char *service_type; /* * Sanity check. */ if (num_reqs <= 0) msg_panic("%s: empty argument list", myname); /* * Preprocessing: split pattern=value, then split the pattern components. */ edit_reqs = (PCF_MASTER_EDIT_REQ *) mymalloc(sizeof(*edit_reqs) * num_reqs); for (req = edit_reqs; *argv != 0; req++, argv++) { req->match_count = 0; req->raw_text = *argv; cp = req->parsed_text = mystrdup(req->raw_text); if (strchr(cp, '\n') != 0) msg_fatal("%s accept no multi-line input", edit_opts); while (ISSPACE(*cp)) cp++; if (*cp == '#') msg_fatal("%s accept no comment input", edit_opts); /* Separate the pattern from the value. */ if (mode & PCF_EDIT_CONF) { if ((err = split_nameval(cp, &pattern, &req->edit_value)) != 0) msg_fatal("%s: \"%s\"", err, req->raw_text); if ((mode & PCF_MASTER_PARAM) && req->edit_value[strcspn(req->edit_value, PCF_MASTER_BLANKS)]) msg_fatal("whitespace in parameter value: \"%s\"", req->raw_text); } else if (mode & (PCF_COMMENT_OUT | PCF_EDIT_EXCL)) { if (strchr(cp, '=') != 0) msg_fatal("-X or -# requires names without value"); pattern = cp; trimblanks(pattern, 0); req->edit_value = 0; } else { msg_panic("%s: unknown mode %d", myname, mode); } #define PCF_MASTER_MASK (PCF_MASTER_ENTRY | PCF_MASTER_FLD | PCF_MASTER_PARAM) /* * Split name/type or name/type/whatever pattern into components. */ switch (mode & PCF_MASTER_MASK) { case PCF_MASTER_ENTRY: if ((req->service_pattern = pcf_parse_service_pattern(pattern, 2, 2)) == 0) msg_fatal("-Me, -MX or -M# requires service_name/type"); break; case PCF_MASTER_FLD: if ((req->service_pattern = pcf_parse_service_pattern(pattern, 3, 3)) == 0) msg_fatal("-Fe or -FX requires service_name/type/field_name"); req->field_number = pcf_parse_field_pattern(req->service_pattern->argv[2]); if (pcf_is_magic_field_pattern(req->field_number)) msg_fatal("-Fe does not accept wild-card field name"); if ((mode & PCF_EDIT_CONF) && req->field_number < PCF_MASTER_FLD_CMD && req->edit_value[strcspn(req->edit_value, PCF_MASTER_BLANKS)]) msg_fatal("-Fe does not accept whitespace in non-command field"); break; case PCF_MASTER_PARAM: if ((req->service_pattern = pcf_parse_service_pattern(pattern, 3, 3)) == 0) msg_fatal("-Pe or -PX requires service_name/type/parameter"); req->param_pattern = req->service_pattern->argv[2]; if (PCF_IS_MAGIC_PARAM_PATTERN(req->param_pattern)) msg_fatal("-Pe does not accept wild-card parameter name"); if ((mode & PCF_EDIT_CONF) && req->edit_value[strcspn(req->edit_value, PCF_MASTER_BLANKS)]) msg_fatal("-Pe does not accept whitespace in parameter value"); break; default: msg_panic("%s: unknown edit mode %d", myname, mode); } } /* * Open a temp file for the result. This uses a deterministic name so we * don't leave behind thrash with random names. */ pcf_set_config_dir(); path = concatenate(var_config_dir, "/", MASTER_CONF_FILE, (char *) 0); if ((ep = edit_file_open(path, O_CREAT | O_WRONLY, 0644)) == 0) msg_fatal("open %s%s: %m", path, EDIT_FILE_SUFFIX); dst = ep->tmp_fp; /* * Open the original file for input. */ if ((src = vstream_fopen(path, O_RDONLY, 0)) == 0) { /* OK to delete, since we control the temp file name exclusively. */ (void) unlink(ep->tmp_path); msg_fatal("open %s for reading: %m", path); } /* * Copy original file to temp file, while replacing service entries on * the fly. */ service_name_type_matched = 0; new_entry = 0; lineno = 0; while ((cp = pcf_next_cf_line(parse_buf, src, dst, &lineno)) != 0) { vstring_strcpy(line_buf, STR(parse_buf)); /* * Copy, skip or replace continued text. */ if (cp > STR(parse_buf)) { if (service_name_type_matched == 0) vstream_fputs(STR(line_buf), dst); else if (mode & PCF_COMMENT_OUT) vstream_fprintf(dst, "#%s", STR(line_buf)); } /* * Copy or replace (start of) logical line. */ else { service_name_type_matched = 0; /* * Parse out the service name and type. */ if ((service_name = mystrtok(&cp, PCF_MASTER_BLANKS)) == 0 || (service_type = mystrtok(&cp, PCF_MASTER_BLANKS)) == 0) msg_fatal("file %s: line %d: specify service name and type " "on the same line", path, lineno); if (strchr(service_name, '=')) msg_fatal("file %s: line %d: service name syntax \"%s\" is " "unsupported with %s", path, lineno, service_name, edit_opts); if (service_type[strcspn(service_type, "=/")] != 0) msg_fatal("file %s: line %d: " "service type syntax \"%s\" is unsupported with %s", path, lineno, service_type, edit_opts); /* * Match each service pattern. */ for (req = edit_reqs; req < edit_reqs + num_reqs; req++) { if (PCF_MATCH_SERVICE_PATTERN(req->service_pattern, service_name, service_type)) { service_name_type_matched = 1; /* Sticky flag */ req->match_count += 1; /* * Generate replacement master.cf entries. */ if ((mode & PCF_EDIT_CONF) || ((mode & PCF_MASTER_PARAM) && (mode & PCF_EDIT_EXCL))) { switch (mode & PCF_MASTER_MASK) { /* * Replace master.cf entry field or parameter * value. */ case PCF_MASTER_FLD: case PCF_MASTER_PARAM: if (new_entry == 0) { /* Gobble up any continuation lines. */ pcf_gobble_cf_line(full_entry_buf, line_buf, src, dst, &lineno); new_entry = (PCF_MASTER_ENT *) mymalloc(sizeof(*new_entry)); if ((err = pcf_parse_master_entry(new_entry, STR(full_entry_buf))) != 0) msg_fatal("file %s: line %d: %s", path, lineno, err); } if (mode & PCF_MASTER_FLD) { pcf_edit_master_field(new_entry, req->field_number, req->edit_value); } else { pcf_edit_master_param(new_entry, mode, req->param_pattern, req->edit_value); } break; /* * Replace entire master.cf entry. */ case PCF_MASTER_ENTRY: if (new_entry != 0) pcf_free_master_entry(new_entry); new_entry = (PCF_MASTER_ENT *) mymalloc(sizeof(*new_entry)); if ((err = pcf_parse_master_entry(new_entry, req->edit_value)) != 0) msg_fatal("%s: \"%s\"", err, req->raw_text); break; default: msg_panic("%s: unknown edit mode %d", myname, mode); } } } } /* * Pass through or replace the current input line. */ if (new_entry) { pcf_print_master_entry(dst, PCF_FOLD_LINE, new_entry); pcf_free_master_entry(new_entry); new_entry = 0; } else if (service_name_type_matched == 0) { vstream_fputs(STR(line_buf), dst); } else if (mode & PCF_COMMENT_OUT) { vstream_fprintf(dst, "#%s", STR(line_buf)); } } } /* * Postprocessing: when editing entire service entries, generate new * entries for services not found. Otherwise (editing fields or * parameters), "service not found" is a fatal error. */ for (req = edit_reqs; req < edit_reqs + num_reqs; req++) { if (req->match_count == 0) { if ((mode & PCF_MASTER_ENTRY) && (mode & PCF_EDIT_CONF)) { new_entry = (PCF_MASTER_ENT *) mymalloc(sizeof(*new_entry)); if ((err = pcf_parse_master_entry(new_entry, req->edit_value)) != 0) msg_fatal("%s: \"%s\"", err, req->raw_text); pcf_print_master_entry(dst, PCF_FOLD_LINE, new_entry); pcf_free_master_entry(new_entry); } else if ((mode & PCF_MASTER_ENTRY) == 0) { msg_warn("unmatched service_name/type: \"%s\"", req->raw_text); } } } /* * When all is well, rename the temp file to the original one. */ if (vstream_fclose(src)) msg_fatal("read %s: %m", path); if (edit_file_close(ep) != 0) msg_fatal("close %s%s: %m", path, EDIT_FILE_SUFFIX); /* * Cleanup. */ myfree(path); vstring_free(line_buf); vstring_free(parse_buf); vstring_free(full_entry_buf); for (req = edit_reqs; req < edit_reqs + num_reqs; req++) { argv_free(req->service_pattern); myfree(req->parsed_text); } myfree((char *) edit_reqs); }
BOUNCE_LOG *bounce_log_read(BOUNCE_LOG *bp) { char *recipient; char *text; char *cp; int state; /* * Our trivial logfile parser state machine. */ #define START 0 /* still searching */ #define FOUND 1 /* in logfile entry */ /* * Initialize. */ state = START; bp->recipient = "(unavailable)"; bp->orig_rcpt = 0; bp->rcpt_offset = 0; bp->dsn_status = "(unavailable)"; bp->dsn_action = "(unavailable)"; bp->text = "(unavailable)"; /* * Support mixed logfile formats to make transitions easier. The same * file can start with old-style records and end with new-style records. * With backwards compatibility, we even have old format followed by new * format within the same logfile entry! */ while ((vstring_get_nonl(bp->buf, bp->fp) != VSTREAM_EOF)) { /* * Logfile entries are separated by blank lines. Even the old ad-hoc * logfile format has a blank line after the last record. This means * we can safely use blank lines to detect the start and end of * logfile entries. */ if (STR(bp->buf)[0] == 0) { if (state == FOUND) return (bp); state = START; continue; } /* * Sanitize. XXX This needs to be done more carefully with new-style * logfile entries. */ cp = printable(STR(bp->buf), '?'); if (state == START) state = FOUND; /* * New style logfile entries are in "name = value" format. */ if (ISALNUM(*cp)) { const char *err; char *name; char *value; /* * Split into name and value. */ if ((err = split_nameval(cp, &name, &value)) != 0) { msg_warn("%s: malformed record: %s", VSTREAM_PATH(bp->fp), err); continue; } /* * Save attribute value. */ if (STREQ(name, MAIL_ATTR_RECIP)) { bp->recipient = STR(vstring_strcpy(bp->rcpt_buf, *value ? value : "(MAILER-DAEMON)")); } else if (STREQ(name, MAIL_ATTR_ORCPT)) { bp->orig_rcpt = STR(vstring_strcpy(bp->orcp_buf, *value ? value : "(MAILER-DAEMON)")); } else if (STREQ(name, MAIL_ATTR_OFFSET)) { bp->rcpt_offset = atol(value); } else if (STREQ(name, MAIL_ATTR_STATUS)) { bp->dsn_status = STR(vstring_strcpy(bp->status_buf, value)); } else if (STREQ(name, MAIL_ATTR_ACTION)) { bp->dsn_action = STR(vstring_strcpy(bp->action_buf, value)); } else if (STREQ(name, MAIL_ATTR_WHY)) { bp->text = STR(vstring_strcpy(bp->text_buf, value)); } else { msg_warn("%s: unknown attribute name: %s, ignored", VSTREAM_PATH(bp->fp), name); } continue; } /* * Old-style logfile record. Find the recipient address. */ if (*cp != '<') { msg_warn("%s: malformed record: %.30s...", VSTREAM_PATH(bp->fp), cp); continue; } recipient = cp + 1; if ((cp = strstr(recipient, ">: ")) == 0) { msg_warn("%s: malformed record: %.30s...", VSTREAM_PATH(bp->fp), cp); continue; } *cp = 0; vstring_strcpy(bp->rcpt_buf, *recipient ? recipient : "(MAILER-DAEMON)"); bp->recipient = STR(bp->rcpt_buf); /* * Find the text that explains why mail was not deliverable. */ text = cp + 2; while (*text && ISSPACE(*text)) text++; vstring_strcpy(bp->text_buf, text); bp->text = STR(bp->text_buf); /* * Add compatibility status and action info, to make up for data that * was not stored in old-style bounce logfiles. */ bp->dsn_status = bp->compat_status; bp->dsn_action = bp->compat_action; } return (0); }
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); }
NORETURN multi_server_main(int argc, char **argv, MULTI_SERVER_FN service,...) { const char *myname = "multi_server_main"; VSTREAM *stream = 0; char *root_dir = 0; char *user_name = 0; int debug_me = 0; int daemon_mode = 1; char *service_name = basename(argv[0]); int delay; int c; int fd; va_list ap; MAIL_SERVER_INIT_FN pre_init = 0; MAIL_SERVER_INIT_FN post_init = 0; MAIL_SERVER_LOOP_FN loop = 0; int key; char *transport = 0; #if 0 char *lock_path; VSTRING *why; #endif int alone = 0; int zerolimit = 0; WATCHDOG *watchdog; char *oname_val; char *oname; char *oval; const char *err; char *generation; int msg_vstream_needed = 0; int redo_syslog_init = 0; /* * Process environment options as early as we can. */ if (getenv(CONF_ENV_VERB)) msg_verbose = 1; if (getenv(CONF_ENV_DEBUG)) debug_me = 1; /* * Don't die when a process goes away unexpectedly. */ signal(SIGPIPE, SIG_IGN); /* * Don't die for frivolous reasons. */ #ifdef SIGXFSZ signal(SIGXFSZ, SIG_IGN); #endif /* * May need this every now and then. */ var_procname = mystrdup(basename(argv[0])); set_mail_conf_str(VAR_PROCNAME, var_procname); /* * Initialize logging and exit handler. Do the syslog first, so that its * initialization completes before we enter the optional chroot jail. */ msg_syslog_init(mail_task(var_procname), LOG_PID, LOG_FACILITY); if (msg_verbose) msg_info("daemon started"); /* * Check the Postfix library version as soon as we enable logging. */ MAIL_VERSION_CHECK; /* * Initialize from the configuration file. Allow command-line options to * override compiled-in defaults or configured parameter values. */ mail_conf_suck(); /* * Register dictionaries that use higher-level interfaces and protocols. */ mail_dict_init(); /* * After database open error, continue execution with reduced * functionality. */ dict_allow_surrogate = 1; /* * Pick up policy settings from master process. Shut up error messages to * stderr, because no-one is going to see them. */ opterr = 0; while ((c = GETOPT(argc, argv, "cdDi:lm:n:o:s:St:uvVz")) > 0) { switch (c) { case 'c': root_dir = "setme"; break; case 'd': daemon_mode = 0; break; case 'D': debug_me = 1; break; case 'i': mail_conf_update(VAR_MAX_IDLE, optarg); break; case 'l': alone = 1; break; case 'm': mail_conf_update(VAR_MAX_USE, optarg); break; case 'n': service_name = optarg; break; case 'o': oname_val = mystrdup(optarg); if ((err = split_nameval(oname_val, &oname, &oval)) != 0) msg_fatal("invalid \"-o %s\" option value: %s", optarg, err); mail_conf_update(oname, oval); if (strcmp(oname, VAR_SYSLOG_NAME) == 0) redo_syslog_init = 1; myfree(oname_val); break; case 's': if ((socket_count = atoi(optarg)) <= 0) msg_fatal("invalid socket_count: %s", optarg); break; case 'S': stream = VSTREAM_IN; break; case 'u': user_name = "setme"; break; case 't': transport = optarg; break; case 'v': msg_verbose++; break; case 'V': if (++msg_vstream_needed == 1) msg_vstream_init(mail_task(var_procname), VSTREAM_ERR); break; case 'z': zerolimit = 1; break; default: msg_fatal("invalid option: %c", c); break; } } /* * Initialize generic parameters. */ mail_params_init(); if (redo_syslog_init) msg_syslog_init(mail_task(var_procname), LOG_PID, LOG_FACILITY); /* * If not connected to stdin, stdin must not be a terminal. */ if (daemon_mode && stream == 0 && isatty(STDIN_FILENO)) { msg_vstream_init(var_procname, VSTREAM_ERR); msg_fatal("do not run this command by hand"); } /* * Application-specific initialization. */ va_start(ap, service); while ((key = va_arg(ap, int)) != 0) { switch (key) { case MAIL_SERVER_INT_TABLE: get_mail_conf_int_table(va_arg(ap, CONFIG_INT_TABLE *)); break; case MAIL_SERVER_LONG_TABLE: get_mail_conf_long_table(va_arg(ap, CONFIG_LONG_TABLE *)); break; case MAIL_SERVER_STR_TABLE: get_mail_conf_str_table(va_arg(ap, CONFIG_STR_TABLE *)); break; case MAIL_SERVER_BOOL_TABLE: get_mail_conf_bool_table(va_arg(ap, CONFIG_BOOL_TABLE *)); break; case MAIL_SERVER_TIME_TABLE: get_mail_conf_time_table(va_arg(ap, CONFIG_TIME_TABLE *)); break; case MAIL_SERVER_RAW_TABLE: get_mail_conf_raw_table(va_arg(ap, CONFIG_RAW_TABLE *)); break; case MAIL_SERVER_NINT_TABLE: get_mail_conf_nint_table(va_arg(ap, CONFIG_NINT_TABLE *)); break; case MAIL_SERVER_NBOOL_TABLE: get_mail_conf_nbool_table(va_arg(ap, CONFIG_NBOOL_TABLE *)); break; case MAIL_SERVER_PRE_INIT: pre_init = va_arg(ap, MAIL_SERVER_INIT_FN); break; case MAIL_SERVER_POST_INIT: post_init = va_arg(ap, MAIL_SERVER_INIT_FN); break; case MAIL_SERVER_LOOP: loop = va_arg(ap, MAIL_SERVER_LOOP_FN); break; case MAIL_SERVER_EXIT: multi_server_onexit = va_arg(ap, MAIL_SERVER_EXIT_FN); break; case MAIL_SERVER_PRE_ACCEPT: multi_server_pre_accept = va_arg(ap, MAIL_SERVER_ACCEPT_FN); break; case MAIL_SERVER_PRE_DISCONN: multi_server_pre_disconn = va_arg(ap, MAIL_SERVER_DISCONN_FN); break; case MAIL_SERVER_IN_FLOW_DELAY: multi_server_in_flow_delay = 1; break; case MAIL_SERVER_SOLITARY: if (stream == 0 && !alone) msg_fatal("service %s requires a process limit of 1", service_name); break; case MAIL_SERVER_UNLIMITED: if (stream == 0 && !zerolimit) msg_fatal("service %s requires a process limit of 0", service_name); break; case MAIL_SERVER_PRIVILEGED: if (user_name) msg_fatal("service %s requires privileged operation", service_name); break; default: msg_panic("%s: unknown argument type: %d", myname, key); } } va_end(ap); if (root_dir) root_dir = var_queue_dir; if (user_name) user_name = var_mail_owner; /* * Can options be required? */ if (stream == 0) { if (transport == 0) msg_fatal("no transport type specified"); if (strcasecmp(transport, MASTER_XPORT_NAME_INET) == 0) multi_server_accept = multi_server_accept_inet; else if (strcasecmp(transport, MASTER_XPORT_NAME_UNIX) == 0) multi_server_accept = multi_server_accept_local; #ifdef MASTER_XPORT_NAME_PASS else if (strcasecmp(transport, MASTER_XPORT_NAME_PASS) == 0) multi_server_accept = multi_server_accept_pass; #endif else msg_fatal("unsupported transport type: %s", transport); } /* * Retrieve process generation from environment. */ if ((generation = getenv(MASTER_GEN_NAME)) != 0) { if (!alldig(generation)) msg_fatal("bad generation: %s", generation); OCTAL_TO_UNSIGNED(multi_server_generation, generation); if (msg_verbose) msg_info("process generation: %s (%o)", generation, multi_server_generation); } /* * Optionally start the debugger on ourself. */ if (debug_me) debug_process(); /* * Traditionally, BSD select() can't handle multiple processes selecting * on the same socket, and wakes up every process in select(). See TCP/IP * Illustrated volume 2 page 532. We avoid select() collisions with an * external lock file. */ /* * XXX Can't compete for exclusive access to the listen socket because we * also have to monitor existing client connections for service requests. */ #if 0 if (stream == 0 && !alone) { lock_path = concatenate(DEF_PID_DIR, "/", transport, ".", service_name, (char *) 0); why = vstring_alloc(1); if ((multi_server_lock = safe_open(lock_path, O_CREAT | O_RDWR, 0600, (struct stat *) 0, -1, -1, why)) == 0) msg_fatal("open lock file %s: %s", lock_path, vstring_str(why)); close_on_exec(vstream_fileno(multi_server_lock), CLOSE_ON_EXEC); myfree(lock_path); vstring_free(why); } #endif /* * Set up call-back info. */ multi_server_service = service; multi_server_name = service_name; multi_server_argv = argv + optind; /* * Run pre-jail initialization. */ if (chdir(var_queue_dir) < 0) msg_fatal("chdir(\"%s\"): %m", var_queue_dir); if (pre_init) pre_init(multi_server_name, multi_server_argv); /* * Optionally, restrict the damage that this process can do. */ resolve_local_init(); tzset(); chroot_uid(root_dir, user_name); /* * Run post-jail initialization. */ if (post_init) post_init(multi_server_name, multi_server_argv); /* * Are we running as a one-shot server with the client connection on * standard input? If so, make sure the output is written to stdout so as * to satisfy common expectation. */ if (stream != 0) { vstream_control(stream, VSTREAM_CTL_DOUBLE, VSTREAM_CTL_WRITE_FD, STDOUT_FILENO, VSTREAM_CTL_END); service(stream, multi_server_name, multi_server_argv); vstream_fflush(stream); multi_server_exit(); } /* * Running as a semi-resident server. Service connection requests. * Terminate when we have serviced a sufficient number of clients, when * no-one has been talking to us for a configurable amount of time, or * when the master process terminated abnormally. */ if (var_idle_limit > 0) event_request_timer(multi_server_timeout, (char *) 0, var_idle_limit); for (fd = MASTER_LISTEN_FD; fd < MASTER_LISTEN_FD + socket_count; fd++) { event_enable_read(fd, multi_server_accept, CAST_INT_TO_CHAR_PTR(fd)); close_on_exec(fd, CLOSE_ON_EXEC); } event_enable_read(MASTER_STATUS_FD, multi_server_abort, (char *) 0); close_on_exec(MASTER_STATUS_FD, CLOSE_ON_EXEC); close_on_exec(MASTER_FLOW_READ, CLOSE_ON_EXEC); close_on_exec(MASTER_FLOW_WRITE, CLOSE_ON_EXEC); watchdog = watchdog_create(var_daemon_timeout, (WATCHDOG_FN) 0, (char *) 0); /* * The event loop, at last. */ while (var_use_limit == 0 || use_count < var_use_limit || client_count > 0) { if (multi_server_lock != 0) { watchdog_stop(watchdog); if (myflock(vstream_fileno(multi_server_lock), INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) msg_fatal("select lock: %m"); } watchdog_start(watchdog); delay = loop ? loop(multi_server_name, multi_server_argv) : -1; event_loop(delay); } multi_server_exit(); }
void edit_parameters(int mode, int argc, char **argv) { char *path; EDIT_FILE *ep; VSTREAM *src; VSTREAM *dst; VSTRING *buf = vstring_alloc(100); VSTRING *key = vstring_alloc(10); char *cp; char *edit_key; char *edit_val; HTABLE *table; struct cvalue { char *value; int found; }; struct cvalue *cvalue; HTABLE_INFO **ht_info; HTABLE_INFO **ht; int interesting; const char *err; /* * Store command-line parameters for quick lookup. */ table = htable_create(argc); while ((cp = *argv++) != 0) { if (strchr(cp, '\n') != 0) msg_fatal("-e or -# accepts no multi-line input"); while (ISSPACE(*cp)) cp++; if (*cp == '#') msg_fatal("-e or -# accepts no comment input"); if (mode & EDIT_MAIN) { if ((err = split_nameval(cp, &edit_key, &edit_val)) != 0) msg_fatal("%s: \"%s\"", err, cp); } else if (mode & COMMENT_OUT) { if (*cp == 0) msg_fatal("-# requires non-blank parameter names"); if (strchr(cp, '=') != 0) msg_fatal("-# requires parameter names only"); edit_key = mystrdup(cp); trimblanks(edit_key, 0); edit_val = 0; } else { msg_panic("edit_parameters: unknown mode %d", mode); } cvalue = (struct cvalue *) mymalloc(sizeof(*cvalue)); cvalue->value = edit_val; cvalue->found = 0; htable_enter(table, edit_key, (char *) cvalue); } /* * Open a temp file for the result. This uses a deterministic name so we * don't leave behind thrash with random names. */ set_config_dir(); path = concatenate(var_config_dir, "/", MAIN_CONF_FILE, (char *) 0); if ((ep = edit_file_open(path, O_CREAT | O_WRONLY, 0644)) == 0) msg_fatal("open %s%s: %m", path, EDIT_FILE_SUFFIX); dst = ep->tmp_fp; /* * Open the original file for input. */ if ((src = vstream_fopen(path, O_RDONLY, 0)) == 0) { /* OK to delete, since we control the temp file name exclusively. */ (void) unlink(ep->tmp_path); msg_fatal("open %s for reading: %m", path); } /* * Copy original file to temp file, while replacing parameters on the * fly. Issue warnings for names found multiple times. */ #define STR(x) vstring_str(x) interesting = 0; while (vstring_get(buf, src) != VSTREAM_EOF) { for (cp = STR(buf); ISSPACE(*cp) /* including newline */ ; cp++) /* void */ ; /* Copy comment, all-whitespace, or empty line. */ if (*cp == '#' || *cp == 0) { vstream_fputs(STR(buf), dst); } /* Copy, skip or replace continued text. */ else if (cp > STR(buf)) { if (interesting == 0) vstream_fputs(STR(buf), dst); else if (mode & COMMENT_OUT) vstream_fprintf(dst, "#%s", STR(buf)); } /* Copy or replace start of logical line. */ else { vstring_strncpy(key, cp, strcspn(cp, " \t\r\n=")); cvalue = (struct cvalue *) htable_find(table, STR(key)); if ((interesting = !!cvalue) != 0) { if (cvalue->found++ == 1) msg_warn("%s: multiple entries for \"%s\"", path, STR(key)); if (mode & EDIT_MAIN) vstream_fprintf(dst, "%s = %s\n", STR(key), cvalue->value); else if (mode & COMMENT_OUT) vstream_fprintf(dst, "#%s", cp); else msg_panic("edit_parameters: unknown mode %d", mode); } else { vstream_fputs(STR(buf), dst); } } } /* * Generate new entries for parameters that were not found. */ if (mode & EDIT_MAIN) { for (ht_info = ht = htable_list(table); *ht; ht++) { cvalue = (struct cvalue *) ht[0]->value; if (cvalue->found == 0) vstream_fprintf(dst, "%s = %s\n", ht[0]->key, cvalue->value); } myfree((char *) ht_info); } /* * When all is well, rename the temp file to the original one. */ if (vstream_fclose(src)) msg_fatal("read %s: %m", path); if (edit_file_close(ep) != 0) msg_fatal("close %s%s: %m", path, EDIT_FILE_SUFFIX); /* * Cleanup. */ myfree(path); vstring_free(buf); vstring_free(key); htable_free(table, myfree); }