void smtpd_chat_query(SMTPD_STATE *state) { int last_char; last_char = smtp_get(state->buffer, state->client, var_line_limit); smtp_chat_append(state, "In: "); if (last_char != '\n') msg_warn("%s[%s]: request longer than %d: %.30s...", state->name, state->addr, var_line_limit, printable(STR(state->buffer), '?')); if (msg_verbose) msg_info("< %s[%s]: %s", state->name, state->addr, STR(state->buffer)); }
static RESPONSE *response(VSTREAM *stream, VSTRING *buf) { static RESPONSE rdata; int more; char *cp; /* * Initialize the response data buffer. Defend against a denial of * service attack by limiting the amount of multi-line text that we are * willing to store. */ if (rdata.buf == 0) { rdata.buf = vstring_alloc(100); vstring_ctl(rdata.buf, VSTRING_CTL_MAXLEN, (ssize_t) var_line_limit, 0); } /* * 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. */ #define BUF ((char *) vstring_str(buf)) VSTRING_RESET(rdata.buf); for (;;) { smtp_get(buf, stream, var_line_limit, SMTP_GET_FLAG_SKIP); for (cp = BUF; *cp != 0; cp++) if (!ISPRINT(*cp) && !ISSPACE(*cp)) *cp = '?'; cp = BUF; if (msg_verbose) msg_info("<<< %s", cp); while (ISDIGIT(*cp)) cp++; rdata.code = (cp - BUF == 3 ? atoi(BUF) : 0); if ((more = (*cp == '-')) != 0) cp++; while (ISSPACE(*cp)) cp++; vstring_strcat(rdata.buf, cp); if (more == 0) break; VSTRING_ADDCH(rdata.buf, '\n'); } VSTRING_TERMINATE(rdata.buf); rdata.str = vstring_str(rdata.buf); return (&rdata); }
int smtpd_peer_from_haproxy(SMTPD_STATE *state) { const char *myname = "smtpd_peer_from_haproxy"; MAI_HOSTADDR_STR smtp_client_addr; MAI_SERVPORT_STR smtp_client_port; MAI_HOSTADDR_STR smtp_server_addr; MAI_SERVPORT_STR smtp_server_port; const char *proxy_err; int io_err; VSTRING *escape_buf; /* * Note: the haproxy_srvr_parse() routine performs address protocol * checks, address and port syntax checks, and converts IPv4-in-IPv6 * address string syntax (:ffff::1.2.3.4) to IPv4 syntax where permitted * by the main.cf:inet_protocols setting, but logs no warnings. */ #define ENABLE_DEADLINE 1 smtp_stream_setup(state->client, var_smtpd_uproxy_tmout, ENABLE_DEADLINE); switch (io_err = vstream_setjmp(state->client)) { default: msg_panic("%s: unhandled I/O error %d", myname, io_err); case SMTP_ERR_EOF: msg_warn("haproxy read: unexpected EOF"); return (-1); case SMTP_ERR_TIME: msg_warn("haproxy read: timeout error"); return (-1); case 0: if (smtp_get(state->buffer, state->client, HAPROXY_MAX_LEN, SMTP_GET_FLAG_NONE) != '\n') { msg_warn("haproxy read: line > %d characters", HAPROXY_MAX_LEN); return (-1); } if ((proxy_err = haproxy_srvr_parse(STR(state->buffer), &smtp_client_addr, &smtp_client_port, &smtp_server_addr, &smtp_server_port)) != 0) { escape_buf = vstring_alloc(HAPROXY_MAX_LEN + 2); escape(escape_buf, STR(state->buffer), LEN(state->buffer)); msg_warn("haproxy read: %s: %s", proxy_err, STR(escape_buf)); vstring_free(escape_buf); return (-1); } state->addr = mystrdup(smtp_client_addr.buf); if (strrchr(state->addr, ':') != 0) { state->rfc_addr = concatenate(IPV6_COL, state->addr, (char *) 0); state->addr_family = AF_INET6; } else { state->rfc_addr = mystrdup(state->addr); state->addr_family = AF_INET; } state->port = mystrdup(smtp_client_port.buf); /* * Avoid surprises in the Dovecot authentication server. */ state->dest_addr = mystrdup(smtp_server_addr.buf); return (0); } }
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 int smtpd_proxy_cmd(SMTPD_STATE *state, int expect, const char *fmt,...) { SMTPD_PROXY *proxy = state->proxy; va_list ap; char *cp; int last_char; int err = 0; static VSTRING *buffer = 0; /* * Errors first. Be prepared for delayed errors from the DATA phase. */ if (vstream_ferror(proxy->service_stream) || vstream_feof(proxy->service_stream) || (err = vstream_setjmp(proxy->service_stream)) != 0) { return (smtpd_proxy_rdwr_error(state, err)); } /* * The command can be omitted at the start of an SMTP session. This is * not documented as part of the official interface because it is used * only internally to this module. */ if (fmt != SMTPD_PROXY_CONN_FMT) { /* * Format the command. */ va_start(ap, fmt); vstring_vsprintf(proxy->request, fmt, ap); va_end(ap); /* * Optionally log the command first, so that we can see in the log * what the program is trying to do. */ if (msg_verbose) msg_info("> %s: %s", proxy->service_name, STR(proxy->request)); /* * Send the command to the proxy server. Since we're going to read a * reply immediately, there is no need to flush buffers. */ smtp_fputs(STR(proxy->request), LEN(proxy->request), proxy->service_stream); } /* * Early return if we don't want to wait for a server reply (such as * after sending QUIT). */ if (expect == SMTPD_PROX_WANT_NONE) return (0); /* * Censor out non-printable characters in server responses and save * complete multi-line responses if possible. * * 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(proxy->reply); if (buffer == 0) buffer = vstring_alloc(10); for (;;) { last_char = smtp_get(buffer, proxy->service_stream, var_line_limit, SMTP_GET_FLAG_SKIP); printable(STR(buffer), '?'); if (last_char != '\n') msg_warn("%s: response longer than %d: %.30s...", proxy->service_name, var_line_limit, STR(buffer)); if (msg_verbose) msg_info("< %s: %.100s", proxy->service_name, STR(buffer)); /* * Defend against a denial of service attack by limiting the amount * of multi-line text that we are willing to store. */ if (LEN(proxy->reply) < var_line_limit) { if (VSTRING_LEN(proxy->reply)) vstring_strcat(proxy->reply, "\r\n"); vstring_strcat(proxy->reply, STR(buffer)); } /* * Parse the response into code and text. Ignore unrecognized * garbage. This means that any character except space (or end of * line) will have the same effect as the '-' line continuation * character. */ for (cp = STR(buffer); *cp && ISDIGIT(*cp); cp++) /* void */ ; if (cp - STR(buffer) == 3) { if (*cp == '-') continue; if (*cp == ' ' || *cp == 0) break; } msg_warn("received garbage from proxy %s: %.100s", proxy->service_name, STR(buffer)); } /* * Log a warning in case the proxy does not send the expected response. * Silently accept any response when the client expressed no expectation. * * Starting with Postfix 2.6 we don't pass through unexpected 2xx or 3xx * proxy replies. They are a source of support problems, so we replace * them by generic server error replies. */ if (expect != SMTPD_PROX_WANT_ANY && expect != *STR(proxy->reply)) { msg_warn("proxy %s rejected \"%s\": \"%s\"", proxy->service_name, fmt == SMTPD_PROXY_CONN_FMT ? "connection request" : STR(proxy->request), STR(proxy->reply)); if (*STR(proxy->reply) == SMTPD_PROX_WANT_OK || *STR(proxy->reply) == SMTPD_PROX_WANT_MORE) { smtpd_proxy_rdwr_error(state, 0); } return (-1); } else { return (0); } }