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