/* * Copyright 1988 by Rayan S. Zachariassen, all rights reserved. * This will be free software, but only when it is finished. * Copyright 1991-2003 by Matti Aarnio -- modifications, including * MIME things... */ #include "smtp.h" /* About timeouts the RFC 1123 recommends: - Initial 220: 5 minutes - MAIL, RCPT : 5 minutes - DATA initialization (until "354.."): 2 minutes - While writing data, a block at the time: 3 minutes (How large a block ?) (We increased this to 5 minutes) - From "." to "250 OK": 10 minutes (We use 20 minutes here - sendmail uses 60 minutes..) */ int timeout = 0; /* how long do we wait for response? (sec.) */ int timeout_cmd = 5*60; int timeout_data = 5*60; /* in PIPELINING mode, 5 minutes is better! */ int timeout_tcpw = 5*60; /* All tcp writes ?? */ int timeout_dot = 20*60; int timeout_conn = 3*60; /* connect() timeout */ int sockwbufsize = 0; const char *defcharset; char myhostname[512]; int myhostnameopt; char errormsg[ZBUFSIZ]; /* Global for the use of dnsgetrr.c */ const char *progname; const char *cmdline, *eocmdline, *logfile, *msgfile; int pid; int debug = 0; int verbosity = 0; int conndebug = 0; int dotmode = 0; /* At the SMTP '.' phase, DON'T LEAVE IMMEDIATELY!. */ int getout = 0; /* signal handler turns this on when we are wanted to abort! */ int gotalarm = 0; /* indicate that alarm happened! */ jmp_buf procabortjmp; int procabortset = 0; int readalready = 0; /* does buffer contain valid message data? */ int wantreserved = 0; /* open connection on secure (reserved) port */ int statusreport = 0; /* put status reports on the command line */ int force_8bit = 0; /* Claim to the remote to be 8-bit system, even when it doesn't report itself as such..*/ int force_7bit = 0; /* and reverse the previous.. */ int keep_header8 = 0; /* Don't do "MIME-2" to the headers */ int checkwks = 0; FILE *logfp = NULL; extern int nobody; char *localidentity = NULL; /* If we are wanted to bind some altenate interface than what the default is thru normal kernel mechanisms.. */ int daemon_uid = -1; int first_uid = 0; /* Make the opening connect with the UID of the sender (atoi(rp->addr->misc)), unless it is "nobody", in which case use "daemon" */ int D_alloc = 0; /* Memory usage debug */ int no_pipelining = 0; /* In case the system just doesn't cope with it */ #if defined(AF_INET6) && defined(INET6) int use_ipv6 = 1; int prefer_ip6 = 1; #endif int close_after_data = 0; int lmtp_mode = 0; /* RFC 2033: LMTP mode */ #ifdef HAVE_OPENSSL int demand_TLS_mode = 0; /* Demand TLS */ int tls_available = 0; /* local client code running ok */ char *tls_conf_file = NULL; #endif /* - HAVE_OPENSSL */ const char *FAILED = "failed"; time_t now; extern time_t retryat_time; /* diagnostic() thing */ static void tcpstream_nagle __((int fd)); static void tcpstream_denagle __((int fd)); time_t starttime, endtime; static const char *add_cname_cache __((SmtpState *SS, const char *host, const char *realname, time_t realnamettl)); static int cname_lookup __((SmtpState *SS, const char *host, const char ** cnamep)); static void SMTP_MIB_diag __((const int smtpstatus)); const char * sysexitstr(r) int r; { static char buf[20]; switch (r) { case EX_OK: return "OK"; case EX_USAGE: return "USAGE"; case EX_DATAERR: return "DATAERR"; case EX_NOINPUT: return "NOINPUT"; case EX_NOUSER: return "NOUSER"; case EX_NOHOST: return "NOHOST"; case EX_UNAVAILABLE: return "UNAVAILABLE"; case EX_SOFTWARE: return "SOFTWARE"; case EX_OSERR: return "OSERR"; case EX_OSFILE: return "OSFILE"; case EX_CANTCREAT: return "CANTCREAT"; case EX_IOERR: return "IOERR"; case EX_TEMPFAIL: return "TEMPFAIL"; case EX_PROTOCOL: return "PROTOCOL"; case EX_NOPERM: return "NOPERM"; #ifdef EX_CONFIG /* Not everywhere! */ case EX_CONFIG: return "CONFIG"; #endif default: sprintf(buf,"SYSEXIT%d",r); return buf; } } char *logtag() { static char buf[30]; static int logcnt = 0; static time_t logstart = 0; if (logstart == 0) time( &logstart ); /* The rewritten log tag is sort(1):able to separate sessions from the file. */ time(&now); sprintf( buf, "%05d%04X-%04d-%04d", pid, ((int)logstart) & 0xFFFF, logcnt, (((int)(now - logstart)) % 10000) ); ++logcnt; if (logcnt > 9999) logcnt = 0; return buf; } /* * ssfgets(bufpp, bufsizep, infilep, SS) * * ALMOST like fgets(), but will do the smtp connection close * after 3 minutes delay of sitting here.. * */ static char * ssfgets __((char **, int*, int, SmtpState *)); static char * ssfgets(bufp, bufsizp, infd, SS) char **bufp; int *bufsizp; int infd; SmtpState *SS; { struct timeval tv; fd_set rdset; int rc, i, buflen, bufsiz; time_t tmout; char *s; time(&now); tmout = now + 3*60; s = *bufp; buflen = 0; bufsiz = *bufsizp -3; outbuf_fillup: while (SS->stdinsize > SS->stdincurs) { if (SS->stdinbuf[SS->stdincurs] == '\n') { *s++ = '\n'; ++buflen; *s = 0; SS->stdincurs += 1; /* Move down the buffer contents (if any) */ if (SS->stdinsize > SS->stdincurs) memcpy(SS->stdinbuf, SS->stdinbuf+SS->stdincurs, (SS->stdinsize - SS->stdincurs)); SS->stdinsize -= SS->stdincurs; SS->stdincurs = 0; return *bufp; } *s = SS->stdinbuf[SS->stdincurs]; SS->stdincurs += 1; ++buflen, ++s; if (buflen >= bufsiz) { /* Grow space */ *bufsizp <<= 1; bufsiz = *bufsizp; *bufp = realloc(*bufp, bufsiz); bufsiz -= 3; if (!*bufp) return NULL; /* OUT OF MEMORY! */ s = *bufp + buflen; } } /* Still here, and nothing to chew on ? Buffer drained.. */ SS->stdincurs = 0; SS->stdinsize = 0; while (!getout) { time(&now); if (now < tmout) tv.tv_sec = tmout - now; else tv.tv_sec = 0; tv.tv_usec = 0; _Z_FD_ZERO(rdset); if (infd >= 0) _Z_FD_SET(infd,rdset); rc = select(infd+1, &rdset, NULL, NULL, &tv); time(&now); if (now >= tmout && SS->smtpfp && sffileno(SS->smtpfp) >= 0) { /* Timed out, and have a writable SMTP connection active.. */ /* Lets write a NOOP there. */ /* We have here a VERY SHORT protocol timeout! */ timeout = 30; if (statusreport) report(SS,"#idle NOOP"); smtp_flush(SS); /* Flush in every case */ i = smtpwrite(SS, 0, "NOOP", 0, NULL); SS->rcptstates = 0; /* ignore replies */ if (i != EX_OK && SS->smtpfp != NULL) { /* No success ? QUIT + close! (if haven't closed yet..) */ if (!getout) smtpwrite(SS, 0, "QUIT", -1, NULL); smtpclose(SS, 0); } } if (now >= tmout) tmout = now + 3*60; /* Another 'keepalive' in 3 minutes */ if (rc == 1) { /* We have only ONE descriptor readable.. */ /* Got something to read on 'infd' (or EOF) .. and we are non-blocking! */ int rdspace = sizeof(SS->stdinbuf) - SS->stdinsize; fd_nonblockingmode(infd); rc = read(infd, SS->stdinbuf + SS->stdinsize, rdspace); fd_blockingmode(infd); if (rc == 0) /* EOF! */ break; if (rc > 0) { /* We have data! */ SS->stdinsize += rc; goto outbuf_fillup; } #if 0 if (rc < 0) /* EINTR, et.al. */ continue; #endif } } if (s == *bufp) return NULL; /* NOTHING received, gotten EOF! */ return *bufp; /* Not EOF, got SOMETHING */ } void wantout(sig) int sig; { getout = 1; SIGNAL_HANDLE(sig,wantout); SIGNAL_RELEASE(sig); if (!dotmode && procabortset) /* Not within protected phase ? */ longjmp(procabortjmp,1); } /* #define GLIBC_MALLOC_DEBUG__ */ #ifdef GLIBC_MALLOC_DEBUG__ /* memory allocation debugging with GLIBC */ #include /* GLIBC malloc.h ! */ /* Global variables used to hold underlaying hook values. */ static void *(*old_malloc_hook) (size_t, const void * ); static void *(*old_realloc_hook) (void *, size_t, const void *); static void (*old_free_hook) (void*, const void *); static void *(*old_memalign_hook) (size_t, size_t, const void *); /* Prototypes for our hooks. */ static void *my_malloc_hook (size_t, const void*); static void *my_realloc_hook (void *,size_t, const void*); static void my_free_hook (void*, const void*); static void *my_memalign_hook (size_t, size_t, const void*); static void * my_malloc_hook (size_t size, const void *CALLER) { void *result; /* Restore all old hooks */ __malloc_hook = old_malloc_hook; __free_hook = old_free_hook; /* Call recursively */ result = malloc (size); /* Save underlaying hooks */ old_malloc_hook = __malloc_hook; old_free_hook = __free_hook; /* `printf' might call `malloc', so protect it too. */ fprintf(stderr,"# malloc (%u) returns %p @%p\n", (unsigned int) size, result, CALLER); /* Restore our own hooks */ __malloc_hook = my_malloc_hook; __free_hook = my_free_hook; return result; } static void * my_realloc_hook (void *ptr, size_t size, const void *CALLER) { void *result; /* Restore all old hooks */ __realloc_hook = old_realloc_hook; __malloc_hook = old_malloc_hook; __free_hook = old_free_hook; /* Call recursively */ result = realloc (ptr, size); /* Save underlaying hooks */ old_realloc_hook = __realloc_hook; old_malloc_hook = __malloc_hook; old_free_hook = __free_hook; /* `printf' might call `malloc', so protect it too. */ fprintf(stderr,"# realloc (%p,%u) returns %p @%p\n", ptr, (unsigned int) size, result, CALLER); /* Restore our own hooks */ __realloc_hook = my_realloc_hook; __malloc_hook = my_malloc_hook; __free_hook = my_free_hook; return result; } static void * my_memalign_hook (size_t align, size_t size, const void *CALLER) { void *result; /* Restore all old hooks */ __memalign_hook = old_memalign_hook; __malloc_hook = old_malloc_hook; __free_hook = old_free_hook; /* Call recursively */ result = memalign (align, size); /* Save underlaying hooks */ old_memalign_hook = __memalign_hook; old_malloc_hook = __malloc_hook; old_free_hook = __free_hook; /* `printf' might call `malloc', so protect it too. */ fprintf(stderr,"# memalign (%u,%u) returns %p @%p\n", (unsigned)align, (unsigned)size, result, CALLER); /* Restore our own hooks */ __memalign_hook = my_memalign_hook; __malloc_hook = my_malloc_hook; __free_hook = my_free_hook; return result; } static void my_free_hook (void *ptr, const void *CALLER) { /* Restore all old hooks */ __malloc_hook = old_malloc_hook; __free_hook = old_free_hook; /* Call recursively */ free (ptr); /* Save underlaying hooks */ old_malloc_hook = __malloc_hook; old_free_hook = __free_hook; /* `printf' might call `free', so protect it too. */ fprintf(stderr,"# freed pointer %p @%p\n", ptr, CALLER); /* Restore our own hooks */ __malloc_hook = my_malloc_hook; __free_hook = my_free_hook; } #endif static char *filename; static int filenamesize; static int task_count; const char *punthost; /* Besided of value, is also used as GLOBAL state variable! */ static int net_socks_open_cnt; static void MIBcountCleanup __((void)) { MIBMtaEntry->tasmtp.TaProcCountG -= 1; /* Clean this counter, just in case it is non-zero... */ MIBMtaEntry->tasmtp.SmtpConnectsCnt -= net_socks_open_cnt; } int main(argc, argv) int argc; char *argv[]; { volatile char *channel = NULL, *host = NULL; int i, fd, errflg, c; volatile int smtpstatus; volatile int need_host = 0; int skip_host = 0; volatile int idle; volatile int noMX = 0; volatile int selfconnect = 1; SmtpState SS; struct ctldesc *dp; #ifdef BIND volatile int checkmx = 0; /* check all destination hosts for MXness */ #endif /* BIND */ RETSIGTYPE (*oldsig)__((int)); volatile const char *smtphost = NULL; #ifdef GLIBC_MALLOC_DEBUG__ /* memory allocation debugging with GLIBC */ old_malloc_hook = __malloc_hook; __malloc_hook = my_malloc_hook; old_memalign_hook = __memalign_hook; __memalign_hook = my_memalign_hook; old_realloc_hook = __realloc_hook; __realloc_hook = my_realloc_hook; old_free_hook = __free_hook; __free_hook = my_free_hook; #endif #if defined(HAVE_LOCALE_H) && defined(HAVE_SETLOCALE) && defined(LC_ALL) setlocale(LC_ALL, "C"); #endif if (getenv("ZCONFIG")) readzenv(getenv("ZCONFIG")); setvbuf(stdout, NULL, _IOFBF, 8096*4 /* 32k */); fd_blockingmode(FILENO(stdout)); /* Just to make sure.. */ Z_SHM_MIB_Attach(1); /* we don't care if it succeeds or fails.. */ MIBMtaEntry->tasmtp.TaProcessStarts += 1; MIBMtaEntry->tasmtp.TaProcCountG += 1; atexit(MIBcountCleanup); pid = getpid(); msgfile = "?"; getout = 0; cmdline = &argv[0][0]; eocmdline = cmdline; memset(&SS,0,sizeof(SS)); SS.main_esmtp_on_banner = -1; /* Presume existing per spec */ SS.servport = -1; SS.smtp_bufsize = 64*1024; SS.ehlo_sizeval = -1; smtp_flush(&SS); for (i = 0; argv[i] != NULL; ++i) eocmdline = strlen(argv[i])+ argv[i] + 1; /* Can overwrite also the environment strings.. */ for (i = 0; environ[i] != NULL; ++i) eocmdline = strlen(environ[i]) + environ[i] + 1; SIGNAL_HANDLESAVE(SIGINT, SIG_IGN, oldsig); if (oldsig != SIG_IGN) SIGNAL_HANDLE(SIGINT, wantout); SIGNAL_HANDLESAVE(SIGTERM, SIG_IGN, oldsig); if (oldsig != SIG_IGN) SIGNAL_HANDLE(SIGTERM, wantout); SIGNAL_HANDLESAVE(SIGQUIT, SIG_IGN, oldsig); if (oldsig != SIG_IGN) SIGNAL_HANDLE(SIGQUIT, wantout); SIGNAL_HANDLESAVE(SIGHUP, SIG_IGN, oldsig); if (oldsig != SIG_IGN) SIGNAL_HANDLE(SIGHUP, wantout); SIGNAL_IGNORE(SIGPIPE); timeout = timeout_cmd; #if defined(AF_INET6) && defined(INET6) { int sk = socket(AF_INET6, SOCK_STREAM, 0); if (sk > 0) close(sk); if (sk < 0) use_ipv6 = 0; /* No go :-( Can't create IPv6 socket */ } #endif progname = PROGNAME; errflg = 0; channel = CHANNEL; wantreserved = debug = statusreport = 0; logfile = NULL; myhostname[0] = '\0'; myhostnameopt = 0; SS.remotemsg[0] = '\0'; SS.remotehost[0] = '\0'; while (1) { c = getopt(argc, argv, "c:deh:l:p:rsvw:xXDEF:L:HMPS:T:VWZ:678"); if (c == EOF) break; switch (c) { case 'c': /* specify channel scanned for */ channel = strdup(optarg); break; case 'd': /* turn on debugging output */ ++debug; break; case 'e': /* expensive MX checking for *all* addresses */ #ifdef BIND checkmx = 1; #else /* !BIND */ ++errflg; fprintf(stderr, "%s: -e unavailable, no nameserver support!\n", progname); #endif /* BIND */ break; case 'h': /* my hostname */ strncpy(myhostname,optarg,sizeof(myhostname)-1); myhostname[sizeof(myhostname)-1] = 0; myhostnameopt = 1; break; case 'l': /* log file */ logfile = strdup(optarg); break; case 'p': /* server port */ SS.servport = atoi(optarg); break; case 'P': no_pipelining = 1; /* It doesn't handle it.. */ break; case 'r': /* use reserved port for SMTP connection */ wantreserved = 1; break; case 's': /* report status to command line */ statusreport = 1; break; case 'X': /* allow self connect for -x */ selfconnect = 1; break; case 'x': /* don't use MX records lookups */ noMX = 1; break; case 'D': /* only try connecting to remote host */ conndebug = 1; break; case 'E': /* don't do EHLO, unless target system has "ESMTP" on its banner */ SS.main_esmtp_on_banner = 0; /* Do test for it */ break; case 'F': /* Send all SMTP sessions to that host, possibly set also '-x' to avoid MXes! */ punthost = strdup(optarg); break; case 'H': keep_header8 = 1; break; case 'L': /* Specify which local identity to use */ localidentity = strdup(optarg); break; case 'M': lmtp_mode = 1; break; case 'T': /* specify Timeout in seconds */ if (CISTREQN(optarg,"conn=",5)) { timeout_conn = parse_interval(optarg+5,NULL); if (timeout_conn < 10) { fprintf(stderr, "%s: bad tcp connection timeout: %s\n", argv[0], optarg+5); ++errflg; } break; } else if (CISTREQN(optarg,"data=",5)) { timeout_data = parse_interval(optarg+5,NULL); if (timeout_data < 10) { fprintf(stderr, "%s: bad data timeout: %s\n", argv[0], optarg+5); ++errflg; } break; } else if (CISTREQN(optarg,"dot=",4)) { timeout_dot = parse_interval(optarg+4,NULL); if (timeout_dot < 10) { fprintf(stderr, "%s: bad data-dot-reply timeout: %s\n", argv[0], optarg+4); ++errflg; } break; } else if (CISTREQN(optarg,"tcpw=",5)) { timeout_tcpw = parse_interval(optarg+5,NULL); if (timeout_tcpw < 10) { fprintf(stderr, "%s: bad tcp-write timeout: %s\n", argv[0], optarg+5); ++errflg; } break; } else if (CISTREQN(optarg,"cmd=",4)) { timeout_cmd = parse_interval(optarg+4,NULL); optarg += 4; } else timeout_cmd = parse_interval(optarg,NULL); if (timeout_cmd < 5) { fprintf(stderr, "%s: bad general cmd timeout: %s\n", argv[0], optarg); ++errflg; } break; case 'v': ++verbosity; break; case 'V': prversion("smtp"); exit(0); break; case 'w': sockwbufsize = atoi(optarg); break; case 'W': /* Enable RFC974 WKS checks */ checkwks = 1; break; case '8': force_8bit = 1; force_7bit = 0; break; case '7': if (force_7bit) /* Double-7 locks the ESMTP away, can then be turned into 'force-8' mode without ESMTP */ SS.main_esmtp_on_banner = -2; ++force_7bit; force_8bit = 0; break; case 'Z': /* Dummy option to carry HUGE parameter string for the report system to make sense.. at OSF/1, at least */ break; #if defined(AF_INET6) && defined(INET6) case '6': prefer_ip6 = !prefer_ip6; break; #endif case 'S': /* -S /path/to/SmtpSSL.conf */ #ifdef HAVE_OPENSSL tls_conf_file = strdup(optarg); #endif /* - HAVE_OPENSSL */ break; default: ++errflg; break; } } if (noMX != 0 && selfconnect != 0) noMX = -2; /* -2 means allow self connect */ time(&now); if (errflg || optind > argc) { fprintf(stderr, "Usage: %s [-8|-8H|-7][-e][-r][-x][-X][-E][-P][-W][-T timeout][-h myhostname][-l logfile][-p portnum][-c channel][-F forcedest][-L localidentity][-S /path/to/SmtpSSL.conf] [host]\n", argv[0]); exit(EX_USAGE); } if (SS.servport < 0) SS.servport = IPPORT_SMTP; if (lmtp_mode && SS.servport == 25 && (!punthost || !STREQN(punthost,"UNIX:/",6))) { fprintf(stderr, "%s: LMTP mode is not allowed without explicite port specifier with value other than 25, or -F to UNIX-socket..\n", argv[0]); exit(EX_USAGE); } if (optind < argc) { host = strdup(argv[optind]); strncpy(SS.remotehost, (char*)host, sizeof(SS.remotehost)); SS.remotehost[sizeof(SS.remotehost)-1] = 0; } else need_host = 1; if (myhostnameopt == 0) { /* Default it only when not having an explicite value in it.. James S MacKinnon */ getmyhostname(myhostname, sizeof myhostname); } if (conndebug && !debug && host) { SS.firstmx = 0; smtpconn(&SS, (char*)host, noMX); exit(0); } logfp = NULL; if (logfile != NULL) { if ((fd = open(logfile, O_CREAT|O_APPEND|O_WRONLY, 0644)) < 0) fprintf(stdout, "# %s: cannot open logfile \"%s\"!\n", argv[0], logfile); else logfp = (FILE *)fdopen(fd, "a"); } if (logfp) setvbuf(logfp, NULL, _IOLBF, 0); getnobody(); getdaemon(); defcharset = getzenv("DEFCHARSET"); if (!defcharset) defcharset = DefCharset; /* We need this later on .. */ zopenlog("smtp", LOG_PID, LOG_MAIL); notary_settaid("smtp",getpid()); /* We defer opening a connection until we know there is work */ smtpstatus = EX_OK; idle = 0; SS.stdinsize = 0; SS.stdincurs = 0; filenamesize = 80; filename = malloc(filenamesize); #ifdef BIND res_init(); #ifdef RES_USE_INET6 #if defined(AF_INET6) && defined(INET6) if (!use_ipv6) _res.options &= ~RES_USE_INET6; #else _res.options &= ~RES_USE_INET6; #endif #endif #endif if (logfp) { char *cp; time( & now ); cp = (char *) rfc822date( & now ); if (cp) cp[strlen(cp)-1] = 0; else cp = "??"; fprintf(logfp,"%s#\tStart time: %s\n", logtag(), cp); } while (!getout && !zmalloc_failure) { /* Input: spool/file/name [ \t host.info ] \n */ char *s; fd_blockingmode(FILENO(stdout)); fprintf(stdout, "#hungry\n"); fflush(stdout); if (statusreport) { if (idle) report(&SS,"#idle"); else report(&SS,"#hungry"); } /* if (fgets(filename, sizeof(filename), stdin) == NULL) break; */ if (ssfgets(&filename, &filenamesize, FILENO(stdin), &SS) == NULL) break; ++task_count; /* Just a debug tool */ readalready = 0; /* internal body read buffer 'flush' */ idle = 0; skip_host = 0; if (strchr(filename, '\n') == NULL) break; /* No ending '\n' ! Must have been partial input! */ if (logfp) fprintf(logfp,"%s#\tjobspec: %s",logtag(),filename); if (STREQ(filename, "#idle\n")) { idle = 1; MIBMtaEntry->tasmtp.TaIdleStates += 1; continue; /* XX: We can't stay idle for very long, but.. */ } if (emptyline(filename, filenamesize)) break; time(&now); MIBMtaEntry->tasmtp.TaMessages += 1; s = strchr(filename,'\t'); if (s != NULL) { *s++ = 0; if (host && CISTREQ((char*)host,s)) { /* XXX: Behaviour with 'close_after_data' ??? */ if (now < retryat_time) { /* Same host, we don't touch on it for a while.. */ /* sleep(2); */ if (logfp && verbosity > 1) { fprintf(logfp,"%s#\t(too soon trying to touch on host with 'retryat' diagnostic -- flushing job queue..host='%s')\n",logtag(),host); } ++skip_host; } } /* If different target host, close the old connection. In theory we could use same host via MX, but... */ if (host && !STREQ(s,(char*)host)) { smtp_flush(&SS); /* Flush in every case */ if (SS.smtpfp) { if (!getout && !zmalloc_failure) { SS.rcptstates = 0; smtpstatus = smtpwrite(&SS, 0, "QUIT", -1, NULL); } else smtpstatus = EX_OK; smtpclose(&SS, 0); notary_setwtt(NULL); notary_setwttip(NULL); if (logfp) fprintf(logfp, "%s#\t(closed SMTP channel - new host)\n", logtag()); strncpy(SS.remotehost, (char*)host, sizeof(SS.remotehost)); SS.remotehost[sizeof(SS.remotehost)-1] = 0; if (statusreport) report(&SS, "NewDomain: %s", host); } close_after_data = 0; } if (host) free((void*)host); host = strdup(s); } else if (need_host) { fprintf(stdout,"# smtp needs defined host!\n"); fflush(stdout); continue; } if (debug > 1) { /* "DBGdiag:"-output */ fprintf(stdout,"# (fdcnt=%d, file:%.200s, host:%.200s)\n", countfds(), filename, host); fflush(stdout); } #ifdef BIND if (checkmx) dp = ctlopen(filename, (char*)channel, (char*)host, &getout, rightmx, &SS); else #endif /* BIND */ dp = ctlopen(filename, (char*)channel, (char*)host, &getout, NULL, NULL); if (dp == NULL) { fprintf(stdout,"#resync %.200s\n", filename); fflush(stdout); if (logfp) fprintf(logfp, "%s#\tc='%s' h='%s' #resync %s\n", logtag(), channel, host, filename); continue; } /* Copy the spoolid string pointer */ SS.taspoolid = dp->taspoolid; time(&starttime); notary_setxdelay(0); if (punthost) smtphost = punthost; else smtphost = host; if (dp->verbose) { if (SS.verboselog) fclose(SS.verboselog); SS.verboselog = (FILE *)fopen(dp->verbose,"a"); if (SS.verboselog) setvbuf(SS.verboselog, NULL, _IONBF, 0); } #ifdef HAVE_OPENSSL if (SS.verboselog && tls_conf_file) fprintf(SS.verboselog, "# tls_conf_file='%s'\n", tls_conf_file); if (tls_conf_file && !tls_available) { /* -S /path/to/SmtpSSL.conf */ tls_available = (tls_init_clientengine(&SS, tls_conf_file) == 0); if (SS.verboselog) fprintf(SS.verboselog, "# -S '%s' tls_init_client_engine() -> tls_available=%d\n", tls_conf_file, tls_available); #if 0 else fprintf(stderr, "# -S '%s' tls_init_client_engine() -> tls_available=%d\n", tls_conf_file, tls_available); #endif } #endif /* - HAVE_OPENSSL */ smtpstatus = process(&SS, (struct ctldesc *)dp, smtpstatus, (char*)smtphost, noMX); if (SS.verboselog) fclose(SS.verboselog); SS.verboselog = NULL; ctlclose(dp); } /* while (!getout) ... */ if (SS.smtpfp && !getout) { smtp_flush(&SS); /* Flush in every case */ smtpstatus = smtpwrite(&SS, 0, "QUIT", -1, NULL); } /* Close the channel -- if it is open anymore .. */ if (SS.smtpfp) { smtpclose(&SS, 0); if (logfp) fprintf(logfp, "%s#\t(closed SMTP channel - final close)\n", logtag()); } if (SS.verboselog != NULL) fclose(SS.verboselog); SS.verboselog = NULL; if (logfp) fclose(logfp); logfp = NULL; return 0; } int process(SS, dp, smtpstatus, host, noMX) SmtpState *SS; struct ctldesc *dp; volatile int smtpstatus; const char *host; int noMX; { if (setjmp(procabortjmp) == 0) { struct rcpt *rp, *rphead; int loggedid; procabortset = 1; smtpstatus = EX_OK; /* Hmm... */ loggedid = 0; SS->firstmx = 0; /* If need be to connect to a new host, because the socket is not on, we start from the begin of the MX list */ *SS->remotemsg = 0; for (rp = rphead = dp->recipients; rp != NULL; rp = rp->next) { if (rp->next == NULL || rp->addr->link != rp->next->addr->link || rp->newmsgheader != rp->next->newmsgheader) { if (smtpstatus == EX_OK) { if (logfp && !loggedid) { loggedid = 1; fprintf(logfp, "%s#\t%s: %s\n", logtag(), dp->msgfile, dp->logident); } smtpstatus = deliver(SS, dp, rphead, rp->next, host, noMX); /* Report (and unlock) all those recipients which aren't otherwise diagnosed.. */ for (;rphead && rphead != rp->next; rphead = rphead->next) { if (rphead->lockoffset) { notaryreport(rphead->addr->user, FAILED, NULL, NULL); diagnostic(SS->verboselog, rphead, smtpstatus, smtpstatus == EX_TEMPFAIL ? 60 : 0, "%s", SS->remotemsg); SMTP_MIB_diag(smtpstatus); if (logfp) { fprintf(logfp, "%s#\t", logtag()); diagnostic(logfp, rphead, smtpstatus, smtpstatus == EX_TEMPFAIL ? 60 : 0, "%s", SS->remotemsg); } } } rphead = rp->next; } else { time(&endtime); notary_setxdelay((int)(endtime-starttime)); while (rphead != rp->next) { /* SMTP open -- meaning (probably) that we got reject from the remote server */ /* NOTARY: address / action / status / diagnostic */ if (rphead->lockoffset) { notaryreport(rp->addr->user,FAILED, "5.0.0 (Target status indeterminable)", NULL); diagnostic(SS->verboselog, rphead, EX_TEMPFAIL, smtpstatus == EX_TEMPFAIL ? 60 : 0, "%s", SS->remotemsg); SMTP_MIB_diag(smtpstatus); if (logfp) { fprintf(logfp, "%s#\t", logtag()); diagnostic(logfp, rphead, EX_TEMPFAIL, smtpstatus == EX_TEMPFAIL ? 60 : 0, "%s", SS->remotemsg); } } rphead = rphead->next; } } } } } else { /* processing fails entirely if PROCABORT is received */ smtpstatus = EX_UNAVAILABLE; smtpclose(SS, 1); if (logfp) fprintf(logfp, "%s#\t(procabort executed)\n", logtag()); } procabortset = 0; return smtpstatus; } /* * deliver - deliver the letter in to user's mail box. Return * errors and requests for further processing in the structure */ int deliver(SS, dp, startrp, endrp, host, noMX) SmtpState *SS; struct ctldesc *dp; struct rcpt *startrp, *endrp; const char *host; int noMX; { struct rcpt *rp = NULL; int nrcpt, rcpt_cnt, size, tout, hdrsize; int content_kind = 0; CONVERTMODE convertmode; int ascii_clean = 0; struct stat stbuf; const char *cname, *u = NULL; char SMTPbuf[2000]; char const * const se = SMTPbuf + 800; /* char const * const se2 = SMTPbuf + sizeof(SMTPbuf)-40; */ char *s; int conv_prohibit = check_conv_prohibit(startrp); int hdr_mime2 = 0; int pipelining = 0; time_t env_start, body_start, body_end; struct rcpt *more_rp = NULL; struct rcpt **more_rpp = NULL; char **chunkblkptr = NULL; char *chunkblk = NULL; int early_bdat_sync = 0; struct ct_data *CT = NULL; struct cte_data *CTE = NULL; char **hdr; int doing_reopen, did_open; int r, once; MIBMtaEntry->tasmtp.TaDeliveryStarts += 1; hdr = has_header(startrp,"Content-Type:"); if (hdr) CT = parse_content_type(*hdr); if (!CT && SS->verboselog) fprintf(SS->verboselog, ".. No Content-Type: header parsed ??\n"); else if (CT && SS->verboselog) fprintf(SS->verboselog, " Content-Type: '%s'/'%s'; charset='%s'; boundary='%s'; name='%s'\n", CT->basetype ? CT->basetype : "", CT->subtype ? CT->subtype : "", CT->charset ? CT->charset : "", CT->boundary ? CT->boundary : "", CT->name ? CT->name : ""); hdr = has_header(startrp,"Content-Transfer-Encoding:"); if (hdr) CTE = parse_content_encoding(*hdr); if (CT) { if (CT->basetype == NULL || CT->subtype == NULL || !CISTREQ(CT->basetype,"text") || !CISTREQ(CT->subtype,"plain")) /* Not TEXT/PLAIN! */ conv_prohibit = -1; /* We don't know how to convert anything BUT TEXT/PLAIN :-( */ } doing_reopen = 0; r = EX_TEMPFAIL; once = 2; re_open: did_open = 0; if (!SS->smtpfp) { int openstatus; /* Make the opening connect with the UID of the sender (atoi(startrp->addr->misc)), unless it is "nobody", in which case use "daemon" */ if ((first_uid = atoi(dp->senders->misc)) < 0 || first_uid == nobody) first_uid = daemon_uid; if (doing_reopen) SS->firstmx = 0; openstatus = smtpopen(SS, host, noMX); if (openstatus != EX_OK) { /* If we are doing reopen, and it fails, we report just EX_TEMPFAIL, and bail out... */ if (doing_reopen) openstatus = EX_TEMPFAIL; for ( rp = startrp; startrp != rp->next; startrp = startrp->next) { if (startrp->lockoffset) { notaryreport(startrp->addr->user, FAILED, NULL, NULL); diagnostic(SS->verboselog, startrp, openstatus, 60, "%s", SS->remotemsg); SMTP_MIB_diag(openstatus); if (logfp) { fprintf(logfp, "%s#\t", logtag()); diagnostic(logfp, startrp, openstatus, 60, "%s", SS->remotemsg); } } } r = openstatus; goto post_cleanup; } did_open = 1; } pipelining = ( SS->ehlo_capabilities & ESMTP_PIPELINING ); if (no_pipelining) pipelining = 0; SS->pipelining = pipelining; if (pipelining && did_open) MIBMtaEntry->tasmtp.SmtpPIPELINING ++; SS->chunking = ( SS->ehlo_capabilities & ESMTP_CHUNKING ); convertmode = _CONVERT_NONE; /* If the header says '8BIT' and ISO-8859-* something, but body is plain 7-bit, turn it to '7BIT', and US-ASCII */ /* Or if this is some more complicated type... */ ascii_clean = check_7bit_cleanness(dp); if (conv_prohibit >= 0) { /* Content-Transfer-Encoding: 8BIT ? */ content_kind = cte_check(startrp); if (ascii_clean && content_kind == 8) { if (downgrade_charset(startrp, SS->verboselog)) content_kind = 7; } if (conv_prohibit == 7) SS->ehlo_capabilities &= ~ESMTP_8BITMIME; if (force_7bit) /* Mark off the 8BIT MIME capability.. */ SS->ehlo_capabilities &= ~ESMTP_8BITMIME; switch (content_kind) { case 0: /* Not MIME */ if ((SS->ehlo_capabilities & ESMTP_8BITMIME) == 0 && !ascii_clean && !force_8bit) { convertmode = _CONVERT_UNKNOWN; /* It is ASCII clean */ downgrade_headers(startrp, convertmode, SS->verboselog); } break; case 2: /* MIME, but no C-T-E: -> defaults to 7BIT */ case 1: /* C-T-E: BASE64 ?? */ case 7: /* C-T-E: 7BIT */ convertmode = _CONVERT_NONE; break; case 8: /* C-T-E: 8BIT */ if ((force_7bit || (SS->ehlo_capabilities & ESMTP_8BITMIME)== 0) && !ascii_clean && !force_8bit) { convertmode = _CONVERT_QP; if (!downgrade_headers(startrp, convertmode, SS->verboselog)) convertmode = _CONVERT_NONE; /* Failed! */ } break; case 9: /* C-T-E: Quoted-Printable */ if (force_8bit || (SS->ehlo_capabilities & ESMTP_8BITMIME)) { /* Force(d) to decode Q-P while transfer.. */ convertmode = _CONVERT_8BIT; /* UPGRADE TO 8BIT ! */ if (!qp_to_8bit(startrp)) convertmode = _CONVERT_NONE; content_kind = 10; ascii_clean = 0; } break; default: /* ???? This should NOT happen! */ break; } /* switch().. */ hdr_mime2 = headers_need_mime2(startrp); if (hdr_mime2 && !keep_header8) { headers_to_mime2(startrp,defcharset,SS->verboselog); } } else if (conv_prohibit == -1) { if (CT && CT->basetype && CISTREQ(CT->basetype,"multipart")) { if ((force_7bit || (SS->ehlo_capabilities & ESMTP_8BITMIME)== 0) && !ascii_clean) { convertmode = _CONVERT_MULTIPARTQP; if (SS->verboselog) fprintf(SS->verboselog, " MULTIPART message with 8-bit set content, AND forced encoding downgrade or with ESMTP encoding downgrade\n"); } } } /* else -- "Content-Conversion: Prohibited" */ notary_setcvtmode(convertmode); if (SS->ehlo_capabilities & ESMTP_SIZEOPT) { /* We can do this SIZE option analysis without trying to feed this in the MAIL command */ if (SS->ehlo_sizeval > 0 && startrp->desc->msgsizeestimate > SS->ehlo_sizeval) { /* Reuse SMTPbuf for writing an error report explaining things a bit.. */ sprintf(SMTPbuf, "smtp; 552 (Current message size %d exceeds limit given by the remote system: %d)", (int)startrp->desc->msgsizeestimate, (int)SS->ehlo_sizeval); if (SS->verboselog) fprintf(SS->verboselog, "%s\n", SMTPbuf+6); time(&endtime); notary_setxdelay((int)(endtime-starttime)); for (rp = startrp; rp && rp != endrp; rp = rp->next) if (rp->lockoffset) { /* NOTARY: address / action / status / diagnostic / wtt */ notaryreport(rp->addr->user, FAILED, "5.3.4 (Message size exceeds limit given by remote system)", SMTPbuf); diagnostic(SS->verboselog, rp, EX_UNAVAILABLE, 0, "\r\r%s", SMTPbuf+6); SMTP_MIB_diag(EX_UNAVAILABLE); if (logfp) { fprintf(logfp, "%s#\t", logtag()); diagnostic(logfp, rp, EX_UNAVAILABLE, 0, "\r\r%s", SMTPbuf+6); } } r = EX_UNAVAILABLE; goto post_cleanup; } } r = EX_OK; more_recipients: if (more_rp != NULL) { startrp = more_rp; *more_rpp = more_rp; more_rp = NULL; more_rpp = NULL; once = 1; } /* Scan onwards over possibly processed instances */ while (startrp && startrp != endrp && startrp->lockoffset == 0) startrp = startrp->next; if (once < 1 /* No progress ? */ || getout || startrp == NULL || startrp == endrp) { if (SS->chunkbuf) free(SS->chunkbuf); SS->chunkbuf = NULL; goto post_cleanup; } --once; if (SS->smtpfp) { if ((SS->lastactiontime + 20) < time(NULL)) SS->do_rset = 1; /* over 20 seconds since last transaction on this link ? */ /* SMTP is open, do we want to RSET ? */ if (SS->do_rset) { SS->rcptstates = 0; if (statusreport) report(SS,"RSET wait"); smtp_flush(SS); /* Flush in every case */ timeout = timeout_cmd; r = smtpwrite(SS, 0, "RSET", 0, NULL); if (statusreport) report(SS,"RSET rc=%d",r); if (r != EX_OK) { smtpclose(SS,1); r = EX_TEMPFAIL; doing_reopen = 1; goto re_open; } r = EX_TEMPFAIL; } } else { /* SMTP isn't open, we re-open.. */ r = EX_TEMPFAIL; doing_reopen = 1; goto re_open; } SS->rcptstates = 0; /* We are starting a new pipelined phase */ smtp_flush(SS); /* Flush in every case */ /* Store estimate on how large a file it is */ if (fstat(dp->msgfd, &stbuf) >= 0) size = stbuf.st_size - dp->msgbodyoffset; else size = -1; SS->msize = size; SS->do_rset = 1; /* Unless completed successfully, we must do RSET later... */ MIBMtaEntry->tasmtp.SmtpMAIL ++; strcpy(SMTPbuf, "MAIL From:<"); s = SMTPbuf + 11; if (!STREQ(startrp->addr->link->channel,"error")) { /* Copy the (possibly quoted) local part */ int quote = 0; u = startrp->addr->link->user; for ( ; *u && (s < se); ++u) { const char c = *u; if (c == '\\') { *s++ = c; ++u; if (*u == 0) break; *s++ = *u; continue; } if (c == quote) /* 'c' is non-zero here */ quote = 0; else if (c == '"') quote = '"'; else if (!quote && (c == '@')) break; *s++ = c; } if (startrp->ezmlm) { /* The EZMLM mode appendix... */ const char *p = startrp->ezmlm; while (*p && (s < se)) *s++ = *p++; } /* Normal (tail) mode */ if (*u == '@') *s++ = *u++; /* If there is a domain ? */ if (*u != 0) { /* Now is the CNAME thingie to be rewritten ? */ if ((cname_lookup(SS, u, & cname) > 0) && cname) { /* Rewrote the domain */ u = cname; } /* Copy the domain (original/cname). */ while ((s < se) && *u) *s++ = *u++; } *s = 0; u = NULL; } /* non-error source address mode */ *s++ = '>'; *s = 0; if (SS->ehlo_capabilities & ESMTP_8BITMIME) { strcpy(s, " BODY=8BITMIME"); s += strlen(s); } /* Size estimate is calculated in the ctlopen() by adding msg-body size to the largest known header size, though excluding possible header and body rewrites.. */ if (SS->ehlo_capabilities & ESMTP_SIZEOPT) { sprintf(s, " SIZE=%ld", startrp->desc->msgsizeestimate); s += strlen(s); MIBMtaEntry->tasmtp.SmtpOPT_SIZE ++; } /* DSN parameters ... */ if (SS->ehlo_capabilities & ESMTP_DSN) { if (startrp->desc->envid != NULL) { sprintf(s," ENVID=%.800s",startrp->desc->envid); s += strlen(s); MIBMtaEntry->tasmtp.SmtpOPT_ENVID ++; } if (startrp->desc->dsnretmode != NULL) { sprintf(s, " RET=%.20s", startrp->desc->dsnretmode); MIBMtaEntry->tasmtp.SmtpOPT_RET ++; } } time(&env_start); /* Mark the timestamp */ /* MAIL FROM:<...> -- pipelineable.. */ r = smtpwrite(SS, 1, SMTPbuf, pipelining, NULL); if (!SS->smtpfp || sffileno(SS->smtpfp) < 0) r = EX_TEMPFAIL; /* ALWAYS! */ if (r != EX_OK) { /* If we err here, we probably are in SYNC mode... */ /* Uh ?? Many new sendmail's have a pathological error mode: MAIL FROM... 451 cannot preopen /etc/aliases.db (wait a bit) 250 ... Sender ok. We try to accomodate that behaviour, and resync, although treat it as temporary error -- 4xx series. */ if (SS->smtpfp) { sleep(10); /* After a sleep of 10 seconds, if we find that we have some new input, do close the connection */ if (has_readable(SS->smtpfd)) { SS->cmdstate = SMTPSTATE_RCPTTO; /* Well, sort of .. */ /* Drain the input, and then close the channel */ smtp_sync(SS, EX_OK, 0); smtpclose(SS, 1); if (logfp) fprintf(logfp, "%s#\t(closed SMTP channel - MAIL FROM:<> got two responses! [or EOF])\n", logtag()); } } time(&endtime); notary_setxdelay((int)(endtime-starttime)); if (SS->smtpfp) { if (pipelining) r = smtp_sync(SS, r, 0); /* Collect reports in blocking mode */ #if 0 } else { r = EX_TEMPFAIL; /* XXX: ??? */ #endif } if (!SS->smtpfp && once > 0) { r = EX_TEMPFAIL; doing_reopen = 1; goto re_open; } SS->cmdstate = SMTPSTATE_RCPTTO; /* 1 + MAILFROM.. */ /* Returning here EX_TEMPFAIL while smtpfp == NULL will do quick retry! DON'T diagnose those now! */ if (r != EX_TEMPFAIL && !SS->smtpfp) { for (rp = startrp; rp && rp != endrp; rp = rp->next) { /* NOTARY: address / action / status / diagnostic */ if (rp->lockoffset) { notaryreport(rp->addr->user, FAILED, "5.5.0 (Undetermined protocol error)",NULL); diagnostic(SS->verboselog, rp, r, 0, "%s", SS->remotemsg); SMTP_MIB_diag(r); if (logfp) { fprintf(logfp, "%s#\t", logtag()); diagnostic(logfp, rp, r, 0, "%s", SS->remotemsg); } } } } /* More recipients to send ? */ goto more_recipients; } nrcpt = 0; rcpt_cnt = 0; for (rp = startrp; rp && rp != endrp; rp = rp->next) { if (!rp->lockoffset) continue; /* SKIP IT! */ /* Make sure the recipient diagnostics status at this point is "OK". */ rp->status = EX_OK; if (++rcpt_cnt >= SS->rcpt_limit) { /* Limit Count full */ more_rp = rp->next; more_rpp = & rp->next; rp->next = NULL; } if (rp->ezmlm) { /* THIS recipient is EZMLM one */ more_rp = rp->next; more_rpp = & rp->next; rp->next = NULL; } if (!rp->ezmlm && rp->next && rp->next->ezmlm) { /* THIS recipient isn't EZMLM one, but NEXT one is! */ more_rp = rp->next; more_rpp = & rp->next; rp->next = NULL; } SS->cmdstate = SMTPSTATE_RCPTTO; { int quote = 0; u = rp->addr->user; strcpy(SMTPbuf, "RCPT To:<"); s = SMTPbuf + 9; /* Copy the (possibly quoted) local part */ quote = 0; for ( ; *u && (s < se); ++u) { const char c = *u; if (c == '\\') { *s++ = c; ++u; if (*u == 0) break; *s++ = *u; continue; } if (c == quote) /* 'c' is non-zero here */ quote = 0; else if (c == '"') quote = '"'; else if (!quote && (c == '@')) break; *s++ = c; } if ('@' == *u) { /* Has "@-full" address, can, perhaps, rewrite. */ *s++ = *u++; if (*u != 0) { cname = NULL; if ((cname_lookup(SS, u, & cname) > 0) && cname) { u = cname; } while (*u && (s < se)) *s++ = *u++; } } *s++ = '>'; *s = 0; } if (SS->ehlo_capabilities & ESMTP_DSN) { if (rp->notifyflgs) { const char *t = ""; strcat(s, " NOTIFY="); s += strlen(s); if (rp->notifyflgs & _DSN_NOTIFY_NEVER) { strcat(s ,"NEVER"); } if (rp->notifyflgs & _DSN_NOTIFY_SUCCESS) { strcat(s, "SUCCESS"); t = ","; } if (rp->notifyflgs & _DSN_NOTIFY_FAILURE) { strcat(s, t); strcat(s, "FAILURE"); t = ","; } if (rp->notifyflgs & _DSN_NOTIFY_DELAY) { strcat(s, t); strcat(s, "DELAY"); } } else strcat(s, " NOTIFY=FAILURE,DELAY"); /* Default value.. */ MIBMtaEntry->tasmtp.SmtpOPT_NOTIFY ++; MIBMtaEntry->tasmtp.SmtpOPT_ORCPT ++; s += strlen(s); if (rp->orcpt != NULL) { sprintf(s, " ORCPT=%.800s", rp->orcpt); } else { const char *p = rp->addr->user; strcpy(s, " ORCPT=rfc822;"); s += strlen(s); while (*p) { const u_char c = *p++; if ('!' <= c && c <= '~' && c != '+' && c != '=') *s++ = c; else { sprintf(s,"+%02X",c); s += 3; } } *s = 0; } } MIBMtaEntry->tasmtp.SmtpRCPT ++; /* RCPT To:<...> -- pipelineable */ r = smtpwrite(SS, 1, SMTPbuf, pipelining, rp); if (r != EX_OK) { if (!SS->smtpfp || sffileno(SS->smtpfp) < 0) r = EX_TEMPFAIL; /* ALWAYS! */ if (!pipelining) { if (r == EX_TEMPFAIL) SS->rcptstates |= RCPTSTATE_400; else SS->rcptstates |= RCPTSTATE_500; rp->status = r; } time(&endtime); notary_setxdelay((int)(endtime-starttime)); if (SS->smtpfp) { if (pipelining) r = smtp_sync(SS, r, 0); /* Collect reports -- by blocking */ } else r = EX_TEMPFAIL; /* NOTARY: address / action / status / diagnostic / wtt */ notaryreport(rp->addr->user, FAILED, NULL, NULL); diagnostic(SS->verboselog, rp, r, 0, "%s", SS->remotemsg); SMTP_MIB_diag(r); if (logfp) { fprintf(logfp, "%s#\t", logtag()); diagnostic(logfp, rp, r, 0, "%s", SS->remotemsg); } if (!SS->smtpfp) break; } else { /* r == EX_OK */ if (!pipelining) SS->rcptstates |= RCPTSTATE_OK; nrcpt += 1; SS->rcptcnt += 1; /* Actually we DO NOT KNOW under PIPELINING, we will need to sync this later on.. */ rp->status = EX_OK; } } /* ... for (rp = startrp; rp && rp != endrp; rp = rp->next) ... */ if (nrcpt == 0) { /* all the RCPT To addresses were rejected, so reset server */ SS->cmdstate = SMTPSTATE_DATA; /* 1 + RCPTTO.. */ r = EX_UNAVAILABLE; if (SS->rcptstates & RCPTSTATE_400) /* The smtpfp != NULL -> no retry for these recipients -- at least not right away! */ r = EX_TEMPFAIL; /* Even ONE temp failure -> total result is then TEMPFAIL */ /* Next time around will need to do "RSET" before MAIL FROM */ /* EZMLM or some such thing runs with more recipients.. */ goto more_recipients; } if (!SS->smtpfp) { r = EX_TEMPFAIL; /* Doing quick retry on these rcpts! */ /* More recipients to send ? */ goto more_recipients; } chunkblkptr = NULL; SS->chunksize = 0; SS->chunkbuf = NULL; #ifndef DO_CHUNKING SS->chunking = 0; #endif SS->cmdstate = SMTPSTATE_DATA; if (SS->chunking) { timeout = timeout_tcpw; chunkblk = NULL; chunkblkptr = & chunkblk; /* We do surprising things here, we construct at first the headers (and perhaps some of the body) into a buffer, then write it out in BDAT transaction. */ time(&endtime); notary_setxdelay((int)(endtime-starttime)); /* Sometimes it MIGHT make sense to sync incoming status data. When and how ? */ if (!pipelining || (startrp->desc->msgsizeestimate >= CHUNK_MAX_SIZE)) early_bdat_sync = 1; if (SS->smtpfp && early_bdat_sync) { /* Now is time to do synchronization .. */ r = smtp_sync(SS, EX_OK, 0); /* Up & until "BDAT".. */ } if (r != EX_OK) { /* XX: #error Uncertain of what to do ... ... reports were given at each recipient, and if all failed, we failed too.. (there should not be any positive diagnostics to report...) */ for (rp = startrp; rp && rp != endrp; rp = rp->next) { if (rp->lockoffset) { /* NOTARY: address / action / status / diagnostic / wtt */ notaryreport(rp->addr->user,FAILED,NULL,NULL); if (rp->status == EX_OK) rp->status = r; diagnostic(SS->verboselog, rp, rp->status, 0, "%s", SS->remotemsg); SMTP_MIB_diag(rp->status); if (logfp) { fprintf(logfp, "%s#\t", logtag()); diagnostic(logfp, rp, rp->status, 0, "%s", SS->remotemsg); } } } /* More recipients to send ? */ goto more_recipients; } /* OK, we synced, lets continue with BDAT ... The RFC 1830 speaks of more elaborate pipelining with BDAT, but lets do this with checkpoints at first */ } else { /* No CHUNKING here... do normal DATA-dot exchange */ /* In PIPELINING mode ...... send "DATA" and SYNC ! */ /* In non-pipelining mode .. send "DATA" and SYNC ! */ MIBMtaEntry->tasmtp.SmtpDATA ++; timeout = timeout_data; r = smtpwrite(SS, 1, "DATA", 0 /* SYNC! */, NULL); timeout = timeout_tcpw; if (r != EX_OK) { if (SS->smtpfp && (SS->rcptstates & DATASTATE_OK)) { /* HUH!!! MAIL FROM/RCPT TO ones have failed, but DATA has succeeded !! This is SERIOUSLY weird, but some may work even that way.. */ smtpclose(SS,1); if (logfp) fprintf(logfp, "%s#\t(closed SMTP channel - DATA ok, but MAIL FROM/RCPT TO failed! rc=%d)\n", logtag(), rp ? rp->status : -999); r = EX_TEMPFAIL; } if (SS->smtpfp && (SS->rcptstates & RCPTSTATE_400) && (SS->rcptstates & FROMSTATE_OK)) { SS->rcptstates = 0; smtp_flush(SS); /* Flush in every case */ smtpwrite(SS, 0, "QUIT", -1, NULL); smtpclose(SS,1); if (logfp) fprintf(logfp, "%s#\t(closed SMTP channel - tempfails for RCPTs; 'too many recipients per session' ?? rc=%d)\n", logtag(), rp ? rp->status : -999); if (SS->verboselog) fprintf(SS->verboselog, "(closed SMTP channel - tempfails for RCPTs; 'too many recipients per session' ?? rc=%d)\n", rp ? rp->status : -999); if (SS->rcptstates & RCPTSTATE_OK) retryat_time = 0; close_after_data = 1; r = EX_TEMPFAIL; } /* Next time around will need to do "RSET" before MAIL FROM */ /* XX: Set r = EX_TEMPFAIL; ??? */ if (SS->verboselog) fprintf(SS->verboselog," .. timeout ? smtp_sync() rc = %d\n",r); /* XX: #error Uncertain of what to do ... ... reports were given at each recipient, and if all failed, we failed too.. (there should not be any positive diagnostics to report...) */ for (rp = startrp; rp && rp != endrp; rp = rp->next) if (rp->lockoffset) { /* NOTARY: address / action / status / diagnostic / wtt */ notaryreport(rp->addr->user,FAILED,NULL,NULL); if (rp->status == EX_OK) rp->status = r; diagnostic(SS->verboselog, rp, rp->status, 0, "%s", SS->remotemsg); SMTP_MIB_diag(rp->status); if (logfp) { fprintf(logfp, "%s#\t", logtag()); diagnostic(logfp, rp, rp->status, 0, "%s", SS->remotemsg); } } /* More recipients to send ? */ goto more_recipients; } timeout = timeout_dot; } /* Headers are 7-bit stuff -- says MIME specs */ time(&body_start); /* "DATA" issued, and synced */ if (SS->smtpfp) { #ifdef HAVE_OPENSSL if (!SS->TLS.sslmode) #endif /* - HAVE_OPENSSL */ tcpstream_nagle(sffileno(SS->smtpfp)); } header_received_for_clause(startrp, rcpt_cnt, SS->verboselog); SS->hsize = swriteheaders(startrp, SS->smtpfp, "\r\n", convertmode, 0, chunkblkptr); if (SS->verboselog) { char **hdrs = *(startrp->newmsgheader); if (*(startrp->newmsgheadercvt) != NULL && convertmode != _CONVERT_NONE) hdrs = *(startrp->newmsgheadercvt); fprintf(SS->verboselog, "Written headers: ContentKind=%d, CvtMode=%d, hsize=%d\n------\n", content_kind, (int)convertmode, SS->hsize); if (chunkblkptr && SS->hsize > 0) fwrite(*chunkblkptr, 1, SS->hsize, SS->verboselog); else if (SS->hsize <= 0) fprintf(SS->verboselog," ****** WRITE FAILURE ****\n"); else for ( ; hdrs && *hdrs; ++hdrs) fprintf(SS->verboselog,"%s\n",*hdrs); } if (SS->hsize >= 0 && chunkblk) { chunkblk = realloc(chunkblk, SS->hsize+2); if (chunkblk) { memcpy(chunkblk + SS->hsize, "\r\n", 2); SS->hsize += 2; } else { SS->hsize = -1; } } else if (SS->hsize >= 0) { if (!sferror(SS->smtpfp)) sfprintf(SS->smtpfp, "\r\n"); if (sferror(SS->smtpfp)) SS->hsize = -1; } if (chunkblk) { SS->chunksize = SS->hsize; SS->chunkspace = SS->hsize; SS->chunkbuf = chunkblk; chunkblk = NULL; } if (SS->hsize < 0) { int r = EX_TEMPFAIL; if (SS->smtpfp) tcpstream_denagle(sffileno(SS->smtpfp)); for (rp = startrp; rp != endrp; rp = rp->next) if (rp->lockoffset) { time(&endtime); notary_setxdelay((int)(endtime-starttime)); /* NOTARY: address / action / status / diagnostic / wtt */ notaryreport(rp->addr->user,FAILED, "5.4.2 (Message header write failure)", /* XX: FIX THE STATUS? */ "smtp; 566 (Message header write failure)"); if (rp->status == EX_OK) rp->status = r; diagnostic(SS->verboselog, rp, rp->status, 0, "%s", "header write error"); SMTP_MIB_diag(rp->status); if (logfp) { fprintf(logfp, "%s#\t", logtag()); diagnostic(logfp, rp, rp->status, 0, "%s", "header write error"); } } if (SS->verboselog) fprintf(SS->verboselog,"Writing headers after DATA failed\n"); if (SS->smtpfp) { smtpclose(SS, 1); if (logfp) fprintf(logfp, "%s#\t(closed SMTP channel - message header write failure, status=%d msg='%s')\n", logtag(), rp ? rp->status : -999, SS->remotemsg); } if (SS->chunkbuf) free(SS->chunkbuf); SS->chunkbuf = NULL; /* More recipients to send ? */ goto more_recipients; } /* Add the header size to the initial body size */ if (SS->msize >= 0) SS->msize += SS->hsize; else SS->msize -= SS->hsize-1; hdrsize = SS->hsize; /* Append the message body itself */ r = appendlet(SS, dp, convertmode, CT); if (r != EX_OK) { time(&endtime); if (SS->smtpfp) tcpstream_denagle(sffileno(SS->smtpfp)); notary_setxdelay((int)(endtime-starttime)); for (rp = startrp; rp && rp != endrp; rp = rp->next) if (rp->lockoffset) { notaryreport(rp->addr->user, FAILED, "5.4.2 (Message write timed out;2)", "smtp; 566 (Message write timed out;2)"); /* XX: FIX THE STATUS? */ if (rp->status == EX_OK) rp->status = r; diagnostic(SS->verboselog, rp, rp->status, 0, "%s", SS->remotemsg); SMTP_MIB_diag(rp->status); if (logfp) { fprintf(logfp, "%s#\t", logtag()); diagnostic(logfp, rp, rp->status, 0, "%s", SS->remotemsg); } } /* Diagnostics are done, protected (failure-)section ends! */ dotmode = 0; /* The failure occurred during processing and was due to an I/O * error. The safe thing to do is to just abort processing. * Don't send the dot! 2/June/94 edwin@cs.toronto.edu */ if (SS->smtpfp) { smtpclose(SS, 1); if (logfp) fprintf(logfp, "%s#\t(closed SMTP channel - appendlet() failure, status=%d msg='%s')\n", logtag(), rp ? rp->status : -999, SS->remotemsg); } if (SS->chunkbuf) free(SS->chunkbuf); SS->chunkbuf = NULL; report(SS, "%s", SS->remotemsg); r = EX_TEMPFAIL; /* ?? */ /* More recipients to send ? */ goto more_recipients; } /* * This is the one place where we *have* to wait forever because * there is no reliable way of aborting the transaction. * Note that a good and useful approximation to "forever" is one day. * Murphy's Law you know: Connections will hang even when they can't. */ /* RFC-1123 says: 10 minutes! */ tout = timeout; timeout = timeout_dot; dotmode = 1; gotalarm = 0; SS->cmdstate = SMTPSTATE_DATADOT; if (lmtp_mode) SS->rcptstates = 0; if (SS->chunking) { /* BDAT mode */ r = bdat_flush(SS, 1); } else { /* Ordinary DATA-dot mode */ /* Following the lead of sendmail, we separate the DATA ending ".CRLF" 'line' into separately pushed TCP frame. That is apparently necessary as the world is full of braindead firewalls which change the ending CRLF.CRLF into something else... Cisco PIX seems to be the most common culprit.. [mea] 2002-Jun-25 Of course this separation might not survive possible packet retransmission, and nagle-re-merge... */ report(SS, "DATA-flush (wait)"); if (SS->smtpfp) sfsync(SS->smtpfp); if (SS->smtpfp) tcpstream_denagle(sffileno(SS->smtpfp)); report(SS, "DATA-dot wait"); r = smtpwrite(SS, 1, ".", lmtp_mode, NULL); if (!lmtp_mode) report(SS, "DATA-dot; rc=%d", r); /* Special case processing: If we are in LMTP's dot-of-DATA phase, always use smtp_sync() to handle our diagnostics. */ if (lmtp_mode && r == EX_OK) { report(SS, "DATA-dot; LMTP sync!"); r = smtp_sync(SS, EX_OK, 0); /* BLOCKING! */ } } timeout = tout; if (r != EX_OK) { time(&endtime); notary_setxdelay((int)(endtime-starttime)); for (rp = startrp; rp && rp != endrp; rp = rp->next) if (rp->lockoffset) { notaryreport(rp->addr->user, FAILED, #if 1 NULL, NULL #else "5.4.2 (Message write failed; possibly remote rejected the message)", "smtp; 566 (Message write failed; possibly remote rejected the message)" #endif ); /* If remote closed socket, don't diagnose here, diagnose later.. (might also retry via other server!) */ if (rp->status == EX_OK) rp->status = r; if (r != EX_TEMPFAIL) { diagnostic(SS->verboselog, rp, rp->status, 0, "%s", SS->remotemsg); SMTP_MIB_diag(rp->status); if (logfp) { fprintf(logfp, "%s#\t", logtag()); diagnostic(logfp, rp, rp->status, 0, "%s", SS->remotemsg); } } } report(SS, "Body done; %s", SS->remotemsg); /* Diagnostics are done, protected (failure-)section ends! */ dotmode = 0; if (SS->smtpfp && gotalarm) { smtpclose(SS, 1); if (logfp) fprintf(logfp, "%s#\t(closed SMTP channel - smtpwrite('.') failure)\n", logtag()); } if (SS->chunkbuf) free(SS->chunkbuf); SS->chunkbuf = NULL; /* More recipients to send ? */ goto more_recipients; } time(&body_end); /* body endtime */ if (logfp != NULL) { if (r != EX_OK) fprintf(logfp, "%s#\t%s\n", logtag(), SS->remotemsg); else fprintf(logfp, "%s#\t%d bytes, %d in header, %d recipients, %d secs for envelope, %d secs for body xfer\n", logtag(), SS->hsize, hdrsize, nrcpt, (int)(body_start - env_start), (int)(body_end - body_start)); } time(&endtime); notary_setxdelay((int)(endtime-starttime)); /* r == EX_OK */ for (rp = startrp; rp && rp != endrp; rp = rp->next) { if (rp->lockoffset) { char *reldel = "-"; /* Turn off the flag of NOTIFY=SUCCESS, we have handled the burden to the next server ... */ if (SS->ehlo_capabilities & ESMTP_DSN) rp->notifyflgs &= ~ _DSN_NOTIFY_SUCCESS; /* Remote wasn't DSN speaker, and we have NOTIFY=SUCCESS, then we say, we "relayed" the message */ if (rp->notifyflgs & _DSN_NOTIFY_SUCCESS) reldel = "relayed"; rp->status = r; notaryreport(rp->addr->user, reldel, NULL, NULL); diagnostic(SS->verboselog, rp, rp->status, 0, "%s", SS->remotemsg); SMTP_MIB_diag(rp->status); if (logfp) { fprintf(logfp, "%s#\t", logtag()); diagnostic(logfp, rp, rp->status, 0, "%s", SS->remotemsg); } } } /* Diagnostics are done, protected section ends! */ dotmode = 0; if (SS->smtpfp && (SS->rcptstates & RCPTSTATE_400) && (SS->rcptstates & FROMSTATE_OK)) { SS->rcptstates = 0; smtpwrite(SS, 0, "QUIT", -1, NULL); smtpclose(SS,1); if (logfp) fprintf(logfp, "%s#\t(closed SMTP channel - tempfails for RCPTs; 'too many recipients per session' ?? rc=%d)\n", logtag(), rp ? rp->status : -999); if (SS->rcptstates & RCPTSTATE_OK) retryat_time = 0; close_after_data = 1; } if (SS->smtpfp && close_after_data) { SS->rcptstates = 0; smtpwrite(SS, 0, "QUIT", -1, NULL); smtpclose(SS,1); if (logfp) fprintf(logfp, "%s#\t(closed SMTP channel - ``close_after_data'' mode)\n", logtag()); retryat_time = 0; } /* If all fine, all is fine... No need to RSET afterwards. */ SS->do_rset = 0; /* More recipients to send ? */ goto more_recipients; post_cleanup: if (CT) free_content_type(CT); if (CTE) free_content_encoding(CTE); return r; } int ehlo_check(SS,buf) SmtpState *SS; const char *buf; { char *r = strchr(buf,'\r'); if (r != NULL) *r = 0; if (STREQ(buf,"8BITMIME")) { SS->ehlo_capabilities |= ESMTP_8BITMIME; MIBMtaEntry->tasmtp.EHLOcapability8BITMIME ++; } else if (STREQ(buf,"DSN")) { SS->ehlo_capabilities |= ESMTP_DSN; MIBMtaEntry->tasmtp.EHLOcapabilityDSN ++; } else if (STREQ(buf,"ENHANCEDSTATUSCODES")) { SS->ehlo_capabilities |= ESMTP_ENHSTATUS; MIBMtaEntry->tasmtp.EHLOcapabilityENHANCEDSTATUSCODES ++; } else if (STREQ(buf,"CHUNKING")) { SS->ehlo_capabilities |= ESMTP_CHUNKING; MIBMtaEntry->tasmtp.EHLOcapabilityCHUNKING ++; } else if (STREQ(buf,"PIPELINING")) { SS->ehlo_capabilities |= ESMTP_PIPELINING; MIBMtaEntry->tasmtp.EHLOcapabilityPIPELINING ++; } else if (STREQ(buf,"STARTTLS")) { #ifdef HAVE_OPENSSL SS->ehlo_capabilities |= ESMTP_STARTTLS; #endif /* - HAVE_OPENSSL */ MIBMtaEntry->tasmtp.EHLOcapabilitySTARTTLS ++; } else if (STREQN(buf,"SIZE ",5) || STREQ (buf,"SIZE") ) { SS->ehlo_capabilities |= ESMTP_SIZEOPT; SS->ehlo_sizeval = -1; if (buf[4] == ' ') sscanf(buf+5,"%ld",&SS->ehlo_sizeval); MIBMtaEntry->tasmtp.EHLOcapabilitySIZE ++; } else if (STREQN(buf,"AUTH ",5) || STREQN(buf,"AUTH=",5) ) { SS->ehlo_capabilities |= ESMTP_AUTH; MIBMtaEntry->tasmtp.EHLOcapabilityAUTH ++; } else if (STREQN(buf,"DELIVERBY ",10) || STREQ (buf,"DELIVERBY") ) { SS->ehlo_capabilities |= ESMTP_DELIVERBY; SS->ehlo_deliverbyval = -1; if (buf[9] == ' ') sscanf(buf+10,"%ld;",&SS->ehlo_deliverbyval); MIBMtaEntry->tasmtp.EHLOcapabilityDELIVERBY ++; } else if (STREQN(buf,"X-RCPTLIMIT ",12)) { int nn = atoi(buf+12); if (nn < 10) nn = 10; if (nn > 100000) nn = 100000; SS->rcpt_limit = nn; } return 0; } /* Flag that banner contained "ESMTP" (case insensitive) */ void esmtp_banner_check(SS,str) SmtpState *SS; char *str; { char *s = str; while (*s) { while (*s && *s != 'e' && *s != 'E') ++s; if (!s) return; if (CISTREQN(s,"ESMTP",5)) { SS->esmtp_on_banner = 1; /* Found it */ return; } ++s; } } int smtpopen(SS, host, noMX) const char *host; SmtpState *SS; int noMX; { int i; int retries = 0; char SMTPbuf[1000]; if (debug && logfp) fprintf(logfp, "%s#\tsmtpopen: connecting to %.200s\n", logtag(), host); do { SS->esmtp_on_banner = SS->main_esmtp_on_banner; /* -1: presume it, 0: test for it */ SS->ehlo_capabilities = 0; SS->ehlo_sizeval = 0; SS->rcpt_limit = 100; /* Max number of recipients per message */ i = smtpconn(SS, host, noMX); if (i != EX_OK) continue; SS->cmdstate = SMTPSTATE_HELO; if (lmtp_mode || (SS->esmtp_on_banner > -2 && force_7bit < 2)) { /* Either it is not tested, or it is explicitely desired to be tested, and was found! */ if (SS->myhostname && !myhostnameopt) sprintf(SMTPbuf, "EHLO %.200s", SS->myhostname); else sprintf(SMTPbuf, "EHLO %.200s", myhostname); if (lmtp_mode) { SMTPbuf[0] = 'L'; MIBMtaEntry->tasmtp.SmtpLHLO ++; } else { MIBMtaEntry->tasmtp.SmtpEHLO ++; } i = smtp_ehlo(SS, SMTPbuf); if (i == EX_OK) { if (lmtp_mode) MIBMtaEntry->tasmtp.SmtpLHLOok ++; else MIBMtaEntry->tasmtp.SmtpEHLOok ++; } else { if (lmtp_mode) MIBMtaEntry->tasmtp.SmtpLHLOfail ++; else MIBMtaEntry->tasmtp.SmtpEHLOfail ++; } #ifdef HAVE_OPENSSL if (logfp) fprintf(logfp, "%s#\tEHLO rc=%d demand_TLS_mode=%d tls_available=%d%s %sPIPELINING\n", logtag(), i, demand_TLS_mode, tls_available, (SS->ehlo_capabilities & ESMTP_STARTTLS) ? " STARTTLS":"", (SS->ehlo_capabilities & ESMTP_PIPELINING) ? "":"no "); if (SS->verboselog) fprintf(SS->verboselog, "--> EHLO rc=%d demand_TLS_mode=%d tls_available=%d%s %sPIPELINING\n", i, demand_TLS_mode, tls_available, (SS->ehlo_capabilities & ESMTP_STARTTLS) ? " STARTTLS":"", (SS->ehlo_capabilities & ESMTP_PIPELINING) ? "":"no "); if ((i == EX_OK) && demand_TLS_mode && tls_available && !(SS->ehlo_capabilities & ESMTP_STARTTLS)) { /* Whoops! No TLS at the server, while we are configured to demand it! */ i = EX_UNAVAILABLE; notaryreport(NULL,NULL,"5.7.3 (Mandated TLS security mode not available)", "local; 500 (Remote system doesn't support mandated TLS mode)"); strcpy(SS->remotemsg,"500 (Remote system doesn't support mandated TLS mode)"); if (lmtp_mode) /* really sort of TLS failure... */ MIBMtaEntry->tasmtp.SmtpLHLOfail ++; else MIBMtaEntry->tasmtp.SmtpEHLOfail ++; continue; } if ((i == EX_OK) && tls_available && (SS->ehlo_capabilities & ESMTP_STARTTLS)) { MIBMtaEntry->tasmtp.SmtpSTARTTLS += 1; SS->rcptstates = 0; i = smtpwrite(SS, 0, "STARTTLS", 0, NULL); if (i == EX_OK) { /* Wow, "STARTTLS" command started successfully! */ i = tls_start_clienttls(SS, host); if (i) MIBMtaEntry->tasmtp.SmtpSTARTTLSfail += 1; else MIBMtaEntry->tasmtp.SmtpSTARTTLSok += 1; if (i != 0) { /* TLS startup failed :-( */ smtpclose(SS, 1); /* Only if we are configured to *demand* the TLS mode, then this situation is an error! */ if (1 /* demand_TLS_mode */) { i = EX_TEMPFAIL; notaryreport(NULL,NULL,"5.7.3 (Mandated TLS security mode not available)", "local; 500 (Remote system doesn't support mandated TLS mode)"); strcpy(SS->remotemsg,"500 (Remote system doesn't support mandated TLS mode)"); continue; } /* Well, TLS startup failed, then just reopen same server, and don't redo STARTTLS. */ } #if 0 /* * Now the connection is established and maybe we * do have a validated cert with a CommonName in it. * In enforce_peername state, the handshake would * already have been terminated so the check here * is for logging only! */ if (session->tls_info.peer_CN) { if (!session->tls_info.peer_verified) { msg_info("Peer certficate could not be verified"); if (session->tls_enforce_tls) { pfixtls_stop_clienttls(session->stream, var_smtp_starttls_tmout, 1, &(session->tls_info)); return(smtp_site_fail(state, 450, "TLS-failure: Could not verify certificate")); } } } else if (session->tls_enforce_tls) { pfixtls_stop_clienttls(session->stream, var_smtp_starttls_tmout, 1, &(session->tls_info)); return (smtp_site_fail(state, 450, "TLS-failure: Cannot verify hostname")); } #endif if (SS->verboselog) { if (i == EX_OK) { fprintf(SS->verboselog, " TLS mode running successfully!\n"); if (SS->TLS.cipher_name) fprintf(SS->verboselog, " TLS cipher: %s\n", SS->TLS.cipher_name); if (SS->TLS.protocol) fprintf(SS->verboselog, " TLS protocol: %s\n", SS->TLS.protocol); fprintf(SS->verboselog, " TLS cipher bits: %d in use: %d\n", SS->TLS.cipher_algbits, SS->TLS.cipher_usebits); fprintf(SS->verboselog, " TLS peer CN-1: %s\n", SS->TLS.peer_CN1 ? SS->TLS.peer_CN1 : "<>"); fprintf(SS->verboselog, " TLS peer cert issuer name: %s\n", SS->TLS.issuer_CN1 ? SS->TLS.issuer_CN1 : "<>"); fprintf(SS->verboselog, " Cert not valid Before: %s\n", SS->TLS.notBefore ? SS->TLS.notBefore : "<>"); fprintf(SS->verboselog, " Cert not valid After: %s\n", SS->TLS.notAfter ? SS->TLS.notAfter : "<>"); } else fprintf(SS->verboselog, " Failed the TLS startup!\n"); } /* Now re-negotiate the modes, possibly after reopening the connection. */ SS->ehlo_capabilities = 0; SS->ehlo_sizeval = 0; SS->rcpt_limit = 100; /* Max number of recipients per msg */ if (i != EX_OK) { SS->esmtp_on_banner = SS->main_esmtp_on_banner; i = makereconn(SS); } else i = EX_OK; /* Even if 'EX_OK' is zero.. */ if (i != EX_OK) continue; } else { smtpclose(SS, 1); /* D'uh.. STARTTLS verb failed! */ MIBMtaEntry->tasmtp.SmtpSTARTTLSfail += 1; SS->esmtp_on_banner = SS->main_esmtp_on_banner; SS->ehlo_capabilities = 0; SS->ehlo_sizeval = 0; SS->rcpt_limit = 100; /* Max number of recipients per msg */ i = makereconn(SS); if (i != EX_OK) continue; } /* The system *did* successfully respond to EHLO previously, why would it not do so now ??? */ i = smtp_ehlo(SS, SMTPbuf); /* ... like for connection failing ... */ } #endif /* - HAVE_OPENSSL */ if (i == EX_TEMPFAIL && !lmtp_mode) { /* There are systems, which hang up on us, when we greet them with an "EHLO".. Do here a normal "HELO".. */ i = makereconn(SS); if (i != EX_OK) continue; i = EX_TEMPFAIL; } } /* END "EHLO" connection */ if (SS->esmtp_on_banner > -2 && i == EX_OK ) { if (SS->verboselog) fprintf(SS->verboselog, " EHLO response flags = 0x%02x, rcptlimit=%d, sizeopt=%ld\n", (int)SS->ehlo_capabilities, (int)SS->rcpt_limit, (long)SS->ehlo_sizeval); } else if (!lmtp_mode) { if (SS->myhostname && !myhostnameopt) sprintf(SMTPbuf, "HELO %.200s", SS->myhostname); else sprintf(SMTPbuf, "HELO %.200s", myhostname); MIBMtaEntry->tasmtp.SmtpHELO ++; i = smtp_ehlo(SS, SMTPbuf); if (i == EX_OK) MIBMtaEntry->tasmtp.SmtpHELOok ++; else MIBMtaEntry->tasmtp.SmtpHELOfail ++; if (i != EX_OK && SS->smtpfp) { smtpclose(SS, 1); if (logfp) fprintf(logfp, "%s#\t(closed SMTP channel - HELO failed ?)\n", logtag()); } if (i == EX_TEMPFAIL || !SS->smtpfp || sffileno(SS->smtpfp) < 0) { /* Ok, sometimes EHLO+HELO cause crash, open and do HELO only */ if (SS->smtpfp) smtpclose(SS, 1); i = makereconn(SS); if (i != EX_OK) continue;; SS->rcptstates = 0; i = smtpwrite(SS, 1, SMTPbuf, 0, NULL); if (i != EX_OK && SS->smtpfp) { smtpclose(SS, 1); if (logfp) fprintf(logfp, "%s#\t(closed SMTP channel - HELO failed(2))\n", logtag()); } } } ++retries; if (SS->verboselog) fprintf(SS->verboselog," retries=%d firstmx=%d mxcount=%d\n", retries, SS->firstmx, SS->mxcount); } while ((i == EX_TEMPFAIL) && (SS->firstmx < SS->mxcount)); if (logfp) fprintf(logfp, "%s#\tsmtpopen: status = %d\n", logtag(), i); if (SS->verboselog) fprintf(SS->verboselog, "smtpopen: result code %d / %s; socket is %sopen\n", i, sysexitstr(i), SS->smtpfp ? "" : "not "); return i; } int smtpconn(SS, host, noMX) SmtpState *SS; const char *host; int noMX; { int i, r, retval; char hbuf[MAXHOSTNAMELEN+1]; char realname[1024]; volatile int rc; time_t realnamettl; SS->literalport = -1; if (SS->firstmx == 0) { SS->mxcount = 0; /* Cleanup of the MXH array */ for (i = 0; i < MAXFORWARDERS; ++i) { if (SS->mxh[i].host != NULL) free(SS->mxh[i].host); /* memset() below clears these pointers */ if (SS->mxh[i].ai != NULL) freeaddrinfo(SS->mxh[i].ai); } if (SS->verboselog) fprintf(SS->verboselog, "memset(SS->mxh, 0, %d)\n",sizeof(SS->mxh)); memset(SS->mxh, 0, sizeof(SS->mxh)); } #ifdef BIND h_errno = 0; #endif /* BIND */ stashmyaddresses(NULL); if (debug && logfp) fprintf(logfp, "%s#\tsmtpconn: host = %.200s\n", logtag(), host); if (host[0] == '"' && host[1] == '[') ++host; #ifndef AF_UNIX # ifdef AF_LOCAL # define AF_UNIX AF_LOCAL # endif #endif #ifdef AF_UNIX if (punthost && STREQN(host,"UNIX:/",6)) { /* We are going into a UNIX domain socket... ... and this socket is defined at our command line! */ struct addrinfo *ai; struct sockaddr_un *su; su = malloc(256+8); ai = malloc(sizeof(*ai)); if (ai && su) { memset(ai, 0, sizeof(*ai)); memset(su, 0, 256+8); ai->ai_next = NULL; ai->ai_family = AF_UNIX; ai->ai_socktype = SOCK_STREAM; ai->ai_protocol = 0; ai->ai_flags = 0; ai->ai_addr = (void *) su; su->sun_family = AF_UNIX; #if HAVE_SA_LEN su->sun_len = sizeof(su); #endif strncpy(su->sun_path, host+5, 256); su->sun_path[255] = 0; SS->mxcount = 0; retval = makeconn(SS, host, ai, -2); } else retval = EX_TEMPFAIL; /* Out of memory... */ if (ai) free(ai); if (su) free(su); goto bail_out; } #endif if (*host == '[') { /* hostname is IP address literal */ char *cp, buf[500]; const char *hcp; struct addrinfo req, *ai; memset(&req, 0, sizeof(req)); req.ai_family = 0; /* Either IPv4 or IPv6 ok */ req.ai_socktype = SOCK_STREAM; req.ai_protocol = IPPROTO_TCP; req.ai_flags = AI_CANONNAME; ai = NULL; if (SS->verboselog) fprintf(SS->verboselog,"SMTP: Connecting to host: %.200s (IP literal)\n",host); for (cp = buf, hcp = host+1 ; *hcp != 0 && *hcp != ']' && cp < (buf+500-1) ; ++cp, ++hcp) *cp = *hcp; *cp = '\0'; if (*hcp == ']' && *++hcp == ':') { ++hcp; sscanf(hcp,"%d",&SS->literalport); } #if defined(AF_INET6) && defined(INET6) if (CISTREQN(buf,"IPv6 ",5) || CISTREQN(buf,"IPv6.",5) || CISTREQN(buf,"IPv6:",5) ) { /* We parse ONLY IPv6 form of address .. well, also the potential IPv4 compability addresses ... */ req.ai_family = PF_INET6; #ifdef HAVE_GETADDRINFO rc = getaddrinfo(buf+5, "0", &req, &ai); #else rc = _getaddrinfo_(buf+5, "0", &req, &ai, SS->verboselog); #endif if (SS->verboselog) fprintf(SS->verboselog, "getaddrinfo(INET6,'%s') -> r=%d, ai=%p\n",buf+5,rc,ai); } else #endif { /* Definitely only IPv4 address ... */ req.ai_family = PF_INET; #ifdef HAVE_GETADDRINFO rc = getaddrinfo(buf, "0", &req, &ai); #else rc = _getaddrinfo_(buf, "0", &req, &ai, SS->verboselog); #endif if (SS->verboselog) fprintf(SS->verboselog, "getaddrinfo(INET,'%s') -> r=%d, ai=%p\n",buf,rc,ai); } { char nbuf[100]; sprintf(nbuf,"X-IP-addr; [%.80s]", buf); notary_setwtt(nbuf); } if (rc != 0) { sprintf(SS->remotemsg, "smtp; 500 (bad IP address: %.500s)", host); time(&endtime); notary_setxdelay((int)(endtime-starttime)); notaryreport(NULL,FAILED,"5.1.2 (bad literal IP address)", SS->remotemsg); if (SS->verboselog) fprintf(SS->verboselog,"%s\n", SS->remotemsg+6); if (ai != NULL) freeaddrinfo(ai); return EX_NOHOST; } SS->mxcount = 0; retval = makeconn(SS, host, ai, -2); goto bail_out; } /* Final "else" branch ... */ if (1) { /* HOSTNAME; (non-literal) */ if (SS->verboselog) fprintf(SS->verboselog,"SMTP: Connecting to host: %.200s firstmx=%d mxcount=? noMX=%d\n",host,SS->firstmx, noMX); hbuf[0] = '\0'; errno = 0; /* use the hostent we got */ #ifdef BIND /* * Look for MX RR's. If none found, use the hostentry in hp. * Otherwise loop through all the mxhosts doing gethostbyname's. */ if (!noMX && SS->firstmx == 0) { if (SS->verboselog) fprintf(SS->verboselog," getmxrr(%.200s)",host); /* We pick (dynamically!) our current interfaces, and thus can (hopefully!) avoid sending mail to ourselves thru MX pointed identity we didn't realize being ours! */ stashmyaddresses(NULL); if (statusreport) report(SS,"MX-lookup: %s", host); SS->mxcount = 0; memset(SS->mxh, 0, sizeof(SS->mxh)); realname[0] = 0; realnamettl = 86400; /* Max cache age.. */ rc = getmxrr(SS, host, SS->mxh, MAXFORWARDERS, 0, realname, sizeof(realname), &realnamettl); realnamettl += now; if (rc == EX_OK) { mxsetsave(SS, host); add_cname_cache(SS, host, *realname ? realname : NULL, realnamettl); } if (SS->verboselog) { if (SS->mxcount == 0) fprintf(SS->verboselog, " rc=%d, no MXes (host='%.200s'; realname='%.200s')\n", rc, host, realname); else fprintf(SS->verboselog, " rc=%d, mxh[0].host=%.200s (host='%.200s', realname='%.200s') mxcnt=%d\n", rc, (SS->mxh[0].host) ? (char*)SS->mxh[0].host : "", host, realname, SS->mxcount); } /* Some error from getmxrr(), bail out immediately */ if (rc != EX_OK) return rc; } #endif /* BIND */ if (!checkwks && SS->mxcount > 0 && SS->mxh[0].host == NULL) { /* Condition ( SS->mxcount > 0 && SS->mxh[0].host == NULL ) can be considered as: Instant (Configuration?) Error; No usable MXes, possibly we are at the lowest MX priority level, and somebody has made some configuration errors... */ strcpy(SS->remotemsg, "smtp; 500 (configuration inconsistency, we are lowest MX, but this is not our local domain!)"); notaryreport(NULL, NULL, "5.4.4 (unable to route)", "smtp; 500 (configuration inconsistency, we are lowest MX but this is not our local domain)"); return EX_NOHOST; } if (SS->mxcount == 0 || SS->mxh[0].host == NULL) { struct addrinfo req, *ai; memset(&req, 0, sizeof(req)); req.ai_socktype = SOCK_STREAM; req.ai_protocol = IPPROTO_TCP; req.ai_flags = AI_CANONNAME; req.ai_family = PF_INET; ai = NULL; errno = 0; /* Either forbidden MX usage, or does not have MX entries! */ #ifdef HAVE_GETADDRINFO r = getaddrinfo(host, "0", &req, &ai); #else r = _getaddrinfo_(host, "0", &req, &ai, SS->verboselog); #endif if (SS->verboselog) fprintf(SS->verboselog,"getaddrinfo(INET,'%s') -> r=%d, ai=%p\n",host,r,ai); #if defined(AF_INET6) && defined(INET6) if (use_ipv6) { struct addrinfo *ai2 = NULL, **aip; int i2; memset(&req, 0, sizeof(req)); req.ai_socktype = SOCK_STREAM; req.ai_protocol = IPPROTO_TCP; req.ai_flags = AI_CANONNAME; req.ai_family = PF_INET6; /* This resolves CNAME, it should not happen in case of MX server, though.. */ #ifdef HAVE_GETADDRINFO i2 = getaddrinfo(host, "0", &req, &ai2); #else i2 = _getaddrinfo_(host, "0", &req, &ai2, SS->verboselog); #endif if (SS->verboselog) fprintf(SS->verboselog, " getaddrinfo(INET6,'%s') -> r=%d, ai=%p\n", host,i2,ai2); if (r != 0 && i2 == 0) { /* IPv6 address, no IPv4 (or error..) */ r = i2; ai = ai2; ai2 = NULL; } if (ai2 && ai) { /* BOTH ?! Catenate them! */ aip = &(ai->ai_next); while (*aip) aip = &((*aip)->ai_next); *aip = ai2; } } #endif if (r != 0) { int gai_err = r; /* getaddrinfo() yields no data, and getmxrr() yielded EX_TEMPFAIL ? Well, getmxrr() did set some reports, lets use them! */ if ((r == EAI_NONAME || r == EAI_AGAIN) && rc == EX_TEMPFAIL) return EX_DEFERALL; if ( r == EAI_AGAIN ) { sprintf(SS->remotemsg,"smtp; 566 (getaddrinfo<%.200s>: try later)",host); time(&endtime); notary_setxdelay((int)(endtime-starttime)); notaryreport(NULL,FAILED,"5.4.3 (dns lookup 'try again')", SS->remotemsg); if (SS->verboselog) fprintf(SS->verboselog,"%s\n",SS->remotemsg+6); if (ai != NULL) freeaddrinfo(ai); return EX_DEFERALL; } #if 0 /* FreeBSD 5.x doesn't have it */ if ( r == EAI_NODATA ) { sprintf(SS->remotemsg,"smtp; 500 (getaddrinfo<%.200s>: No data)",host); time(&endtime); notary_setxdelay((int)(endtime-starttime)); notaryreport(NULL,FAILED,"5.4.3 (dns lookup 'no data')", SS->remotemsg); if (SS->verboselog) fprintf(SS->verboselog,"%s\n",SS->remotemsg+6); if (ai != NULL) freeaddrinfo(ai); if (rc == EX_TEMPFAIL) return EX_DEFERALL; return EX_UNAVAILABLE; } #endif r = EX_UNAVAILABLE; /* This gives instant rejection */ if (rc == EX_TEMPFAIL) r = rc; if (strchr(host,'_') != NULL) { sprintf(SS->remotemsg, "smtp; 500 (Hostname with illegal [to the DNS] underscore in it: '%.200s')", host); } else if (noMX) { sprintf(SS->remotemsg, "smtp; 500 (configuration inconsistency. MX usage forbidden, no address in the DNS: '%.200s')", host); } else { if (SS->mxcount > 0) { sprintf(SS->remotemsg, "smtp; 500 (nameserver data inconsistency. All MXes rejected [we are the best?], no address: '%.200s')", host); #if 1 zsyslog((LOG_NOTICE, "%s", SS->remotemsg)); if (r != EX_TEMPFAIL) r = EX_NOHOST; #endif } else if (gai_err == EAI_NONAME) { sprintf(SS->remotemsg, "smtp; 500 (nameserver data inconsistency. No MX, no address: '%.200s' (%s))", host, gai_err == EAI_NONAME ? "NONAME" : "NODATA"); zsyslog((LOG_NOTICE, "%s r=%d", SS->remotemsg, r)); #if 0 if (r != EX_TEMPFAIL) r = EX_NOHOST; /* Can do instant reject */ #else r = EX_TEMPFAIL; #endif } else { sprintf(SS->remotemsg, "smtp; 500 (nameserver data inconsistency. No MX, no address: '%.200s', errno=%s, gai_errno='%s')", host, strerror(errno), gai_strerror(gai_err)); #if 1 zsyslog((LOG_NOTICE, "%s", SS->remotemsg)); r = EX_TEMPFAIL; /* This gives delayed rejection (after a timeout) */ #endif } } time(&endtime); notary_setxdelay((int)(endtime-starttime)); notaryreport(NULL,FAILED,"5.4.4 (nameserver data inconsistency)", SS->remotemsg); if (SS->verboselog) fprintf(SS->verboselog,"%s\n",SS->remotemsg+6); /* it was: EX_UNAVAILABLE, but such blocks retrying, thus current EX_TEMPFAIL, which will cause timeout later on.. */ if (ai != NULL) freeaddrinfo(ai); /* We translate these TEMPFAILs to DEFERALLs! */ if (r == EX_TEMPFAIL) r = EX_DEFERALL; return r; } { char buf[512]; sprintf(buf,"dns; %.200s", host); notary_setwtt(buf); } if (noMX == -2) retval = makeconn(SS, host, ai, -2); else retval = makeconn(SS, host, ai, -1); if (ai != NULL) freeaddrinfo(ai); } else { /* Has valid MX records, they have been suitably randomized at getmxrr(), and are now ready for use. */ retval = EX_TEMPFAIL; for (i = SS->firstmx; (i < SS->mxcount && SS->mxh[i].host != NULL); ++i) { char buf[512]; sprintf(buf,"dns; %.200s", SS->mxh[i].host); notary_setwtt(buf); r = makeconn(SS, SS->mxh[i].host, SS->mxh[i].ai, i); SS->firstmx = i+1; if (r == EX_OK) { retval = EX_OK; break; } else if (r == EX_TEMPFAIL || r == EX_DEFERALL) retval = r; } } } /* end of HOSTNAME MX lookup processing */ bail_out: if (debug && logfp) fprintf(logfp, "%s#\tsmtpconn: retval = %d\n", logtag(), retval); return retval; } void deducemyifname(SS) SmtpState *SS; { Usockaddr laddr; int laddrsize; struct hostent *hp; if (SS->myhostname != NULL) free(SS->myhostname); SS->myhostname = NULL; laddrsize = sizeof(laddr); if (getsockname(sffileno(SS->smtpfp), (struct sockaddr*) &laddr, &laddrsize) != 0) return; /* Failure .. */ if (laddr.v4.sin_family == AF_INET) hp = gethostbyaddr((char*)&laddr.v4.sin_addr, 4, AF_INET); #if defined(AF_INET6) && defined(INET6) /* No need to check for IPv4-MAPPED addresses here. Presumption is: If connection is made with IPv4 socket, laddr is of IPv4 type. If connection is made with IPv6, the destination is presumably also of IPv6, and thus a mapped address here is most unlikely.. */ else if (laddr.v6.sin6_family == AF_INET6) hp = gethostbyaddr((char*)&laddr.v6.sin6_addr, 16, AF_INET6); #endif else hp = NULL; if (hp == NULL) return; /* Ok, NOW we have a hostent with our IP-address reversed to a name */ SS->myhostname = strdup(hp->h_name); } int makeconn(SS, hostname, ai, ismx) SmtpState *SS; const char *hostname; struct addrinfo *ai; int ismx; { int retval; int mfd; int isreconnect = (ai == &SS->ai); char hostbuf[MAXHOSTNAMELEN+1]; MIBMtaEntry->tasmtp.SmtpStarts += 1; #ifdef BIND #ifdef RFC974 { int ttl; if (ai->ai_canonname) strncpy(hostbuf, ai->ai_canonname, sizeof(hostbuf)); else if (hostname) strncpy(hostbuf, hostname, sizeof(hostbuf)); else *hostbuf = 0; hostbuf[sizeof(hostbuf)-1] = 0; if (checkwks && SS->verboselog) fprintf(SS->verboselog," makeconn(): checkwks of host %.200s\n", hostbuf); if (checkwks && getrr(hostbuf, &ttl, sizeof hostbuf, (u_short)T_WKS, 2, SS->verboselog) != 1) { sprintf(SS->remotemsg,"smtp; 550 (WKS checks: no SMTP reception capability registered for host %.200s)", hostbuf); time(&endtime); notary_setwttip(NULL); notary_setxdelay((int)(endtime-starttime)); notaryreport(NULL,FAILED,"5.4.4 (WKS Checks: no SMTP reception capability registered)", SS->remotemsg); if (SS->verboselog) fprintf(SS->verboselog,"%s\n",SS->remotemsg+6); return EX_UNAVAILABLE; } } #endif /* RFC974 */ #endif /* BIND */ retval = EX_DEFERALL; #if 0 if (SS->verboselog) { fprintf(SS->verboselog,"makeconn('%.200s') to IP addresses:", hostbuf); for ( ; ai ; ai = ai->ai_next ) { /* XX: print the addresses... */ fprintf(SS->verboselog," %s", dottedquad((struct in_addr*)*hp_getaddr())); } fprintf(SS->verboselog,"\n"); } #endif for ( ; ai && !getout ; ai = ai->ai_next ) { int i = 0; struct sockaddr_in *si; #if defined(AF_INET6) && defined(INET6) struct sockaddr_in6 *si6; #endif if (! isreconnect) { /* For possible reconnect */ if (SS->ai.ai_canonname) free(SS->ai.ai_canonname); SS->ai.ai_canonname = NULL; memcpy(&SS->ai, ai, sizeof(*ai)); memset(&SS->ai_addr, 0, sizeof(SS->ai_addr)); if (ai->ai_family == AF_INET) memcpy(&SS->ai_addr.v4, ai->ai_addr, sizeof(SS->ai_addr.v4)); #if defined(AF_INET6) && defined(INET6) else memcpy(&SS->ai_addr.v6, ai->ai_addr, sizeof(SS->ai_addr.v6)); #endif SS->ai.ai_addr = (struct sockaddr *) & SS->ai_addr; SS->ai.ai_canonname = NULL; if (ai->ai_canonname) SS->ai.ai_canonname = strdup(ai->ai_canonname); else if (hostname) SS->ai.ai_canonname = strdup(hostname); SS->ai.ai_next = NULL; SS->ismx = ismx; } switch (ai->ai_family) { case AF_INET: si = (struct sockaddr_in *)ai->ai_addr; i = matchmyaddress((Usockaddr *) ai->ai_addr); inet_ntop(AF_INET, &si->sin_addr, SS->ipaddress, sizeof(SS->ipaddress)); sprintf(SS->ipaddress + strlen(SS->ipaddress), "|%d", SS->servport); break; #if defined(AF_INET6) && defined(INET6) case AF_INET6: si6 = (struct sockaddr_in6*)ai->ai_addr; i = matchmyaddress((Usockaddr *)ai->ai_addr); strcpy(SS->ipaddress,"ipv6 "); inet_ntop(AF_INET6, &si6->sin6_addr, SS->ipaddress+5, sizeof(SS->ipaddress)-5); sprintf(SS->ipaddress + strlen(SS->ipaddress), "|%d", SS->servport); break; #endif #ifdef AF_UNIX case AF_UNIX: { struct sockaddr_un *un = (struct sockaddr_un *)ai->ai_addr; sprintf(SS->ipaddress, "UNIX:%s", un->sun_path); } break; #endif default: sprintf(SS->ipaddress,"UNKNOWN-ADDR-FAMILY-%d", ai->ai_family); break; } notary_setwttip(SS->ipaddress); if (i != 0 && ismx == -2) i = 0; /* Allow routing back to [1.2.3.4] ! */ if (SS->verboselog) fprintf(SS->verboselog,"Trying address: %s port %d\n", SS->ipaddress, SS->servport); /* XXX: Locally matched address is on some MX target, if ismx >= 0. In such a case, the error should be ???? What ? */ if (i != 0 && SS->servport == IPPORT_SMTP) { time(&endtime); notary_setxdelay((int)(endtime-starttime)); switch (i) { case 3: notaryreport(NULL,FAILED,"5.4.6 (trying to use invalid destination address)","smtp; 500 (Trying to talk to invalid destination network address!)"); break; case 2: notaryreport(NULL,FAILED,"5.4.6 (trying to talk to loopback (=myself)!)","smtp; 500 (Trying to talk to loopback (=myself)!)"); break; default: notaryreport(NULL,FAILED,"5.4.6 (trying to talk with myself!)","smtp; 500 (Trying to talk with myself!)"); break; } sprintf(SS->remotemsg,"Trying to talk with myself!"); retval = EX_UNAVAILABLE; break; /* TEMPFAIL or UNAVAILABLE.. */ } if (SS->smtpfp) { /* Clean (close) these fds -- they have been noted to leak.. */ smtpclose(SS, 1); if (logfp) fprintf(logfp,"%s#\t(closed SMTP channel at makeconn())\n",logtag()); } i = vcsetup(SS, /* (struct sockaddr*) */ ai->ai_addr, &mfd, hostbuf); retval = i; switch (i) { case EX_OK: if (lmtp_mode) MIBMtaEntry->tasmtp.LmtpConnects += 1; else MIBMtaEntry->tasmtp.SmtpConnects += 1; SS->smtpfd = mfd; SS->smtpfp = sfnew(NULL, NULL, SS->smtp_bufsize, mfd, SF_WRITE); memset(&SS->smtpdisc, 0, sizeof(SS->smtpdisc)); SS->smtpdisc.D.readf = NULL; SS->smtpdisc.D.writef = smtp_sfwrite; SS->smtpdisc.D.seekf = NULL; SS->smtpdisc.D.exceptf = NULL; SS->smtpdisc.SS = SS; sfdisc(SS->smtpfp, &SS->smtpdisc.D); SS->lasterrno = 0; if (SS->smtpfp == NULL) { int err; err = errno; fprintf(stdout,"# smtp: Failed to fdopen() a socket stream, errno=%d, err='%s' Hmm ??\n",err, strerror(err)); fflush(stdout); abort(); /* sock-stream fdopen() failure! */ } MIBMtaEntry->tasmtp.SmtpConnectsCnt += 1; ++ net_socks_open_cnt; deducemyifname(SS); SS->smtp_outcount = 0; SS->block_written = 0; SS->do_rset = 0; if (SS->esmtp_on_banner > 0) SS->esmtp_on_banner = 0; /* Wait for the initial "220-" greeting */ SS->rcptstates = 0; retval = smtpwrite(SS, 1, NULL, 0, NULL); if (logfp) fprintf(logfp,"%s#\t('220' expectance did yield %d )\n", logtag(), retval); if (retval != EX_OK) /* * If you want to continue with the next host, * the below should be 'return EX_TEMPFAIL'. */ break; /* try another host address */ if (SS->verboselog) fprintf(SS->verboselog,"Connection attempt did yield code %d / %s\n", retval, sysexitstr(retval)); return EX_OK; default: MIBMtaEntry->tasmtp.SmtpConnectFails += 1; if (logfp) fprintf(logfp,"%s#\t(vcsetup() did yield %d )\n",logtag(), i); break; } } /* end of for-loop */ if (getout) retval = EX_TEMPFAIL; if (SS->verboselog) fprintf(SS->verboselog,"Connection attempt did yield code %d / %s\n", retval, sysexitstr(retval)); return retval; } int makereconn(SS) SmtpState *SS; { smtpclose(SS, 1); return makeconn(SS, SS->ai.ai_canonname, & SS->ai, SS->ismx); } int vcsetup(SS, sa, fdp, hostname) SmtpState *SS; struct sockaddr *sa; int *fdp; char *hostname; { int af, port; volatile int addrsiz; int sk; Usockaddr *sai = (Usockaddr *)sa; Usockaddr sad; int wantbindaddr = 0; Usockaddr upeername; int upeernamelen = 0; u_short p; int errnosave, flg; char *se; time(&now); af = sa->sa_family; switch (af) { #if defined(AF_INET6) && defined(INET6) case AF_INET6: addrsiz = sizeof(sai->v6); memset(&sad.v6, 0, sizeof(sad.v6)); break; #endif #ifdef AF_UNIX case AF_UNIX: { struct sockaddr_un *sau = (struct sockaddr_un *)sa; addrsiz = 3 + strlen( sau->sun_path ); } break; #endif default: /* PRESUME: AF_INET */ addrsiz = sizeof(*sai); memset(&sad, 0, sizeof(sad)); break; } if (conndebug) fprintf(stderr, "Trying %.200s [%.200s] ... ", hostname, SS->ipaddress); if (logfp) fprintf(logfp, "%s#\t(Connecting to `%.200s' [%.200s] %24.24s)\n", logtag(), hostname, SS->ipaddress, ctime(&now)); strncpy(SS->remotehost, hostname, sizeof(SS->remotehost)); SS->remotehost[sizeof(SS->remotehost)-1] = 0; if (statusreport) { report(SS,"connecting to [%s]",SS->ipaddress); } smtp_flush(SS); /* Flush in every case */ SS->writeclosed = 0; sk = socket(af, SOCK_STREAM, 0); if (sk < 0) { se = strerror(errno); sprintf(SS->remotemsg, "smtp; 500 (Internal error, socket(AF=%d): %s)", af, se); time(&endtime); notary_setxdelay((int)(endtime-starttime)); notaryreport(NULL,FAILED,"5.4.0 (internal error)",SS->remotemsg); if (conndebug) fprintf(stderr, "%s\n", SS->remotemsg+6); if (SS->verboselog) fprintf(SS->verboselog,"%s\n",SS->remotemsg+6); if (logfp) fprintf(logfp,"%s#\t(Internal error, socket: %s)\n",logtag(),se); abort(); return EX_TEMPFAIL; } wantbindaddr = !zgetbindaddr(localidentity, af, &sad); if (wantreserved && getuid() == 0) { /* try grabbing a port */ for (p = IPPORT_RESERVED-1; p >= (u_short)(IPPORT_RESERVED/2); --p) { if (af == AF_INET) { sad.v4.sin_family = AF_INET; sad.v4.sin_port = htons(p); if (bind(sk, (struct sockaddr *)&sad, sizeof sad.v4) >= 0) break; } #if defined(AF_INET6) && defined(INET6) else if (af == AF_INET6) { sad.v6.sin6_family = AF_INET6; sad.v6.sin6_port = htons(p); if (bind(sk, (struct sockaddr *)&sad, sizeof sad.v6) >= 0) break; } #endif if (errno != EADDRINUSE && errno != EADDRNOTAVAIL) { char *s = strerror(errno); sprintf(SS->remotemsg, "smtp; 500 (Internal error, bind: %s)", s); time(&endtime); notary_setxdelay((int)(endtime-starttime)); notaryreport(NULL,FAILED,"5.4.0 (internal error, bind)",SS->remotemsg); if (SS->verboselog) fprintf(SS->verboselog,"%s\n", SS->remotemsg+6); if (conndebug) fprintf(stderr, "%s\n", SS->remotemsg+6); if (logfp) fprintf(logfp, "%s#\t(Internal error, bind: %s)\n", logtag(), s); return EX_UNAVAILABLE; } } if (p < (u_short)(IPPORT_RESERVED/2)) { sprintf(SS->remotemsg, "too many busy ports"); time(&endtime); notary_setxdelay((int)(endtime-starttime)); notaryreport(NULL,FAILED,"5.4.0 (internal error, too many busy ports)","smtp; 500 (Internal error, too many busy ports)"); if (conndebug) fprintf(stderr, "%s\n", SS->remotemsg+6); if (SS->verboselog) fprintf(SS->verboselog,"%s\n",SS->remotemsg+6); if (logfp) fprintf(logfp,"%s#\t(Internal error, too many busy ports)\n", logtag()); return EX_TEMPFAIL; } } else if (wantbindaddr) { /* Ok, it wasn't a desire for any PRIVILEGED port, just binding on the specific IP will be accepted. */ errno = 0; if (af == AF_INET) bind(sk, (struct sockaddr *)&sad, sizeof sad.v4); #if defined(AF_INET6) && defined(INET6) if (af == AF_INET6) bind(sk, (struct sockaddr *)&sad, sizeof sad.v6); #endif if (logfp) fprintf(logfp,"%s#\tlocalidentity=%s bind() errno = %d\n", logtag(), localidentity, errno); /* If it fails, what could we do ? */ } port = SS->servport; if (SS->literalport > 0) port = SS->literalport; switch (af) { case AF_INET: sai->v4.sin_port = htons(port); break; #if defined(AF_INET6) && defined(INET6) case AF_INET6: sai->v6.sin6_port = htons(port); break; #endif } /* setreuid(0,first_uid); if(SS->verboselog) fprintf(SS->verboselog,"setreuid: first_uid=%d, ruid=%d, euid=%d\n",first_uid,getuid(),geteuid()); */ if (SS->verboselog) { switch (af) { case AF_INET: #if defined(AF_INET6) && defined(INET6) case AF_INET6: #endif fprintf(SS->verboselog, "Connecting to %s [%s] port %d\n", hostname, SS->ipaddress, ntohs(sai->v4.sin_port)); break; #ifdef AF_UNIX case AF_UNIX: fprintf(SS->verboselog, "Connecting to %s [%s]\n", hostname, SS->ipaddress); break; #endif default: fprintf(SS->verboselog, "Connecting to %s [UNKNOWN-ADDRESS-FAMILY-%d]\n", hostname, af); break; } } gotalarm = 0; /* The socket will be non-blocking for its entire lifetime.. */ fd_nonblockingmode(sk); errnosave = errno = 0; smtp_flush(SS); SS->cmdstate = SMTPSTATE_CONNECT; if (sa->sa_family == AF_INET) { struct sockaddr_in *si = (struct sockaddr_in*) sa; unsigned long ia = ntohl(si->sin_addr.s_addr); int anet = ia >> 24; if (anet <= 0 || anet >= 224) { close(sk); errno = EADDRNOTAVAIL; return EX_UNAVAILABLE; } } if (connect(sk, sa, addrsiz) < 0 && (errno == EWOULDBLOCK || errno == EINPROGRESS)) { /* Wait for the connection -- or timeout.. */ struct timeval tv; fd_set wrset; int rc; errno = 0; /* Pick our local socket name */ /* NOTE: At Solaris 2.5.1 (STREAMS based) this may take lots of time! */ memset(&upeername, 0, sizeof(upeername)); upeernamelen = sizeof(upeername); getsockname(sk, (struct sockaddr*) &upeername, &upeernamelen); errnosave = errno; /* Select for the establishment, or for the timeout */ tv.tv_sec = timeout_conn; tv.tv_usec = 0; _Z_FD_ZERO(wrset); _Z_FD_SET(sk, wrset); rc = select(sk+1, NULL, &wrset, NULL, &tv); errno = 0; /* All fine ? */ if (rc == 0) { /* Timed out :-( */ gotalarm = 1; /* Well, sort of ... */ errno = ETIMEDOUT; } } if (!errnosave) errnosave = errno; #ifdef SO_ERROR flg = 0; if (errnosave == 0) { int flglen = sizeof(flg); getsockopt(sk, SOL_SOCKET, SO_ERROR, (void*)&flg, &flglen); } if (flg != 0 && errnosave == 0) errnosave = flg; /* "flg" contains socket specific error condition data */ #endif if (errnosave == 0) { /* We have successfull connection, lets record its peering data */ memset(&upeername, 0, sizeof(upeername)); upeernamelen = sizeof(upeername); getsockname(sk, (struct sockaddr*) &upeername, &upeernamelen); } if (upeernamelen != 0) { #if defined(AF_INET6) && defined(INET6) if (upeername.v6.sin6_family == AF_INET6) { int len = strlen(SS->ipaddress); char *s = SS->ipaddress + len; strcat(s++, "|"); inet_ntop(AF_INET6, &upeername.v6.sin6_addr, s, sizeof(SS->ipaddress)-len-9); s = s + strlen(s); sprintf(s, "|%d", ntohs(upeername.v6.sin6_port)); } else #endif if (upeername.v4.sin_family == AF_INET) { int len = strlen(SS->ipaddress); char *s = SS->ipaddress + len; strcat(s++, "|"); inet_ntop(AF_INET, &upeername.v4.sin_addr, s, sizeof(SS->ipaddress)-len-9); s = s + strlen(s); sprintf(s, "|%d", ntohs(upeername.v4.sin_port)); } else { strcat(SS->ipaddress, "|UNKNOWN-LOCAL-ADDRESS"); } notary_setwttip(SS->ipaddress); } if (errnosave == 0 && !gotalarm) { int on = 1; /* setreuid(0,0); */ *fdp = sk; #ifdef SO_KEEPALIVE #if 1 setsockopt(sk, SOL_SOCKET, SO_KEEPALIVE, (void*)&on, sizeof on); #else #if defined(__svr4__) || defined(BSD) && (BSD-0) >= 43 setsockopt(sk, SOL_SOCKET, SO_KEEPALIVE, (void*)&on, sizeof on); #else /* BSD < 43 */ setsockopt(sk, SOL_SOCKET, SO_KEEPALIVE, 0, 0); #endif /* BSD >= 43 */ #endif #endif #ifdef SO_SNDBUF if (sockwbufsize > 0) { on = sockwbufsize; #if 1 setsockopt(sk, SOL_SOCKET, SO_SNDBUF, (void*)&on, sizeof on); #else #if defined(__svr4__) || defined(BSD) && (BSD-0) >= 43 setsockopt(sk, SOL_SOCKET, SO_SNDBUF, (void*)&on, sizeof on); #else /* BSD < 43 */ setsockopt(sk, SOL_SOCKET, SO_SNDBUF, 0, 0); #endif /* BSD >= 43 */ #endif } #endif if (conndebug) fprintf(stderr, "connected!\n"); return EX_OK; } /* setreuid(0,0); */ se = strerror(errnosave); sprintf(SS->remotemsg, "smtp; 500 (connect to %.200s [%.200s]: %s)", hostname, SS->ipaddress, se); if (statusreport) report(SS,"%s",SS->remotemsg+4); time(&endtime); notary_setxdelay((int)(endtime-starttime)); notaryreport(NULL,FAILED,"5.4.1 (TCP/IP-connection failure)", SS->remotemsg); if (conndebug) fprintf(stderr, "%s\n", SS->remotemsg); if (SS->verboselog) fprintf(SS->verboselog,"%s\n",SS->remotemsg); if (logfp) fprintf(logfp,"%s#\t%s\n", logtag(), SS->remotemsg+4); close(sk); switch (errnosave) { /* from sendmail... */ case EBADF: case EFAULT: case ENOSYS: #ifdef ENOMSG case ENOMSG: #endif #ifdef ENOSTR case ENOSTR: #endif case ENOTSOCK: case EDESTADDRREQ: case EMSGSIZE: case EPROTOTYPE: case ENOPROTOOPT: case EPROTONOSUPPORT: case ESOCKTNOSUPPORT: case EOPNOTSUPP: case EPFNOSUPPORT: case EAFNOSUPPORT: return EX_SOFTWARE; case EACCES: #ifdef ENONET case ENONET: #endif return EX_UNAVAILABLE; /* wonder how Sendmail missed this one... */ case EINTR: case ENOMEM: return EX_TEMPFAIL; } return EX_DEFERALL; } RETSIGTYPE sig_pipe(sig) int sig; { if (logfp != NULL) { fprintf(logfp, "%s#\t*** Received SIGPIPE!\n", logtag()); /* abort(); */ } SIGNAL_HANDLE(sig, sig_pipe); SIGNAL_RELEASE(sig); } /* * SMTP PIPELINING (RFC 1854/2197) support uses model of: * 1st RCPT is for "MAIL From:<>".. -line * 2..n-1: are actual RCPT To:<> -lines * n:th is the "DATA"-line. */ void smtpclose(SS, failure) SmtpState *SS; int failure; { if (SS->smtpfp != NULL) { MIBMtaEntry->tasmtp.SmtpConnectsCnt -= 1; -- net_socks_open_cnt; /* First close the socket so that no FILE buffered stuff can become flushed out anymore. */ if (SS->smtpfd >= 0) close(SS->smtpfd); SS->smtpfd = -1; /* Absolutely NO SFIO SYNC AT THIS POINT! */ zsfsetfd(SS->smtpfp, -1); /* Now do all normal SFIO close things -- including buffer flushes... */ sfclose(SS->smtpfp); SS->smtpfp = NULL; } #ifdef HAVE_OPENSSL if (SS->TLS.sslmode) tls_stop_clienttls(SS, failure); SS->TLS.sslmode = 0; #endif /* - HAVE_OPENSSL */ /* Purge former state */ smtp_flush(SS); } void smtp_flush(SS) SmtpState *SS; { int i; SS->pipebufsize = 0; if (SS->pipebuf == NULL) { SS->pipebufspace = 240; SS->pipebuf = malloc(SS->pipebufspace); if (! SS->pipebuf) zmalloc_failure = 1; } for (i = 0; i < SS->pipeindex; ++i) { if (SS->pipecmds[i]) free(SS->pipecmds[i]); SS->pipecmds[i] = NULL; } SS->pipeindex = 0; SS->pipereplies = 0; SS->rcptstates = 0; SS->prevcmdstate = SMTPSTATE99; SS->cmdstate = SMTPSTATE_MAILFROM; } int bdat_flush(SS, lastflg) SmtpState *SS; int lastflg; { int pos, i, wrlen; volatile int r; /* longjmp() globber danger */ char lbuf[80]; if (SS->smtpfp) tcpstream_denagle(sffileno(SS->smtpfp)); MIBMtaEntry->tasmtp.SmtpBDAT ++; if (lastflg) sprintf(lbuf, "BDAT %d LAST", SS->chunksize); else sprintf(lbuf, "BDAT %d", SS->chunksize); report(SS, "%s", lbuf); r = smtpwrite(SS, 1, lbuf, 1 /* ALWAYS "pipeline" */, NULL); if (r != EX_OK) { return r; } for ( pos = 0; pos < SS->chunksize && !sferror(SS->smtpfp); ) { report(SS, "%s - writing: %d/%d", lbuf, pos, SS->chunksize); wrlen = SS->chunksize - pos; i = sfwrite(SS->smtpfp, SS->chunkbuf + pos, wrlen); if (i >= 0) pos += i; else { /* ERROR!!! */ SS->chunksize = 0; notaryreport(NULL,NULL, "5.4.2 (BDAT message write failed)", "smtp; 566 (BDAT Message write failed)"); report(SS, "%s - write failed; pos=%d/%d", lbuf, r, pos, SS->chunksize); return EX_TEMPFAIL; } } sfsync(SS->smtpfp); if (SS->smtpfp && !sferror(SS->smtpfp)) { if (lastflg || ! SS->pipelining) { r = smtp_sync(SS, r, 0); /* blocking */ } else r = smtp_sync(SS, r, 1); /* non-blocking */ } else { r = EX_TEMPFAIL; notaryreport(NULL,NULL, "5.4.2 (BDAT message write failed)", "smtp; 566 (BDAT message write failed)"); } report(SS, "%s; wrote %d/%d - rc=%d\n", lbuf, pos, SS->chunksize, r); SS->chunksize = 0; return r; } extern int select_sleep __((int fd, time_t when_tout, int waitwr)); #ifdef HAVE_SELECT int select_sleep(fd, when_tout, waitwr) int fd; time_t when_tout; int waitwr; { struct timeval tv; int rc; fd_set rdmask; fd_set wrmask; time(&now); tv.tv_sec = when_tout - now; if (when_tout < now) tv.tv_sec = 0; tv.tv_usec = 0; _Z_FD_ZERO(rdmask); _Z_FD_ZERO(wrmask); if (waitwr) _Z_FD_SET(fd,wrmask); else _Z_FD_SET(fd,rdmask); rc = select(fd+1, &rdmask, &wrmask, NULL, &tv); if (rc == 0) /* Timeout w/o input */ return -1; if (rc == 1) /* There is something to read (or write)! */ return 0; /* Return soft errors first .. */ if (errno == EINTR || errno == EAGAIN) return 1; return -1; /* definitely bad things! */ } int has_readable(fd) int fd; { struct timeval tv; int rc; fd_set rdmask; tv.tv_sec = 0; tv.tv_usec = 0; _Z_FD_ZERO(rdmask); _Z_FD_SET(fd,rdmask); rc = select(fd+1,&rdmask,NULL,NULL,&tv); if (rc > 0) /* There is something to read! */ return 1; return 0; /* interrupt or timeout, or some such.. */ } #else /* not HAVE_SELECT */ int select_sleep(fd, when_tout, waitwr) int fd; time_t when_tout; int waitwr; { errno = ENOSYS; return -1; } int has_readable(fd) int fd; { errno = ENOSYS; return 1; } #endif static int code_to_status(code,statusp) int code; char **statusp; { int rc; char *status; switch (code) { case 211: /* System status, or system help reply */ case 214: /* Help message */ case 220: /* Service ready */ case 221: /* Service closing transmission channel */ case 250: /* Requested mail action okay, completed */ case 251: /* User not local; will forward to */ case 255: /* Something the PMDF 4.1 returns.. for EHLO */ case 354: /* Start mail input; end with . */ status = "2.0.0"; rc = EX_OK; break; case 421: /* Service not available, closing transmission channel */ case 450: /* Requested mail action not taken: mailbox unavailable */ case 451: /* Requested action aborted: local error in processing */ case 452: /* Requested action not taken: insufficient system storage */ status = "4.0.0"; rc = EX_TEMPFAIL; break; case 455: /* ESMTP parameter failure */ status = "5.5.4"; rc = EX_USAGE; break; case 501: /* Syntax error in parameters or arguments */ status = "5.5.2"; rc = EX_USAGE; break; case 500: /* Syntax error, command unrecognized */ case 502: /* Command not implemented */ status = "5.5.1"; rc = EX_PROTOCOL; break; case 503: /* Bad sequence of commands */ status = "5.5.0"; rc = EX_TEMPFAIL; break; case 504: /* Command parameter not implemented */ status = "5.5.4"; rc = EX_PROTOCOL; break; case 550: /* Requested action not taken: mailbox unavailable */ status = "5.1.1 (bad destination mailbox)"; rc = EX_NOUSER; break; case 551: /* User not local; please try */ status = "5.1.6 (mailbox has moved)"; rc = EX_NOUSER; break; case 552: /* Requested mail action aborted: exceeded storage allocation */ status = "5.2.3 (Some content related rejection, size ? text ?)"; rc = EX_UNAVAILABLE; break; case 553: /* Requested action not taken: mailbox name not allowed */ status = "5.1.3 (bad destination mailbox address [syntax])"; rc = EX_NOUSER; break; case 554: status = "5.1.1 (No acceptable recipients given)"; rc = EX_NOUSER; break; case 555: /* Unknown MAIL From:<>/RCPT To:<> parameter */ status = "5.5.4 (invalid parameters)"; rc = EX_USAGE; break; case 571: status = "5.7.1 (Delivery not authorized, message refused)"; rc = EX_NOUSER; break; default: switch (code/100) { case 2: case 3: status = "2.0.0 (generic ok)"; rc = EX_OK; break; case 4: status = "4.0.0 (generic temporary failure)"; rc = EX_TEMPFAIL; break; case 5: status = "5.0.0 (generic permanent failure)"; rc = EX_UNAVAILABLE; break; default: status = "5.5.0 (generic protocol failure)"; rc = EX_TEMPFAIL; break; } break; } *statusp = status; return rc; } int smtp_sync(SS, r, nonblocking) SmtpState *SS; int r, nonblocking; { char *s, *eof, *eol; int idx = 0, nextidx, code = 0; int rc = EX_OK, len; int err = 0; int i, found_any; char buf[512]; char *p; char *status = NULL; int statesave; time_t when_timeout; SS->smtp_outcount = 0; SS->block_written = 0; eol = SS->pipebuf; #if 0 if (SS->verboselog) fprintf(SS->verboselog, "smtp_sync(SS, r=%d, nonblocking=%d) idx=%d\n", r, nonblocking, SS->pipereplies); #endif if (SS->pipereplies == 0) { SS->continuation_line = 0; SS->first_line = 1; } if (!nonblocking && SS->smtpfp && sffileno(SS->smtpfp) >= 0) sfsync(SS->smtpfp); /* Flush output */ /* We have TWO exceptions of the rule about "one reply per pipereplies count"; Namely "BDAT nn LAST" MAY yield zero or more replies in LMTP mode, and its sender has *no* clue about when that may happen. While DATA's "dot" knows how many it needs to pick, things are cleaner when we treat it similarly to "BDAT nn LAST". To recognize when this is the case: lmtp_mode && (idx == (SS->pipeindex-1)) && SS->cmdstate >= SMTPSTATE_DATADOT At the begin of the loop we must check if there are any nondiagnosed recipients left. If none are, we exit the loop. */ for (idx = SS->pipereplies; idx < SS->pipeindex; idx = nextidx) { struct rcpt *datarp = NULL; nextidx = idx+1; /* Collect MULTIPLE missing replies IF WE ARE 1) IN LMTP MODE, 2) at the last item of commands, 3) we are at the DOT of DATA or at BDAT LAST phase. */ found_any = 0; if (lmtp_mode && (idx == (SS->pipeindex-1)) && SS->cmdstate >= SMTPSTATE_DATADOT) { for (i = 0; i < idx; ++i) { datarp = SS->pipercpts[i]; if (datarp && datarp->lockoffset) { found_any = 1; nextidx = idx; /* if (SS->verboselog) fprintf(SS->verboselog, " lmtp: Data-dot/bdat-last; i=%d datarp=('%s' '%s' '%s')\n", i, datarp->addr->channel, datarp->addr->host, datarp->addr->user); */ break; } } if (!found_any) break; } /* Special LMTP BDAT LAST/DATA mode */ rescan_line_0: /* processed some continuation line */ s = eol; rescan_line: /* Got additional input */ when_timeout = time(&now) + timeout; eof = SS->pipebuf + SS->pipebufsize; for (eol = s; eol < eof; ++eol) if (*eol == '\n') break; if (eol < eof && *eol == '\n') { ++eol; /* points to char AFTER the newline */ if (debug && logfp) fprintf(logfp,"%s#\t(pipebufsize=%d, s=%d, eol=%d)\n", logtag(), SS->pipebufsize,(int)(s-SS->pipebuf), (int)(eol-SS->pipebuf)); } else { /* No newline.. Read more.. */ int en, waitwr; err = 0; reread_line: /* Blocking read mode */ err = 0; len = smtp_nbread(SS, buf, sizeof(buf)); waitwr = 0; #ifdef HAVE_OPENSSL if (SS->TLS.sslmode && SS->TLS.wantreadwrite > 0) waitwr = 1; #endif /* - HAVE_OPENSSL */ if (len < 0) err = errno; if (!nonblocking && len < 0) { /* Blocking mode, and didn't succeed in reading, lets use select to see what we can do. */ int infd = SS->smtpfd; if (infd >= 0) { err = select_sleep(infd, when_timeout, waitwr); } else { err = -1; /* Write has failed, but we still drop here to read.. or something */ } en = errno; if (debug && logfp) fprintf(logfp,"%s#\tselect_sleep(%d,%d); rc=%d\n", logtag(),infd,(int)(when_timeout - now),err); if (err < 0) { if (logfp) fprintf(logfp,"%s#\tTimeout (%d sec) while waiting responses from remote (errno=%d)\n",logtag(),timeout,en); if (SS->smtpfp) if (SS->verboselog) fprintf(SS->verboselog,"Timeout (%d sec) while waiting responses from remote\n",timeout); rmsgappend(SS, 1, "\rTimeout of %d sec while waiting responses from remote", timeout); smtpclose(SS, 1); break; } /* select_sleep() indicated that yes, something is available! */ goto reread_line; } /* Here we are because we are non-blocking, and have no data, OR we have some data! */ if (len < 0) { /* Some error ?? How come ? We have select() confirmed input! */ if (nonblocking) { if (err == EINTR || err == EAGAIN #ifdef EWOULDBLOCK || err == EWOULDBLOCK #endif ) { err = 0; break; /* XX: return ?? */ } } else { /* blocking mode -- we won't come here ?? */ if (err == EINTR || err == EAGAIN #ifdef EWOULDBLOCK || err == EWOULDBLOCK #endif ) { abort(); /*goto reread_line;*/ } } /* XX: what to do with the error ? */ if (logfp) fprintf(logfp,"%s#\tRemote gave error %d (%s) while %d responses missing\n", logtag(), err, strerror(err), SS->pipeindex - 1 - idx); if (SS->verboselog) fprintf(SS->verboselog,"Remote gave error %d (%s) while %d responses missing\n", err, strerror(err), SS->pipeindex - 1 - idx); break; } else if (len == 0) { /* The remote hung up! */ if (logfp) fprintf(logfp,"%s#\tRemote hung up on us while %d responses missing\n", logtag(), SS->pipeindex - idx); if (SS->verboselog) fprintf(SS->verboselog,"Remote hung up on us while %d responses missing\n", SS->pipeindex - idx); rmsgappend(SS, 1, "\rremote hung up on us while %d responses missing", SS->pipeindex - idx); err = EX_TEMPFAIL; break; } else { /* more data for processing.. munch munch.. */ if (s > SS->pipebuf) { /* Compress the buffer at first */ memcpy(SS->pipebuf, s, SS->pipebufsize - (s - SS->pipebuf)); SS->pipebufsize -= (s - SS->pipebuf); s = SS->pipebuf; eol = SS->pipebuf; } eof = SS->pipebuf; if ((SS->pipebufsize+len+1) > SS->pipebufspace) { while ((SS->pipebufsize+len+2) > SS->pipebufspace) SS->pipebufspace <<= 1; /* Double the size */ SS->pipebuf = (void*)realloc(SS->pipebuf,SS->pipebufspace); if (! SS->pipebuf) zmalloc_failure = 1; } if (SS->pipebuf != eof) { /* Block changed.. Reset those pointers */ long offsetchange = SS->pipebuf - eof; eol += offsetchange; s += offsetchange; } memcpy(SS->pipebuf + SS->pipebufsize, buf, len); SS->pipebufsize += len; goto rescan_line; } } /* -- endif -- ... globbing more input */ p = eol-1; /* The '\n' at the end of the line */ if (p > s && p[-1] == '\r') --p; /* "\r\n" ? */ *p = 0; if (SS->within_ehlo) ehlo_check(SS, s+4); if (!SS->esmtp_on_banner && SS->esmtp_on_banner > -2) esmtp_banner_check(SS, s+4); if (logfp) fprintf(logfp, "%sr\t%s\n", logtag(), s); if (SS->verboselog) fprintf(SS->verboselog,"%s\n",s); if (s[0] >= '0' && s[0] <= '9' && s[1] >= '0' && s[1] <= '9' && s[2] >= '0' && s[2] <= '9' && (s[3] == ' ' || s[3] == 0)) { code = atoi(s); /* We have a 'terminal' line */ SS->continuation_line = 0; } else { /* it is 'continuation line', or some such, ignore */ SS->continuation_line = 1; } statesave = SS->cmdstate; SS->cmdstate = SS->pipestates[idx]; if (idx == 0 && SS->first_line) SS->prevcmdstate = SMTPSTATE99; if (SS->first_line) rmsgappend(SS, 0, "\r<<- %s", SS->pipecmds[idx] ? SS->pipecmds[idx] : "(null)"); /* first_line is not exactly a complement of continuation_line, it is rather a more complex entity. */ SS->first_line = !SS->continuation_line; rmsgappend(SS, 1, "\r->> %s", s); SS->cmdstate = statesave; if (SS->continuation_line) goto rescan_line_0; else SS->pipereplies = nextidx; /* Final line, mark this as processed! */ /* If write-fd has closed(shut down), we shall turn all 500-series hard errors into soft ones, as we must try re-sending the message sometime. */ if ((SS->smtpfp == NULL || sffileno(SS->smtpfp) < 0) && code >= 500) code -= 100; /* SOFTEN IT! */ rc = code_to_status(code, &status); notarystatsave(SS,s,status); if (SS->verboselog) fprintf(SS->verboselog, " lmtp_mode=%d code=%d rc=%d idx=%d datarp=%p pipercpts[idx]=%p pipecmds[idx]='%s'\n", lmtp_mode, code, rc, idx, datarp, SS->pipercpts[idx], SS->pipecmds[idx] ? SS->pipecmds[idx] : ""); if (SS->rcptstates & (FROMSTATE_500|HELOSTATE_500)) { /* If "MAIL From:<..>" tells non-200 report, and causes "RCPT To:<..>" commands to yield "400/500", we IGNORE the "500" status. */ rc = EX_UNAVAILABLE; } else if (SS->rcptstates & (FROMSTATE_400|HELOSTATE_400)) { /* If "MAIL From:<..>" tells non-200 report, and causes "RCPT To:<..>" commands to yield "400/500", we IGNORE the "500" status. */ SS->rcptstates |= RCPTSTATE_400; rc = EX_TEMPFAIL; } if (code >= 400) { /* Errors */ /* MAIL From:<*>: 250/ 552/451/452/ 500/501/421 */ /* DATA: 354/ 451/554/ 500/501/503/421 */ /* RCPT To:<*>: 250/251/ 550/551/552/553/450/451/452/455/ 500/501/503/421 */ if (SS->pipercpts[idx] != NULL) { if (code >= 500) SS->rcptstates |= RCPTSTATE_500; else SS->rcptstates |= RCPTSTATE_400; /* ``rc'' is correct. */ /* Diagnose the errors, we report successes AFTER the DATA phase.. */ /* if (SS->verboselog) fprintf(SS->verboselog, " -> diagnostic(rc=%d idx=%d) remotemsg='%s'\n", rc, idx, SS->remotemsg); */ time(&endtime); notary_setxdelay((int)(endtime-starttime)); notaryreport(SS->pipercpts[idx]->addr->user,FAILED,NULL,NULL); diagnostic(SS->verboselog, SS->pipercpts[idx], rc, 0, "%s", SS->remotemsg); SMTP_MIB_diag(rc); if (logfp) { fprintf(logfp, "%s#\t", logtag()); diagnostic(logfp, SS->pipercpts[idx], rc, 0, "%s", SS->remotemsg); } } else { /* SS->pipercpts[idx] == NULL --> Connect, HELO, MAIL FROM or DATA/BDAT */ /* No diagnostic() calls for MAIL FROM:<>, nor for DATA/BDAT phases (except in LMTP mode) */ /* if (SS->verboselog) fprintf(SS->verboselog, " -> diagnostic(rc=%d idx=%d) remotemsg='%s'\n", rc, idx, SS->remotemsg); */ if ((idx == 0) && (SS->pipecmds[idx] != NULL) && (STREQN(SS->pipecmds[idx],"MAIL", 4))) { /* We are working on MAIL From:<...> command here */ if (code >= 500) SS->rcptstates |= FROMSTATE_500; else if (code >= 400) SS->rcptstates |= FROMSTATE_400; else SS->rcptstates |= FROMSTATE_OK; } else { /* "DATA" or "BDAT" phase */ if (lmtp_mode && datarp) { /* LMTP is different animal.. We do diagnostic() for all recipients who have been reported as RCPTSTATE_OK */ notary_setxdelay((int)(endtime-starttime)); notaryreport(datarp->addr->user, FAILED, NULL, NULL); diagnostic(SS->verboselog, datarp, rc, 0, "%s", SS->remotemsg); SMTP_MIB_diag(rc); if (logfp) { fprintf(logfp, "%s#\t", logtag()); diagnostic(logfp, datarp, rc, 0, "%s", SS->remotemsg); } SS->rcptstates |= ((code >= 500) ? RCPTSTATE_500 : RCPTSTATE_400); } /* LMTP mode */ if (code >= 500) { if (SS->rcptstates & (FROMSTATE_400|FROMSTATE_500)) { /* The FROM failed already, make us 'soft' */ SS->rcptstates |= DATASTATE_400; } else if (SS->rcptstates & RCPTSTATE_OK) { /* At least one OK result for RCPTs, It means we are REALLY hard error! */ SS->rcptstates |= DATASTATE_500; } else if (SS->rcptstates & RCPTSTATE_400) { /* TMPFAIL RCPTs, make us 'soft' error! */ SS->rcptstates |= DATASTATE_400; } else { /* All others are HARD errors! */ SS->rcptstates |= DATASTATE_500; } } else if (code >= 400) { SS->rcptstates |= DATASTATE_400; } } } /* SS->pipercpts[idx] == NULL */ } else { /* code < 400 */ /* Ok results */ if (SS->pipercpts[idx] != NULL) { if (SS->rcptstates & (FROMSTATE_400|FROMSTATE_500)) { /* MAIL FROM gave error, we won't believe OK on recipients either. */ SS->rcptstates |= RCPTSTATE_400; /* Actually we SHOULD NOT arrive here, but we never know, what kind of smtp-servers are out there... */ } else { /* MAIL FROM was apparently ok. */ SS->rcptstates |= RCPTSTATE_OK; SS->pipercpts[idx]->status = EX_OK; if (SS->verboselog) fprintf(SS->verboselog,"[Some OK - code=%d, idx=%d, pipeindex=%d]\n",code,idx,SS->pipeindex-1); } } else { /* MAIL FROM or DATA/BDAT */ if (idx == 0) SS->rcptstates |= FROMSTATE_OK; /* DATA/BDAT phase */ if (lmtp_mode && datarp) { /* LMTP is different animal.. We do diagnostic() for all recipients who have been reported as RCPTSTATE_OK */ time(&endtime); notary_setxdelay((int)(endtime-starttime)); notaryreport(datarp->addr->user, "delivered", NULL, NULL); diagnostic(SS->verboselog, datarp, rc, 0, "%s", SS->remotemsg); SMTP_MIB_diag(rc); if (logfp) { fprintf(logfp, "%s#\t", logtag()); diagnostic(logfp, datarp, rc, 0, "%s", SS->remotemsg); } SS->rcptstates |= RCPTSTATE_OK; if (SS->verboselog) fprintf(SS->verboselog, " LMTP diagnostic() done; rc=%d code=%d datarp->lockoffset=%d%s\n", rc, code, datarp->lockoffset, datarp->lockoffset ? " **NOT ZERO!**":""); } /* LMTP mode */ if (idx > 0) { if (SS->rcptstates & RCPTSTATE_OK) SS->rcptstates |= DATASTATE_OK; } } } /* end if 'code' interpretation */ /* Now compress away that processed dataset */ if (eol > SS->pipebuf) { int sz = eol - SS->pipebuf; SS->pipebufsize -= sz; if (SS->pipebufsize > 0) memcpy(SS->pipebuf, eol, SS->pipebufsize); s -= sz; eol = SS->pipebuf; } } /* for(..; idx < SS->pipeindex ; ..) */ rc = EX_OK; if (rc == EX_OK && (SS->rcptstates & FROMSTATE_500)) rc = EX_UNAVAILABLE; /* MAIL FROM was a 5** code */ if (rc == EX_OK && (SS->rcptstates & FROMSTATE_400)) rc = EX_TEMPFAIL; /* MAIL FROM was a 4** code */ if (rc == EX_OK && err != 0) rc = EX_TEMPFAIL; /* Some timeout happened at the response read */ if (rc == EX_OK) { /* Study the RCPT STATES! */ if (SS->rcptstates & RCPTSTATE_OK) { rc = EX_OK; /* SOME OK */ } else if (SS->rcptstates & RCPTSTATE_400) { /* Some TEMPFAIL */ rc = EX_TEMPFAIL; } else if (SS->rcptstates & RCPTSTATE_500) { /* only full failures :-( */ rc = EX_UNAVAILABLE; } } if (rc == EX_OK) { /* Study the DATA STATES! */ if (SS->rcptstates & DATASTATE_500) rc = EX_UNAVAILABLE; /* All hard failures */ else if (SS->rcptstates & DATASTATE_400) rc = EX_TEMPFAIL; /* Some TEMPFAIL */ #if 0 else if (SS->rcptstates & DATASTATE_OK) rc = EX_OK; /* Some ok! */ #endif } if (rc != EX_OK && logfp) fprintf(logfp,"%s#\t smtp_sync() did yield code %d/%s\n", logtag(), rc, sysexitstr(rc)); if (SS->verboselog) fprintf(SS->verboselog," smtp_sync() did yield code %d/%s (rcptstates = 0x%x)\n", rc, sysexitstr(rc), SS->rcptstates); return rc; } /* */ int pipeblockread(SS) SmtpState *SS; { int infd = SS->smtpfd; char buf[512]; int rc = EX_OK; /* BLOCKALARM; */ if (SS->block_written && has_readable(infd)) { /* Read and buffer all so far accumulated responses.. */ for (;;) { /* Do non-blocking */ int r = smtp_nbread(SS, buf, sizeof buf); if (r <= 0) break; /* Nothing to read ? EOF ?! */ if (SS->pipebuf == NULL) { SS->pipebufspace = 240; SS->pipebufsize = 0; } while (SS->pipebufspace < (SS->pipebufsize+r+2)) SS->pipebufspace <<= 1; /* malloc(size) == realloc(NULL,size) */ SS->pipebuf = realloc(SS->pipebuf,SS->pipebufspace); if (! SS->pipebuf) zmalloc_failure = 1; if (SS->pipebuf) memcpy(SS->pipebuf+SS->pipebufsize,buf,r); SS->pipebufsize += r; SS->block_written = 0; /* We drain the accumulated input here, and can thus mark this draining unneeded for a while. */ } /* Continue the processing... */ } if (SS->pipebufsize) rc = smtp_sync(SS, EX_OK, 1); /* NON-BLOCKING! */ /* ENABLEALARM; */ return rc; } void smtppipestowage(SS, strbuf, syncrp) SmtpState *SS; const char *strbuf; struct rcpt *syncrp; { if (SS->pipespace <= SS->pipeindex + 2) { SS->pipespace += 8; /* realloc(NULL,size) == malloc(size) */ SS->pipecmds = (char**)realloc((void*)SS->pipecmds, SS->pipespace * sizeof(char*)); SS->pipercpts = (struct rcpt **)realloc((void*)SS->pipercpts, SS->pipespace * sizeof(struct rcpt*)); SS->pipestates = (int*)realloc((void*)SS->pipestates, SS->pipespace * sizeof(int)); } SS->pipecmds [SS->pipeindex] = strbuf ? strdup(strbuf) : NULL; SS->pipercpts [SS->pipeindex] = syncrp; /* RCPT or NULL */ SS->pipestates[SS->pipeindex] = SS->cmdstate; SS->pipeindex += 1; } int smtpwrite(SS, saverpt, strbuf, pipelining, syncrp) SmtpState *SS; int saverpt; const char *strbuf; int pipelining; struct rcpt *syncrp; { char buf[8192]; int r = EX_OK, r2 = EX_OK; volatile int err = 0; gotalarm = 0; /* smtp_sfwrite() may set it.. */ smtppipestowage(SS, strbuf, syncrp); if (strbuf != NULL) { int len = strlen(strbuf) + 2; if (pipelining > 0) { /* We are asynchronous! */ SS->smtp_outcount += len; /* Where will we grow to ? */ /* Read possible responses into response buffer.. */ r2 = pipeblockread(SS); /* FIXME: If we are seeing some errors ??? */ memcpy(buf,strbuf,len-2); memcpy(buf+len-2,"\r\n",2); if (SS->verboselog) fwrite(buf, 1, len, SS->verboselog); if (SS->smtpfp && !sferror(SS->smtpfp)) r = sfwrite(SS->smtpfp, buf, len); else r = -1; err = (r != len) || !SS->smtpfp || sferror(SS->smtpfp); if (SS->smtp_outcount > SS->smtp_bufsize) { SS->smtp_outcount -= SS->smtp_bufsize; SS->block_written = 1; } } else { /* We act synchronously */ memcpy(buf,strbuf,len-2); memcpy(buf+len-2,"\r\n",2); if (SS->verboselog) fwrite(buf, 1, len, SS->verboselog); if (SS->smtpfp && !sferror(SS->smtpfp)) r = sfwrite(SS->smtpfp, buf, len); else r = -1; err = (r != len); if (!SS->smtpfp || sferror(SS->smtpfp) || sfsync(SS->smtpfp)) err = 1; } SS->lastactiontime = time(NULL); if (err) { if (gotalarm) { strcpy(SS->remotemsg, "Timeout on cmd write"); time(&endtime); notary_setxdelay((int)(endtime-starttime)); notaryreport(NULL,FAILED,"5.4.2 (timeout on cmd write)", "smtp; 500 (timeout on cmd write)"); } else { char *se = strerror(errno); sprintf(SS->remotemsg, "smtp; 500 (write to server error: %s)", se); time(&endtime); notary_setxdelay((int)(endtime-starttime)); notaryreport(NULL,FAILED,"5.4.2 (write to server, err)",SS->remotemsg); } if (SS->verboselog) fprintf(SS->verboselog,"%s\n",SS->remotemsg); #if 0 smtpclose(SS, 1); if (logfp) fprintf(logfp, "%s#\t(closed SMTP channel - timeout on smtpwrite())\n", logtag()); /* Alarm OFF */ r = EX_TEMPFAIL; #endif } else if (r != len) { sprintf(SS->remotemsg, "smtp; 500 (SMTP cmd write failure: Only wrote %d of %d bytes!)", r, len); time(&endtime); notary_setxdelay((int)(endtime-starttime)); notaryreport(NULL,FAILED,"5.4.2 (SMTP cmd partial write failure)",SS->remotemsg); if (SS->verboselog) fprintf(SS->verboselog,"%s\n",SS->remotemsg); #if 0 smtpclose(SS, 1); if (logfp) fprintf(logfp, "%s#\t(closed SMTP channel - second timeout on smtpwrite() )\n", logtag()); /* Alarm OFF */ r = EX_TEMPFAIL; #endif } if (logfp) fprintf(logfp, "%sw\t%s\n", logtag(), strbuf); if (err) r = EX_TEMPFAIL; else r = EX_OK; } if (SS->smtpfp && sffileno(SS->smtpfp) >= 0) { if (strbuf) { rmsgappend(SS, 0, "\r<<- %s", strbuf); } else { SS->remotemsg[0] = 0; rmsgappend(SS, 0, "\r<<- (null)"); } } else if (strbuf != NULL) { /* socket closed outwards, commands not written! */ if (strbuf) rmsgappend(SS, 0, "\rWrite Failure; shunted cmd: %s", strbuf); else strcpy(SS->remotemsg, "\rWrite Failure; expecting initial greeting??"); } /* ------------------------------------------------ */ /* _________ Now begins reply collection _________ */ if (pipelining) { /* With "QUIT" this is negative value, and we are not in reality interested of the return value... */ /* Read possible responses into response buffer.. */ r2 = pipeblockread(SS); if (r != EX_OK) return r; return r2; } return smtp_sync(SS, r, pipelining); } int smtp_ehlo(SS, strbuf) SmtpState *SS; const char *strbuf; { int rc; SS->within_ehlo = (SS->esmtp_on_banner > -2); SS->ehlo_capabilities = 0; SS->rcptstates = 0; rc = smtpwrite(SS, 1, strbuf, 0, NULL); SS->within_ehlo = 0; return rc; } /* * In theory, this should modify the command that ps shows for this process. * This is known not to be portable, hopefully it will break badly on systems * where it doesn't work. */ #ifdef HAVE_STDARG_H #ifdef __STDC__ void report(SmtpState *SS, char *fmt, ...) #else /* Not ANSI-C */ void report(SS, fmt) SmtpState *SS; char *fmt; #endif #else /* VARARGS */ void report(va_alist) va_dcl #endif { va_list ap; char buf[8192]; int cmdlen; #ifdef HAVE_STDARG_H va_start(ap,fmt); #else SmtpState *SS; char *fmt; va_start(ap); SS = va_arg(ap, SmtpState *); fmt = va_arg(ap, char *); #endif memset(buf, 0, sizeof(buf)); if (SS->smtpfp && sffileno(SS->smtpfp) >= 0) sprintf(buf, ">%.200s ", SS->remotehost); else sprintf(buf, ">[%.200s] ", SS->remotehost); #ifdef notdef if (logfp) sprintf(buf+strlen(buf), ">>%s ", logfile); strcat(buf, "# "); #endif #ifdef HAVE_VPRINTF vsprintf(buf+strlen(buf), fmt, ap); #else /* !HAVE_VPRINTF */ sprintf(buf+strlen(buf), fmt, va_arg(ap, char *)); #endif /* HAVE_VPRINTF */ #ifdef HAVE_SETPROCTITLE setproctitle("%s", buf); #else cmdlen = (eocmdline - cmdline); if (cmdlen >= sizeof(buf)) cmdlen = sizeof(buf) - 1; for (fmt = buf+strlen(buf); fmt < buf + cmdlen; ++fmt) *fmt = '\0'; buf[cmdlen] = '\0'; memcpy((char*)cmdline, buf, cmdlen); /* Overwrite it! */ #endif va_end(ap); } #ifdef BIND /* * This is the callback function for ctlopen. It should return 0 to reject * an address, and 1 to accept it. This routine will only be used if we've * been asked to check MX RR's for all hosts for applicability. Therefore we * check whether the addr_host has an MX RR pointing at the host that we have * an SMTP connection open with. Return 1 if it is so. */ int rightmx(spec_host, addr_host, cbparam) const char *spec_host, *addr_host; void *cbparam; { SmtpState *SS = cbparam; int i, rc; char realname[1024]; if (CISTREQ(spec_host, addr_host)) return 1; if (SS->remotehost[0] == '\0') return 0; memset(SS->mxh, 0, sizeof(SS->mxh)); SS->mxh[0].host = NULL; SS->mxcount = 0; SS->firstmx = 0; if (statusreport) report(SS,"MX-lookup: %s", addr_host); realname[0] = 0; switch (getmxrr(SS, addr_host, SS->mxh, MAXFORWARDERS, 0, realname, sizeof(realname), NULL)) { case EX_OK: if (SS->mxh[0].host == NULL) return CISTREQ(addr_host, SS->remotehost); break; default: return 0; } rc = 0; for (i = 0; SS->mxh[i].host != NULL; ++i) { if (CISTREQ((const void*)SS->mxh[i].host, SS->remotehost)) rc = 1; freeaddrinfo(SS->mxh[i].ai); SS->mxh[i].ai = NULL; free(SS->mxh[i].host); SS->mxh[i].host = NULL; } return 0; } /* Hook for possible future implementation of a tricky thing to tell to the *scheduler*, what MXes each destination domain has, so that the scheduler could combine alike looking MX serviced destination domains together... */ void mxsetsave(SS, host) SmtpState *SS; const char *host; { } #endif /* BIND */ void notarystatsave(SS,smtpline,status) SmtpState *SS; char *smtpline, *status; { char statbuf[10]; int len = strlen(smtpline)+8+6; #ifdef USE_ALLOCA char *str = alloca(len); #else char *str = malloc(len); #endif char *s = str; #if 0 if (SS->verboselog) fprintf(SS->verboselog," notarystatsave1(len=%d status='%s', smtpline='%s')\n",len,status,smtpline); #endif *statbuf = 0; strcpy(s,"smtp; "); s += 6; *s++ = *smtpline++; *s++ = *smtpline++; *s++ = *smtpline++; *s++ = ' '; if (*smtpline == ' ') ++smtpline; if (len >= 11) { if (ESMTP_ENHSTATUS & SS->ehlo_capabilities) { char *p = statbuf; status = statbuf; while ((p - statbuf) < sizeof(statbuf)-1) { int c = (*smtpline) & 0xFF; if (('0' <= c && c <= '9') || c == '.') *p++ = c; else break; ++smtpline; } *p = 0; while (*smtpline == ' ' || *smtpline == '\t') ++smtpline; } } if (*smtpline) { *s++ = '('; while (*smtpline) { switch (*smtpline) { case '(': *s++ = '['; break; case ')': *s++ = ']'; break; default: *s++ = *smtpline; } ++smtpline; } *s++ = ')'; } *s = 0; notaryreport(NULL,NULL,status,str); #if 0 if (SS->verboselog) fprintf(SS->verboselog," notarystatsave2(status='%s', smtpline='%s')\n",status,str); #endif #ifndef USE_ALLOCA free(str); #endif } void getdaemon() { struct Zpasswd *pw = zgetpwnam("daemon"); if (!pw) pw = zgetpwnam("daemons"); /* Some SGI machines! */ if (!pw) pw = zgetpwnam("uucp"); if (!pw) daemon_uid = 0; /* Let it be root, if nothing else */ else daemon_uid = pw->pw_uid; } /* .. */ #ifndef SOL_TCP /* Latter Linuxes have SOL_TCP, Solaris IPPROTO_TCP .. */ # define SOL_TCP IPPROTO_TCP #endif static void tcpstream_nagle(fd) int fd; { int i, r; if (fd < 0) return; #ifdef TCP_CORK /* Linux 2.4 / FreeBSD 5.x ? */ i = 1; r = setsockopt(fd, SOL_TCP, TCP_CORK, &i, sizeof(i)); #else #ifdef TCP_NOPUSH /* FreeBSD -- relates to T/TCP */ i = 1; r = setsockopt(fd, SOL_TCP, TCP_NOPUSH, &i, sizeof(i)); #else #ifdef TCP_NODELAY /* old original BSD network stack thing */ i = 0; r = setsockopt(fd, SOL_TCP, TCP_NODELAY, &i, sizeof(i)); #else /* No method at hand if none of above.. */ i = r = 0; /* silense the compiler */ #endif #endif #endif } static void tcpstream_denagle(fd) int fd; { int i, r; if (fd < 0) return; #ifdef TCP_CORK i = 0; r = setsockopt(fd, SOL_TCP, TCP_CORK, &i, sizeof(i)); if (r < 0) sleep(1); /* Fall back to classic timeout based anti-nagle.. */ #else #ifdef TCP_NOPUSH i = 0; r = setsockopt(fd, SOL_TCP, TCP_NOPUSH, &i, sizeof(i)); #if 0 /* original sendmail didn't have any code for this, actually */ if (r < 0) sleep(1); /* Fall back to classic timeout based anti-nagle.. */ #endif #else #ifdef TCP_NODELAY i = 1; /* Turning this on DOES NOT FLUSH accumulated data immediately! Unlike TCP_CORK at Linux.. */ r = setsockopt(fd, SOL_TCP, TCP_NODELAY, &i, sizeof(i)); #if 0 /* original sendmail didn't have any code for this, actually */ if (r < 0) sleep(1); /* Fall back to classic timeout based anti-nagle.. */ #endif #else i = r = 0; #if 0 /* original sendmail didn't have any code for this, actually */ sleep(1); /* Fall back to classic timeout based anti-nagle.. */ #endif #endif #endif #endif #if 0 /* In BSD (and Linux) systems we can ask what the outgoing queue size is -- not so at Solaris ? */ while (ioctl(fd, SIOCOUTQ, &count) == 0 && count != 0) { if (poll(1, {fd, POLLIN}, 200)) abort_on_protocol_violation; if ((tmo += 200) > long_timeo) abort_on_stuck_connection; } #endif } /* ------------------------------------------------------------------ * * CNAME caused translations; cache.. * * ------------------------------------------------------------------ */ struct cnamecache_struct { int hash; int next; time_t ttl; const char *name; const char *cname; }; #define CNAMECACHESIZE 1000 static int cnamecache_head; static int cnamecache_free; static struct cnamecache_struct *cnamecache, *cp; /* BSS NULL value */ static void cnamecache_init __((FILE *verboselog)); static void cnamecache_init(verboselog) FILE *verboselog; { int i; cnamecache = malloc(sizeof(struct cnamecache_struct) * CNAMECACHESIZE); if (!cnamecache) return; /* Urgh... */ for (i = 0; i < CNAMECACHESIZE; ++i) { cp = & cnamecache[i]; cp->next = i+1; cp->hash = cp->ttl = 0; cp->name = cp->cname = NULL; } cnamecache[CNAMECACHESIZE-1].next = -1; cnamecache_free = 0; cnamecache_head = -1; /* if (verboselog) fprintf(verboselog,"CNAMEcache init() done\n"); */ } static int namehash __((const char *)); static int namehash(s) const char *s; { int hash = 0; for ( ; *s ; ++s) { unsigned char c = *s; if ('A' <= c && c <= 'Z') c += ('a' - 'A'); hash += c; } return hash; } static int cname_lookup(SS, host, cnamep) SmtpState *SS; const char *host; const char ** cnamep; { int hhash = namehash(host); int idx, nextidx; struct cnamecache_struct *ci, *cp; if (!cnamecache) cnamecache_init(SS->verboselog); if (!cnamecache) return -1; /* OOPS! */ /* if (SS->verboselog) fprintf(SS->verboselog,"cname_lookup(name='%s'[%d])\n", host, hhash); */ for ( idx = cnamecache_head, cp = NULL ; idx >= 0; cp = ci, idx = nextidx) { ci = & cnamecache[idx]; nextidx = ci->next; if (ci->ttl < now) { /* Move this entry to FREE chain */ if (cp) cp->next = ci->next; else /* When CP == NULL, we are at the FIRST cell, move head */ cnamecache_head = ci->next; ci->next = cnamecache_free; cnamecache_free = idx; if (ci->name) free((void*)(ci->name)); ci->name = NULL; if (ci->cname) free((void*)(ci->cname)); ci->cname = NULL; ci = cp; /* Keep PREV pointer.. */ continue; } if ((ci->hash == hhash) && ci->name && CISTREQ(ci->name, host)) { *cnamep = ci->cname; /* if (SS->verboselog) fprintf(SS->verboselog," ... found '%s'\n", ci->cname); */ return 1; } } /* Not in cache.. */ /* DNS LOOKUP! */ { char realname[1024]; time_t realnamettl; struct mxdata mxh[4]; int rc, i; realname[0] = 0; realnamettl = 86400; /* Max cache age.. */ memset( mxh, 0, sizeof(mxh) ); /* if (SS->verboselog) fprintf(SS->verboselog," ... looking it up from DNS\n"); */ rc = getmxrr(SS, host, mxh, 2, 0, realname, sizeof(realname), &realnamettl); /* We did possibly collect up to two address entries, discard them here. */ for (i = 0; i < 2; ++i) { if (mxh[i].host) free(mxh[i].host); if (mxh[i].ai) freeaddrinfo(mxh[i].ai); } realnamettl += now; if (rc == EX_OK) { mxsetsave(SS, host); *cnamep = add_cname_cache(SS, host, *realname ? realname : NULL, realnamettl); /* if (SS->verboselog) fprintf(SS->verboselog," ... returning successfully\n"); */ return 1; } } /* if (SS->verboselog) fprintf(SS->verboselog," ... found nothing, not even from DNS.\n"); */ return 0; } static const char *add_cname_cache(SS, host, cname, ttl) SmtpState *SS; const char *host, *cname; const time_t ttl; { int hhash = namehash(host); int idx, nextidx; struct cnamecache_struct *ci, *cp; if (!cnamecache) cnamecache_init(SS->verboselog); if (!cnamecache) return NULL; /* OOPS! */ /* if (SS->verboselog) fprintf(SS->verboselog,"add_cname_cache(host='%s', cname='%s', ttl= +%d)\n",host,cname?cname:"",(int)(ttl-now)); */ /* See if the table is full... */ if (cnamecache_free < 0) { /* FULL! We must blow a hole somewhere.. */ /* .. anywhere! .. then main-loop finds an empty spot. */ cnamecache[ (rand() >> 12) % CNAMECACHESIZE ].ttl = now -1; } /* Check active list.. */ for ( idx = cnamecache_head, cp = NULL ; idx >= 0 ; cp = ci, idx = nextidx) { ci = & cnamecache[idx]; nextidx = ci->next; if (ci->ttl < now) { /* Move this entry to FREE chain */ if (cp) cp->next = ci->next; else /* When CP == NULL, we are at the FIRST cell, move head */ cnamecache_head = ci->next; ci->next = cnamecache_free; cnamecache_free = idx; if (ci->name) free((void*)(ci->name)); ci->name = NULL; if (ci->cname) free((void*)(ci->cname)); ci->cname = NULL; ci = cp; /* Keep PREV pointer.. */ continue; } if (ci->hash == hhash && ci->name && CISTREQ(ci->name, host)) { if (ci->cname) free((void*)(ci->cname)); ci->cname = NULL; if (cname) ci->cname = strdup(cname); /* if (SS->verboselog)fprintf(SS->verboselog," ... inserted into idx=%d\n",idx); */ return ci->cname; } } /* thru active list */ /* Pick first from the free chain: */ idx = cnamecache_free; ci = & cnamecache[ idx ]; cnamecache_free = ci->next; ci->next = cnamecache_head; cnamecache_head = idx; if (ci->name) { free((void*)(ci->name)); } if (ci->cname) { free((void*)(ci->cname)); } if (cname) ci->cname = strdup(cname); else ci->cname = NULL; ci->name = strdup(host); ci->hash = hhash; ci->ttl = ttl; /* if (SS->verboselog)fprintf(SS->verboselog," ... inserted into idx=%d\n",idx); */ return ci->cname; } static void SMTP_MIB_diag(rc) const int rc; { switch (rc) { case EX_OK: /* OK */ MIBMtaEntry->tasmtp.TaRcptsOk ++; break; case EX_TEMPFAIL: case EX_IOERR: case EX_OSERR: case EX_CANTCREAT: case EX_SOFTWARE: case EX_DEFERALL: /* DEFER */ MIBMtaEntry->tasmtp.TaRcptsRetry ++; break; case EX_NOPERM: case EX_PROTOCOL: case EX_USAGE: case EX_NOUSER: case EX_NOHOST: case EX_UNAVAILABLE: default: /* FAIL */ MIBMtaEntry->tasmtp.TaRcptsFail ++; break; } }