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