Logo Search packages:      
Sourcecode: zmailer version File versions  Download package

sfmail.c

/*
 *    Copyright 1988 by Rayan S. Zachariassen, all rights reserved.
 *    This will be free software, but only when it is finished.
 *
 *    Some modifications  by
 *    Matti Aarnio <mea@nic.funet.fi>  (copyright) 1992-2003
 *
 *    SFIO version by Matti Aarnio, copyright 1999-2003
 */

/*LINTLIBRARY*/

#include "hostenv.h"

#include <stdio.h>
#ifndef FILE /* Some systems don't have this as a MACRO.. */
# define FILE FILE
#endif
#include <sfio.h>

#include <errno.h>
#include <sys/param.h>
#include <sys/stat.h>
#ifdef HAVE_FCNTL_H
# include <fcntl.h>
#endif
#include <sys/file.h>
#include <sys/socket.h>
#ifdef HAVE_SYS_UN_H
#include <sys/un.h>
#endif

#include "mail.h"
#include "zmsignal.h"

#include "listutils.h"
#include "libc.h"
#include "libsh.h"

/*
 * Standard routines that may be used by any program to submit mail.
 *
 * This file should be part of the standard C library on your system.
 * 
 * The proper use of these routines is as follows:
 *
 *    ...
 *      mail_priority = 0;
 *    Sfio_t *msp = sfmail_open(type);
 *    if (msp != NULL) {
 *    ... output the mail message to msp ...
 *    } else
 *          ... error handling for not even being able to open the file ...
 *    if (oops)
 *          (void) sfmail_abort(msp);
 *    else if (sfmail_close(msp) == EOF)
 *          ... error handling if something went wrong ...
 *    ...
 *
 * Note that the return value from these routines corresponds to the
 * return values of sfopen() and sfclose() respectively. The routines
 * are single-threaded due to the need to remember a filename.
 *
 * Note also that the mail_alloc() routine is called instead of malloc()
 * directly, allowing applications that make many calls to these routines
 * during the process lifetime to provide an alternate byte allocator
 * that will not cause them to run out of data space.  Similarly, the
 * mail_host() routine is expected to return a unique host identification.
 *
 * Some simple signal handling is advisable too.
 */


/* array of message file name associated with a file descriptor */
static char **mail_file = NULL;
static char **mail_type = NULL;
static int mail_nfiles  = 0;
extern const char *postoffice;      /* may be extern or local */

static int eqrename __((const char *, const char *));
static int
eqrename(from, to)
      const char *from, *to;
{
#ifdef      HAVE_RENAME
      while (rename(from, to) < 0) {
        int serrno = errno;
        if (errno == EBUSY || errno == EINTR) {
          /* Solaris says EBUSY, we clean up.. */
          while (unlink(to) < 0) {
            if (errno == EBUSY || errno == EINTR)
            continue; /* Crazy Solaris 2.x (including 2.6!) */
            /* Actually Solaris reports only EBUSY, but .. */
            break;
          }
          /* Solaris says EBUSY, we retry.. */
          continue;
        }
        errno = serrno;
        return -1;
      }

#else /* !HAVE_RENAME */
      
      if ((unlink(to) < 0 && errno != ENOENT) ||
          (link(from, to) < 0)) {
        return -1;
      }

      if (unlink(from) < 0) {
        int serrno = errno;
        unlink(to);
        errno = serrno;
        return -1;
      }
#endif      /* !HAVE_RENAME */

      return 0;
}



/*
  Define sending mail priority.
*/

extern int mail_priority;

/*
 * Makes a temporary file under the postoffice, based on a file name
 * template.  The last '%' character of the file name passed will be
 * overwritten with different suffix characters until the open()
 * succeeds or we have exhausted the search space.  Note: a single
 * process cannot hold more than number-of-suffix-characters message
 * files at once.
 */

Sfio_t *
_sfmail_fopen(filenamep)
      char **filenamep;
{
      const char *suffix, *post;
      char *path, *cp;
      Sfio_t *fp;
      int fd, eno;

      if (postoffice == NULL &&
          (postoffice = getzenv("POSTOFFICE")) == NULL)
        postoffice = POSTOFFICE;

      path = mail_alloc(strlen(postoffice)+strlen(*filenamep)+2);
      sprintf(path, "%s/%s", postoffice, *filenamep);
      for (cp = *filenamep; *cp != '\0' && *cp != '%'; ++cp)
            continue;
      if (*cp == '%') {
            post = cp + 1;
            cp = (cp - *filenamep) + strlen(postoffice) + 1 + path;
      } else
            post = cp = NULL;
      fp = NULL;
      eno = 0;
      for (suffix = SUFFIXCHARS; *suffix != 0; ++suffix) {
            if (cp == NULL)
                  sleep(2); /* hope something happens meanwhile */
            else if (*suffix != ' ') {
                  *cp = *suffix;
                  strcpy(cp+1, post);
            } else
                  strcpy(cp, post);
            fd = open(path, O_CREAT|O_EXCL|O_RDWR, 0600);
            if (fd >= 0) {
                  fcntl(fd, F_SETFD,
                        fcntl(fd, F_GETFD, 0) | FD_CLOEXEC);
                  fp = sfnew(NULL, NULL, 8192, fd,
                           SF_READ|SF_WRITE|SF_WHOLE);
                  if (fp) {
                    sfsetbuf(fp, NULL, 8192);
                    mail_free(*filenamep);
                    *filenamep = path;
                  }
                  return fp;
            }
            eno = errno;
      }
      mail_free(path);
      errno = eno;
      return fp;
}

/*
 * Link from-file to a file given by the to-file template.
 * The last '%' character of the to-file name passed will be overwritten
 * with different suffix characters until the link() succeeds or we have
 * exhausted the search space.
 */

int
sfmail_link(from, tonamep)
      const char *from;
      char **tonamep;
{
      char *path, *cp;
      const char *suffix, *post;
      int eno;

      if (postoffice == NULL &&
          (postoffice = getzenv("POSTOFFICE")) == NULL)
            postoffice = POSTOFFICE;

      path = mail_alloc(strlen(postoffice)+strlen(*tonamep)+2);
      sprintf(path, "%s/%s", postoffice, *tonamep);
      for (cp = *tonamep; *cp != '\0' && *cp != '%'; ++cp)
            continue;
      if (*cp == '%') {
            post = cp + 1;
            cp = (cp - *tonamep) + strlen(postoffice) + 1 + path;
      } else
            post = cp = NULL;
      eno = 0;
      for (suffix = SUFFIXCHARS; *suffix != 0; ++suffix) {
            if (cp == NULL)
                  sleep(2); /* hope something happens meanwhile */
            else if (*suffix != ' ') {
                  *cp = *suffix;
                  strcpy(cp+1, post);
            } else
                  strcpy(cp, post);
            if (eqrename(from, path) >= 0) {
                  mail_free(*tonamep);
                  *tonamep = path;
                  return 0;
            }
            eno = errno;
      }
      mail_free(path);
      errno = eno;
      return -1;
}

/*
 * Open a message file of the specified type and initialize generic
 * envelope information (i.e. the file position on return may not be 0).
 */

Sfio_t *
sfmail_open(type)
      const char *type;
{
      char *scratch;
      const char *cp;
      Sfio_t *fp;
      int eno, fn;
      struct stat stbuf;
      char namebuf[BUFSIZ];
      static const char *host = NULL;
      
      /* Create a file, any file, in the PUBLIC directory */

      if (host == NULL)
            host = mail_host();
      cp = (host == NULL) ? "I" : host ;
      scratch = mail_alloc(strlen(PUBLICDIR)+strlen(cp)+3+1+10);

      sprintf(scratch, "%s/%.19s:%d%%", PUBLICDIR, cp, (int)getpid());

      fp = _sfmail_fopen(&scratch);
      if (fp == NULL) {
        eno = errno;
        fprintf(stderr, "sfmail_fopen(\"%s\", \"w+\"): errno %d\n",
              scratch, errno);
        mail_free(scratch);
        errno = eno;
        return NULL;
      }

      /* Determine a unique id associated with the file
         (inode number) */

      fn = sffileno(fp);
      if (fstat(fn, &stbuf) < 0) {
        eno = errno;
        fprintf(stderr, "fstat(\"%s\"): errno %d\n", scratch, errno);
        mail_free(scratch);
        errno = eno;
        return NULL;
      }

      /* Rename the scratch file to the message file name
         based on the id */

      if (type == NULL)
            type = MSG_RFC822;

#ifdef notype
      type = "";
#endif

      /* Extend when need! */
      
      if (fn >= mail_nfiles) {
        int nfile = fn+1;
        if (mail_file == NULL) {
          mail_file = (char**)mail_alloc((u_int)(sizeof(char*) *
                                       nfile));
          mail_type = (char**)mail_alloc((u_int)(sizeof(char*) *
                                       nfile));
        } else {
          mail_file = (char**)mail_realloc((char*)mail_file,
                                   (sizeof(char*) * nfile));
          mail_type = (char**)mail_realloc((char*)mail_type,
                                   (sizeof(char*) * nfile));
        }
        while (mail_nfiles < nfile) {
          mail_file[mail_nfiles] = NULL;
          mail_type[mail_nfiles] = NULL;
          ++mail_nfiles;
        }
      }

      mail_file[fn] = scratch;
      mail_type[fn] = strdup(type);

      /* Grab preferences from the environment to initialize
         the envelope */

#ifndef     notype
      if (type != NULL && *type != '\0')
        sfprintf(fp, "type %s\n", type);
#endif
      cp = getenv("FULLNAME");
      if (cp != NULL)
        sfprintf(fp, "fullname %s\n",
               fullname(cp, namebuf, sizeof namebuf, (char *)NULL));
      cp = getenv("PRETTYLOGIN");
      if (cp != NULL)
        sfprintf(fp, "loginname %s\n", cp);

      /*
       * If the postoffice lives elsewhere, put our hostname
       * in the Received-from header, to aid in message tracing.
       */
#if 0
      host = whathost(scratch);
      if (getzenv("MAILSERVER") != NULL ||
          (host != NULL && strcmp(host,"localhost") != 0))
#endif
        if (getmyhostname(namebuf, sizeof namebuf) == 0) {
          cp = getenv("LOGNAME");
          if (cp == NULL)
            cp = getenv("USERNAME");
          if (cp == NULL)
            cp = getenv("USER");
          if (cp == NULL)
            cp = "\"??\"";
          sfprintf(fp, "rcvdfrom STDIN (%s@%s)\n", cp, namebuf);
        }
      return fp;
}


/*
 * Return currently open spool file name
 */

char *
sfmail_fname(fp)
      Sfio_t *fp;
{
      int fd = sffileno(fp);

      if (fd < 0 || fd >= mail_nfiles)
        return NULL;

      return mail_file[fd];
}


/*
 * Abort the message file composition on the indicated stream.
 */

int
sfmail_abort(fp)
      Sfio_t *fp;
{
      register char *message;
      int r, fn;

      if (fp == NULL) {
            errno = EBADF;
            return -1;
      }
      fn = sffileno(fp);
      if (fn >= mail_nfiles)
            abort(); /* Usage error -- no such fileno in our use! */
      if (mail_type[ fn ]) mail_free(mail_type[fn]);
      mail_type[ fn ] = NULL;
      message = mail_file[ fn ];
      if (message == NULL) {
            errno = ENOENT;
            return -1;
      }
      sfclose(fp);
      mail_file[ fn ] = NULL;
      r = unlink(message);
      mail_free(message);
      return r;
}

/*
 * Close the message file on the indicated stream and submit it to
 * the mailer.
 */

int sfmail_close(fp)
      Sfio_t *fp;
{
      return _sfmail_close_(fp, NULL, NULL);
}


static int routersubdirhash = -1;

static int
_sfmail_close__(fp,inop, mtimep, async)
      Sfio_t *fp;
      long *inop;
      time_t *mtimep;
      int async;
{
      char *message, *nmessage, *type, *ftype;
      const char *routerdir;
      const char *inputdirs;
      char *s = NULL;
      struct stat stb;
      int disable_routerdirhash = 0;
      int fn;
      long ino;
      char subdirhash[6];

      if (postoffice == NULL) {
            fprintf(stderr, "mail_close: called out of order!\n");
            errno = EINVAL;
            return -1;
      }
      if (fp == NULL) {
            errno = EBADF;
            return -1;
      }
      fn = sffileno(fp);
      if (fn >= mail_nfiles)
            abort(); /* Usage error -- no such fileno in our use! */
      message = mail_file[fn];
      if (message == NULL) {
            errno = ENOENT;
            return -1;
      }
      ftype = type = mail_type[fn];
      if (type == NULL) {
            type = "";
      }

      mail_type[fn] = NULL;
      mail_file[fn] = NULL;

      if (fstat(fn, &stb)) {
        /* XXX: error processing */
      }
      ino = stb.st_ino;

      /*
       * *** NFS users beware ***
       * the fsync() between fflush() and fclose() may be mandatory
       * on NFS mounted postoffices if you want to guarantee not losing
       * data without being told about it.
       */


      while (sfsync(fp) != 0) {
        if (errno == EINTR || errno == EAGAIN)
          continue;
        mail_free(message);
        if (ftype) mail_free(ftype);
        errno = EIO;
        return -1;
      }
#ifdef HAVE_FSYNC
      if (!async) {
        while (fsync(fn) < 0) {
          if (errno == EINTR || errno == EAGAIN)
            continue;
          if (ftype) mail_free(ftype);
          mail_free(message);
          errno = EIO;
          return -1;
        }
      }
#endif
      if (sfclose(fp) == EOF) {
        mail_free(message);
        if (ftype) mail_free(ftype);
        errno = EIO;
        return -1;
      }

      inputdirs = getzenv("INPUTDIRS");

      routerdir = NULL;
      nmessage  = NULL;
      s         = NULL;

      if (inputdirs) {
        int i = mail_priority;
        const char *rd = inputdirs;
        const char *ord = NULL;
#ifdef HAVE_ALLOCA
        nmessage = alloca(strlen(postoffice)+strlen(inputdirs)+3+
                      9+4+strlen(type));
#else
        nmessage = mail_realloc(nmessage,
                          strlen(postoffice)+strlen(inputdirs)+3+
                          9+4+strlen(type));
#endif
        /* There are some defined!   A ":" separated list of strings */

        /* mail_priority == 1 pics first, 2 pics second, ..
           if segments run out, last one is kept at  rd     */

        while (i-- && (s = strchr(rd,':'))) {
          *s = 0;
          sprintf(nmessage, "%s/%s", postoffice, rd);
          *s = ':';
          if ((stat(nmessage,&stb) < 0) || !S_ISDIR(stb.st_mode)) {
            rd = s+1;
            continue;   /* Not ok -- not a dir, for example */
          }
          ord = rd;
          rd = s+1;
        }
        
        /* Here we are when there is only one entry in the inputdirs:*/
        if (s == NULL && i > 0 && *rd != 0) {
          if (s) *s = 0;
          sprintf(nmessage, "%s/%s", postoffice, rd);
          if (s) *s = ':';
          /* Is it a valid directory ? */
          if ((stat(nmessage,&stb) == 0) && S_ISDIR(stb.st_mode))
            ord = rd; /* IT IS ! */
        }
        routerdir = ord;
        if (ord) disable_routerdirhash = 1;
      }


      if (!routerdir && !mail_priority)
        routerdir = ROUTERDIR;

      if (mail_priority && !routerdir) {
        /* We are asked to place the mail somewhere else */
        const char *routerdirs = getzenv("ROUTERDIRS");
        routerdir = ROUTERDIR;
        if (routerdirs) {
          int i = mail_priority;
          const char *rd = routerdirs;
          const char *ord = routerdir;
#ifdef HAVE_ALLOCA
          nmessage = alloca(strlen(postoffice)+strlen(routerdirs)+
                        3+9+4+strlen(type));
#else
          nmessage = mail_realloc(nmessage,
                            strlen(postoffice)+strlen(routerdirs)+
                            3+9+4+strlen(type));
#endif
          /* There are some defined!
             A ":" separated list of strings */

          /* mail_priority == 1 pics first, 2 pics second, ..
             if segments run out, last one is kept at  rd     */

          while (i-- && (s = strchr(rd,':'))) {
            *s = 0;
            sprintf(nmessage, "%s/%s", postoffice, rd);
            *s = ':';
            if ((stat(nmessage,&stb) < 0) || !S_ISDIR(stb.st_mode)) {
            rd = s+1;
            continue;   /* Not ok -- not a dir, for example */
            }
            ord = rd;
            rd = s+1;
          }

          /* Here we are when there is only one entry in
             the routerdirs: */
          if (s == NULL && i > 0 && *rd != 0) {
            if (s) *s = 0;
            sprintf(nmessage, "%s/%s", postoffice, rd);
            if (s) *s = ':';
            /* Is it a valid directory ? */
            if ((stat(nmessage,&stb) == 0) && S_ISDIR(stb.st_mode))
            ord = rd; /* IT IS ! */
          }
          routerdir = ord;
        }
      }

      if (routersubdirhash < 0) {
        const char *ss;
        if (disable_routerdirhash)
          ss = getzenv("INPUTDIRHASH");
        else
          ss = getzenv("ROUTERDIRHASH");

        if (ss && *ss == '1')
          routersubdirhash = 1;
        else
          routersubdirhash = 0;
      }

      if (routersubdirhash > 0) {
        sprintf(subdirhash, "%c/", (int)('A' + (ino % 26)));
      } else
        *subdirhash = 0;

      /* Assert postoffice != NULL */
      if (nmessage == NULL) {
#ifdef HAVE_ALLOCA
        nmessage = alloca(strlen(postoffice)+strlen(routerdir)+
                      9+4+2+1+strlen(type));
#else
        nmessage = mail_realloc(nmessage,
                          strlen(postoffice)+strlen(routerdir)+
                          9+4+2+1+strlen(type));
#endif
        sprintf(nmessage, "%s/%s/%s%ld%s", postoffice, routerdir,
              subdirhash, ino ,type);
      } else {
        s = strchr(routerdir,':');
        if (s) *s = 0;
        sprintf(nmessage, "%s/%s/%s%ld%s", postoffice, routerdir,
              subdirhash, ino, type);
        if (s) *s = ':';
      }

      /* For performance reasons we optimize heavily.. */

      if (eqrename(message,nmessage) != 0) {
        int eno = errno;
        fprintf(stderr, "link(\"%s\", \"%s\"): errno %d\n",
              message, nmessage, errno);
        if (ftype) mail_free(ftype);
        mail_free(message);
        mail_free(nmessage);
        errno = eno;
        return -1;
      }

      stat(nmessage, &stb);

#ifdef AF_UNIX
      do {

        const char *routernotify;
        char buf[1000];
        int notifysocket;
        struct sockaddr_un sad;
#ifndef MSG_NOSIGNAL
        RETSIGTYPE (*oldsig)__((int));
#endif

        if (disable_routerdirhash)
          routernotify = getzenv("INPUTNOTIFY");
        else
          routernotify = getzenv("ROUTERNOTIFY");

        if (!routernotify) break;

        memset(&sad, 0, sizeof(sad));
        sad.sun_family = AF_UNIX;
        strncpy(sad.sun_path, routernotify, sizeof(sad.sun_path));
        sad.sun_path[ sizeof(sad.sun_path)-1 ] = 0;

        notifysocket = socket(PF_UNIX, SOCK_DGRAM, 0);
        if (notifysocket < 0) {
          perror("notifysocket: socket(PF_UNIX)");
          break;
        }

        fcntl(notifysocket, F_SETFL, O_NONBLOCK);

        s = strchr(routerdir,':');
        if (s) *s = 0;

        sprintf(buf, "NEW %s %s%ld%s",
              routerdir, subdirhash, ino, type);

        if (s) *s = ':';

#ifndef MSG_NOSIGNAL
        SIGNAL_HANDLESAVE(SIGPIPE, SIG_IGN, oldsig);
#endif

        sendto(notifysocket, buf, strlen(buf),
#ifdef MSG_NOSIGNAL
             MSG_NOSIGNAL|
#endif
#ifdef MSG_DONTWAIT
             MSG_DONTWAIT
#endif
             ,
             (struct sockaddr *)&sad, sizeof(sad));

        close(notifysocket);
#ifndef MSG_NOSIGNAL
        SIGNAL_HANDLE(SIGPIPE, oldsig);
#endif

      } while (0);
#endif /* AF_UNIX */

#ifndef HAVE_ALLOCA
      mail_free(nmessage);
#endif
      mail_free(message);
      if (ftype) mail_free(ftype);

      if (inop != NULL)
        *inop   = (long) stb.st_ino;
      if (mtimep != NULL)
        *mtimep = (time_t) stb.st_mtime;


      return 0;
}



int
_sfmail_close_(fp,inop, mtimep)
      Sfio_t *fp;
      long *inop;
      time_t *mtimep;
{
      return _sfmail_close__(fp, inop, mtimep, 0);
}

 int
_sfmail_close_async(fp,inop, mtimep, async)
      Sfio_t *fp;
      long *inop;
      time_t *mtimep;
      int async;
{
      return _sfmail_close__(fp, inop, mtimep, async);
}




/*
 * Close the message file on the indicated stream, and submit
 * it to alternate directory. (For smtpserver->scheduler messages,
 * for example.)
 */

static int
sfmail_close_alternate_(fp,where,suffix,async)
      Sfio_t *fp;
      const char *where, *suffix;
      int async;
{
      char *message, *nmessage, *msgbase;
      char *type, *ftype;
      struct stat stbuf;
      int fn;

      if (postoffice == NULL) {
        fprintf(stderr,
              "sfmail_close_alternate: called out of order!\n");
        errno = EINVAL;
        return -1;
      }
      if (fp == NULL) {
        errno = EBADF;
        return -1;
      }
      fn = sffileno(fp);
      fstat(fn, &stbuf);
      if (fn >= mail_nfiles)
        abort(); /* Usage error -- no such fileno in our use! */

      message = mail_file[fn];
      if (message == NULL) {
            errno = ENOENT;
            return -1;
      }
      type = ftype = mail_type[fn];
      if (type == NULL)
        type = "";

      mail_file[fn] = NULL;
      mail_type[fn] = NULL;

      /*
       * *** NFS users beware ***
       * the fsync() between fflush() and fclose() may be mandatory
       * on NFS mounted postoffices if you want to guarantee not losing
       * data without being told about it.
       */


      while (sfsync(fp) != 0) {
        if (errno == EINTR || errno == EAGAIN)
          continue;
        mail_free(message);
        if (ftype) mail_free(ftype);
        errno = EIO;
        return -1;
      }
#ifdef HAVE_FSYNC
      if (!async) {
        while (fsync(fn) < 0) {
          if (errno == EINTR || errno == EAGAIN)
            continue;
          if (ftype) mail_free(ftype);
          mail_free(message);
          errno = EIO;
          return -1;
        }
      }
#endif
      if (sfclose(fp) == EOF) {
        mail_free(message);
        if (ftype) mail_free(ftype);
        errno = EIO;
        return -1;
      }

      /* Find the base name (we know format is PUBLICDIR/basename) */
      msgbase = strrchr(message, '/');
      if (msgbase == NULL)
            msgbase = message;
      else
            ++msgbase;

      /* Assert postoffice != NULL */

      nmessage = mail_alloc(strlen(postoffice)+1+strlen(where)+1+
                        20+strlen(suffix)+1+strlen(type));

      sprintf(nmessage, "%s/%s/%ld%s%s",
            postoffice, where, (long)stbuf.st_ino, suffix, type);

      if (eqrename(message,nmessage) != 0) {
        int eno = errno;
        fprintf(stderr, "eqrename(\"%s\", \"%s\"): errno %d\n",
              message, nmessage, errno);
        mail_free(message);
        mail_free(nmessage);
        if (ftype) mail_free(ftype);
        errno = eno;
        return -1;
      }

      mail_free(message);
      mail_free(nmessage);
      if (ftype) mail_free(ftype);
      return 0;
}

int
sfmail_close_alternate(fp,where,suffix)
      Sfio_t *fp;
      const char *where, *suffix;
{
      return sfmail_close_alternate_(fp, where, suffix, 0);
}


int
sfmail_close_alternate_async(fp,where,suffix,async)
      Sfio_t *fp;
      const char *where, *suffix;
      int async;
{
      return sfmail_close_alternate_(fp, where, suffix, async);
}


Generated by  Doxygen 1.6.0   Back to index