/* * 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 (copyright) 1992-2003 * * SFIO version by Matti Aarnio, copyright 1999-2003 */ /*LINTLIBRARY*/ #include "hostenv.h" #include #ifndef FILE /* Some systems don't have this as a MACRO.. */ # define FILE FILE #endif #include #include #include #include #ifdef HAVE_FCNTL_H # include #endif #include #include #ifdef HAVE_SYS_UN_H #include #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); }