void smtp_chat_cmd(SMTP_SESSION *session, const char *fmt,...) { va_list ap; /* * Format the command, and update the transaction log. */ va_start(ap, fmt); vstring_vsprintf(session->buffer, fmt, ap); va_end(ap); smtp_chat_append(session, "Out: ", STR(session->buffer)); /* * Optionally log the command first, so we can see in the log what the * program is trying to do. */ if (msg_verbose) msg_info("> %s: %s", session->namaddrport, STR(session->buffer)); /* * Send the command to the SMTP server. */ smtp_fputs(STR(session->buffer), LEN(session->buffer), session->stream); /* * Force flushing of output does not belong here. It is done in the * smtp_loop() main protocol loop when reading the server response, and * in smtp_helo() when reading the EHLO response after sending the EHLO * command. * * If we do forced flush here, then we must longjmp() on error, and a * matching "prepare for disaster" error handler must be set up before * every smtp_chat_cmd() call. */ #if 0 /* * Flush unsent data to avoid timeouts after slow DNS lookups. */ if (time((time_t *) 0) - vstream_ftime(session->stream) > 10) vstream_fflush(session->stream); /* * Abort immediately if the connection is broken. */ if (vstream_ftimeout(session->stream)) vstream_longjmp(session->stream, SMTP_ERR_TIME); if (vstream_ferror(session->stream)) vstream_longjmp(session->stream, SMTP_ERR_EOF); #endif }
int smtp_sasl_passwd_lookup(SMTP_SESSION *session) { const char *myname = "smtp_sasl_passwd_lookup"; SMTP_STATE *state = session->state; const char *value; char *passwd; /* * Sanity check. */ if (smtp_sasl_passwd_map == 0) msg_panic("%s: passwd map not initialized", myname); /* * Look up the per-server password information. Try the hostname first, * then try the destination. * * XXX Instead of using nexthop (the intended destination) we use dest * (either the intended destination, or a fall-back destination). * * XXX SASL authentication currently depends on the host/domain but not on * the TCP port. If the port is not :25, we should append it to the table * lookup key. Code for this was briefly introduced into 2.2 snapshots, * but didn't canonicalize the TCP port, and did not append the port to * the MX hostname. */ smtp_sasl_passwd_map->error = 0; if (((state->misc_flags & SMTP_MISC_FLAG_USE_LMTP) == 0 && var_smtp_sender_auth && state->request->sender[0] && (value = mail_addr_find(smtp_sasl_passwd_map, state->request->sender, (char **) 0)) != 0) || (smtp_sasl_passwd_map->error == 0 && (value = maps_find(smtp_sasl_passwd_map, session->host, 0)) != 0) || (smtp_sasl_passwd_map->error == 0 && (value = maps_find(smtp_sasl_passwd_map, session->dest, 0)) != 0)) { if (session->sasl_username) myfree(session->sasl_username); session->sasl_username = mystrdup(value); passwd = split_at(session->sasl_username, ':'); if (session->sasl_passwd) myfree(session->sasl_passwd); session->sasl_passwd = mystrdup(passwd ? passwd : ""); if (msg_verbose) msg_info("%s: host `%s' user `%s' pass `%s'", myname, session->host, session->sasl_username, session->sasl_passwd); return (1); } else if (smtp_sasl_passwd_map->error) { msg_warn("%s: %s lookup error", state->request->queue_id, smtp_sasl_passwd_map->title); vstream_longjmp(session->stream, SMTP_ERR_DATA); } else { if (msg_verbose) msg_info("%s: no auth info found (sender=`%s', host=`%s')", myname, state->request->sender, session->host); return (0); } }
void pop3d_chat_reply(POP3D_STATE *state, char *format,...) { va_list ap; int delay = 0; va_start(ap, format); vstring_vsprintf(state->buffer, format, ap); va_end(ap); if (var_soft_bounce && STR(state->buffer)[0] == '5') STR(state->buffer)[0] = '4'; pop3_chat_append(state, "Out: "); if (msg_verbose) msg_info("> %s[%s]: %s", state->name, state->addr, STR(state->buffer)); /* * Slow down clients that make errors. Sleep-on-anything slows down * clients that make an excessive number of errors within a session. */ if (state->error_count >= var_pop3d_soft_erlim) sleep(delay = var_pop3d_err_sleep); pop3_fputs(STR(state->buffer), LEN(state->buffer), state->client); /* * Flush unsent output if no I/O happened for a while. This avoids * timeouts with pipelined POP3 sessions that have lots of server-side * delays (tarpit delays or DNS lookups for UCE restrictions). */ if (delay || time((time_t *) 0) - vstream_ftime(state->client) > 10) vstream_fflush(state->client); /* * Abort immediately if the connection is broken. */ if (vstream_ftimeout(state->client)) vstream_longjmp(state->client, POP3_ERR_TIME); if (vstream_ferror(state->client)) vstream_longjmp(state->client, POP3_ERR_EOF); }
void smtp_fputc(int ch, VSTREAM *stream) { int stat; /* * Do the I/O, protected against timeout. */ smtp_timeout_reset(stream); stat = VSTREAM_PUTC(ch, stream); smtp_timeout_detect(stream); /* * See if there was a problem. */ if (stat == VSTREAM_EOF) { if (msg_verbose) msg_info("smtp_fputc: EOF"); vstream_longjmp(stream, SMTP_ERR_EOF); } }
void smtp_flush(VSTREAM *stream) { int err; /* * Do the I/O, protected against timeout. */ smtp_timeout_reset(stream); err = vstream_fflush(stream); smtp_timeout_detect(stream); /* * See if there was a problem. */ if (err != 0) { if (msg_verbose) msg_info("smtp_flush: EOF"); vstream_longjmp(stream, SMTP_ERR_EOF); } }
void smtp_vprintf(VSTREAM *stream, const char *fmt, va_list ap) { int err; /* * Do the I/O, protected against timeout. */ smtp_timeout_reset(stream); vstream_vfprintf(stream, fmt, ap); vstream_fputs("\r\n", stream); err = vstream_ferror(stream); smtp_timeout_detect(stream); /* * See if there was a problem. */ if (err != 0) { if (msg_verbose) msg_info("smtp_vprintf: EOF"); vstream_longjmp(stream, SMTP_ERR_EOF); } }
void smtp_fwrite(const char *cp, int todo, VSTREAM *stream) { unsigned err; if (todo < 0) msg_panic("smtp_fwrite: negative todo %d", todo); /* * Do the I/O, protected against timeout. */ smtp_timeout_reset(stream); err = (vstream_fwrite(stream, cp, todo) != todo); smtp_timeout_detect(stream); /* * See if there was a problem. */ if (err != 0) { if (msg_verbose) msg_info("smtp_fwrite: EOF"); vstream_longjmp(stream, SMTP_ERR_EOF); } }
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); }
void netstring_except(VSTREAM *stream, int exception) { vstream_longjmp(stream, exception); }
int smtp_get(VSTRING *vp, VSTREAM *stream, int bound) { int last_char; int next_char; /* * It's painful to do I/O with records that may span multiple buffers. * Allow for partial long lines (we will read the remainder later) and * allow for lines ending in bare LF. The idea is to be liberal in what * we accept, strict in what we send. * * XXX 2821: Section 4.1.1.4 says that an SMTP server must not recognize * bare LF as record terminator. */ smtp_timeout_reset(stream); last_char = (bound == 0 ? vstring_get(vp, stream) : vstring_get_bound(vp, stream, bound)); switch (last_char) { /* * Do some repair in the rare case that we stopped reading in the * middle of the CRLF record terminator. */ case '\r': if ((next_char = VSTREAM_GETC(stream)) == '\n') { VSTRING_ADDCH(vp, '\n'); last_char = '\n'; /* FALLTRHOUGH */ } else { if (next_char != VSTREAM_EOF) vstream_ungetc(stream, next_char); break; } /* * Strip off the record terminator: either CRLF or just bare LF. * * XXX RFC 2821 disallows sending bare CR everywhere. We remove bare CR * if received before CRLF, and leave it alone otherwise. */ case '\n': vstring_truncate(vp, VSTRING_LEN(vp) - 1); while (VSTRING_LEN(vp) > 0 && vstring_end(vp)[-1] == '\r') vstring_truncate(vp, VSTRING_LEN(vp) - 1); VSTRING_TERMINATE(vp); /* * Partial line: just read the remainder later. If we ran into EOF, * the next test will deal with it. */ default: break; } smtp_timeout_detect(stream); /* * EOF is bad, whether or not it happens in the middle of a record. Don't * allow data that was truncated because of EOF. */ if (vstream_feof(stream) || vstream_ferror(stream)) { if (msg_verbose) msg_info("smtp_get: EOF"); vstream_longjmp(stream, SMTP_ERR_EOF); } return (last_char); }
static void smtp_timeout_detect(VSTREAM *stream) { if (vstream_ftimeout(stream)) vstream_longjmp(stream, SMTP_ERR_TIME); }