/* * Copyright (c) 1996-2007, OpenFWTK Development Group * All rights reserved. See LICENSE. */ /* * This is independant code branch modified to fit fwtk configuration and * logging style by ArkanoiD, 2001 * * Original copyright info follows. */ /* * smtpd, Obtuse SMTP daemon, storing agent. does simple collection of * mail messages, for later forwarding by smtpfwdd. * * Copyright (c) 1996, 1997 Obtuse Systems Corporation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Obtuse Systems * Corporation and its contributors. * 4. Neither the name of the Obtuse Systems Corporation nor the names * of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY OBTUSE SYSTEMS CORPORATION AND * CONTRIBUTORS ``AS IS''AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL OBTUSE SYSTEMS CORPORATION OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ char *obtuse_copyright = "Copyright 1996 - Obtuse Systems Corporation - All rights reserved."; #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef IRIX_BROKEN_INCLUDES /* IRIX 5.3 defines EX_OK (see sysexits.h) as something very strange in unistd.h :-) */ #ifdef EX_OK #undef EX_OK #endif #endif #ifndef NS_MAXDNAME #define NS_MAXDNAME MAXDNAME #endif #include #include #include #include #include #include #include #include #include #include #ifdef NEEDS_STRINGS_H #include #endif #ifdef NEEDS_FCNTL_H #include #endif #ifdef NEEDS_BSTRING_H #include #endif #ifdef NEEDS_SELECT_H #include #endif #include "smtp.h" #include "smtpd.h" #include "firewall.h" #include "firewall2.h" #include "fwfunc.h" static char* moduleId ATTR_UNUSED = "$Id: smtpd.c,v 1.10 2007/09/22 15:59:43 arkenoi Exp $"; extern struct fwstats proxy_stats; extern char proxy_chroot[]; extern Cfg* proxy_confp; extern int proxy_timeout; extern int proxy_uid, proxy_gid; #ifndef SPOOLSUBDIR #define SPOOLSUBDIR "." #endif #define SPANBLANK(p) while (isspace(*p)) p += 1 struct sockaddr_in my_sa; char *my_name = NULL; char *current_from_mailpath = NULL; char *client_claimed_name = "UNKNOWN"; char *spoolfile = NULL; char *spooldir = SPOOLSUBDIR; /* this is relative to our chroot. */ long maxsize = 0; int maxheader; int maxrecip; int outfd, replyfd; #ifdef SUNOS_GETOPT extern char *optarg; extern int optind; #else char *optarg; int optind; #endif struct smtp_mbuf *input_buf, *output_buf, *reply_buf; int exiting = 0; int VerboseSyslog = 0; char* my_clean_reverse_name = NULL; static int dscp = 0; static fwparm options[] = { {FWPARM_BOOL, "-log", (char*) &VerboseSyslog }, {FWPARM_DSCP, "-client-dscp", (char*) &dscp }, {FWPARM_DSCP, "-dscp", (char*) &dscp }, {0, 0, 0 }}; extern int matches_spam_db(char *sender, char *hostname, char *addr, Cfg *config); extern int valid_recipient(char *rcpt, char *addr, Cfg *config); extern int valid_sender(char *sender, Cfg *configTable); extern int matches_maps_rbl(char *addr, Cfg *configTable); /* * Signal handler that shuts us down if a read on the socket times out */ static void read_alarm_timeout(int s) { if (s != SIGALRM) { syslog(LLEV, "fwtksyserr: read timeout alarm handler called with signal %d!, (not SIGALRM!) - Aborting!", s); abort(); } syslog(LLEV, "Network timeout signal after %d seconds", proxy_timeout); smtp_exit(EX_OSERR); } #ifdef NO_MEMMOVE /* * Use bcopy on platforms that don't support the newer memmove function */ void memmove(void *to, void *from, int len) { bcopy(from, to, len); } #endif /* * Return to the initial state */ void reset_state(smtp_state state) { if (test_state(OK_HELO, state)) { zap_state(state); set_state(OK_HELO, state); } else { zap_state(state); } /* * we must throw away anything in the output buffer */ output_buf->tail = output_buf->data; output_buf->offset = 0; } /* * returns the index of the start of the first end-of-command token * * in buf. returns len if it couldn't find one. */ int crlf_left(unsigned char *buf, size_t len) { int i; i = 0; while (i < len) { #ifdef CRLF_PEDANTIC /* * This is how the RFC says the world should work. * Unfortunately, it doesn't. */ if (buf[i] == CR) if (i < (len - 1)) if (buf[i + 1] == LF) return (i); #else /* * The world works like this. */ if (buf[i] == LF) { if (i > 0 && buf[i - 1] == CR) { return (i - 1); } else { return (i); } } #endif i++; } /* * couldn't find one */ return (len); } /* * find next crlf in buf, starting at offset. On finding one, replaces * the first byte of crlf with \0, sets offset to first byte after end * of crlf, and returns start of string if we don't find one we return * NULL */ char * smtp_get_line(struct smtp_mbuf *mbuf, size_t * offset) { int i; size_t len; unsigned char *buf; buf = mbuf->data + *offset; len = mbuf->offset - *offset; if (len == 0) { return (NULL); } i = crlf_left(buf, len); if (i < (len)) { buf[i] = '\0'; /* * jump over end of line token */ i++; if ((i < len) && (buf[i] == LF)) { i++; } *offset += i; return ((char*) buf); } return (NULL); } /* * flush len bytes of an mbuf to file descriptor fd. */ void flush_smtp_mbuf(struct smtp_mbuf *buf, int fd, int len) { int foo = 0; static int deaththroes=0; if (deaththroes) { return; /* We've already had a write barf. Don't try again */ } if (len <= buf->offset) { while (foo < len) { int i; i = write(fd, (buf->data) + foo, len); if (i < 0) { syslog(LLEV, "write failed: (%s)", strerror(errno)); deaththroes=1; smtp_exit(EX_OSERR); } foo += i; /* * ok. reset the mbuf. */ if (foo == buf->offset) { buf->offset = 0; buf->tail = buf->data; } else { clean_smtp_mbuf(buf, foo); } } } else { syslog(LLEV, "fwtksyserr: you can't write %d bytes from a buffer with only %d in it!", len, (int) buf->offset); } } /* * Strip some data out of an smtp_mbuf */ void clean_smtp_mbuf(struct smtp_mbuf *buf, int len) { if (len > buf->offset) { abort(); } if (len < buf->offset) { memmove(buf->data, (buf->data) + len, (buf->offset) - len); buf->offset = buf->offset - len; buf->tail = (buf->data) + (buf->offset); } else { buf->offset = 0; buf->tail = buf->data; } } /* * Allocate and initialize an smtp_mbuf */ struct smtp_mbuf * alloc_smtp_mbuf(size_t size) { struct smtp_mbuf *newbuf; newbuf = (struct smtp_mbuf *) malloc(sizeof(struct smtp_mbuf)); if (newbuf == NULL) { return (NULL); } newbuf->data = (unsigned char *) malloc(sizeof(unsigned char) * size); if (newbuf->data == NULL) { free(newbuf); return (NULL); } newbuf->size = size; newbuf->offset = 0; newbuf->tail = newbuf->data; return (newbuf); } /* * Grow data area by "bloat" preserving everything else */ int grow_smtp_mbuf(struct smtp_mbuf *tiny, size_t bloat) { unsigned char *newdata; newdata = (unsigned char *) malloc(tiny->size + bloat); if (newdata == NULL) return (0); memcpy((void *) newdata, (void *) tiny->data, tiny->offset); free(tiny->data); tiny->data = newdata; tiny->size += bloat; tiny->tail = tiny->data + tiny->offset; return (1); } /* * write len bytes from data into buffer mbuf. growing if necessary. * return 1 if successful, 0 for failure. */ int write_smtp_mbuf(struct smtp_mbuf *mbuf, char *data, size_t len) { if (len > (mbuf->size - mbuf->offset)) { /* * we need a bigger buffer */ if (!(grow_smtp_mbuf(mbuf, ((len / 1024) + 1) * 1024))) { /* * let's hope there is enough to syslog :-) */ syslog(LLEV, "fwtksyserr: malloc said no to a %d byte buffer!", (int)(mbuf->size + len + 1024)); return (0); } } /* * buffer is now big enough */ memcpy((void *) mbuf->tail, (void *) (data), len); mbuf->tail += len; mbuf->offset += len; return (1); } /* * read up to len bytes from fd into buffer mbuf. growing if * neccessary. * return amount read if successful. * set errno and return -1 for failure. */ int read_smtp_mbuf(struct smtp_mbuf *mbuf, int fd, size_t len) { int howmany; if (len > (mbuf->size - mbuf->offset)) { /* * we need a bigger buffer */ if (!(grow_smtp_mbuf(mbuf, ((len / 1024) + 1) * 1024))) { /* * let's hope there is enough to syslog :-) */ syslog(LLEV, "fwtksyserr: malloc said no to a %d byte buffer!", (int)(mbuf->size + len + 1024)); errno = ENOMEM; return (-1); } } /* * buffer is now big enough */ fflush(NULL); signal(SIGALRM, read_alarm_timeout); alarm(proxy_timeout); howmany = read(fd, mbuf->tail, len); alarm(0); signal(SIGALRM, SIG_DFL); if (howmany > 0) { mbuf->tail += howmany; mbuf->offset += howmany; } return (howmany); } /* * Write a possibly multi-segement reply into mbuf "outbuf" */ int writereply(struct smtp_mbuf *outbuf, int code, int more, ...) { int ok; char message[5]; va_list ap; char *msg; va_start(ap, more); snprintf(message, 5, "%3d%s", code, (more) ? "-" : " "); ok = write_smtp_mbuf(outbuf, message, strlen(message)); while (ok && (msg = va_arg(ap, char *)) != NULL) { ok = write_smtp_mbuf(outbuf, msg, strlen(msg)); } if (ok) { ok = write_smtp_mbuf(outbuf, CRLF, 2); } va_end(ap); return (ok); } /* * open a new spoolfile with appropriate lock and permissions */ int smtp_open_spoolfile() { int fd; int len; if (spoolfile != NULL) { syslog(LLEV, "fwtksyserr: attempt to open new spoolfile with %s already open - aborting", spoolfile); abort(); } putenv(xstrdup("TMPDIR=/")); /* * Linux's tempnam() requires this kludge * or we have to make a /tmp in our * chrootdir. In it's wisdom it decides * that if /tmp doesn't exist we can't * have a tmpname anywhere.. Grumble.. */ /* If someone does manage to misconfigure us so people have * access to the spool dir they probably have worse things to * worry about than the race condition but.. Oh well, keeps gcc * from complaining. */ spoolfile= (char *) xmalloc(len = (strlen(spooldir)+13)); strlcpy(spoolfile, spooldir, len); strlcat(spoolfile, "/smtpdXXXXXX", len); if ((fd = mkstemp(spoolfile)) < 0) { syslog(LLEV, "fwtksyserr: couldn't create spool file %s!", spoolfile); free(spoolfile); spoolfile=NULL; smtp_exit(EX_CONFIG); } if (lock_fd(fd) != 0) { syslog(LLEV, "fwtksyserr: couldn't lock spool file %s", spoolfile); smtp_exit(EX_TEMPFAIL); } return (fd); } /* * close spoolfile, unlock, and open permissions */ void smtp_close_spoolfile(int fd) { if (spoolfile == NULL) { syslog(LLEV, "fwtksyserr: attempt to close NULL spoolfile!"); smtp_exit(EX_CONFIG); } if (locktest_fd(fd) == 0) if (lockun_fd(fd) != 0) { syslog(LLEV, "fwtksyserr: couldn't unlock spool file %s", spoolfile); smtp_exit(EX_OSERR); } close(fd); chmod(spoolfile, 0750); /* * Mark file as 'complete' */ free(spoolfile); spoolfile = NULL; } /* * unlock spoolfile and remove it */ void smtp_nuke_spoolfile(int fd) { if (spoolfile == NULL) { syslog(LLEV, "fwtksyserr: attempt to remove NULL spoolfile!"); smtp_exit(EX_SOFTWARE); } if (unlink(spoolfile) != 0) { syslog(LLEV, "fwtksyserr: couldn't remove spool file %s! (%s)", spoolfile, strerror(errno)); free(spoolfile); spoolfile = NULL; smtp_exit(EX_CONFIG); } if (locktest_fd(fd) == 0) if (lockun_fd(fd) != 0) { syslog(LLEV, "fwtksyserr: couldn't unlock spool file %s using lockf! (%s)", spoolfile, strerror(errno)); free(spoolfile); spoolfile = NULL; smtp_exit(EX_OSERR); } close(fd); free(spoolfile); spoolfile = NULL; } /* * Try to say something meaningful to our client and then exit. */ void smtp_exit(int val) { if (val != 0) { /* * we're leaving the client hanging. attempt to tell them we're * going away */ if (exiting++<1) { writereply(reply_buf, 421, 0, m421msg, NULL); } /* * if we have an open spool file that's unclosed, blast it out of * existence */ if (exiting++<2) { if (spoolfile != NULL) { smtp_nuke_spoolfile(outfd); } } } else { if (exiting++<1) { if (spoolfile != NULL) { smtp_close_spoolfile(outfd); exiting++; } } } if (exiting++<3) { flush_smtp_mbuf(reply_buf, replyfd, reply_buf->offset); } if (!VerboseSyslog) { accumlog(LOG_INFO, 0); /* flush? */ } syslog(LLEV,"exit host=%.128s/%.128s bytes=%u",proxy_stats.rladdr,proxy_stats.riaddr,proxy_stats.outbytes); exit(val); } /* * clean up things (mostly hostnames) that will go into the syslogs */ unsigned char * cleanitup(const unsigned char *s) { static unsigned char *bufs[20]; static int first = 1, next_buffer; unsigned char *dst, *buffer_addr; const unsigned char *src; int saw_weird_char, saw_bad_char, saw_high_bit = 0; if (first) { int i; for (i = 0; i < 20; i += 1) { bufs[i] = malloc(1024); if (bufs[i] == NULL) { syslog(LLEV, "fwtksyserr: malloc (1024 bytes) failed in cleanitup"); abort(); } } first = 0; } src = s; dst = bufs[(next_buffer++) % 20]; buffer_addr = dst; saw_weird_char = 0; saw_bad_char = 0; saw_high_bit = 0; while (*src != '\0') { unsigned char xch, ch; xch = *src++; ch = xch & 0x7f; if (ch != xch) { saw_high_bit = 1; *dst++ = '^'; *dst++ = '='; } if (isalnum(ch) || strchr(" -/:=.@_[]", ch) != NULL) { *dst++ = ch; } else if (strchr("\\`$|;()*", ch) != NULL) { saw_bad_char = 1; *dst++ = '^'; *dst++ = 'x'; *dst++ = "0123456789abcdef"[(xch >> 4) & 0xf]; *dst++ = "0123456789abcdef"[(xch) & 0xf]; } else { saw_weird_char = 1; switch (ch) { case '\n': *dst++ = '^'; *dst++ = 'n'; break; case '\r': *dst++ = '^'; *dst++ = 'r'; break; case '\t': *dst++ = '^'; *dst++ = 't'; break; case '\b': *dst++ = '^'; *dst++ = 'b'; break; case '^': *dst++ = '^'; *dst++ = '^'; break; default: *dst++ = '^'; *dst++ = 'x'; *dst++ = "0123456789abcdef"[(xch >> 4) & 0xf]; *dst++ = "0123456789abcdef"[(xch) & 0xf]; } } if (dst - buffer_addr > 1024 - 10) { syslog(LLEV, "INFO(cleanitup) - buffer overflow - chopping"); break; } } *dst = '\0'; if (saw_bad_char) { syslog(LLEV, "securityalert: (cleanitup) - saw VERY unusual character (cleaned string is \"%s\")", buffer_addr); } if (saw_weird_char) { syslog(LLEV, "INFO(cleanitup) - saw unusual character (cleaned string is \"%s\")", buffer_addr); } if (saw_high_bit) { syslog(LLEV, "INFO(cleanitup) - saw character with high bit set (cleaned string is \"%s\")", buffer_addr); } return (buffer_addr); } /* * less paranoid version of cleanitup that tries to handle mail addresses * without mangling them. */ unsigned char * smtp_cleanitup(const unsigned char *s) { static unsigned char *bufs[20]; static int first = 1, next_buffer; unsigned char *dst, *buffer_addr; const unsigned char *src; int firstone, arg_attempt, saw_weird_char, saw_bad_char, saw_high_bit = 0; if (first) { int i; for (i = 0; i < 20; i += 1) { bufs[i] = malloc(1024); if (bufs[i] == NULL) { syslog(LLEV, "fwtksyserr: CRITICAL - malloc (1024 bytes) failed in smtp_cleanitup"); abort(); } } first = 0; } src = s; dst = bufs[(next_buffer++) % 20]; buffer_addr = dst; saw_weird_char = 0; saw_bad_char = 0; saw_high_bit = 0; arg_attempt = 0; firstone = 1; while (*src != '\0') { unsigned char xch, ch; xch = *src++; ch = xch & 0x7f; if (ch != xch) { saw_high_bit = 1; *dst++ = '^'; *dst++ = '='; } /* * RFC822 allows both ' and " in local-part. * " is infact _required_ if local-part contains spaces as is * common in x400 (yuk). */ if (isalnum(ch) || strchr(" -,:=.@_!<>()[]/+%'\"", ch) != NULL) { if (firstone && (ch == '-')) { arg_attempt = 1; *dst++ = '^'; *dst++ = '-'; } else { *dst++ = ch; } } else if (strchr("\\`$|*;", ch) != NULL) { saw_bad_char = 1; *dst++ = '^'; *dst++ = 'x'; *dst++ = "0123456789abcdef"[(xch >> 4) & 0xf]; *dst++ = "0123456789abcdef"[(xch) & 0xf]; } else { saw_weird_char = 1; switch (ch) { case '\n': *dst++ = '^'; *dst++ = 'n'; break; case '\r': *dst++ = '^'; *dst++ = 'r'; break; case '\t': *dst++ = '^'; *dst++ = 't'; break; case '\b': *dst++ = '^'; *dst++ = 'b'; break; case '^': *dst++ = '^'; *dst++ = '^'; break; default: *dst++ = '^'; *dst++ = 'x'; *dst++ = "0123456789abcdef"[(xch >> 4) & 0xf]; *dst++ = "0123456789abcdef"[(xch) & 0xf]; } } if (dst - buffer_addr > 1024 - 10) { syslog(LLEV, "INFO(smtp_cleanitup) - buffer overflow - chopping"); break; } firstone = 0; } *dst = '\0'; if (arg_attempt) { syslog(LLEV, "securityalert: (smtp_cleanitup) - '-' as first character in address (cleaned string is \"%s\")", buffer_addr); } if (saw_bad_char) { syslog(LLEV, "securityalert: (smtp_cleanitup) - saw VERY unusual character (cleaned string is \"%s\")", buffer_addr); } if (saw_weird_char) { syslog(LLEV, "INFO(smtp_cleanitup) - saw unusual character (cleaned string is \"%s\")", buffer_addr); } if (saw_high_bit) { syslog(LLEV, "INFO(smtp_cleanitup) - saw character with high bit set cleaned string is \"%s\")", buffer_addr); } return (buffer_addr); } /* * is smtp command "cmd" legal in state "state" */ int cmd_ok(int cmd, smtp_state state) { if (sane_state(state)) { switch (cmd) { case HELO: return (!(test_state(OK_HELO, state))); #if EHLO_KLUDGE case EHLO: return(test_state(OK_EHLO, state)); #endif case MAIL: if (test_state(OK_HELO, state) && (!test_state(OK_MAIL, state))) return (1); else return (0); case RCPT: if (test_state(OK_MAIL, state) && (!test_state(SNARF_DATA, state))) return (1); else return (0); case DATA: if (test_state(OK_RCPT, state)) return (1); else return (0); default: return (1); } } return (0); } /* * is this state legal? returns 1 if so, 0 if not */ int sane_state(smtp_state state) { if (test_state(OK_MAIL, state) && !test_state(OK_HELO, state)) { syslog(LOG_DEBUG, "Bad state. can't be OK_MAIL and not OK_HELO"); return (0); } if (test_state(OK_RCPT, state) && !test_state(OK_MAIL, state)) { syslog(LOG_DEBUG, "Bad state. can't be OK_RCPT and not OK_MAIL"); return (0); } if (test_state(SNARF_DATA, state) && !test_state(OK_RCPT, state)) { syslog(LOG_DEBUG, "Bad state. can't be SNARF_DATA and not OK_RCPT"); return (0); } return (1); } /* * state change engine. given "state", change state after processing * * command "cmd" with status "status", */ void state_change(smtp_state state, int cmd, int status) { /* * basic state sanity checks */ if (!sane_state(state)) { reset_state(state); return; } switch (cmd) { case HELO: switch (status) { case SUCCESS: set_state(OK_HELO, state); /* * we got a helo */ return; case ERROR: clear_state(OK_HELO, state); return; case FAILURE: reset_state(state); return; default: syslog(LLEV, "fwtksyserr: hey, I shouldn't be here (Bad HELO status in change_state)!\n"); abort(); } #if EHLO_KLUDGE case EHLO: switch (status) { case SUCCESS: set_state(OK_EHLO, state); /* * we got a ehlo */ return; case ERROR: clear_state(OK_EHLO, state); return; case FAILURE: reset_state(state); return; default: syslog(LLEV, "fwtksyserr: hey, I shouldn't be here (Bad EHLO status in change_state)!\n"); abort(); } #endif case MAIL: switch (status) { case SUCCESS: set_state(OK_MAIL, state); return; case ERROR: /* * no state change */ return; case FAILURE: reset_state(state); return; default: syslog(LLEV, "fwtksyserr: hey, I shouldn't be here (Bad MAIL status in change_state)!\n"); abort(); } case RCPT: switch (status) { case SUCCESS: set_state(OK_RCPT, state); return; case ERROR: /* * no state change */ return; case FAILURE: reset_state(state); return; default: syslog(LLEV, "fwtksyserr: hey, I shouldn't be here (Bad RCPT status in change_state)!\n"); abort(); } case NOOP: switch (status) { case SUCCESS: return; default: syslog(LLEV, "fwtksyserr: hey, I shouldn't be here (Bad NOOP status in change_state)!\n"); abort(); } case DATA: switch (status) { case SUCCESS: set_state(SNARF_DATA, state); return; case ERROR: /* * hmm. hard to do this */ return; case FAILURE: reset_state(state); return; default: syslog(LLEV, "fwtksyserr: hey, I shouldn't be here (Bad DATA status in change_state)!\n"); abort(); } case UNKNOWN: switch (status) { case SUCCESS: return; case ERROR: return; case FAILURE: reset_state(state); return; default: syslog(LLEV, "fwtksyserr: hey, I shouldn't be here (Bad UNKNOWN status in change_state)!\n"); abort(); } case QUIT: /* * one can always quit */ smtp_exit(EX_OK); break; case RSET: /* * one can always reset */ reset_state(state); break; default: /* * shouldn't get here on valid input. */ syslog(LLEV, "fwtksyserr: hey, I shouldn't be here (end of change_state)!\n"); abort(); } } /* * parse a single smtp command in inbuf. * * PRE: "inbuf" contains one read line, \0 terminated without CRLF * at the end, and a non-whitespace character at the start. initial * state pointer passed in as "state". "outbuf" is our buffer for output * we're keeping, "replybuf" is our buffer for replies to the client. * * POST: any output from the command is output to the end of outbuf, * any replys to the client are output to the end of replybuf. * state is changed accordingly. * */ void smtp_parse_cmd(unsigned char *inbuf, struct smtp_mbuf *outbuf, struct smtp_mbuf *replybuf, smtp_state state) { unsigned char *buf, *cp; size_t ilen; unsigned char verb[5]; int currecip = 0; ilen = strlen((char*) inbuf); if (ilen < 4) { if (ilen == 3) { memcpy(verb, inbuf, 3); verb[3] = '\0'; if (strcasecmp((char*)verb, "WIZ") == 0) { syslog(LLEV, "securityalert: wizard command attempted from address %.128s/%.128s, name %.256s", proxy_stats.rladdr, proxy_stats.riaddr, client_claimed_name); writereply(replybuf, 250, 0, m250msg, NULL); state_change(state, NOOP, SUCCESS); return; } } else { /* * we need at least one complete verb. */ writereply(replybuf, 500, 0, m500msg, NULL); state_change(state, UNKNOWN, ERROR); return; } } proxy_update_operation((char*)inbuf); memcpy(verb, inbuf, 4); verb[4] = '\0'; buf = inbuf + 4; /* The basic vanilla SMTP commands, minimum as specified in RFC 821; * HELO, MAIL, RCPT, DATA, RSET, NOOP, QUIT. Added minimal VRFY * after rumors (never substantiated) that some mail agents might * try it thanks to RFC 1123. We don't bother checking address * parameter syntax rigidly like RFC 1123 says we should, * leaving that up to the MTA invoked at the end. I don't believe * the added code complexity is worth any practical benefit here * when we are invoking the MTA after. Feel free to convince me * otherwise. */ /* * HELO */ #if EHLO_KLUDGE if ((strcasecmp((char*)verb, "HELO") == 0) || (cmd_ok(EHLO, state) && (strcasecmp((char*)verb, "EHLO") == 0))) { #else if (strcasecmp((char*)verb, "HELO") == 0) { #endif /* * Hello hello.. a-la RFC 821 */ if (!cmd_ok(HELO, state)) { writereply(replybuf, 503, 0, m503msg, NULL); state_change(state, HELO, FAILURE); return; } /* * at this point I shouldn't have anything bigger than a hostname * left. */ SPANBLANK(buf); if (strlen((char*)buf) > SMTP_MAXFQNAME) { /* * someone gave us a *big* name for themselves. draw them to * our attention, and fail. */ syslog(LLEV, "securityalert: more than %u bytes on HELO from %.128s/%.128s).", SMTP_MAXFQNAME, proxy_stats.rladdr, proxy_stats.riaddr); state_change(state, HELO, FAILURE); return; } client_claimed_name = xstrdup((char*)cleanitup(buf)); if (strcmp((char*) buf, client_claimed_name) != 0) { syslog(LLEV, "securityalert: suspicious characters in HELO: hostname from host %.128s/%.128s, exiting", proxy_stats.rladdr, proxy_stats.riaddr); smtp_exit(EX_PROTOCOL); } writereply(replybuf, 250, 0, my_clean_reverse_name, " ", m250helook, " ", client_claimed_name, NULL); /* * log the connection */ if (VerboseSyslog) { syslog(LLEV, "SMTP HELO from %.128s/%.128s as \".256%s\"", proxy_stats.rladdr, proxy_stats.riaddr, client_claimed_name); } else { accumlog(LLEV, 0); /* flush anything left */ accumlog(LLEV, "relay=%s/%s", proxy_stats.rladdr,proxy_stats.riaddr); if (strcasecmp(proxy_stats.rladdr, client_claimed_name)) accumlog(LLEV, " as \"%s\"", client_claimed_name); } state_change(state, HELO, SUCCESS); } else if (strcasecmp((char*)verb, "MAIL") == 0) { if (!cmd_ok(MAIL, state)) { writereply(replybuf, 554, 0, m554msg, NULL); state_change(state, MAIL, ERROR); return; } /* * at this point I shouldn't have anything bigger than a return * * address and a FROM: left. */ if (strlen((char*)buf) > SMTP_MAX_MAILPATH + 7) { /* * someone gave us a *big* name * for themselves. draw them to * our attention, and fail. */ syslog(LLEV, "securityalert: more than %d bytes on MAIL from address %.128s/%.128s", SMTP_MAX_MAILPATH, proxy_stats.rladdr, proxy_stats.riaddr); state_change(state, MAIL, FAILURE); return; } SPANBLANK(buf); if (strncasecmp((char*) buf, "FROM:", 5) != 0) { writereply(replybuf, 501, 0, m501msg, NULL); state_change(state, MAIL, ERROR); return; } buf += 5; SPANBLANK(buf); /* * if local-part contains ", then spaces are allowed */ cp = NULL; if (buf[0] == '"' || buf[1] == '"') cp = (unsigned char*) strrchr((char*)buf, '"'); /* REVISIT: find last " */ if (cp == NULL) cp = buf+1; cp = (unsigned char*) strchr((char*)cp+1, ' '); if (cp != NULL) { /* stuff on the end */ *cp = '\0'; cp++; SPANBLANK(cp); if (*cp != '\0') { /* We could deal with ESMTP SIZE here. If so it's either * OK or bogus, in which we have to return 555. * * Without ESMTP. this is crud on the end, and we give 501 */ writereply(replybuf, 501, 0, m501msg, NULL); state_change(state, MAIL, ERROR); return; } } current_from_mailpath = xstrdup((char*)smtp_cleanitup(buf)); if (strcmp((char*)buf, current_from_mailpath) != 0) { syslog(LLEV,"securityalert: suspicious characters in FROM: address from host %.128s/%.128s, exiting", proxy_stats.rladdr, proxy_stats.riaddr); smtp_exit(EX_PROTOCOL); } if (matches_spam_db(current_from_mailpath,proxy_stats.rladdr, proxy_stats.riaddr,proxy_confp)) { syslog(LLEV,"spam ignored address=%s host=%.512s/%.20s (DB)", current_from_mailpath,proxy_stats.rladdr, proxy_stats.riaddr); state_change(state, MAIL, FAILURE); return; } if (!valid_sender(current_from_mailpath,proxy_confp)) { syslog(LLEV,"sender's domain does not have an NS record: from=%.512s host=%.512s/%.20s", current_from_mailpath,proxy_stats.rladdr, proxy_stats.riaddr); state_change(state, MAIL, FAILURE); return; } writereply(replybuf, 250, 0, "sender is ", current_from_mailpath, ", ", m250fromok, NULL); /* * log the connection */ if (VerboseSyslog) { syslog(LLEV, "mail from %s", current_from_mailpath); } else { accumlog(LLEV, " from=%s", current_from_mailpath); } /* * put our output in the outbuf */ if (write_smtp_mbuf(outbuf, "FROM ", strlen("FROM ")) && write_smtp_mbuf(outbuf, current_from_mailpath, strlen(current_from_mailpath)) && write_smtp_mbuf(outbuf, "\n", 1)) { state_change(state, MAIL, SUCCESS); } else { state_change(state, MAIL, FAILURE); } } else if (strcasecmp((char*)verb, "RCPT") == 0) { char *victim; int badrcpt=0; if (!cmd_ok(RCPT, state)) { writereply(replybuf, 554, 0, m554nofrom, NULL); state_change(state, RCPT, ERROR); return; } /* * at this point I shouldn't have anything bigger than a return * * address and a RCPT: left. */ if (strlen((char*)buf) > SMTP_MAX_MAILPATH + 1) { /* * someone gave us a *big* name for themselves. * draw them to * our attention, and fail. */ syslog(LLEV, "securityalert: more than %d bytes on RCPT from address %.128s/%.128s", SMTP_MAX_MAILPATH, proxy_stats.rladdr, proxy_stats.riaddr); state_change(state, RCPT, FAILURE); return; } SPANBLANK(buf); if ((strlen((char*)buf) < 3) || strncasecmp((char*)buf, "TO:", 3) != 0) { writereply(replybuf, 501, 0, m501msg, NULL); state_change(state, RCPT, ERROR); return; } buf += 3; SPANBLANK(buf); /* * if local-part contains ", then spaces are allowed */ cp = NULL; if (buf[0] == '"' || buf[1] == '"') cp = (unsigned char*) strrchr((char*)buf, '"'); /* REVISIT: find last " */ if (cp == NULL) cp = buf; cp = (unsigned char*) strchr((char*) cp+1, ' '); if (cp != NULL) { /* stuff on the end */ *cp = '\0'; cp++; SPANBLANK(cp); if (*cp != '\0') { /* * Without ESMTP. this is crud on the end, and we give 501 */ writereply(replybuf, 501, 0, m501msg, NULL); state_change(state, RCPT, ERROR); return; } } victim = xstrdup((char*)smtp_cleanitup(buf)); if (strcmp((char*)buf, victim) != 0) { syslog(LLEV, "securityalert: suspicious characters in RCPT: address from host %.128s/%.128s, exiting", proxy_stats.rladdr, proxy_stats.riaddr); smtp_exit(EX_PROTOCOL); } if (maxrecip && (++currecip > maxrecip)) { writereply(replybuf, 550, 0, "too many recipients", NULL); syslog(LLEV,"exiting - too many recipients"); smtp_exit(1); } if (!badrcpt) { writereply(replybuf, 250, 0, "recipient ", victim, ", ", m250rcptok, NULL); /* * log the recipient. */ if (VerboseSyslog) { syslog(LLEV, "Recipient %s", victim); } else { accumlog(LLEV, " to=%s", victim); } if (write_smtp_mbuf(outbuf, "RCPT ", strlen("RCPT ")) && write_smtp_mbuf(outbuf, victim, strlen(victim)) && write_smtp_mbuf(outbuf, "\n", 1)) { state_change(state, RCPT, SUCCESS); } else { state_change(state, RCPT, FAILURE); } } free(victim); } else if (strcasecmp((char*)verb, "NOOP") == 0) { writereply(replybuf, 250, 0, m250msg, NULL); state_change(state, NOOP, SUCCESS); } else if (strcasecmp((char*)verb, "VRFY") == 0) { writereply(replybuf, 252, 0, m252msg, NULL); state_change(state, NOOP, SUCCESS); } else if (strcasecmp((char*)verb, "HELP") == 0) { writereply(replybuf, 214, 1, "Commands", NULL); writereply(replybuf, 214, 1, "HELO MAIL RCPT DATA RSET", NULL); writereply(replybuf, 214, 0, "NOOP QUIT HELP VRFY EXPN", NULL); state_change(state, NOOP, SUCCESS); } else if (strcasecmp((char*)verb, "ONEX") == 0) { writereply(replybuf, 200, 0, "Only one transaction", NULL); state_change(state, NOOP, SUCCESS); } else if (strcasecmp((char*)verb, "VERB") == 0) { writereply(replybuf, 200, 0, "Verbose mode", NULL); state_change(state, NOOP, SUCCESS); } else if (strcasecmp((char*)verb, "DEBU") == 0) { syslog(LLEV, "securityalert: wizard command attempted from address %.128s/%.128s, name %.256s", proxy_stats.rladdr, proxy_stats.riaddr, client_claimed_name); writereply(replybuf, 250, 0, m250msg, NULL); state_change(state, NOOP, SUCCESS); } else if (strcasecmp((char*)verb, "RSET") == 0) { writereply(replybuf, 250, 0, m250msg, NULL); state_change(state, RSET, SUCCESS); } else if (strcasecmp((char*)verb, "QUIT") == 0) { writereply(replybuf, 221, 0, m221msg, NULL); state_change(state, QUIT, SUCCESS); } else if (strcasecmp((char*)verb, "DATA") == 0) { if (cmd_ok(DATA, state)) { writereply(replybuf, 354, 0, m354msg, NULL); state_change(state, SNARF_DATA, SUCCESS); } else { writereply(replybuf, 554, 0, m554norcpt, NULL); state_change(state, SNARF_DATA, ERROR); } #if EHLO_KLUDGE } else if (strcasecmp((char*)verb, "EHLO") == 0) { writereply(replybuf, 500, 0, m500msg, NULL); state_change(state, EHLO, SUCCESS); #endif } else { /* * if we get here our verb don't look like a verb. this means that * we should fire off a syntax error to the client */ writereply(replybuf, 500, 0, m500msg, NULL); state_change(state, UNKNOWN, ERROR); } return; } /* * Read a message body. * return values: * 1 - everything OK * 2 - read failed or connection * died before we got it all * 3 - size exceeded * 4 - no space on * write device * 5 - not enough memory */ int snarfdata(int in, int out, long *size, int bin) { struct smtp_mbuf *buf; struct smtp_mbuf *outbuf; int snarfed; int dot = 0; int i; long max, outbytes; int body = 0; /* * initial message size */ max = (*size ? *size : LONG_MAX); /* * Initialize the smtp_mbuf's. * We start of with absurdly small sizes in order to ensure that the * code which grows an mbuf gets exercised (i.e. if it is broken then * the program will probably die and the bug will (hopefully) get fixed). */ buf = alloc_smtp_mbuf(1024); if (buf == NULL) { syslog(LLEV, "Couldn't allocate input buffer for data command"); return (5); } outbuf = alloc_smtp_mbuf(1024); if (outbuf == NULL) { syslog(LLEV, "Couldn't allocate output buffer for data command"); return (5); } outbytes = 0; while (1) { int linestart; int lineend; linestart = lineend = 0; snarfed = read_smtp_mbuf(buf, in, 1024); if (snarfed < 0) { if (VerboseSyslog) { syslog(LLEV, "read error receiving message body: %s", strerror(errno)); } else { accumlog(LLEV, " read error receiving message body: %s", strerror(errno)); } return (2); } if (snarfed == 0) { if (VerboseSyslog) { syslog(LLEV, "EOF while receiving message body"); } else { accumlog(LLEV, " EOF while receiving message body"); } return (2); } if (outbuf->size < buf->size) { if (grow_smtp_mbuf(outbuf, (buf->size - outbuf->size)) == 0) { syslog(LLEV, "Couldn't grow #1"); return (5); } } for (i = 0; i < buf->offset; i++) { switch (buf->data[i]) { case LF: /* * we got an LF */ if (dot == 1) { /* * Lonesome Dot sings: "We're done!" */ *size = outbytes; return (1); break; } else { /* * write out from linestart to lineend (inclusive) */ buf->data[lineend] = LF; /* * I must at least write * out this LF */ /* * check for unusual headers. * these form the basis of a number of more interesting attacks * - Julian Assange */ if (!body) { if (buf->data[linestart] == LF || (buf->data[linestart] == '\r' && buf->data[linestart+1] == LF)) { body = 1; } else { char *p; int off=0; int unprintable = 0; for (p=(char*)buf->data+linestart; *p != LF; p++) { if (!isalpha(*p) && !isprint(*p) && !isspace(*p) && ((*p & 0x7f) < 0x20)) { syslog(LOG_DEBUG, "Unprintable character value=%d in message header at offset %d", (int)*p, off); *p='?'; unprintable++; } off++; } if (unprintable) { buf->data[lineend]='\0'; syslog(LLEV, "%d unprintable characters in \"%.255s\"", unprintable, buf->data+linestart); buf->data[lineend]=LF; syslog(LLEV, "securityalert: abandoning session from %.128s/%.128s) due to unprintable message header", proxy_stats.rladdr,proxy_stats.riaddr); smtp_exit(EX_PROTOCOL); } if (lineend - linestart > maxheader) { syslog(LLEV, "securityalert: unusually long header (trucated) [%d bytes] = \"%.255s\"...", lineend-linestart, buf->data+linestart); buf->data[linestart+maxheader]=LF; lineend = linestart + maxheader; } if (((lineend - linestart) > 7) && !strncmp((char*)buf->data+linestart,"From: ",6)) { char envfrom[1024]; strncpy(envfrom,(char*)buf->data+linestart+6, lineend - (linestart+6)); envfrom[lineend - (linestart+6)] = '\0'; } } } while (linestart <= lineend) { int j; j = write(out, (buf->data) + linestart, ((lineend - linestart) + 1)); if (j < 0) { /* * we can't write to the out fd. return * indicating that. */ syslog(LLEV, "fwtksyserr: write failed to spoolfile! (%s)", strerror(errno)); return (4); } else if (j == 0) { syslog(LLEV, "fwtksyserr: zero length write to file - bye!"); exit(EX_CONFIG); } outbytes += j; linestart += j; if (outbytes >= max) { /* * we've blown over our maxsize limit. */ syslog(LLEV, "Message body exceeds maximum size of %ld", max); return (3); } } } dot = 0; linestart = i + 1; lineend = i + 1; break; case CR: /* * we got a CR. if it's at the end of a line, (right * before an LF), we ignore it, and it goes away. Any * other character will advance lineend past it, meaning * it gets picked up as data. Dot is also unchanged * since this could be the start of a crlf after we saw * a first character dot. The next character will bring * enlightenment. */ break; case '.': if (i == (linestart)) { if (dot == 0) { /* * this is a dot at start of line. It could mean * we're finished. We're either finished, or we * do not replicate this first dot in the output. * (RFC 821, 4.5.2) */ dot = 1; /* * if we aren't finished, then this dot can't * appear, so increment linestart by one */ linestart++; } else { /* * this is a second dot, after we saw one last * time and moved linestart. This one stays, and * this ain't Lonsesome Dot */ dot = 0; } } else { /* * this is a plain ordinary dot with no pretensions, * it's like any other character. Clear dot in order * to properly handle ".\r." case at the start of a * line (i.e ".\r." is NOT the same as ".." even if * it appears at the start of a line). */ dot = 0; } lineend = i + 1; break; default: dot = 0; lineend = i + 1; break; } } /* * we had part of a line left in the buffer. Keep it and throw * away the rest. */ clean_smtp_mbuf(buf, linestart); } } /* * The brains of this operation */ int main(int argc, char **argv) { int i, k; smtp_state_set last_state_s, current_state_s; /* The real state vector. */ smtp_state last_state, current_state; /* Pointers to the state vector. */ struct sigaction new_sa; Cfg *cfp; socklen_t slen; struct hostent *tmp_he; proxy_init(argc,argv); proxy_update_status(); if (dscp) proxy_set_dscp(0,dscp); if ((cfp = proxy_conf_hosts(proxy_confp,proxy_stats.rladdr, proxy_stats.riaddr))) proxy_parse_options(cfp,options); else { printf("550 Access denied\r\n"); exit(1); } /* * 998 is rfc2822 recommended limit for header line length */ maxsize = proxy_conf_int(proxy_confp,"maxbytes", 1, INT_MAX, INT_MAX); maxheader = proxy_conf_int(proxy_confp,"maxheaderline",128,1023,998); maxrecip = proxy_conf_int(proxy_confp,"maxrecip",1,10000,2048); if ((proxy_uid == 0) || (proxy_uid == -1)) { syslog(LLEV, "fwtkcfgerr: sorry, I don't want to run as root! It's a bad idea!"); exit(EX_CONFIG); } if ((proxy_gid == 0) || (proxy_gid == -1)) { syslog(LLEV, "fwtkcfgerr: sorry, I don't want to run as group 0. It's a bad idea!"); exit(EX_CONFIG); } if (!*proxy_chroot) { syslog(LLEV,"fwtkcfgerr: no directory specified"); abort(); } proxy_chroot_setugid(); if (spooldir == NULL) { syslog(LLEV, "fwtkcfgerr: NULL spool directory! Aborting."); abort(); } /* We need to ignore SIGPIPE */ #ifdef BSD_SIGNAL signal(SIGPIPE, SIG_IGN); #else memset(&new_sa, 0, sizeof(new_sa)); new_sa.sa_handler = SIG_IGN; (void)sigemptyset(&new_sa.sa_mask); new_sa.sa_flags = SA_RESTART; if ( sigaction( SIGPIPE, &new_sa, NULL ) != 0 ) { syslog(LLEV,"fwtksyserr: CRITICAL - sigaction failed (%s)", strerror(errno)); exit(EX_OSERR); } #endif /* * Who's on the other end of this line? */ /* * set who we are in case our caller didn't tell us to be someone * else */ slen = sizeof(my_sa); if (getsockname(0, (struct sockaddr *) &my_sa, &slen) != 0) { syslog(LLEV, "fwtksyserr: getsockname failed (%s) Who am i?", strerror(errno)); exit(EX_OSERR); } if (my_clean_reverse_name == NULL) { tmp_he = gethostbyaddr((char *) &(my_sa.sin_addr.s_addr), sizeof(my_sa.sin_addr.s_addr), AF_INET); if (tmp_he != NULL) { my_clean_reverse_name = xstrdup((char*)cleanitup((unsigned char*)tmp_he->h_name)); if (strcmp(tmp_he->h_name, my_clean_reverse_name) != 0) { syslog(LLEV, "fwtkcfgerr: CRITICAL - Suspicious characters in MY hostname! (for ip=%s) cleaned to %s.\n", proxy_stats.riaddr, my_clean_reverse_name); syslog(LLEV, "CRITICAL - YOUR DNS IS EITHER COMPROMISED OR MISCONFIGURED! INVESTIGATE!"); smtp_exit(EX_CONFIG); } } } if (my_clean_reverse_name == NULL) { /* * Our caller didn't say who we're gonna claim to be, and we * didn't get one from a getsockname. get our hostname and use * that. */ char hname[MAXHOSTNAMELEN]; struct hostent *hp; if (gethostname(hname, sizeof hname) != 0) { syslog(LLEV, "fwtksyserr: gethostname() call failed! (%s) Who am I?", strerror(errno)); exit(EX_OSERR); } if ((hp = gethostbyname(hname)) != NULL) { my_clean_reverse_name = xstrdup(hp->h_name); } else { my_clean_reverse_name = xstrdup(hname); } } /* * Allocate the mbuf's - start off small to ensure that 'grow mbuf' * code gets exercised (detects bugs faster). */ input_buf = alloc_smtp_mbuf(64); if (input_buf == NULL) { syslog(LLEV, "fwtksyserr: malloc failed, abandoning session."); exit(EX_OSERR); } output_buf = alloc_smtp_mbuf(64); if (output_buf == NULL) { syslog(LLEV, "fwtksyserr: malloc failed, abandoning session."); exit(EX_OSERR); } reply_buf = alloc_smtp_mbuf(64); if (reply_buf == NULL) { syslog(LLEV, "fwtksyserr: malloc failed, abandoning session."); exit(EX_OSERR); } last_state = &last_state_s; current_state = ¤t_state_s; zap_state(current_state); zap_state(last_state); writereply(reply_buf, 220, 0, my_clean_reverse_name, " ", m220msg, NULL); replyfd = 1; for (;;) { char *line; size_t offset; time_t tt; flush_smtp_mbuf(reply_buf, replyfd, reply_buf->offset); k = read_smtp_mbuf(input_buf, 0, 1024); if (k < 0) { syslog(LLEV, "Read failed from client fd (%s) - Abandoning session", strerror(errno)); smtp_exit(EX_OSERR); } else if (k == 0) { /* * eof */ if (VerboseSyslog) { syslog(LLEV, "EOF on client fd. At least they could say goodbye!"); } else { accumlog(LLEV, "EOF on client fd."); } smtp_exit(EX_OSERR); } offset = 0; line = smtp_get_line(input_buf, &offset); while (line != NULL) { clean_smtp_mbuf(input_buf, offset); offset = 0; memcpy(last_state, current_state, sizeof(smtp_state_set)); smtp_parse_cmd((unsigned char*) line, output_buf, reply_buf, current_state); flush_smtp_mbuf(reply_buf, replyfd, reply_buf->offset); if (sane_state(current_state)) { if ((!test_state(SNARF_DATA, last_state)) && (test_state(SNARF_DATA, current_state))) { char head[512]; char *foo; long msize =0; memset(head, 0, sizeof(head)); outfd = smtp_open_spoolfile(); time(&tt); strlcpy(head, "BODY\nReceived: from ", 512); strlcat(head, proxy_stats.rladdr, 512); strlcat(head, "[", 512); strlcat(head, proxy_stats.riaddr, 512); if (strcasecmp(proxy_stats.rladdr, client_claimed_name) == 0) { strlcat(head, "]", 512); } else { strlcat(head, "], claiming to be \"", 512); strlcat(head, client_claimed_name, 512); strlcat(head, "\"", 512); } strlcat(head, " via smtpd by ", 512); strlcat(head, my_clean_reverse_name, 512); strlcat(head, "\n\tid ", 512); if ((foo = strrchr(spoolfile, '/')) != NULL) { strlcat(head, foo + 1, 512); } else { strlcat(head, spoolfile, 512); } strlcat(head, "; ", 512); strlcat(head, ctime(&tt), 512); if (!write_smtp_mbuf(output_buf, head, strlen(head))) { syslog(LLEV, "fwtksyserr: couldn't write to output buffer, abandoning session"); smtp_exit(EX_OSERR); } flush_smtp_mbuf(output_buf, outfd, output_buf->offset); msize = maxsize; i = snarfdata(0, outfd, &msize, 0); proxy_stats.outbytes += msize; proxy_update_status(); switch (i) { case 1: /* * success */ smtp_close_spoolfile(outfd); writereply(reply_buf, 250, 0, m250gotit, NULL); flush_smtp_mbuf(reply_buf, replyfd, reply_buf->offset); if (VerboseSyslog) { syslog(LLEV, "Received %ld bytes of message body from %.128s/%.128s", msize, proxy_stats.rladdr, proxy_stats.riaddr); } else { accumlog(LLEV, " bytes=%ld", msize); accumlog(LLEV, 0); /* flush */ } clear_state(SNARF_DATA, current_state); clear_state(OK_RCPT, current_state); clear_state(OK_MAIL, current_state); break; case 2: /* * read failure on input, or something horrific */ writereply(reply_buf, 554, 0, m554msg, NULL); flush_smtp_mbuf(reply_buf, replyfd, reply_buf->offset); smtp_nuke_spoolfile(outfd); clear_state(SNARF_DATA, current_state); clear_state(OK_RCPT, current_state); clear_state(OK_MAIL, current_state); break; case 3: /* * maxsize exceeded */ writereply(reply_buf, 552, 0, m552msg, NULL); flush_smtp_mbuf(reply_buf, replyfd, reply_buf->offset); smtp_nuke_spoolfile(outfd); clear_state(SNARF_DATA, current_state); clear_state(OK_RCPT, current_state); clear_state(OK_MAIL, current_state); break; case 4: /* * No room on spool device */ writereply(reply_buf, 452, 0, m452msg, NULL); flush_smtp_mbuf(reply_buf, replyfd, reply_buf->offset); smtp_nuke_spoolfile(outfd); clear_state(SNARF_DATA, current_state); clear_state(OK_RCPT, current_state); clear_state(OK_MAIL, current_state); break; case 5: /* * malloc barfed */ writereply(reply_buf, 452, 0, m452msg, NULL); flush_smtp_mbuf(reply_buf, replyfd, reply_buf->offset); smtp_nuke_spoolfile(outfd); clear_state(SNARF_DATA, current_state); clear_state(OK_RCPT, current_state); clear_state(OK_MAIL, current_state); break; default: /* * muy trabajo */ writereply(reply_buf, 451, 0, m451msg, NULL); flush_smtp_mbuf(reply_buf, replyfd, reply_buf->offset); smtp_nuke_spoolfile(outfd); smtp_exit(EX_SOFTWARE); } } } else { /* * evil state. */ syslog(LLEV, "fwtksyserr: CRITICAL - bad state, aborting"); abort(); } line = smtp_get_line(input_buf, &offset); } } }