1/*++ 2/* NAME 3/* smtpd_chat 3 4/* SUMMARY 5/* SMTP server request/response support 6/* SYNOPSIS 7/* #include <smtpd.h> 8/* #include <smtpd_chat.h> 9/* 10/* void smtpd_chat_query(state) 11/* SMTPD_STATE *state; 12/* 13/* void smtpd_chat_reply(state, format, ...) 14/* SMTPD_STATE *state; 15/* char *format; 16/* 17/* void smtpd_chat_notify(state) 18/* SMTPD_STATE *state; 19/* 20/* void smtpd_chat_reset(state) 21/* SMTPD_STATE *state; 22/* DESCRIPTION 23/* This module implements SMTP server support for request/reply 24/* conversations, and maintains a limited SMTP transaction log. 25/* 26/* smtpd_chat_query() receives a client request and appends a copy 27/* to the SMTP transaction log. 28/* 29/* smtpd_chat_reply() formats a server reply, sends it to the 30/* client, and appends a copy to the SMTP transaction log. 31/* When soft_bounce is enabled, all 5xx (reject) reponses are 32/* replaced by 4xx (try again). In case of a 421 reply the 33/* SMTPD_FLAG_HANGUP flag is set for orderly disconnect. 34/* 35/* smtpd_chat_notify() sends a copy of the SMTP transaction log 36/* to the postmaster for review. The postmaster notice is sent only 37/* when delivery is possible immediately. It is an error to call 38/* smtpd_chat_notify() when no SMTP transaction log exists. 39/* 40/* smtpd_chat_reset() resets the transaction log. This is 41/* typically done at the beginning of an SMTP session, or 42/* within a session to discard non-error information. 43/* DIAGNOSTICS 44/* Panic: interface violations. Fatal errors: out of memory. 45/* internal protocol errors. 46/* LICENSE 47/* .ad 48/* .fi 49/* The Secure Mailer license must be distributed with this software. 50/* AUTHOR(S) 51/* Wietse Venema 52/* IBM T.J. Watson Research 53/* P.O. Box 704 54/* Yorktown Heights, NY 10598, USA 55/*--*/ 56 57/* System library. */ 58 59#include <sys_defs.h> 60#include <setjmp.h> 61#include <unistd.h> 62#include <time.h> 63#include <stdlib.h> /* 44BSD stdarg.h uses abort() */ 64#include <stdarg.h> 65 66/* Utility library. */ 67 68#include <msg.h> 69#include <argv.h> 70#include <vstring.h> 71#include <vstream.h> 72#include <stringops.h> 73#include <line_wrap.h> 74#include <mymalloc.h> 75 76/* Global library. */ 77 78#include <smtp_stream.h> 79#include <record.h> 80#include <rec_type.h> 81#include <mail_proto.h> 82#include <mail_params.h> 83#include <mail_addr.h> 84#include <post_mail.h> 85#include <mail_error.h> 86#include <smtp_reply_footer.h> 87 88/* Application-specific. */ 89 90#include "smtpd.h" 91#include "smtpd_expand.h" 92#include "smtpd_chat.h" 93 94#define STR vstring_str 95#define LEN VSTRING_LEN 96 97/* smtp_chat_reset - reset SMTP transaction log */ 98 99void smtpd_chat_reset(SMTPD_STATE *state) 100{ 101 if (state->history) { 102 argv_free(state->history); 103 state->history = 0; 104 } 105} 106 107/* smtp_chat_append - append record to SMTP transaction log */ 108 109static void smtp_chat_append(SMTPD_STATE *state, char *direction, 110 const char *text) 111{ 112 char *line; 113 114 if (state->notify_mask == 0) 115 return; 116 117 if (state->history == 0) 118 state->history = argv_alloc(10); 119 line = concatenate(direction, text, (char *) 0); 120 argv_add(state->history, line, (char *) 0); 121 myfree(line); 122} 123 124/* smtpd_chat_query - receive and record an SMTP request */ 125 126void smtpd_chat_query(SMTPD_STATE *state) 127{ 128 int last_char; 129 130 /* 131 * We can't parse or store input that exceeds var_line_limit, so we skip 132 * over it to avoid loss of synchronization. 133 */ 134 last_char = smtp_get(state->buffer, state->client, var_line_limit, 135 SMTP_GET_FLAG_SKIP); 136 smtp_chat_append(state, "In: ", STR(state->buffer)); 137 if (last_char != '\n') 138 msg_warn("%s: request longer than %d: %.30s...", 139 state->namaddr, var_line_limit, 140 printable(STR(state->buffer), '?')); 141 142 if (msg_verbose) 143 msg_info("< %s: %s", state->namaddr, STR(state->buffer)); 144} 145 146/* smtpd_chat_reply - format, send and record an SMTP response */ 147 148void smtpd_chat_reply(SMTPD_STATE *state, const char *format,...) 149{ 150 va_list ap; 151 int delay = 0; 152 char *cp; 153 char *next; 154 char *end; 155 156 /* 157 * Slow down clients that make errors. Sleep-on-anything slows down 158 * clients that make an excessive number of errors within a session. 159 */ 160 if (state->error_count >= var_smtpd_soft_erlim) 161 sleep(delay = var_smtpd_err_sleep); 162 163 va_start(ap, format); 164 vstring_vsprintf(state->buffer, format, ap); 165 va_end(ap); 166 167 if (*var_smtpd_rej_footer 168 && (*(cp = STR(state->buffer)) == '4' || *cp == '5')) 169 smtp_reply_footer(state->buffer, 0, var_smtpd_rej_footer, 170 STR(smtpd_expand_filter), smtpd_expand_lookup, 171 (char *) state); 172 173 /* All 5xx replies must have a 5.xx.xx detail code. */ 174 for (cp = STR(state->buffer), end = cp + strlen(STR(state->buffer));;) { 175 if (var_soft_bounce) { 176 if (cp[0] == '5') { 177 cp[0] = '4'; 178 if (cp[4] == '5') 179 cp[4] = '4'; 180 } 181 } 182 /* This is why we use strlen() above instead of VSTRING_LEN(). */ 183 if ((next = strstr(cp, "\r\n")) != 0) { 184 *next = 0; 185 if (next[2] != 0) 186 cp[3] = '-'; /* contact footer kludge */ 187 else 188 next = end; /* strip trailing \r\n */ 189 } else { 190 next = end; 191 } 192 smtp_chat_append(state, "Out: ", cp); 193 194 if (msg_verbose) 195 msg_info("> %s: %s", state->namaddr, cp); 196 197 smtp_fputs(cp, next - cp, state->client); 198 if (next < end) 199 cp = next + 2; 200 else 201 break; 202 } 203 204 /* 205 * Flush unsent output if no I/O happened for a while. This avoids 206 * timeouts with pipelined SMTP sessions that have lots of server-side 207 * delays (tarpit delays or DNS lookups for UCE restrictions). 208 */ 209 if (delay || time((time_t *) 0) - vstream_ftime(state->client) > 10) 210 vstream_fflush(state->client); 211 212 /* 213 * Abort immediately if the connection is broken. 214 */ 215 if (vstream_ftimeout(state->client)) 216 vstream_longjmp(state->client, SMTP_ERR_TIME); 217 if (vstream_ferror(state->client)) 218 vstream_longjmp(state->client, SMTP_ERR_EOF); 219 220 /* 221 * Orderly disconnect in case of 421 or 521 reply. 222 */ 223 if (strncmp(STR(state->buffer), "421", 3) == 0 224 || strncmp(STR(state->buffer), "521", 3) == 0) 225 state->flags |= SMTPD_FLAG_HANGUP; 226} 227 228/* print_line - line_wrap callback */ 229 230static void print_line(const char *str, int len, int indent, char *context) 231{ 232 VSTREAM *notice = (VSTREAM *) context; 233 234 post_mail_fprintf(notice, " %*s%.*s", indent, "", len, str); 235} 236 237/* smtpd_chat_notify - notify postmaster */ 238 239void smtpd_chat_notify(SMTPD_STATE *state) 240{ 241 const char *myname = "smtpd_chat_notify"; 242 VSTREAM *notice; 243 char **cpp; 244 245 /* 246 * Sanity checks. 247 */ 248 if (state->history == 0) 249 msg_panic("%s: no conversation history", myname); 250 if (msg_verbose) 251 msg_info("%s: notify postmaster", myname); 252 253 /* 254 * Construct a message for the postmaster, explaining what this is all 255 * about. This is junk mail: don't send it when the mail posting service 256 * is unavailable, and use the double bounce sender address to prevent 257 * mail bounce wars. Always prepend one space to message content that we 258 * generate from untrusted data. 259 */ 260#define NULL_TRACE_FLAGS 0 261#define NO_QUEUE_ID ((VSTRING *) 0) 262#define LENGTH 78 263#define INDENT 4 264 265 notice = post_mail_fopen_nowait(mail_addr_double_bounce(), 266 var_error_rcpt, 267 INT_FILT_MASK_NOTIFY, 268 NULL_TRACE_FLAGS, NO_QUEUE_ID); 269 if (notice == 0) { 270 msg_warn("postmaster notify: %m"); 271 return; 272 } 273 post_mail_fprintf(notice, "From: %s (Mail Delivery System)", 274 mail_addr_mail_daemon()); 275 post_mail_fprintf(notice, "To: %s (Postmaster)", var_error_rcpt); 276 post_mail_fprintf(notice, "Subject: %s SMTP server: errors from %s", 277 var_mail_name, state->namaddr); 278 post_mail_fputs(notice, ""); 279 post_mail_fputs(notice, "Transcript of session follows."); 280 post_mail_fputs(notice, ""); 281 argv_terminate(state->history); 282 for (cpp = state->history->argv; *cpp; cpp++) 283 line_wrap(printable(*cpp, '?'), LENGTH, INDENT, print_line, 284 (char *) notice); 285 post_mail_fputs(notice, ""); 286 if (state->reason) 287 post_mail_fprintf(notice, "Session aborted, reason: %s", state->reason); 288 post_mail_fputs(notice, ""); 289 post_mail_fprintf(notice, "For other details, see the local mail logfile"); 290 (void) post_mail_fclose(notice); 291} 292