char *fold_addr(VSTRING *result, const char *addr, int flags) { char *cp; /* * Fold the address as appropriate. */ switch (flags & FOLD_ADDR_ALL) { case FOLD_ADDR_HOST: if ((cp = strrchr(addr, '@')) != 0) { cp += 1; vstring_strncpy(result, addr, cp - addr); casefold_append(result, cp); break; } /* FALLTHROUGH */ case 0: vstring_strcpy(result, addr); break; case FOLD_ADDR_USER: if ((cp = strrchr(addr, '@')) != 0) { casefold_len(result, addr, cp - addr); vstring_strcat(result, cp); break; } /* FALLTHROUGH */ case FOLD_ADDR_USER | FOLD_ADDR_HOST: casefold(result, addr); break; } return (STR(result)); }
VSTRING *unquote_822_local(VSTRING *dst, const char *mbox) { const char *start; /* first byte of localpart */ const char *end; /* first byte after localpart */ const char *colon; const char *cp; if (mbox[0] == '@' && (colon = strchr(mbox, ':')) != 0) { start = colon + 1; vstring_strncpy(dst, mbox, start - mbox); } else { start = mbox; VSTRING_RESET(dst); } if ((end = strrchr(start, '@')) == 0) end = start + strlen(start); for (cp = start; cp < end; cp++) { if (*cp == '"') continue; if (*cp == '\\') { if (cp[1] == 0) continue; cp++; } VSTRING_ADDCH(dst, *cp); } if (*end) vstring_strcat(dst, end); else VSTRING_TERMINATE(dst); return (dst); }
static const char *psc_get_footer(const char *text, ssize_t text_len) { static VSTRING *footer_buf = 0; if (footer_buf == 0) footer_buf = vstring_alloc(100); /* Strip the \r\n for consistency with smtpd. */ vstring_strncpy(footer_buf, text, text_len); return (psc_maps_find(psc_rej_ftr_maps, STR(footer_buf), 0)); }
char *sane_dirname(VSTRING *bp, const char *path) { static VSTRING *buf; const char *last; /* * Your buffer or mine? */ if (bp == 0) { bp = buf; if (bp == 0) bp = buf = vstring_alloc(10); } /* * Special case: return "." for null or zero-length input. */ if (path == 0 || *path == 0) return (STR(vstring_strcpy(bp, "."))); /* * Remove trailing '/' characters from input. Return "/" if input is all * '/' characters. */ last = path + strlen(path) - 1; while (*last == '/') { if (last == path) return (STR(vstring_strcpy(bp, "/"))); last--; } /* * This pathname does not end in '/'. Skip to last '/' character if any. */ while (last >= path && *last != '/') last--; if (last < path) /* no '/' */ return (STR(vstring_strcpy(bp, "."))); /* * Strip trailing '/' characters from dirname (not strictly needed). */ while (last > path && *last == '/') last--; return (STR(vstring_strncpy(bp, path, last - path + 1))); }
char *data_redirect_map(VSTRING *result, const char *map) { const char *path; const char *map_type; size_t map_type_len; #define MAP_DELIMITER ":" /* * Sanity check. */ if (map == STR(result)) msg_panic("data_redirect_map: result clobbers input"); /* * Parse the input into map type and map name. */ path = strchr(map, MAP_DELIMITER[0]); if (path != 0) { map_type = map; map_type_len = path - map; path += 1; } else { map_type = var_db_type; map_type_len = strlen(map_type); path = map; } /* * Redirect the pathname. */ vstring_strncpy(result, map_type, map_type_len); if (name_code(data_redirect_map_types, NAME_CODE_FLAG_NONE, STR(result))) { data_redirect_path(result, path, "table", map); } else { vstring_strcpy(result, path); } /* * (Re)combine the map type with the map name. */ vstring_prepend(result, MAP_DELIMITER, sizeof(MAP_DELIMITER) - 1); vstring_prepend(result, map_type, map_type_len); return (STR(result)); }
char *sane_basename(VSTRING *bp, const char *path) { static VSTRING *buf; const char *first; const char *last; /* * Your buffer or mine? */ if (bp == 0) { bp = buf; if (bp == 0) bp = buf = vstring_alloc(10); } /* * Special case: return "." for null or zero-length input. */ if (path == 0 || *path == 0) return (STR(vstring_strcpy(bp, "."))); /* * Remove trailing '/' characters from input. Return "/" if input is all * '/' characters. */ last = path + strlen(path) - 1; while (*last == '/') { if (last == path) return (STR(vstring_strcpy(bp, "/"))); last--; } /* * The pathname does not end in '/'. Skip to last '/' character if any. */ first = last - 1; while (first >= path && *first != '/') first--; return (STR(vstring_strncpy(bp, first + 1, last - first))); }
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; message->rcpt_unread = 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, CHARS_COMMA_SP); 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 const char *smtpd_expand_addr(VSTRING *buf, const char *addr, const char *name, int prefix_len) { const char *p; const char *suffix; /* * Return NULL only for unknown names in expansion requests. */ if (addr == 0) return (""); suffix = name + prefix_len; /* * MAIL_ATTR_SENDER or MAIL_ATTR_RECIP. */ if (*suffix == 0) { if (*addr) return (addr); else return ("<>"); } /* * "sender_name" or "recipient_name". */ #define STREQ(x,y) (*(x) == *(y) && strcmp((x), (y)) == 0) else if (STREQ(suffix, MAIL_ATTR_S_NAME)) { if (*addr) { if ((p = strrchr(addr, '@')) != 0) { vstring_strncpy(buf, addr, p - addr); return (STR(buf)); } else { return (addr); } } else return ("<>"); } /* * "sender_domain" or "recipient_domain". */ else if (STREQ(suffix, MAIL_ATTR_S_DOMAIN)) { if (*addr) { if ((p = strrchr(addr, '@')) != 0) { return (p + 1); } else { return (""); } } else return (""); } /* * Unknown. Return NULL to indicate an "unknown name" error. */ else { smtpd_expand_unknown(name); return (0); } }
ARGV *mail_addr_map(MAPS *path, const char *address, int propagate) { VSTRING *buffer = 0; const char *myname = "mail_addr_map"; const char *string; char *ratsign; char *extension = 0; ARGV *argv = 0; int i; /* * Look up the full address; if no match is found, look up the address * with the extension stripped off, and remember the unmatched extension. */ if ((string = mail_addr_find(path, address, &extension)) != 0) { /* * Prepend the original user to @otherdomain, but do not propagate * the unmatched address extension. */ if (*string == '@') { buffer = vstring_alloc(100); if ((ratsign = strrchr(address, '@')) != 0) vstring_strncpy(buffer, address, ratsign - address); else vstring_strcpy(buffer, address); if (extension) vstring_truncate(buffer, LEN(buffer) - strlen(extension)); vstring_strcat(buffer, string); string = STR(buffer); } /* * Canonicalize and externalize the result, and propagate the * unmatched extension to each address found. */ argv = mail_addr_crunch(string, propagate ? extension : 0); if (buffer) vstring_free(buffer); if (msg_verbose) for (i = 0; i < argv->argc; i++) msg_info("%s: %s -> %d: %s", myname, address, i, argv->argv[i]); if (argv->argc == 0) { msg_warn("%s lookup of %s returns non-address result \"%s\"", path->title, address, string); argv = argv_free(argv); path->error = DICT_ERR_RETRY; } } /* * No match found. */ else { if (msg_verbose) msg_info("%s: %s -> %s", myname, address, path->error ? "(try again)" : "(not found)"); } /* * Cleanup. */ if (extension) myfree(extension); return (argv); }
SMTP_RESP *smtp_chat_resp(SMTP_SESSION *session) { static SMTP_RESP rdata; char *cp; int last_char; int three_digs = 0; size_t len; const char *new_reply; int chat_append_flag; int chat_append_skipped = 0; /* * Initialize the response data buffer. */ if (rdata.str_buf == 0) { rdata.dsn_buf = vstring_alloc(10); rdata.str_buf = vstring_alloc(100); } /* * Censor out non-printable characters in server responses. Concatenate * multi-line server responses. Separate the status code from the text. * Leave further parsing up to the application. * * We can't parse or store input that exceeds var_line_limit, so we just * skip over it to simplify the remainder of the code below. */ VSTRING_RESET(rdata.str_buf); for (;;) { last_char = smtp_get(session->buffer, session->stream, var_line_limit, SMTP_GET_FLAG_SKIP); /* XXX Update the per-line time limit. */ printable(STR(session->buffer), '?'); if (last_char != '\n') msg_warn("%s: response longer than %d: %.30s...", session->namaddrport, var_line_limit, STR(session->buffer)); if (msg_verbose) msg_info("< %s: %.100s", session->namaddrport, STR(session->buffer)); /* * Defend against a denial of service attack by limiting the amount * of multi-line text that we are willing to store. */ chat_append_flag = (LEN(rdata.str_buf) < var_line_limit); if (chat_append_flag) smtp_chat_append(session, "In: ", STR(session->buffer)); else { if (chat_append_skipped == 0) msg_warn("%s: multi-line response longer than %d %.30s...", session->namaddrport, var_line_limit, STR(rdata.str_buf)); if (chat_append_skipped < INT_MAX) chat_append_skipped++; } /* * Server reply substitution, for fault-injection testing, or for * working around broken systems. Use with care. */ if (smtp_chat_resp_filter != 0) { new_reply = dict_get(smtp_chat_resp_filter, STR(session->buffer)); if (new_reply != 0) { msg_info("%s: replacing server reply \"%s\" with \"%s\"", session->namaddrport, STR(session->buffer), new_reply); vstring_strcpy(session->buffer, new_reply); if (chat_append_flag) { smtp_chat_append(session, "Replaced-by: ", ""); smtp_chat_append(session, " ", new_reply); } } else if (smtp_chat_resp_filter->error != 0) { msg_warn("%s: table %s:%s lookup error for %s", session->state->request->queue_id, smtp_chat_resp_filter->type, smtp_chat_resp_filter->name, printable(STR(session->buffer), '?')); vstream_longjmp(session->stream, SMTP_ERR_DATA); } } if (chat_append_flag) { if (LEN(rdata.str_buf)) VSTRING_ADDCH(rdata.str_buf, '\n'); vstring_strcat(rdata.str_buf, STR(session->buffer)); } /* * Parse into code and text. Do not ignore garbage (see below). */ for (cp = STR(session->buffer); *cp && ISDIGIT(*cp); cp++) /* void */ ; if ((three_digs = (cp - STR(session->buffer) == 3)) != 0) { if (*cp == '-') continue; if (*cp == ' ' || *cp == 0) break; } /* * XXX Do not simply ignore garbage in the server reply when ESMTP * command pipelining is turned on. For example, after sending * ".<CR><LF>QUIT<CR><LF>" and receiving garbage followed by a * legitimate 2XX reply, Postfix recognizes the server's QUIT reply * as the END-OF-DATA reply after garbage, causing mail to be lost. * * Without the ability to store per-domain status information in queue * files, automatic workarounds are problematic: * * - Automatically deferring delivery creates a "repeated delivery" * problem when garbage arrives after the DATA stage. Without the * workaround, Postfix delivers only once. * * - Automatically deferring delivery creates a "no delivery" problem * when the garbage arrives before the DATA stage. Without the * workaround, mail might still get through. * * - Automatically turning off pipelining for delayed mail affects * deliveries to correctly implemented servers, and may also affect * delivery of large mailing lists. * * So we leave the decision with the administrator, but we don't force * them to take action, like we would with automatic deferral. If * loss of mail is not acceptable then they can turn off pipelining * for specific sites, or they can turn off pipelining globally when * they find that there are just too many broken sites. */ session->error_mask |= MAIL_ERROR_PROTOCOL; if (session->features & SMTP_FEATURE_PIPELINING) { msg_warn("%s: non-%s response from %s: %.100s", session->state->request->queue_id, smtp_mode ? "ESMTP" : "LMTP", session->namaddrport, STR(session->buffer)); if (var_helpful_warnings) msg_warn("to prevent loss of mail, turn off command pipelining " "for %s with the %s parameter", STR(session->iterator->addr), VAR_LMTP_SMTP(EHLO_DIS_MAPS)); } } /* * Extract RFC 821 reply code and RFC 2034 detail. Use a default detail * code if none was given. * * Ignore out-of-protocol enhanced status codes: codes that accompany 3XX * replies, or codes whose initial digit is out of sync with the reply * code. * * XXX Potential stability problem. In order to save memory, the queue * manager stores DSNs in a compact manner: * * - empty strings are represented by null pointers, * * - the status and reason are required to be non-empty. * * Other Postfix daemons inherit this behavior, because they use the same * DSN support code. This means that everything that receives DSNs must * cope with null pointers for the optional DSN attributes, and that * everything that provides DSN information must provide a non-empty * status and reason, otherwise the DSN support code wil panic(). * * Thus, when the remote server sends a malformed reply (or 3XX out of * context) we should not panic() in DSN_COPY() just because we don't * have a status. Robustness suggests that we supply a status here, and * that we leave it up to the down-stream code to override the * server-supplied status in case of an error we can't detect here, such * as an out-of-order server reply. */ VSTRING_TERMINATE(rdata.str_buf); vstring_strcpy(rdata.dsn_buf, "5.5.0"); /* SAFETY! protocol error */ if (three_digs != 0) { rdata.code = atoi(STR(session->buffer)); if (strchr("245", STR(session->buffer)[0]) != 0) { for (cp = STR(session->buffer) + 4; *cp == ' '; cp++) /* void */ ; if ((len = dsn_valid(cp)) > 0 && *cp == *STR(session->buffer)) { vstring_strncpy(rdata.dsn_buf, cp, len); } else { vstring_strcpy(rdata.dsn_buf, "0.0.0"); STR(rdata.dsn_buf)[0] = STR(session->buffer)[0]; } } } else { rdata.code = 0; } rdata.dsn = STR(rdata.dsn_buf); rdata.str = STR(rdata.str_buf); return (&rdata); }
static const char *dict_nis_lookup(DICT *dict, const char *key) { DICT_NIS *dict_nis = (DICT_NIS *) dict; static char *result; int result_len; int err; static VSTRING *buf; /* * Sanity check. */ if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) msg_panic("dict_nis_lookup: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); dict_errno = 0; if (dict_nis_domain == dict_nis_disabled) return (0); /* * Optionally fold the key. */ if (dict->flags & DICT_FLAG_FOLD_FIX) { if (dict->fold_buf == 0) dict->fold_buf = vstring_alloc(10); vstring_strcpy(dict->fold_buf, key); key = lowercase(vstring_str(dict->fold_buf)); } /* * See if this NIS map was written with one null byte appended to key and * value. */ if (dict->flags & DICT_FLAG_TRY1NULL) { err = yp_match(dict_nis_domain, dict_nis->dict.name, (void *) key, strlen(key) + 1, &result, &result_len); if (err == 0) { dict->flags &= ~DICT_FLAG_TRY0NULL; return (result); } } /* * See if this NIS map was written with no null byte appended to key and * value. This should never be the case, but better play safe. */ if (dict->flags & DICT_FLAG_TRY0NULL) { err = yp_match(dict_nis_domain, dict_nis->dict.name, (void *) key, strlen(key), &result, &result_len); if (err == 0) { dict->flags &= ~DICT_FLAG_TRY1NULL; if (buf == 0) buf = vstring_alloc(10); vstring_strncpy(buf, result, result_len); return (vstring_str(buf)); } } /* * When the NIS lookup fails for reasons other than "key not found", keep * logging warnings, and hope that someone will eventually notice the * problem and fix it. */ if (err != YPERR_KEY) { msg_warn("lookup %s, NIS domain %s, map %s: %s", key, dict_nis_domain, dict_nis->dict.name, dict_nis_strerror(err)); dict_errno = DICT_ERR_RETRY; } return (0); }
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); }
int mime_state_update(MIME_STATE *state, int rec_type, const char *text, int len) { int input_is_text = (rec_type == REC_TYPE_NORM || rec_type == REC_TYPE_CONT); MIME_STACK *sp; HEADER_OPTS *header_info; const unsigned char *cp; #define SAVE_PREV_REC_TYPE_AND_RETURN_ERR_FLAGS(state, rec_type) do { \ state->prev_rec_type = rec_type; \ return (state->err_flags); \ } while (0) /* * Be sure to flush any partial output line that might still be buffered * up before taking any other "end of input" actions. */ if (!input_is_text && state->prev_rec_type == REC_TYPE_CONT) mime_state_update(state, REC_TYPE_NORM, "", 0); /* * This message state machine is kept simple for the sake of robustness. * Standards evolve over time, and we want to be able to correctly * process messages that are not yet defined. This state machine knows * about headers and bodies, understands that multipart/whatever has * multiple body parts with a header and body, and that message/whatever * has message headers at the start of a body part. */ switch (state->curr_state) { /* * First, deal with header information that we have accumulated from * previous input records. Discard text that does not fit in a header * buffer. Our limit is quite generous; Sendmail will refuse mail * with only 32kbyte in all the message headers combined. */ case MIME_STATE_PRIMARY: case MIME_STATE_MULTIPART: case MIME_STATE_NESTED: if (LEN(state->output_buffer) > 0) { if (input_is_text) { if (state->prev_rec_type == REC_TYPE_CONT) { if (LEN(state->output_buffer) < var_header_limit) { vstring_strcat(state->output_buffer, text); } else { if (state->static_flags & MIME_OPT_REPORT_TRUNC_HEADER) REPORT_ERROR(state, MIME_ERR_TRUNC_HEADER, STR(state->output_buffer)); } SAVE_PREV_REC_TYPE_AND_RETURN_ERR_FLAGS(state, rec_type); } if (IS_SPACE_TAB(*text)) { if (LEN(state->output_buffer) < var_header_limit) { vstring_strcat(state->output_buffer, "\n"); vstring_strcat(state->output_buffer, text); } else { if (state->static_flags & MIME_OPT_REPORT_TRUNC_HEADER) REPORT_ERROR(state, MIME_ERR_TRUNC_HEADER, STR(state->output_buffer)); } SAVE_PREV_REC_TYPE_AND_RETURN_ERR_FLAGS(state, rec_type); } } /* * The input is (the beginning of) another message header, or is * not a message header, or is not even a text record. With no * more input to append to this saved header, do output * processing and reset the saved header buffer. Hold on to the * content transfer encoding header if we have to do a 8->7 * transformation, because the proper information depends on the * content type header: message and multipart require a domain, * leaf entities have either a transformation or a domain. */ if (LEN(state->output_buffer) > 0) { header_info = header_opts_find(STR(state->output_buffer)); if (!(state->static_flags & MIME_OPT_DISABLE_MIME) && header_info != 0) { if (header_info->type == HDR_CONTENT_TYPE) mime_state_content_type(state, header_info); if (header_info->type == HDR_CONTENT_TRANSFER_ENCODING) mime_state_content_encoding(state, header_info); } if ((state->static_flags & MIME_OPT_REPORT_8BIT_IN_HEADER) != 0 && (state->err_flags & MIME_ERR_8BIT_IN_HEADER) == 0) { for (cp = CU_CHAR_PTR(STR(state->output_buffer)); cp < CU_CHAR_PTR(END(state->output_buffer)); cp++) if (*cp & 0200) { REPORT_ERROR(state, MIME_ERR_8BIT_IN_HEADER, STR(state->output_buffer)); break; } } /* Output routine is explicitly allowed to change the data. */ if (header_info == 0 || header_info->type != HDR_CONTENT_TRANSFER_ENCODING || (state->static_flags & MIME_OPT_DOWNGRADE) == 0 || state->curr_domain == MIME_ENC_7BIT) HEAD_OUT(state, header_info, len); state->prev_rec_type = 0; VSTRING_RESET(state->output_buffer); } } /* * With past header information moved out of the way, proceed with a * clean slate. */ if (input_is_text) { int header_len; /* * See if this input is (the beginning of) a message header. * Normalize obsolete "name space colon" syntax to "name colon". * Things would be too confusing otherwise. */ if ((header_len = is_header(text)) > 0) { vstring_strncpy(state->output_buffer, text, header_len); for (text += header_len; IS_SPACE_TAB(*text); text++) /* void */ ; vstring_strcat(state->output_buffer, text); SAVE_PREV_REC_TYPE_AND_RETURN_ERR_FLAGS(state, rec_type); } } /* * This input terminates a block of message headers. When converting * 8-bit to 7-bit mail, this is the right place to emit the correct * content-transfer-encoding header. With message or multipart we * specify 7bit, with leaf entities we specify quoted-printable. * * We're not going to convert non-text data into base 64. If they send * arbitrary binary data as 8-bit text, then the data is already * broken beyond recovery, because the Postfix SMTP server sanitizes * record boundaries, treating broken record boundaries as CRLF. * * Clear the output buffer, we will need it for storage of the * conversion result. */ if ((state->static_flags & MIME_OPT_DOWNGRADE) && state->curr_domain != MIME_ENC_7BIT) { if (state->curr_ctype == MIME_CTYPE_MESSAGE || state->curr_ctype == MIME_CTYPE_MULTIPART) cp = CU_CHAR_PTR("7bit"); else cp = CU_CHAR_PTR("quoted-printable"); vstring_sprintf(state->output_buffer, "Content-Transfer-Encoding: %s", cp); HEAD_OUT(state, (HEADER_OPTS *) 0, len); VSTRING_RESET(state->output_buffer); } /* * This input terminates a block of message headers. Call the * optional header end routine at the end of the first header block. */ if (state->curr_state == MIME_STATE_PRIMARY && state->head_end) state->head_end(state->app_context); /* * This is the right place to check if the sender specified an * appropriate identity encoding (7bit, 8bit, binary) for multipart * and for message. */ if (state->static_flags & MIME_OPT_REPORT_ENCODING_DOMAIN) { if (state->curr_ctype == MIME_CTYPE_MESSAGE) { if (state->curr_stype == MIME_STYPE_PARTIAL || state->curr_stype == MIME_STYPE_EXTERN_BODY) { if (state->curr_domain != MIME_ENC_7BIT) REPORT_ERROR(state, MIME_ERR_ENCODING_DOMAIN, mime_state_enc_name(state->curr_encoding)); } else { if (state->curr_encoding != state->curr_domain) REPORT_ERROR(state, MIME_ERR_ENCODING_DOMAIN, mime_state_enc_name(state->curr_encoding)); } } else if (state->curr_ctype == MIME_CTYPE_MULTIPART) { if (state->curr_encoding != state->curr_domain) REPORT_ERROR(state, MIME_ERR_ENCODING_DOMAIN, mime_state_enc_name(state->curr_encoding)); } } /* * Find out if the next body starts with its own message headers. In * agressive mode, examine headers of partial and external-body * messages. Otherwise, treat such headers as part of the "body". Set * the proper encoding information for the multipart prolog. * * XXX This changes state to MIME_STATE_NESTED and then outputs a body * line, so that the body offset is not properly reset. */ if (input_is_text) { if (*text == 0) { state->body_offset = 0; /* XXX */ if (state->curr_ctype == MIME_CTYPE_MESSAGE) { if (state->curr_stype == MIME_STYPE_RFC822 || (state->static_flags & MIME_OPT_RECURSE_ALL_MESSAGE)) SET_MIME_STATE(state, MIME_STATE_NESTED, MIME_CTYPE_TEXT, MIME_STYPE_PLAIN, MIME_ENC_7BIT, MIME_ENC_7BIT); else SET_CURR_STATE(state, MIME_STATE_BODY); } else if (state->curr_ctype == MIME_CTYPE_MULTIPART) { SET_MIME_STATE(state, MIME_STATE_BODY, MIME_CTYPE_OTHER, MIME_STYPE_OTHER, MIME_ENC_7BIT, MIME_ENC_7BIT); } else { SET_CURR_STATE(state, MIME_STATE_BODY); } } /* * Invalid input. Force output of one blank line and jump to the * body state, leaving all other state alone. */ else { SET_CURR_STATE(state, MIME_STATE_BODY); BODY_OUT(state, REC_TYPE_NORM, "", 0); } } /* * This input is not text. Go to body state, unconditionally. */ else { SET_CURR_STATE(state, MIME_STATE_BODY); } /* FALLTHROUGH */ /* * Body text. Look for message boundaries, and recover from missing * boundary strings. Missing boundaries can happen in agressive mode * with text/rfc822-headers or with message/partial. Ignore non-space * cruft after --boundary or --boundary--, because some MUAs do, and * because only perverse software would take advantage of this to * escape detection. We have to ignore trailing cruft anyway, because * our saved copy of the boundary string may have been truncated for * safety reasons. * * Optionally look for 8-bit data in content that was announced as, or * that defaults to, 7-bit. Unfortunately, we cannot turn this on by * default. Majordomo sends requests for approval that do not * propagate the MIME information from the enclosed message to the * message headers of the approval request. * * Set the proper state information after processing a message boundary * string. * * Don't look for boundary strings at the start of a continued record. */ case MIME_STATE_BODY: if (input_is_text) { if ((state->static_flags & MIME_OPT_REPORT_8BIT_IN_7BIT_BODY) != 0 && state->curr_encoding == MIME_ENC_7BIT && (state->err_flags & MIME_ERR_8BIT_IN_7BIT_BODY) == 0) { for (cp = CU_CHAR_PTR(text); cp < CU_CHAR_PTR(text + len); cp++) if (*cp & 0200) { REPORT_ERROR(state, MIME_ERR_8BIT_IN_7BIT_BODY, text); break; } } if (state->stack && state->prev_rec_type != REC_TYPE_CONT && text[0] == '-' && text[1] == '-') { for (sp = state->stack; sp != 0; sp = sp->next) { if (strncmp(text + 2, sp->boundary, sp->bound_len) == 0) { while (sp != state->stack) mime_state_pop(state); if (strncmp(text + 2 + sp->bound_len, "--", 2) == 0) { mime_state_pop(state); SET_MIME_STATE(state, MIME_STATE_BODY, MIME_CTYPE_OTHER, MIME_STYPE_OTHER, MIME_ENC_7BIT, MIME_ENC_7BIT); } else { SET_MIME_STATE(state, MIME_STATE_MULTIPART, sp->def_ctype, sp->def_stype, MIME_ENC_7BIT, MIME_ENC_7BIT); } break; } } } /* Put last for consistency with header output routine. */ if ((state->static_flags & MIME_OPT_DOWNGRADE) && state->curr_domain != MIME_ENC_7BIT) mime_state_downgrade(state, rec_type, text, len); else BODY_OUT(state, rec_type, text, len); } /* * The input is not a text record. Inform the application that this * is the last opportunity to send any pending output. */ else { if (state->body_end) state->body_end(state->app_context); } SAVE_PREV_REC_TYPE_AND_RETURN_ERR_FLAGS(state, rec_type); /* * Oops. This can't happen. */ default: msg_panic("mime_state_update: unknown state: %d", state->curr_state); } }