Back to index

courier  0.68.2
courieresmtp.c
Go to the documentation of this file.
00001 /*
00002 ** Copyright 1998 - 2006 Double Precision, Inc.
00003 ** See COPYING for distribution information.
00004 */
00005 
00006 #if    HAVE_CONFIG_H
00007 #include      "config.h"
00008 #endif
00009 #include      "courier.h"
00010 #include      "moduledel.h"
00011 #include      "maxlongsize.h"
00012 #include      "comreadtime.h"
00013 #include      <sys/types.h>
00014 #include      <sys/uio.h>
00015 #if TIME_WITH_SYS_TIME
00016 #include      <sys/time.h>
00017 #include      <time.h>
00018 #else
00019 #if HAVE_SYS_TIME_H
00020 #include      <sys/time.h>
00021 #else
00022 #include      <time.h>
00023 #endif
00024 #endif
00025 #if HAVE_UNISTD_H
00026 #include      <unistd.h>
00027 #endif
00028 #include      <stdio.h>
00029 #include      <stdlib.h>
00030 #include      <string.h>
00031 #include      <signal.h>
00032 #include      <errno.h>
00033 #include      "waitlib/waitlib.h"
00034 #include      "numlib/numlib.h"
00035 #include      "mybuf.h"
00036 #include      "esmtpconfig.h"
00037 #include      "rfc1035/rfc1035.h"
00038 
00039 static time_t esmtpkeepalive;
00040 
00041 struct esmtpchildinfo {
00042        pid_t  pid;          /* Just a marker */
00043        int    cmdpipe;      /* Pipe to the child process */
00044        char   *host;        /* Host the child process is connected to */
00045        int    isbusy;              /* The child process is delivering a message */
00046        char   *pendel;      /* Line that is queued up for this child
00047                             ** process to terminate, so we can start
00048                             ** another process to a different host
00049                             */
00050        time_t termtime;
00051        } ;
00052 
00053 /* A child process is always in one of the following states. */
00054 
00055 #define       ESMTP_NOCHILD(i)     ( (i)->pid < 0)
00056        /* No child process */
00057 
00058 #define       ESMTP_BUSY(i) (!ESMTP_NOCHILD(i) && (i)->cmdpipe >= 0 && (i)->isbusy)
00059        /* Busy deliverin a message */
00060 
00061 #define       ESMTP_IDLE(i) (!ESMTP_NOCHILD(i) && (i)->cmdpipe >= 0 &&!(i)->isbusy)
00062        /* Delivered a message, just idling by */
00063 
00064 #define       ESMTP_TERMINATING(i) (!ESMTP_NOCHILD(i) && (i)->cmdpipe < 0)
00065        /* Being shut down for inactivity, or in order to reuse the proc */
00066 
00067 
00068 static struct esmtpchildinfo *info;
00069 
00070 static void start_child(unsigned, char *, char **);
00071 static void send_child(unsigned, char *, char **);
00072 static void terminated_child(unsigned, unsigned);
00073 
00074 static int completionpipe[2];
00075 static FILE *childresultpipe;
00076 
00077 static struct mybuf  courierdbuf, childbuf;
00078 static void esmtpparent();
00079 extern void esmtpchild(unsigned);
00080 
00081 static int call_mybuf_get(void *p)
00082 {
00083        return (mybuf_get( (struct mybuf *)p ));
00084 }
00085 
00086 static struct rfc1035_ifconf *interfaces=NULL;
00087 
00088 int isloopback(const char *ip)
00089 {
00090        struct rfc1035_ifconf *p;
00091 
00092        for (p=interfaces; p; p=p->next)
00093        {
00094               char buf[RFC1035_NTOABUFSIZE];
00095 
00096               rfc1035_ntoa(&p->ifaddr, buf);
00097               if (strcmp(buf, ip) == 0)
00098                      return (1);
00099        }
00100        return (0);
00101 }
00102 
00103 int main(int argc, char **argv)
00104 {
00105         clog_open_syslog("courieresmtp");
00106        esmtpkeepalive=config_time_esmtpkeepalive();
00107 
00108        if (argc < 2)
00109        {
00110               esmtpparent();
00111        }
00112        else
00113        {
00114               interfaces=rfc1035_ifconf(NULL);
00115               esmtpchild(atoi(argv[1]));
00116        }
00117        return (0);
00118 }
00119 
00120 /*
00121 ** Responses from child processes are of the form "index pid".
00122 ** We only need "index", but we also check "pid" in order to catch a rather
00123 ** rare race condition: child process notifies of a delivery completion,
00124 ** immediately crashes, we reap him before we are notified of the delivery
00125 ** completion, then restart another process in the same slot, then get
00126 ** the original delivery completion message - oops.
00127 */
00128 
00129 static int parse_ack(const char *msg, unsigned *index, pid_t *pid)
00130 {
00131        *index=0;
00132        *pid=0;
00133 
00134        while (*msg != ' ')
00135        {
00136               if (*msg < '0' || *msg > '9')      return (-1);
00137               *index = *index * 10 + (*msg++ - '0');
00138        }
00139        ++msg;
00140        while (*msg)
00141        {
00142               if (*msg < '0' || *msg > '9')      return (-1);
00143               *pid = *pid * 10 + (*msg++ - '0');
00144        }
00145        return (0);
00146 }
00147 
00148 static void esmtpparent()
00149 {
00150 unsigned i;
00151 fd_set fdc, fds;
00152 time_t current_time;
00153 
00154        libmail_changeuidgid(MAILUID, MAILGID);
00155        module_init(&terminated_child);
00156 
00157        if ((info=(struct esmtpchildinfo *)malloc(sizeof(*info)*
00158               module_nchildren)) == 0)
00159               clog_msg_errno();
00160        for (i=0; i<module_nchildren; i++)
00161        {
00162               info[i].pid= -1;
00163               info[i].cmdpipe= -1;
00164               info[i].host=0;
00165               info[i].pendel=0;
00166        }
00167        if (pipe(completionpipe) < 0)
00168               clog_msg_errno();
00169 
00170        if ((childresultpipe=fdopen(completionpipe[0], "r")) == 0)
00171               clog_msg_errno();
00172        FD_ZERO(&fdc);
00173        FD_SET(0, &fdc);
00174        FD_SET(completionpipe[0], &fdc);
00175        mybuf_init(&courierdbuf, 0);
00176        mybuf_init(&childbuf, completionpipe[0]);
00177 
00178        module_blockset();
00179        time(&current_time);
00180 
00181        for (;;)
00182        {
00183        time_t wait_time;
00184        struct timeval       tv;
00185 
00186               wait_time=0;
00187               for (i=0; i<module_nchildren; i++)
00188               {
00189                      if (!ESMTP_IDLE(&info[i]))  continue;
00190                      if (info[i].termtime <= current_time)
00191                      {
00192                             close(info[i].cmdpipe);
00193                             info[i].cmdpipe= -1;
00194                             continue;
00195                      }
00196 
00197                      if (wait_time == 0 || info[i].termtime < wait_time)
00198                             wait_time=info[i].termtime;
00199               }
00200 
00201               if (wait_time)
00202               {
00203                      tv.tv_sec= wait_time - current_time;
00204                      tv.tv_usec=0;
00205               }
00206 
00207               fds=fdc;
00208 
00209               module_blockclr();
00210                 while (select(completionpipe[0]+1, &fds, (fd_set *)0, (fd_set *)0,
00211                                 (wait_time ? &tv:(struct timeval *)0)) < 0)
00212                 {
00213                         if (errno != EINTR)     clog_msg_errno();
00214                 }
00215 
00216               module_blockset();
00217               time(&current_time);
00218 
00219               if (FD_ISSET(completionpipe[0], &fds))
00220               {
00221               char   *line;
00222 
00223                      do
00224                      {
00225                      pid_t  p;
00226 
00227                             line=module_getline( &call_mybuf_get,
00228                                           &childbuf);
00229 
00230                             if (parse_ack(line, &i, &p) ||
00231                                    i >= module_nchildren ||
00232                                    (p == info[i].pid &&
00233                                           !ESMTP_BUSY(&info[i])))
00234                             {
00235                                    clog_msg_start_err();
00236                                    clog_msg_str("INVALID message from child process.");
00237                                    clog_msg_send();
00238                                    _exit(0);
00239                             }
00240                             if (p != info[i].pid)       continue;
00241                             info[i].isbusy=0;
00242                             info[i].termtime=current_time + esmtpkeepalive;
00243                             if (info[i].pendel)
00244                             {
00245                                    free(info[i].pendel);
00246                                    info[i].pendel=0;
00247                             }
00248 
00249                             module_completed(i, module_delids[i]);
00250                      } while (mybuf_more(&childbuf));
00251               }
00252 
00253               if (!FD_ISSET(0, &fds))     continue;
00254 
00255               do
00256               {
00257               char   **cols;
00258               const char *hostp;
00259               size_t hostplen;
00260               time_t misctime;
00261               unsigned j;
00262               char   *line;
00263 
00264                      line=module_getline( &call_mybuf_get, &courierdbuf);
00265                      if (!line)
00266                      {
00267                             module_restore();
00268 
00269                             /*
00270                             ** If all processes are idle, wait for them
00271                             ** to finish normally.  Otherwise, kill
00272                             ** the processes.
00273                             */
00274 
00275                             for (j=0; j<module_nchildren; j++)
00276                                    if (ESMTP_BUSY(&info[j]))
00277                                           break;
00278 
00279                             if (j < module_nchildren)
00280                             {
00281                                    for (j=0; j<module_nchildren; j++)
00282                                           if (info[j].pid > 0)
00283                                                  kill(info[j].pid,
00284                                                         SIGTERM);
00285                             }
00286                             else
00287                             {
00288                             int    waitstat;
00289 
00290                                    for (j=0; j<module_nchildren; j++)
00291                                    {
00292                                           if (info[j].cmdpipe > 0)
00293                                           {
00294                                                  close(info[j].cmdpipe);
00295                                                  info[j].cmdpipe= -1;
00296                                           }
00297                                    }
00298                                    while (wait(&waitstat) != -1 ||
00299                                           errno == EINTR)
00300                                           ;
00301                             }
00302                             _exit(0);
00303                      }
00304 
00305                      cols=module_parsecols(line);
00306 
00307                      if (!cols)    _exit(0);
00308 
00309                      hostp=MODULEDEL_HOST(cols);
00310                      for (hostplen=0; hostp[hostplen] &&
00311                             hostp[hostplen] != '\t'; hostplen++)
00312                             ;
00313 
00314                      for (i=0; i<module_nchildren; i++)
00315                      {
00316                             if (!ESMTP_IDLE(&info[i])) continue;
00317                             if (memcmp(info[i].host, hostp, hostplen) == 0
00318                                    && info[i].host[hostplen] == 0)
00319                                    break;
00320                      }
00321 
00322                      if (i < module_nchildren)   /* Reuse a process */
00323                      {
00324                             send_child(i, line, cols);
00325                             continue;
00326                      }
00327 
00328                      for (i=0; i<module_nchildren; i++)
00329                             if (ESMTP_NOCHILD(&info[i]))       break;
00330 
00331                      if (i < module_nchildren)   /* We can fork */
00332                      {
00333                             start_child(i, line, cols);
00334                             send_child(i, line, cols);
00335                             continue;
00336                      }
00337 
00338                      /*
00339                      ** Find a process that's been idled the longest,
00340                      ** and reuse that one.
00341                      */
00342 
00343                      misctime=0;
00344                      j=0;
00345                      for (i=0; i<module_nchildren; i++)
00346                      {
00347                             if (ESMTP_IDLE(&info[i]) &&
00348                                    (misctime == 0 || misctime >
00349                                           info[i].termtime))
00350                             {
00351                                    j=i;
00352                                    misctime=info[i].termtime;
00353                             }
00354                      }
00355                      if (misctime)
00356                      {
00357                             if (info[j].pendel)
00358                             {
00359                                    clog_msg_start_err();
00360                                    clog_msg_str("INTERNAL ERROR: unexpected scheduled delivery.");
00361                                    clog_msg_send();
00362                                    _exit(1);
00363                             }
00364 
00365                             info[j].pendel=strcpy(
00366                                    courier_malloc(strlen(line)+1),
00367                                    line);
00368                             close(info[j].cmdpipe);
00369                             info[j].cmdpipe= -1;
00370                             continue;
00371                      }
00372 
00373                      /* The ONLY remaining possibility is something in
00374                      ** the TERMINATING stage, without another delivery
00375                      ** already scheduled for that slot.
00376                      */
00377 
00378                      for (i=0; i<module_nchildren; i++)
00379                      {
00380                             if (ESMTP_TERMINATING(&info[i]) &&
00381                                    info[i].pendel == 0)
00382                                    break;
00383                      }
00384 
00385                      if (i < module_nchildren)
00386                      {
00387                             info[i].pendel=strcpy(
00388                                    courier_malloc(strlen(line)+1),
00389                                    line);
00390                             continue;
00391                      }
00392 
00393                      clog_msg_start_err();
00394                      clog_msg_str("INTERNAL ERROR: unexpected delivery.");
00395                      clog_msg_send();
00396                      _exit(1);
00397               } while (mybuf_more(&courierdbuf));
00398        }
00399 }
00400 
00401 static void terminated_child(unsigned idx, unsigned delid)
00402 {
00403        if (ESMTP_TERMINATING(&info[idx]))
00404        {
00405        char   *p;
00406 
00407               if ((p=info[idx].pendel) != 0)
00408               {
00409               char **cols=module_parsecols(info[idx].pendel);
00410 
00411                      /*
00412                      ** Clear to 0 to prevent infinite loop if fork fails
00413                      ** in start_child.
00414                      */
00415                      info[idx].pendel=0;
00416                      start_child(idx, info[idx].pendel, cols);
00417                      info[idx].pendel=p;
00418                      send_child(idx, info[idx].pendel, cols);
00419                      return;
00420               }
00421 
00422               info[idx].pid= -1;
00423               return;
00424        }
00425 
00426        /* Oops, something crashed.  Clean it up */
00427 
00428        clog_msg_start_err();
00429        if (info[idx].pid < 0)
00430        {
00431               clog_msg_str("Unable to fork");
00432        }
00433        else
00434        {
00435               clog_msg_str("Crashed child process ");
00436               clog_msg_uint(info[idx].pid);
00437        }
00438 
00439        if (ESMTP_BUSY(&info[idx]))
00440        {
00441               clog_msg_str(", while delivering to ");
00442               clog_msg_str(info[idx].host);
00443               module_completed(idx, delid);
00444        }
00445        clog_msg_send();
00446        close(info[idx].cmdpipe);
00447        info[idx].cmdpipe= -1;
00448        info[idx].pid= -1;
00449 }
00450 
00451 static void start_child(unsigned i, char *line, char **cols)
00452 {
00453 int    pipebuf[2];
00454 const char *hostp;
00455 size_t hostplen;
00456 pid_t  pid;
00457 
00458        hostp=MODULEDEL_HOST(cols);
00459        for (hostplen=0; hostp[hostplen] &&
00460               hostp[hostplen] != '\t'; hostplen++)
00461               ;
00462 
00463        if (info[i].host)    free(info[i].host);
00464        memcpy(info[i].host=courier_malloc(hostplen+1), hostp, hostplen);
00465        info[i].host[hostplen]=0;
00466 
00467        if (pipe(pipebuf) < 0)      clog_msg_errno();
00468 
00469        pid=module_fork_noblock(0, &i);
00470 
00471        if (pid == 0)
00472        {
00473        unsigned      j;
00474        char   buf[MAXLONGSIZE+1];
00475        char *p;
00476 
00477               dup2(pipebuf[0], 0);
00478               close(pipebuf[0]);
00479               close(pipebuf[1]);
00480               fclose(childresultpipe);
00481               dup2(completionpipe[1], 1);
00482               close(completionpipe[1]);
00483               close(completionpipe[0]);
00484               for (j=0; j<module_nchildren; j++)
00485                      if (info[j].cmdpipe >= 0)
00486                             close(info[j].cmdpipe);
00487 
00488               for (j=0; MODULEDEL_HOST(cols)[j]; j++)
00489                      if (MODULEDEL_HOST(cols)[j] == '\t')
00490                      {
00491                             MODULEDEL_HOST(cols)[j]=0;
00492                             break;
00493                      }
00494 
00495               p=buf+MAXLONGSIZE;
00496               *p=0;
00497               do
00498               {
00499                      * --p = '0' + (i % 10);
00500               } while ( (i=i / 10) != 0);
00501 
00502               execl("courieresmtp", "courieresmtp", p,
00503                      MODULEDEL_HOST(cols), (char *)0);
00504               clog_msg_errno();
00505               _exit(1);
00506        }
00507        info[i].pid=pid;
00508        close (pipebuf[0]);
00509        info[i].cmdpipe=pipebuf[1];
00510 }
00511 
00512 static void send_child(unsigned i, char *line, char **cols)
00513 {
00514 const char *p;
00515 struct iovec  iov[2];
00516 size_t l=strlen(line);
00517 
00518        module_delids[i]=0;
00519 
00520        for (p=MODULEDEL_DELID(cols); *p >= '0' && *p <= '9'; p++)
00521               module_delids[i] = module_delids[i] * 10 + (*p - '0');
00522 
00523        if (info[i].pid < 0) /* Failure in start_child */
00524        {
00525               terminated_child(i, module_delids[i]);
00526               return;
00527        }
00528 
00529         iov[0].iov_base=(caddr_t)line;
00530         iov[0].iov_len=l;
00531        iov[1].iov_base="\n";
00532        iov[1].iov_len=1;
00533 
00534        if (writev(info[i].cmdpipe, iov, 2) != l+1)
00535        {
00536               clog_msg_prerrno();
00537               /* This is usually because the process has terminated,
00538               ** but we haven't cleaned this up yet, but just make sure.
00539               */
00540               kill(info[i].pid, SIGKILL);
00541        }
00542        info[i].isbusy=1;
00543 }