/* * Copyright (c) 1996-2007, OpenFWTK Development Group * All rights reserved. See LICENSE. */ /* * This is independant code branch modified to fit fwtk configuration and * logging style by ArkanoiD, 2001 * * Original copyright info follows. */ /* * smtpfwdd, Obtuse SMTP forward daemon, master process watches spool * directory for files spooled by smtpd. On seeing one, spawns a child * to pick it up and invokes sendmail (or sendmail-like agent) to * deliver it. * * $Id: smtpfwdd.c,v 1.8 2007/09/21 18:33:17 xsaper Exp $ * * Copyright (c) 1996, 1997 Obtuse Systems Corporation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Obtuse Systems * Corporation and its contributors. * 4. Neither the name of the Obtuse Systems Corporation nor the names * of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY OBTUSE SYSTEMS CORPORATION AND * CONTRIBUTORS ``AS IS''AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL OBTUSE SYSTEMS CORPORATION OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ char *obtuse_copyright = "Copyright 1996 - Obtuse Systems Corporation - All rights reserved."; #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef IRIX_BROKEN_INCLUDES /* IRIX 5.3 defines EX_OK (see sysexits.h) as something very strange in unistd.h :-) */ #ifdef EX_OK #undef EX_OK #endif #endif #include #include #include #include #include #include "smtp.h" #include "firewall.h" #include "firewall2.h" #include "fwfunc.h" #ifndef MAIL_AGENT #define MAIL_AGENT PREFIX"/bin/ssmtp" #endif #ifndef SENDMAIL_OITRUE #define SENDMAIL_OITRUE 0 #endif #ifndef MAXCHILDREN #define MAXCHILDREN 10 #endif #ifndef MAXARGS #define MAXARGS 100 #endif #if MAXARGS < 6 %%%MAXARGS must be at least 6 %%% #endif #ifndef POLL_TIME #define POLL_TIME 10 #endif #ifndef SENDMAIL_RETRY #define SENDMAIL_RETRY 1 #endif /* * How long to wait before trying to re-process a file */ #ifndef RETRY_DELAY_TIME #define RETRY_DELAY_TIME 600 #endif /* * How long can a spool file be incomplete before I start * yelling about it? */ #ifndef COMPLETION_WAIT #define COMPLETION_WAIT 86400 #endif char *spooldir = NULL; char *mailagent; char *baddir = NULL; char *badadmin = NULL; char *pidfile = NULL; int children = 0; int maxchildren = MAXCHILDREN; int poll_time = POLL_TIME; int gc_int = COMPLETION_WAIT; int VerboseSyslog = 1; extern int proxy_uid, proxy_gid; #if defined(linux) || defined (SOLARIS) extern void initsetproctitle(int,char**); #endif static char* moduleId ATTR_UNUSED = "$Id: smtpfwdd.c,v 1.8 2007/09/21 18:33:17 xsaper Exp $"; void proxy_update_status(); void proxy_update_operation(char*); void proxy_parse_options(Cfg*,fwparm*); int searchlist(); int proxy_conf_int(Cfg*,const char*,int,int,int); int proxy_conf_groupid(Cfg*); int proxy_conf_userid(Cfg*); char* proxy_conf_string(Cfg*,const char*); Cfg* proxy_conf_hosts(Cfg*,char*,char*); void proxy_setugid(); void sendadmin(char*,char*); /* * zap spoolfile and leave */ void fail_abort(FILE * f, char *fname, int save) { char buf[MAXPATHLEN]; if (locktest_fd(fileno(f)) == 0 && lockun_fd(fileno(f)) != 0) { syslog(LLEV, "fwtksyserr: couldn't unlock spool file %s using lockf after removal (%s)!", fname, strerror(errno)); exit(EX_CONFIG); } fclose(f); if (save) { if (badadmin) sendadmin(fname,badadmin); if (baddir) { snprintf(buf,MAXPATHLEN,"%s/%s",baddir,fname); if (rename(fname,buf)) { syslog(LLEV,"fwtksyserr: cannot move to baddir %.128s, %s",buf, strerror(errno)); exit(EX_CONFIG); } } } if (unlink(fname) != 0) { /* we could be here after a sibling removed the file. If this is * the case, no problem. Otherwise something's wrong with our * setup. */ if (errno != ENOENT) { syslog(LLEV, "fwtksyserr: couldn't remove spool file %s! (%s)", fname, strerror(errno)); exit(EX_CONFIG); } } exit(EX_DATAERR); } /* * leave and unlock spoolfile for retry */ void fail_retry(FILE * f, char *fname) { struct utimbuf utb; /* * first make sure the others x bit is on so we know this file has * been tried. */ if (chmod(fname, 0755) != 0) { syslog(LLEV, "fwtksyserr: couldn't change mode of %s for retry (%s)! abandoning message!", fname, strerror(errno)); fail_abort(f, fname, 1); } /* * touch the file, so we base the time of the next retry on the * current time. */ utb.actime = utb.modtime = time(NULL); if (utime(fname, &utb) != 0) { syslog(LLEV, "fwtksyserr: couldn't set modification time of %s for retry (%s)! abandoning message!", fname, strerror(errno)); fail_abort(f, fname, 1); } if (locktest_fd(fileno(f)) == 0) if (lockun_fd(fileno(f)) != 0) { syslog(LLEV, "fwtksyserr: couldn't unlock spool file %s with lockf for retry (%s)!", fname, strerror(errno)); exit(EX_CONFIG); } fclose(f); exit(EX_TEMPFAIL); } void sendadmin(path, admin) char *path, *admin; { char *faka[MAXARGS]; int i, pid; int w; i = 0; faka[i++] = mailagent; faka[i++] = "-f"; faka[i++] = "root"; faka[i++] = admin; faka[i] = (char *)0; if ((pid = fork()) == 0) { int fd; if ((fd = open(path,O_RDONLY)) < 0) { syslog(LLEV, "fwtksyserr: cannot open %.512s: %s",path, strerror(errno)); exit(1); } close(0); if(dup(fd) < 0) { syslog(LLEV, "fwtksyserr: cannot dup %.512s: %s",path, strerror(errno)); exit(1); } execv(faka[0], faka); syslog(LLEV,"fwtksyserr: cannot exec %.512s: %s",faka[0], strerror(errno)); exit(1); } if (pid == -1) { syslog(LLEV, "fwtksyserr: cannot send bad msg to admin! fork failed: %s", strerror(errno)); return; } wait(&w); #ifdef WTERMSIG if (WIFSIGNALED(w)) w = WTERMSIG(w); else #endif #ifdef WEXITSTATUS if (WIFEXITED(w)) w = WEXITSTATUS(w); else w = 0; #endif if (w != 0) syslog(LLEV,"cannot send bad msg to admin! exit status %d",w); } /* * is spool file fname complete? it's complete if it's mode 750. * This doesn't mean we can lock it, but means it's ok to try. */ int smtp_spoolfile_complete(const char *fname) { struct stat buf; if (stat(fname, &buf) != 0) { /* * If the file doesn't exist then some other child just finished * processing it - not a problem! * Anything else is a serious problem (OS is probably insane). */ if (errno != ENOENT) { syslog(LLEV, "fwtksyserr: can't stat %s (%s) - bye!", fname, strerror(errno)); exit(EX_CONFIG); } return (0); } if (!(S_ISREG(buf.st_mode))) { syslog(LLEV, "fwtksyserr: spool file %s isn't a regular file!", fname); exit(EX_CONFIG); } if ((buf.st_mode & 0110) != 0110) { #ifdef VERBOSE syslog(LOG_DEBUG, "%s not complete now.", fname); #endif if (gc_int && (buf.st_mtime+gc_int <= time(NULL))) { /* * This file has been hanging around incomplete for more than * gc_int, This could be due simply to a (really) slow connection/big * message tying up an smtpd process for a long time, or it could * be due to something like the machine being rebooted killing off * an smtpd process that had started to receive a message before * it was able to finish and mark this file as complete. * * Therefore we better let the appropriate authority know about this * file. */ struct utimbuf utb; utb.actime = utb.modtime = time(NULL); if (utime(fname, &utb) != 0) { syslog(LLEV, "fwtksyserr: utime() failed on spool file %s (%s)", fname, strerror(errno)); } syslog(LLEV, "fwtksyserr: spool file %s has been incomplete since %s. Please investigate.", fname, ctime(&(buf.st_ctime))); } return (0); } if ((buf.st_mode & 0111) == 0111) { /* * if the others execute bit is ticked, then this file had been * previously tried, and we got a temp. sendmail failure. We * don't want to retry too often, so make sure the mtime is more * than RETRY_DELAY_TIME seconds ago. */ if ((time(NULL) - buf.st_mtime) < RETRY_DELAY_TIME) { #ifdef VERBOSE syslog(LOG_DEBUG, "Skipping file %s, delivery last attempted at %s.", fname, ctime(&(buf.st_mtime))); #endif return (0); } else { syslog(LLEV, "Retrying delivery of file %s, last attempted at %s.", fname, ctime(&(buf.st_mtime))); } } return (1); } /* * Generate obituaries for our dead children and keep track of how many * of our kids are still alive. */ void reap_children(void) { while (1) { pid_t pid; int status; pid = waitpid(-1, &status, WNOHANG); if (pid == 0) { return; } else if (pid == -1) { if (errno != ECHILD) { syslog(LLEV, "fwtksyserr: CRITICAL - waitpid failed (%s) - aborting", strerror(errno)); abort(); } return; } children--; if ((!WIFEXITED(status)) || (WEXITSTATUS(status) != 0)) { switch (WEXITSTATUS(status)) { case EX_TEMPFAIL: /* * normal retry case */ syslog(LLEV, "Child process (%d) exited indicating retry", pid); break; case EX_CONFIG: /* * we only exit with this code if we know we've got * configuration problems. If a child exits like this, * we also should exit */ syslog(LLEV, "fwtkcfgerr: child process (%d) failed due to configuration problems. Exiting", pid); exit(EX_CONFIG); break; default: /* * permanent failure or something unusual */ syslog(LLEV, "Child process (%d) failed - no retry", pid); break; } } } } /* * forward a mail message recieved by smtpd contained in file fname. * file is expected to be as follows: * ------------------- * FROM addr * RCPT addr (or SENT addr) * ... * BODY * message body * ... * ------------------- * * The FROM line indicates who sent this message. * The RCPT lines each indicate an intended recipient. * Any SENT lines indicate recipients that this message has already been * delivered to (these only happen if a message is partially processed * before sendmail signals a temporary failure). * * Everything before "BODY" will have been sanitized by smtpd. It's up * to us to do anything we want to the message body, as smtpd takes that * in verbatim. * * A message is processed MAXARGS recipients at a time. As each batch * is processed, the RCPT verbs for the batch are turned into SENT verbs. * This prevents the message from being sent to the same people more than * once if a subsequent batch fails with a retry-able error. It also * limits the number of people who will get the message twice if the system * crashes at a bad moment. * * We call MAIL_AGENT -f fromaddr toaddr toaddr toaddr ... * to forward mail. I.E. MAIL_AGENT should be sendmail or something * else that delivers mail and will take those arguments a-la sendmail. * For filtering message bodies for unwanted things, one can call a filter * program which checks the message body as or before passing it through * to a delivery program. MAIL_AGENT needs to exit with sendmail-like * exit codes. * * We exit with * EX_TEMPFAIL - Retry later for whatever reason * EX_CONFIG - Something's horribly wrong, and our parent should exit * EX_OK - We have removed the spoolfile after success * anything else - We have removed the spoolfile after failure (no retry) */ void forward(char *fname) { FILE *f = NULL; char line[SMTP_MAX_CMD_LINE]; char *c, *from; int sentout; off_t body; struct smtp_victim *victim, *victims; int status, pid, pidw, i, rstart; proxy_update_operation("FORWARDING"); victim = (struct smtp_victim *) malloc(sizeof(struct smtp_victim)); victim->name = NULL; victim->next = NULL; victims = victim; if (victims == NULL) { syslog(LLEV, "fwtksyserr: malloc failed, aborting delivery of %s", fname); fail_abort(f, fname, 1); } /* * Step 1 - open the file for updating. exit silently if it fails, * since that is most likely due to one of our siblings having dealt * with it and removed it. */ f = fopen(fname, "r+"); if (f == NULL) { syslog(LLEV, "fwtksyserr: couldn't open spool file %s! (%s)", fname, strerror(errno)); exit(EX_TEMPFAIL); } /* * Step 2 - try to get a non-blocking exclusive lock on the file. * Just exit (relatively) silently if it fails. This happens for a number * of reasons: * * - one of our siblings has already got the file * - smtpd isn't done with it yet */ if (locktest_fd(fileno(f)) != 0) { exit(EX_TEMPFAIL); } /* * Step 3 - do a basic sanity test on the file * * We do the test using the file's name instead of the just opened * file descriptor to avoid the following race condition: * * - we and one of our siblings both open the file successfully above * - we're suspended while our sibling completely processes the file * (including unlinking the file). * - we finally get around to locking the file. Since our sibling is * done, the lock attempt works. * - we do the sanity test using the file descriptor (which is associated * with a file that no longer has a name). * - we process the file again. * * By doing the following sanity check using the file's name instead * of the file descriptor, we avoid the race because, if the above sequence * of events occurs, the file won't exist when we do the sanity test * (which will cause the sanity test to fail). * */ if (!smtp_spoolfile_complete(fname)) { /* * smtpd hasn't finished with this one yet or the file is gone. * Bail out. If the file still exists, it will get tried again later. */ /* If we locked the file (above) and have discovered it isn't complete, * be sure to unlock it. Sadly, some OS's seem to think that locks * can stay after a process goes away. Sigh. -BB */ if (locktest_fd(fileno(f)) == 0) if (lockun_fd(fileno(f)) != 0) { syslog(LLEV, "fwtksyserr: couldn't unlock incomplete spool file %s (%s)!", fname, strerror(errno)); exit(EX_CONFIG); } exit(EX_TEMPFAIL); } /* * parse file */ if (fgets(line, sizeof(line), f) == NULL) { syslog(LLEV, "fwtksyserr: read failed on spool file %s (%s) - message not forwarded", fname, strerror(errno)); fail_abort(f, fname, 1); } line[SMTP_MAX_CMD_LINE - 1] = '\0'; if (strncmp(line, "FROM ", 5) != 0) { syslog(LLEV, "fwtksyserr: file %s corrupt (no FROM line) - message not forwarded", fname); fail_abort(f, fname, 1); } c = strchr(line, '\n'); if (c == NULL) { syslog(LLEV, "fwtksyserr: FROM line too long in %s - message not forwarded", fname); fail_abort(f, fname, 1); } *c = '\0'; from = strdup(line + 5); if (from == NULL) { syslog(LLEV, "fwtksyserr: malloc failed - retrying later"); fail_retry(f, fname); } #if STRIP_QUOTES /* remove <> quotes from sender, as some MTA's (like qmail) don't deal * with it well. */ if ((from[0]=='<') && (from[strlen(from)-1]=='>')) { from[strlen(from)-1]='\0'; from++; } #endif for (;;) { long vloc; vloc = ftell(f); if (fgets(line, sizeof(line), f) == NULL) { syslog(LLEV, "fwtksyserr: read failed on spool file %s (%s) - message not forwarded", fname, strerror(errno)); fail_abort(f, fname, 1); } line[SMTP_MAX_CMD_LINE - 1] = '\0'; if (strncmp(line, "SENT ", 5) == 0) { /* * we already sent it to this victim on a previous attempt. */ continue; } if (strncmp(line, "RCPT ", 5) != 0) { break; } /* * we have a RCPT */ if (victim->name != NULL) { victim->next = (struct smtp_victim *) malloc(sizeof(struct smtp_victim)); victim = victim->next; victim->name = NULL; victim->next = NULL; } c = strchr(line, '\n'); if (c == NULL) { syslog(LLEV, "securityalert: RCPT line too long in %s - message not forwarded", fname); fail_abort(f, fname, 1); } *c = '\0'; if ((victim->name = strdup(line + 5)) == NULL) { syslog(LLEV, "fwtksyserr: malloc failed - retrying later"); fail_retry(f, fname); } #if STRIP_QUOTES /* again, strip <> if present in case MTA can't handle it */ if ((victim->name[0]=='<') && (victim->name[strlen(victim->name)-1]=='>')) { victim->name[strlen(victim->name)-1]='\0'; (victim->name)++; } #endif victim->location = vloc; } c = strchr(line, '\n'); if (c == NULL) { syslog(LLEV, "securityalert: BODY line too long in %s - message not forwarded", fname); fail_abort(f, fname, 1); } *c = '\0'; if (strcmp(line, "BODY") != 0) { syslog(LLEV, "fwtksyserr: file %s corrupt (no BODY after RCPT) - message not forwarded", fname); fail_abort(f, fname, 1); } /* * We're now at the start of our message body with the list of * recipients in "victims" and the sender in "from". fire off our * mail program to send it out */ body = ftell(f); victim = victims; sentout = 0; if (!VerboseSyslog) { accumlog(LOG_INFO, "Forwading %s", fname); } while (victim != NULL) { struct smtp_victim *sv = victim; char *av[MAXARGS]; i=0; av[i++] = mailagent; #if SENDMAIL_OITRUE if (strstr(mailagent, "sendmail") != 0) { /* * Sendmail has a feature/bug that it will by default * stop on a line with just a '.'. We need to * tell sendmail to ignore a line that contains just a '.' * otherwise it decides that it's the end of the message. * We may not need this if "sendmail" isn't really sendmail. * (for example, qmail's phony "sendmail" that calls qmail-inject * doesn't need this). */ av[i++] = "-oiTrue"; } #endif av[i++] = "-f"; av[i++] = from; rstart = i; while (i < MAXARGS - 2) { if (VerboseSyslog) { syslog(LLEV, "forwarding to recipient %s", victim->name); } else { accumlog(LLEV, " to=%s", victim->name); } av[i++] = victim->name; victim = victim->next; if (victim == NULL) { break; } } av[i] = NULL; if ((pid = fork()) == 0) { int xerrno; close(0); close(1); close(2); if (dup(fileno(f)) != 0) { syslog(LLEV, "fwtksyserr: couldn't dup open %s to stdin (%s)", fname, strerror(errno)); exit(EX_OSERR); } fclose(f); if (lseek(0, body, SEEK_SET) < 0) { syslog(LLEV, "fwtksyserr: can't lseek spool file %s! (%s)", fname, strerror(errno)); exit(EX_OSERR); } execv(av[0], av); xerrno = errno; errno = xerrno; if (errno == ENOMEM) { syslog(LLEV, "fwtksyserr: exec of %s failed (%s) - retrying it later", av[0] , strerror(errno)); fail_retry(fdopen(0, "r+"), fname); } else { syslog(LLEV, "fwtksyserr: exec of %s failed! (%s)", av[0], strerror(errno)); exit(EX_CONFIG); } } else if (pid < 0) { syslog(LLEV, "fwtksyserr: fork failed - retrying message later"); fail_retry(f, fname); } do { pidw = wait(&status); } while ((pidw != pid) && (pidw != -1)); if ((!WIFEXITED(status)) || (WEXITSTATUS(status) != 0)) { /* * Sendmail go boom. boo hoo. */ switch (WEXITSTATUS(status)) { case EX_IOERR: case EX_UNAVAILABLE: case EX_TEMPFAIL: syslog(LLEV, "Temporary sendmail failure (status %d), will retry later", status); fail_retry(f, fname); break; #ifdef EX_NOUSER case EX_NOUSER: syslog(LLEV, "Sendmail exited indicating one or more local recipients did not exist (no retry)"); fail_abort(f, fname, 0); #endif case EX_CONFIG: syslog(LLEV, "fwtkcfgerr: sendmail configuration error!"); exit(EX_CONFIG); default: syslog(LLEV, "fwtksyserr: sendmail exited abnormally (status %d) - message not forwarded.", status); fail_abort(f, fname, 1); } } /* * yippee. so far so good */ sentout += (i - rstart); if (victim != NULL) { /* * We got more, and have to do it all again. Before we do, * tag the existing recipients who got sent out by changing * RCPT to SENT in the spoolfile. In this way we avoid * delivering this again if we have a temporary sendmail * failure and retry after having sent it out to part of the * recipients successfully. */ for (i = rstart; i < (MAXARGS - 2); i++) { if (fseek(f, sv->location, SEEK_SET) != 0) { syslog(LLEV, "fwtksyserr: couldn't fseek %s (%s)\n - message abandoned after delivery to first %d recipients", fname, strerror(errno), sentout); fail_abort(f, fname, 1); } fprintf(f, "SENT"); fflush(f); sv = sv->next; if (sv == NULL) { break; } } if (fseek(f, body, SEEK_SET) != 0) { syslog(LLEV, "fwtksyserr: couldn't fseek %s (%s)\n - message abandoned after delivery to first %d recipients", fname, strerror(errno), sentout); fail_abort(f, fname, 1); } } } /* * All seems to have worked */ syslog(LLEV,"delivered file=%.512s pid=%d code=%d recipients=%d",fname, pid,WEXITSTATUS(status),sentout); if (unlink(fname) != 0) { syslog(LLEV, "fwtksyserr: couldn't remove spool file %s! (%s)", fname, strerror(errno)); exit(EX_CONFIG); } if (locktest_fd(fileno(f)) == 0) if (lockun_fd(fileno(f)) != 0) { syslog(LLEV, "fwtksyserr: couldn't unlock spool file %s using lockf after removal (%s)!", fname, strerror(errno)); exit(EX_CONFIG); } fclose(f); exit(EX_OK); } /* * The brains of this operation */ int main(int argc, char **argv) { int pid; Cfg *cfp; #ifndef LOG_DAEMON openlog(basename(argv[0]),LOG_PID); #else openlog(basename(argv[0]),LOG_PID|LOG_NDELAY,LFAC); #endif #if defined(linux) || defined (SOLARIS) initsetproctitle(argc,argv); #endif if ((cfp = cfg_read(basename(argv[0]))) == (Cfg *)-1) { fprintf(stderr,"no configuration found for %s",basename(argv[0])); exit(EX_CONFIG); } if (!(mailagent = proxy_conf_string(cfp,"sendmail"))) mailagent=MAIL_AGENT; spooldir = proxy_conf_string(cfp,"directory"); maxchildren = proxy_conf_int(cfp,"maxchildren",1,1000,10); poll_time = proxy_conf_int(cfp,"wakeup",1,1000,30); baddir = proxy_conf_string(cfp,"baddir"); badadmin = proxy_conf_string(cfp,"badadmin"); pidfile = proxy_conf_string(cfp,"pidfile"); proxy_conf_userid(cfp); proxy_conf_groupid(cfp); if (proxy_uid == 0) { fprintf(stderr, "Sorry, I don't want to run as root! It's a bad idea!\n"); exit(EX_CONFIG); } if (proxy_gid == 0) { fprintf(stderr, "Sorry, I don't want to run as group 0. It's a bad idea!\n"); exit(EX_CONFIG); } proxy_setugid(); if (spooldir == NULL) { syslog(LLEV,"fwtkcfgerr: no directory specified"); fprintf(stderr,"fwtkcfgerr: no directory specified"); exit(EX_CONFIG); } /* * OK, we're now running as a non-root user and group, hopefully one * that can run sendmail -f and have it work. */ if (chdir(spooldir) != 0) { perror("Chdir failed!"); fprintf(stderr, "Can't change directory to spooldir %s\n", spooldir); exit(EX_CONFIG); } if ((pid = fork()) != 0) { if (pid < 0) { syslog(LLEV, "fwtksyserr: fork failed (%s) while trying to become a daemon", strerror(errno)); } exit(EX_OSERR); } else { DIR *dir; /* * Try to get a semaphore file. Prevents multiple instances of * smtpfwdd from running at once on the same spool directory. */ { int lfd,pfd; char tbuf[100]; lfd = open(".smtpfwdd.lock", O_WRONLY | O_CREAT, 0644); if (lfd < 0) { syslog(LLEV, "fwtksyserr: can't open semaphore file in \"%s\" (%s) - bye!", spooldir, strerror(errno)); exit(EX_CONFIG); } if (locktest_fd(lfd) != 0) { syslog(LLEV, "fwtkcfgerr: I'm already running in %s", spooldir); exit(EX_CONFIG); } snprintf(tbuf, 99, "%7d\n", (int) getpid()); if (pidfile) { if ((pfd = open(pidfile, O_WRONLY | O_CREAT, 0644)) < 0) { syslog(LLEV, "fwtksyserr: can't open pid file \"%s\" (%s)",pidfile, strerror(errno)); exit(EX_CONFIG); } else write(pfd, tbuf, strlen(tbuf)); } /* * Done - put our pid in the semaphore file. * Note that we keep the semaphore file open but forget the file's fd. */ write(lfd, tbuf, strlen(tbuf)); } setsid(); signal(SIGCHLD, SIG_DFL); dir = opendir("."); if (dir == NULL) { syslog(LLEV, "fwtksyserr: can't open directory %s (%s) - exiting", spooldir, strerror(errno)); exit(EX_CONFIG); } for (;;) { struct dirent *direct; int cpid; proxy_update_operation("SCAN"); while ((direct = readdir(dir)) != NULL) { int groks = 0; reap_children(); while (children >= maxchildren) { groks++; if (groks == 60) { syslog(LLEV, "fwtksyserr: too many children for last minute! Please investigate!"); groks = 0; } sleep(1); reap_children(); } if (!VerboseSyslog) { /* should be empty - but just in case */ accumlog(LOG_INFO, 0); } /* * If we have a file with an appropriate name and it is * complete then create a child which will try to forward the * message. */ if (strncmp(direct->d_name, "smtpd", 5) == 0 && smtp_spoolfile_complete(direct->d_name)) { children++; if ((cpid = fork()) == 0) { forward(direct->d_name); /* * NOTREACHED */ syslog(LLEV, "fwtksyserr: returned from forward()! SHOULD NOT HAPPEN!"); exit(EX_CONFIG); } if (cpid < 0) { syslog(LLEV, "fwtksyserr: fork failed! (%s)", strerror(errno)); children--; } } } rewinddir(dir); proxy_update_operation("IDLE"); sleep(poll_time); } } }