/* * Copyright 1988 by Rayan S. Zachariassen, all rights reserved. * This will be free software, but only when it is finished. * Copyright 1992-2003 Matti Aarnio -- MIME processing et.al. */ /* History: * * Based on code by Geoff Collyer. * Rewritten for Sun environment by Dennis Ferguson and Rayan Zachariassen. * RBIFF code originally added by Jean-Francois Lamy. * Heavily modified for ZMailer by Rayan Zachariassen. * Still more modifications by Matti Aarnio */ /* ZENV variables used by this program: * * ZCONFIG -- passed to programs run at pipes * MAILBOX -- same * MAILBIN -- same * MAILSHARE -- same * PATH -- used to find programs, passed to programs * MBOXLOCKS -- defines file/mbox lock mechanisms; a string with: * '.' -- "dotlock" mechanism for mail spool files * ':' -- separates mailbox, and file lock schemes * 'L' -- lockf() lock * 'F' -- flock() lock * 'N' -- NFSMBOX (uses special locking daemon at remote system) */ #define DefCharset "ISO-8859-1" #include "mailer.h" #include #include #include #include #include #include #include #ifdef HAVE_UNISTD_H #include /* F_LOCK is there at some systems.. */ #endif #include #include "ta.h" #include "mail.h" #include "zsyslog.h" #include "zmsignal.h" #include "zmalloc.h" #include "libz.h" #include "libc.h" #include "splay.h" #include "sieve.h" #include "shmmib.h" #ifdef HAVE_SYS_WAIT_H /* POSIX.1 compatible */ # include #else /* Not POSIX.1 compatible, lets fake it.. */ extern int wait(); #endif #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_LOCALE_H #include #endif #ifdef HAVE_LOCKF #ifdef F_OK #undef F_OK #endif /* F_OK */ #ifdef X_OK #undef X_OK #endif /* X_OK */ #ifdef W_OK #undef W_OK #endif /* W_OK */ #ifdef R_OK #undef R_OK #endif /* R_OK */ #endif /* HAVE_LOCKF */ #ifndef HAVE_LSTAT #define lstat stat #endif /* !HAVE_LSTAT */ #ifdef HAVE_MAILLOCK_H # include #endif extern time_t time __((time_t *)); #ifdef HAVE_DOTLOCK #include "dotlock.c" #endif #ifdef HAVE_SYS_TIME_H # include #endif #ifdef HAVE_UTIME_H # include #else /* XXX: some systems have utimbuf defined in unistd.h, some don't... */ struct utimbuf { time_t actime; time_t modtime; }; #endif #ifndef SEEK_SET #define SEEK_SET 0 #endif /* !SEEK_SET */ #ifndef SEEK_END #define SEEK_END 2 #endif /* !SEEK_END */ #ifndef F_OK #define F_OK 0 #endif /* !F_OK */ #ifdef HAVE_LOCKF const char *MBOXLOCKS_default = ".L:L"; #else #ifdef HAVE_FLOCK const char *MBOXLOCKS_default = ".F:F"; #else const char *MBOXLOCKS_default = ".:"; /* Huh ?? No lockf() nor flock() ??? */ #endif #endif #ifdef HAVE_DIRENT_H # include #else /* not HAVE_DIRENT_H */ # define dirent direct # ifdef HAVE_SYS_NDIR_H # include # endif /* HAVE_SYS_NDIR_H */ # ifdef HAVE_SYS_DIR_H # include # endif /* HAVE_SYS_DIR_H */ # ifdef HAVE_NDIR_H # include # endif /* HAVE_NDIR_H */ #endif /* HAVE_DIRENT_H */ #ifdef HAVE_SOCKET #include #include #include #endif /* HAVE_SOCKET */ #ifdef HAVE_PROTOCOLS_RWHOD_H #include #include #define RWHODIR "/var/spool/rwho" #define WHDRSIZE (sizeof (wd) - sizeof (wd.wd_we)) #define RBIFFRC ".rbiff" #endif /* HAVE_PROTOCOLS_RWHOD_H */ #define PROGNAME "mailbox" /* for logging */ #define CHANNEL "local" /* the default channel name we deliver for */ #define TO_FILE '/' /* first character of file username */ #define TO_PIPE '|' /* first character of program username */ #define TO_USER '\\' /* first character of escaped username */ #define QUOTE '"' /* some usernames have these around them */ #define FROM_ "From " /* mailbox separator string (q.v. writebuf) */ #define MAILMODE 0600 /* prevent snoopers from looking at mail */ #define NONUIDSTR "6963" /* X:magic nonsense uid if nobody uid is bad */ /* [Thomas Knott] [mea] * The form for "-S" (sendmail-style-behaviour) enabled * "Return-Receipt-To:" -header processing. * This is a file in $MAILSHARE/FORMSDIR/ -directory */ #define RETURN_RECEIPT_FORM "return-receipt" /* * The following is stuck in for reference only. You could add * alternate spool directories to the list (they are checked in * order, first to last) but if the MAILBOX zenvariable exists it will * override the entire list. */ const char *maildirs[] = { "/var/mail", "/usr/mail", "/var/spool/mail", "/usr/spool/mail", 0 }; /* * Macro to determine from the given error number whether this * should be considered a temporary failure or not. */ #ifdef USE_NFSMBOX #define TEMPFAIL(err) (err == EIO || err == ENETRESET || \ err == ECONNRESET || err == ENOBUFS || \ err == ETIMEDOUT || err == ECONNREFUSED || \ err == EHOSTDOWN || err == ENOTCONN) #else /* !USE_NFSMBOX */ #define TEMPFAIL(err) (err == EIO) #endif /* USE_NFSMBOX */ #define DIAGNOSTIC(R,U,E,A1,A2) diagnostic(verboselog, (R), \ (*(U) == TO_PIPE \ && (R)->status != EX_OK \ && (R)->status != EX_TEMPFAIL) ? \ EX_UNAVAILABLE : (E), 0, (A1), (A2)); \ SHM_MIB_diag(E) #define DIAGNOSTIC3(R,U,E,A1,A2,A3) diagnostic(verboselog, (R), \ (*(U) == TO_PIPE \ && (R)->status != EX_OK \ && (R)->status != EX_TEMPFAIL) ? \ EX_UNAVAILABLE : (E), 0, \ (A1), (A2), (A3)); \ SHM_MIB_diag(E) #define DIAGNOSTIC4(R,U,E,A1,A2,A3,A4) diagnostic(verboselog, (R), \ (*(U) == TO_PIPE \ && (R)->status != EX_OK \ && (R)->status != EX_TEMPFAIL) ? \ EX_UNAVAILABLE : (E), 0, \ (A1), (A2), (A3), (A4)); \ SHM_MIB_diag(E) #ifdef CHECK_MB_SIZE extern int checkmbsize(const char *uname, const char *host, const char *user, size_t cursize, struct Zpasswd *pw); #endif #if defined(HAVE_SOCKET) /* * Biff strategy: * * If any biff is desired, add user to list with empty structure. * For all users in list, if remote biff is desired, read rwho files * to determine the hosts user is on. Add user to list for each host. * The "user" is really a struct containing username and offset into * their mailbox. */ struct biffer { struct biffer *next; char *user; long offset; int wantrbiff; }; struct biffer *biffs = NULL; struct biffer *eobiffs = NULL; struct userhost { struct userhost *next; char *hostname; }; struct wsdisc { Sfdisc_t D; /* Sfio Discipline structure */ void *WS; /* Ptr to SS context */ }; struct writestate { Sfio_t *fp; int lastch; int lasterrno; char expect; char frombuf[8]; char *fromp; char *buf2; /* writemimeline() processing aux buffer */ int buf2len; int epipe_seen; struct wsdisc WSdisc; }; struct sptree *spt_users = NULL; #endif /* HAVE_SOCKET -- no use to biff, if no socket available .. */ const char *defcharset; const char *progname; const char *channel; const char *logfile; FILE *logfp = NULL; FILE *verboselog = NULL; int readalready = 0; /* does buffer contain valid message data? */ uid_t currenteuid; /* the current euid */ extern int nobody; /* safe uid for file/program delivery */ int dobiff = 1; /* enable biff notification */ int dorbiff = 0; /* " rbiff " */ int dorbiff_always = 0; int keepatime = 1; /* preserve mbox st_atime, for login etc. */ int creatflag = 1; /* attempt to create files that don't exist */ int mime8bit = 0; /* Has code which translates MIME text/plain Quoted-Printable to 8BIT.. */ int keep_header8 = 0; /* Headers can have 8-bit stuff ? */ int convert_qp = 0; /* Flag: We have a job.. */ int conversion_prohibited = 0; /* Flag: Under no circumstances touch on it.. */ int is_mime = 0; /* Flag: Msg is MIME msg .. */ char *mime_boundary = NULL; int mime_boundary_len = 0; int mmdf_mode = 0; /* Write out MMDF-style mail folder ("\001\001\001\001" as the separator..) */ long eofindex = -1; /* When negative, putmail() can't truncate() */ int dirhashes = 0; int pjwhashes = 0; int crchashes = 0; int canonify_user = 0; int do_xuidl = 0; /* Store our own X-UIDL: header to allow POP3 server to have some unique id for the messages.. IMAP4 does require something different -- 32-bit unique counter.. See RFC 2060 for IMAP4. */ int edquot_is_fatal = 0; static const char *s_delivered = "delivered"; static const char *s_delayed = "delayed"; static const char *s_failed = "failed"; extern int fmtmbox __((char *, int, const char *, const char *, \ const struct Zpasswd *)); extern RETSIGTYPE wantout __((int)); extern int optind; extern char *optarg; extern void biff __((const char *, const char *, long)); extern void rbiff __((struct biffer *)); extern void prversion __((const char *)); extern int setupuidgid __((struct rcpt *, int, int)); extern int createfile __((struct rcpt *, const char *, int, int)); extern int exstat __((struct rcpt *, const char *, struct stat *, int (*)(const char*, struct stat*) )); extern int creatembox __((struct rcpt *, const char *, char **, uid_t*, gid_t*, struct Zpasswd *)); extern char *exists __((const char *, const char *, struct Zpasswd *, struct rcpt *)); extern void setrootuid __((struct rcpt *)); extern void process __((struct ctldesc *dp)); extern void deliver __((struct ctldesc *dp, struct rcpt *rp, const char *userbuf, const char *timestring)); extern Sfio_t *putmail __((struct ctldesc *dp, struct rcpt *rp, int fdmail, const char *fdopmode, const char *timestring, const char *file, uid_t)); extern int appendlet __((struct ctldesc *dp, struct rcpt *rp, struct writestate *WS, const char *file, int ismime)); extern char **environ; extern int writebuf __((struct writestate *, const char *buf, int len)); extern int writemimeline __((struct writestate *, const char *buf, int len)); extern int program __((struct ctldesc *dp, struct rcpt *rp, const char *cmdbuf, const char *usernam, const char *timestring, int pipeuid)); static int do_return_receipt_to = 0; static void return_receipt __((struct ctldesc *dp, const char *retrecpaddr, const char *uidstr)); static const char *find_return_receipt_hdr __((struct rcpt *rp)); extern int qp_to_8bit __((struct rcpt *rp)); extern int qptext_check __((struct rcpt *rp)); extern void hp_init(); extern char **hp_getaddr(); extern int errno; #ifndef strchr extern char *strchr(), *strrchr(); #endif extern int lstat __((const char *, struct stat *)); /* remaps to stat() if USE_LSTAT is not defined.. */ extern FILE *fdopen(); #ifndef MALLOC_TRACE extern void * emalloc __((size_t)); #else struct conshell *envarlist = NULL; #endif int D_alloc = 0; static int domain_aware_getpwnam; static int zsfsetfd(fp, fd) Sfio_t *fp; int fd; { /* This is *NOT* the SFIO's sfsetfd() -- we do no sfsync() at any point.. */ fp->file = fd; return fd; } static void zsfclose(fp) Sfio_t *fp; { sfpurge(fp); sfclose(fp); } static void MIBcountCleanup __((void)) { MIBMtaEntry->tambox.TaProcCountG -= 1; } static void SHM_MIB_diag(rc) const int rc; { switch (rc) { case EX_OK: /* OK */ MIBMtaEntry->tambox.TaRcptsOk ++; break; case EX_TEMPFAIL: case EX_IOERR: case EX_OSERR: case EX_CANTCREAT: case EX_SOFTWARE: case EX_DEFERALL: /* DEFER */ MIBMtaEntry->tambox.TaRcptsRetry ++; break; case EX_NOPERM: case EX_PROTOCOL: case EX_USAGE: case EX_NOUSER: case EX_NOHOST: case EX_UNAVAILABLE: default: /* FAIL */ MIBMtaEntry->tambox.TaRcptsFail ++; break; } } static void decodeXtext __((Sfio_t *, const char *)); #if defined(HAVE_SOCKET) && defined(HAVE_PROTOCOLS_RWHOD_H) static int readrwho __((void)); #endif static void sig_alrm __((int)); static void sig_alrm(sig) int sig; { /* Sigh, actually dummy routine.. */ } static int free_spu __((void *)); static int free_spu(p) void *p; { free(p); return 0; } #ifndef MAXPATHLEN #define MAXPATHLEN 1024 #endif char filename[MAXPATHLEN+8000]; int main(argc, argv) int argc; const char *argv[]; { char *s; const char *cs; int c, errflg, fd; char *host = NULL; /* .. and what is my host ? */ int matchhost = 0; #if defined(HAVE_SOCKET) struct biffer *nbp; #endif struct ctldesc *dp; RETSIGTYPE (*oldsig) __((int)); 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_HANDLE(SIGALRM, sig_alrm); /* Actually ignored, but fcntl() will break ? */ SIGNAL_IGNORE(SIGPIPE); #if defined(HAVE_LOCALE_H) && defined(HAVE_SETLOCALE) && defined(LC_ALL) setlocale(LC_ALL, "C"); #endif if (getenv("ZCONFIG")) readzenv(getenv("ZCONFIG")); Z_SHM_MIB_Attach(1); /* we don't care if it succeeds or fails.. */ MIBMtaEntry->tambox.TaProcessStarts += 1; MIBMtaEntry->tambox.TaProcCountG += 1; atexit(MIBcountCleanup); progname = strrchr(argv[0], '/'); if (progname == NULL) progname = argv[0]; else ++progname; cs = getzenv("MAILBOX"); if (cs != NULL) { maildirs[0] = cs; maildirs[1] = NULL; } umask(002); errflg = 0; logfile = NULL; channel = CHANNEL; while (1) { c = getopt(argc, (char*const*)argv, "abc:Cd:DF:gh:Hl:MPrRSVUX8"); if (c == EOF) break; switch (c) { case 'c': /* specify channel scanned for */ channel = optarg; break; case 'C': canonify_user = 1; break; case 'd': /* mail directory */ maildirs[0] = strdup(optarg); maildirs[1] = NULL; break; case 'D': ++dirhashes; /* For user "abcdefg" the mailbox path is: MAILBOX/a/b/abcdefg -- supported by qpopper, for example... */ break; case 'P': /* pjwhash32() from the last component of the mailbox file path (filename) is used to create hashes by calculating N levels (one or two) of modulo 26 ('A'..'Z') alike the scheduler does its directory hashes. */ ++pjwhashes; break; case 'X': /* Like pjwhash32() above, but with crc32() */ ++crchashes; break; case 'F': if (STREQ(optarg,"edquot")) edquot_is_fatal = 1; break; case 'U': do_xuidl = 1; break; case 'M': mmdf_mode = 1; break; case 'V': prversion(PROGNAME); exit(EX_OK); break; case 'b': /* toggle biffing */ dobiff = !dobiff; break; case 'r': /* toggle rbiffing */ dorbiff = !dorbiff; break; case 'R': dorbiff_always = !dorbiff_always; break; case 'S': do_return_receipt_to = 1; break; case 'a': /* toggle mbox st_atime preservation */ keepatime = !keepatime; break; case 'g': /* toggle file creation */ creatflag = !creatflag; break; case 'h': host = strdup(optarg); matchhost = 1; break; case 'l': /* log file */ logfile = strdup(optarg); break; case 'H': keep_header8 = 1; break; case '8': mime8bit = 1; break; default: ++errflg; break; } } if (errflg || optind != argc) { fprintf(stderr, "Usage: %s [-8abDPXgHMrSV] [-F edquot] [-l logfile] [-c channel] [-h host] [-d mailboxdir]\n", argv[0]); exit(EX_USAGE); } if (geteuid() != 0 || getuid() != 0) { fprintf(stderr, "%s: not running as root!\n", progname); exit(EX_NOPERM); } #if 0 /* hmm.. weird obsolete code ? */ SETEUID(0); /* make us root all over */ currenteuid = 0; #endif logfp = NULL; if (logfile != NULL) { if ((fd = open(logfile, O_CREAT|O_APPEND|O_WRONLY, 0644)) < 0) fprintf(stderr, "%s: open(\"%s\") failed: %s\n", progname, logfile, strerror(errno)); else { logfp = fdopen(fd, "a"); fcntl(fd, F_SETFD, 1); } } /* We need this later on .. */ zopenlog("mailbox", LOG_PID, LOG_MAIL); getnobody(); defcharset = getzenv("DEFCHARSET"); if (!defcharset) defcharset = DefCharset; if (domain_aware_getpwnam == 0) { const char *s = getzenv("DOMAIN_AWARE_GETPWNAM"); if (s && (*s == '1')) { domain_aware_getpwnam=1; } else { domain_aware_getpwnam=-1; } } while (!getout) { /* Input: spool/file/name [ \t host.info ] \n */ printf("#hungry\n"); fflush(stdout); if (fgets(filename, sizeof(filename), stdin) == NULL) break; if (strchr(filename, '\n') == NULL) break; /* No ending '\n' ! Must have been partial input! */ if (strcmp(filename, "#idle\n") == 0) { MIBMtaEntry->tambox.TaIdleStates += 1; continue; /* Ah well, we can stay idle.. */ } if (emptyline(filename, sizeof filename)) break; MIBMtaEntry->tambox.TaMessages += 1; s = strchr(filename,'\t'); if (s != NULL) { if (host) free(host); host = strdup(s+1); *s = 0; } SETUID(0); /* We begin as roots.. process() may change us */ SETEUID(0); currenteuid = 0; notaryflush(); notary_setxdelay(0); /* Our initial speed estimate is overtly optimistic.. */ dp = ctlopen(filename, channel, host, &getout, NULL, NULL); if (dp == NULL) { printf("#resync %s\n",filename); fflush(stdout); continue; } if (verboselog) { fclose(verboselog); verboselog = NULL; } if (dp->verbose) { verboselog = fopen(dp->verbose,"a"); if (verboselog) { /* Buffering, and Close-On-Exec bit! */ setbuf(verboselog,NULL); fcntl(FILENO(verboselog), F_SETFD, 1); } } process(dp); ctlclose(dp); #if defined(HAVE_SOCKET) /* now that the delivery phase is over, hoot'n wave */ for (nbp = biffs ; nbp != NULL; nbp = nbp->next) { if (dobiff && nbp->offset >= 0) biff("localhost", nbp->user, nbp->offset); #ifdef HAVE_PROTOCOLS_RWHOD_H /* RBIFF */ if (nbp->wantrbiff != 0) { if (spt_users == NULL) spt_users = sp_init(); sp_install(symbol((void*)nbp->user), NULL, 0, spt_users); } #endif /* RBIFF */ } #ifdef HAVE_PROTOCOLS_RWHOD_H /* RBIFF */ if (spt_users != NULL) { if (readrwho()) for (nbp = biffs ; nbp != NULL; nbp = nbp->next) { if (nbp->offset >= 0 && nbp->wantrbiff) rbiff(nbp); } } #endif /* RBIFF */ while ((nbp = biffs) != NULL) { biffs = nbp->next; free(nbp->user); free(nbp); } sp_scan( (int(*)__((struct spblk*))) free_spu, NULL, spt_users); free((void*)spt_users); spt_users = NULL; #endif /* HAVE_SOCKET */ } exit(EX_OK); /* NOTREACHED */ return 0; } static void rfc822localize __((char *)); static void rfc822localize(user) char *user; { char *s = user; if (*s == '"') { /* Quoted local part */ ++user; while (*user && *user != '"') { if (*user == '\\' && user[1] != 0) ++user; *s = *user; ++s; ++user; } *s = 0; if (*user == '"') ++user; /* End of local part, now it MUST be '@' */ } else { /* Non-quoted local part */ while (*user && *user != '@') { if (*user == '\\' && user[1] != 0) ++user; *s = *user; ++s; ++user; } *s = 0; } /* Now the '*user' points to a NIL, or '@' */ } static void rfc822dequote __((char *)); static void rfc822dequote(user) char *user; { char *s = user; int escaped=0; for ( ; *user; ++user) { if (escaped) { *s++=*user; escaped=0; } else { if (*user == '"') continue; if (*user == '\\') { escaped=1; continue; } *s++=*user; } } *s='\0'; } void process(dp) struct ctldesc *dp; { char *ts; struct rcpt *rp; time_t curtime; int is_mime_qptext = 0; char *userbuf = NULL; char *user; int userlen; int userspace = 0; struct ct_data *CT = NULL; struct cte_data *CTE = NULL; char **hdr; rp = dp->recipients; conversion_prohibited = check_conv_prohibit(rp); hdr = has_header(rp,"Content-Type:"); if (hdr) CT = parse_content_type(*hdr); hdr = has_header(rp,"Content-Transfer-Encoding:"); if (hdr) CTE = parse_content_encoding(*hdr); if (CT) { if (CT->basetype == NULL || CT->subtype == NULL || cistrcmp(CT->basetype,"text") != 0 || cistrcmp(CT->subtype,"plain") != 0) /* Not TEXT/PLAIN! */ conversion_prohibited = -1; /* We don't know how to convert anything BUT TEXT/PLAIN :-( */ } if (!conversion_prohibited) is_mime_qptext = qptext_check(dp->recipients); if (!keep_header8 && headers_need_mime2(dp->recipients)) { headers_to_mime2(dp->recipients,defcharset,verboselog); } /* * Strip backslashes prefixed to the user name, * and strip the quotes from around a name. */ readalready = 0; /* ignore any previous message data cache */ for (rp = dp->recipients; rp != NULL; rp = rp->next) { userlen = strlen(rp->addr->user); if (userlen >= userspace) { userspace = userlen + 1; userbuf = realloc(userbuf, userspace); } memcpy(userbuf, rp->addr->user, userlen+1); user = userbuf; while (*user == TO_USER) ++user; if (domain_aware_getpwnam == 1) rfc822dequote(user); else rfc822localize(user); time(&curtime); ts = ctime(&curtime); /* seek to message body start on each iteration */ if (lseek(dp->msgfd, (off_t)dp->msgbodyoffset, SEEK_SET) < 0L) { warning("Cannot seek to message body in %s! (%m)", dp->msgfile); DIAGNOSTIC(rp, user, EX_TEMPFAIL, "cannot seek to message body!", 0); } else { if (verboselog) fprintf(verboselog,"mailbox: %s/%s %s %s\n", rp->addr->channel, rp->addr->host, rp->addr->user, rp->addr->misc ); if (dp->msgbodyoffset == 0) warning("Null message offset in \"%s\"!", dp->msgfile); convert_qp = is_mime_qptext && mime8bit; deliver(dp, rp, userbuf, ts); } } if (userbuf != NULL) free(userbuf); if (CT) free_content_type(CT); if (CTE) free_content_encoding(CTE); } /* * probably_x400() -- some heuristics to see if this is likely * a mis-written X.400 address */ int probably_x400 __((const char *)); int probably_x400(addr) const char *addr; { int slashes = 0; int equs = 0; while (*addr) { if (*addr == '/') { ++slashes; } else if (*addr == '=') { ++equs; } else { ; /* Hmm... No other testings */ } ++addr; } /* Match things like: /X=yyy/Z=xxx/ /G=fff/S=kkk/O=gggg/@foo.faa /=/ But not: /foo/faa/path */ if (equs > 0 && equs <= slashes) return 1; return 0; } /* Various lock acquisition routines */ int havedotlock = 0, havemaillock = 0; int acquire_mboxlock __((struct rcpt *, const char *, int)); int acquire_mboxlock(rp,file,iuid) struct rcpt *rp; const char *file; int iuid; { #ifdef HAVE_MAILLOCK const char *maillockuser; struct Zpasswd *pw; int i; char errbuf[30]; const char *cp; uid_t uid = (uid_t) iuid; pw = zgetpwuid(uid); if (*(rp->addr->user) == TO_FILE) { if (pw == NULL) { notaryreport(file, notaryacct(EX_NOPERM, NULL), "5.2.1 (Target file has no known owner)", "x-local; 510 (Target file has no known owner)"); DIAGNOSTIC(rp, file, EX_NOPERM, "File \"%s\" has no owner", file); return 1; } maillockuser = pw->pw_name; } else { char *at, *plus; maillockuser = rp->addr->user; at = strchr(maillockuser, '@'); if (at) *at = 0; plus = strchr(maillockuser, '+'); if (plus) *plus = 0; pw = zgetpwnam(maillockuser); if (plus) *plus = '+'; if (at) *at = '@'; if (pw) maillockuser = pw->pw_name; } i = maillock((char*)maillockuser, 2); /* Sigh.. proto vs. man-page */ switch (i) { case L_SUCCESS: cp = NULL; break; case L_MAXTRYS: notaryreport(file, s_failed, "5.4.5 (Mailbox unlocking fails)", "x-local; 550 (Mailbox unlocking fails)"); DIAGNOSTIC(rp, file, EX_TEMPFAIL, "\"%s\" is locked", file); return 1; case L_NAMELEN: cp = "recipient name > 13 chars"; break; case L_TMPLOCK: cp = "problem creating temp lockfile"; break; case L_TMPWRITE: cp = "problem writing pid into temp lockfile"; break; case L_ERROR: cp = "Something other than EEXIST happened"; break; #ifdef L_MANLOCK /* Some Debian version has copied this mechanism, but has done imperfect job on it.. I hope they don't do ``enum'' on these error codes. [mea] */ case L_MANLOCK: cp = "cannot set mandatory lock on temp lockfile"; break; #endif default: sprintf(errbuf, "maillock() error %d", i); cp = errbuf; break; } if (cp != NULL) { DIAGNOSTIC3(rp, file, EX_UNAVAILABLE, "Error maillock()ing \"%s\": %s", file, cp); return 1; } havemaillock = 1; #endif /* HAVE_MAILLOCK */ #ifdef HAVE_DOTLOCK havedotlock = (dotlock(file) == 0); if (!havedotlock) { char mbuf[256]; int rc, err = errno; sprintf(mbuf, "\"%s\": %s", file, strerror(err)); notaryreport(file, s_failed, "5.4.5 (File locking with dotlock failed)", "x-local; 550 (File locking with dotlock failed)"); rc = EX_UNAVAILABLE; switch (err) { case EBUSY: rc = EX_TEMPFAIL; break; case EACCES: rc = EX_NOPERM; break; default: break; } DIAGNOSTIC(rp, file, rc, "can't dotlock %s", mbuf); return 1; } #endif /* HAVE_DOTLOCK */ return 0; } int acquire_nfsmboxlock __((struct rcpt *, const char *)); int acquire_nfsmboxlock(rp,file) struct rcpt *rp; const char *file; { #ifdef USE_NFSMBOX if (nfslock(file, LOCK_EX) != 0) { alarm(0); notaryreport(file, s_failed, "5.4.5 (File locking with NFS lock failed)", "x-local; 550 (File locking with NFS lock failed)"); DIAGNOSTIC(rp, file, EX_TEMPFAIL, "nfs lock() can't nfslock \"%s\"", file); return 1; } #endif /* USE_NFSMBOX */ return 0; } int acquire_lockflock __((int, struct rcpt *, const char *)); int acquire_lockflock(fdmail,rp,file) int fdmail; struct rcpt *rp; const char *file; { #if defined(HAVE_LOCKF) && defined(F_LOCK) /* Seek to begining for locking! [mea@utu.fi] */ if (lseek(fdmail, (off_t)0, SEEK_SET) < 0 || lockf(fdmail, F_LOCK, 0) < 0) { alarm(0); notaryreport(file, s_failed, "5.4.5 (File locking with lockf failed)", "x-local; 550 (File locking with lockf failed)"); DIAGNOSTIC(rp, file, EX_TEMPFAIL, "can't lockf() \"%s\"", file); return 1; } lseek(fdmail, (off_t)0, SEEK_END); /* To the end of the file */ #endif /* HAVE_LOCKF */ return 0; } int acquire_flocklock __((int, struct rcpt *, const char *)); int acquire_flocklock(fdmail,rp,file) int fdmail; struct rcpt *rp; const char *file; { #ifdef HAVE_FLOCK if (flock(fdmail, LOCK_EX) < 0) { alarm(0); notaryreport(file, s_failed, "5.4.5 (File locking with flock failed)", "x-local; 550 (File locking with flock failed)"); DIAGNOSTIC(rp, file, EX_TEMPFAIL, "can't flock() \"%s\"", file); return 1; } #endif /* HAVE_FLOCK */ return 0; } /* * deliver - deliver the letter in to user's mail box. Return * errors and requests for further processing in the structure */ #ifndef __STDC__ extern void store_to_file(); #else extern void store_to_file(struct ctldesc *dp, struct rcpt *rp, const char *file, int ismbox, const char *usernam, struct stat *st, uid_t uid, #if defined(HAVE_SOCKET) struct biffer *nbp, #endif time_t starttime, const char *timestring ); #endif void deliver(dp, rp, usernam, timestring) struct ctldesc *dp; struct rcpt *rp; const char *usernam; const char *timestring; { register const char **maild; int fdmail; long uid; int hasdir; struct stat st, s2; const char *file = NULL; char *cp, *plus; const char *retrecptaddr; int ismbox = 0; #if defined(HAVE_SOCKET) struct biffer *nbp = NULL; #ifdef HAVE_PROTOCOLS_RWHOD_H char *path; #endif #endif const char *unam = usernam; struct Zpasswd *pw = NULL; time_t starttime; MIBMtaEntry->tambox.TaDeliveryStarts += 1; time(&starttime); notary_setxdelay(0); /* Our initial speed estimate is overtly optimistic.. */ if (sscanf(rp->addr->misc,"%ld",&uid) != 1) { char buf[1000]; if (verboselog) { fprintf(verboselog,"mailbox: User recipient address privilege code invalid (non-numeric!): '%s'\n",rp->addr->misc); } sprintf(buf,"x-local; 500 (User recipient address privilege code invalid [non-numeric!]: '%.200s')", rp->addr->misc); notaryreport(NULL, s_failed, "5.3.0 (User address recipient privilege code invalid)", buf); DIAGNOSTIC(rp, usernam, EX_SOFTWARE, "Non-numeric recipient privilege code: \"%s\"", rp->addr->misc); return; } plus = strchr(usernam,'+'); switch (*usernam) { case TO_PIPE: /* pipe to program */ /* one should disallow this if uid == nobody? */ if (uid == nobody) { if (verboselog) fprintf(verboselog, " the recipient address privilege == NOBODY!\n"); notaryreport("?program?", s_failed, "5.2.1 (Mail to program disallowed w/o proper privileges)", "x-local; 550 (Mail to program disallowed w/o proper privileges)"); DIAGNOSTIC(rp, usernam, EX_UNAVAILABLE, "mail to program disallowed", 0); return; } program(dp, rp, usernam, "", timestring, uid); /* DIAGNOSTIC(rp, usernam, EX_UNAVAILABLE, "mailing to programs (%s) is not supported", rp->addr->user); */ return; case TO_FILE: /* append to file */ /* Solaris has "interesting" /dev/null -- it is a symlink to the actual device.. So lets just use that magic name and create a FAST "write" to /dev/null.. */ if (strcmp(usernam,"/dev/null") == 0) { notaryreport(rp->addr->user, s_delivered, "2.2.0 (delivered successfully)", "x-local; 250 (Delivered successfully)"); DIAGNOSTIC(rp, usernam, EX_OK, "Ok", 0); return; } if (uid == nobody) { if (probably_x400(usernam)) { if (verboselog) fprintf(verboselog, " This smells of misdirected X.400 message? Rejected due to priviledge == NOBODY\n"); notaryreport(rp->addr->user, s_failed, "5.1.4 (this feels like a misplaced X.400 address -- no support for them)", "x-local; 550 (this feels like a misplaced X.400 address -- no support for them)"); DIAGNOSTIC(rp, usernam, EX_UNAVAILABLE, "This appears to be a misplaced X.400 address, no support for them", 0); } else { if (verboselog) fprintf(verboselog, " Mail to file rejected due to priviledge == NOBODY\n"); notaryreport(rp->addr->user, s_failed, "5.2.1 (Mail to file disallowed w/o proper privileges)", "x-local; 550 (mail to file disallowed w/o proper privileges)"); DIAGNOSTIC(rp, usernam, EX_UNAVAILABLE, "mail to file disallowed", 0); } return; } if (!setupuidgid(rp, uid, -3 /* GID of user with UID=='uid' */)) { setrootuid(rp); return; } file = strdup(usernam); if (access(file, F_OK) < 0) { fdmail = createfile(rp, file, uid, 0); /* Did the create fail for some other reason, than that the file exists already ? (Due to a race in between two processes creating the files.) */ if (fdmail < 0 && errno != EEXIST) { setrootuid(rp); return; } #ifdef HAVE_FSYNC while (fsync(fdmail) < 0) if (errno != EINTR && errno != EAGAIN) break; #endif close(fdmail); } setrootuid(rp); break; default: /* local user */ /* Zap the possible '@' for a moment -- and restore later [mea@utu.fi] -- same with '+' -- user+localspecs */ ismbox = 1; if (plus) *plus = 0; #if 0 setpwent(); /* Effectively rewind the database, needed for multi-recipient processing ? */ #endif unam = usernam; pw = zgetpwnam(usernam); if (pw == NULL) { /* No match as is ? Lowercasify, and try again! */ strlower((char*)usernam); pw = zgetpwnam(usernam); if (pw == NULL) { if (plus) *plus = '+'; /* Linux, very least, seems to sometimes yield NULL and errno=ENOENT, when Single Unix Spec tells it to yield NULL and errno==0 :-| The problem seems to be quite broad, as most systems don't do sensible things, when two conditions hold: All database lookups were without errors (although did yield notning), and nothing was found! The sensible thing would be to yield NULL along with errno==0 ! */ /* Now given the above, how the hell are we going to detect when any of the databases used for the username resolution has a hickup, and the lack of found username is simply due to the db problem, which time will solve (as with system operator taking some action) ? */ if (errno != 0) { /* zgetpwnam() failed for some other reason than simply not finding the given user... */ int err = errno; if (verboselog) fprintf(verboselog, " zgetpwnam(\"%s\") failed (%d)\n", usernam, errno); notaryreport(rp->addr->user, s_failed, "5.3.0 (Error getting user identity)", "x-local; 550 (Error getting user identity)"); DIAGNOSTIC3(rp, usernam, EX_TEMPFAIL, "zgetpwnam for user \"%s\" failed; errno=%d", usernam, err); } else if (probably_x400(usernam)) { if (verboselog) fprintf(verboselog, " User '%s' unknown, feels like misplaced X.400 address?\n", usernam); notaryreport(rp->addr->user, s_failed, "5.1.4 (this feels like a misplaced X.400 address -- no support for them)", "x-local; 550 (this feels like a misplaced X.400 address -- no support for them)"); DIAGNOSTIC(rp, usernam, EX_UNAVAILABLE, "This appears to be a misplaced X.400 address, no support for them", 0); } else { if (verboselog) fprintf(verboselog, " User '%s' unknown\n", usernam); notaryreport(rp->addr->user, s_failed, "5.1.1 (User does not exist)", "x-local; 550 (User does not exist)"); DIAGNOSTIC(rp, usernam, EX_NOUSER, "user \"%s\" doesn't exist", usernam); } return; } } if (canonify_user) unam = pw->pw_name; hasdir = 0; for (maild = maildirs; *maild != 0; maild++) { if ((strchr(*maild,'%') == NULL) && (stat(*maild,&st) < 0 || !S_ISDIR(st.st_mode))) /* Does not exist, or is not a directory */ continue; hasdir = 1; file = exists(*maild, unam, pw, rp); if (file != NULL) break; /* found it */ if (rp->status != EX_OK) return; } if (hasdir && *maild == 0 && !creatembox(rp, unam, (char**)&file, &st.st_uid, &st.st_gid, pw)) return; /* creatembox() sets status */ if (!hasdir) { /* No directory ?? */ const char *mailbox = getzenv("MAILBOX"); notaryreport(rp->addr->user, s_failed, "5.3.5 (System mailbox configuration is wrong, we are in deep trouble..)", "x-local; 566 (System mailbox configuration is wrong! No such directory! Aargh!)"); DIAGNOSTIC(rp, usernam, EX_TEMPFAIL, "System mailbox configuration is wrong! No such directory (%s)! Aargh!",maildirs[0]); if (!mailbox) zsyslog((LOG_ALERT, "ZMailer mailbox can't open local mail folder directory! (Using BUILTIN list of choices)\n")); else zsyslog((LOG_ALERT, "ZMailer mailbox can't open local mail folder directory! $MAILBOX='%s'\n", mailbox)); zcloselog(); return; } #if defined(HAVE_SOCKET) if (!dobiff && !dorbiff && !dorbiff_always) break; nbp = (struct biffer *)emalloc(sizeof (struct biffer)); nbp->next = NULL; nbp->user = strdup(rp->addr->user); nbp->offset = -1; nbp->wantrbiff = 0; #ifdef HAVE_PROTOCOLS_RWHOD_H if (dorbiff) { if (dorbiff_always) nbp->wantrbiff = 1; else { path = emalloc(2+strlen(pw->pw_dir)+strlen(RBIFFRC)); sprintf(path, "%s/%s", pw->pw_dir, RBIFFRC); nbp->wantrbiff = (access(path, F_OK) == 0); } } #endif /* RBIFF */ if (biffs == NULL) biffs = eobiffs = nbp; else { eobiffs->next = nbp; eobiffs = nbp; } #endif /* BIFF || RBIFF */ if (plus) *plus = '+'; break; } /* end of switch (*username) */ /* Solaris has "interesting" /dev/null -- it is a symlink to the actual device.. So lets just use that magic name and create a FAST "write" to /dev/null.. */ if (strcmp(file,"/dev/null") == 0) { notaryreport(rp->addr->user, s_delivered, "2.2.0 (delivered successfully)", "x-local; 250 (Delivered successfully)"); DIAGNOSTIC(rp, usernam, EX_OK, "Ok", 0); free((void*)file); return; } /* we only deliver to singly-linked, regular file */ if (exstat(rp, file, &st, lstat) < 0) { notaryreport(rp->addr->user, s_failed, "5.2.0 (User's mailbox disappeared, will retry)", "x-local; 566 (User's mailbox disappeared, will retry)"); DIAGNOSTIC(rp, usernam, EX_TEMPFAIL, "mailbox file \"%s\" disappeared", file); free((void*)file); return; } if (!S_ISREG(st.st_mode)) { /* XX: may want to deliver to named pipes */ notaryreport(rp->addr->user, s_failed, "5.2.1 (Attempting to deliver into non-regular file)", "x-local; 500 (Attempting to deliver into non-regular file)"); DIAGNOSTIC(rp, usernam, EX_UNAVAILABLE, "attempted delivery to special file \"%s\"", file); free((void*)file); return; } #ifdef CHECK_MB_SIZE if (1) { extern int checkmbsize __((const char *uname, const char *host, const char *user, size_t cursize, struct Zpasswd *pw)); /* external procedure checkmbsize() accepts user name, "host" name as on routing result, "user" part of routed data, and current mailbox size. It should return 0 if it is OK to write to the mailbox, or non-zero if `mailbox full' condition encountered. The procedure itself is not included in ZMailer distribution; you need to write it yourself and modify the Makefile to pass -DCHECK_MB_SIZE to the compiler and to link with the object containing your custom checkmbsize() procedure. == */ if (checkmbsize(usernam, rp->addr->host, rp->addr->user, st.st_size, pw)) { notaryreport(usernam, s_failed, "4.2.2 (Destination mailbox full)", "x-local; 500 (Attempting to deliver to full mailbox)"); DIAGNOSTIC(rp, usernam, EX_UNAVAILABLE, "size of mailbox \"%s\" exceeds quota for the user", file); free((void*)file); return; } } #endif if (st.st_nlink > 1) { notaryreport(rp->addr->user, s_failed, "5.2.1 (Destination file ambiguous)", "x-local; 500 (Destination file has more than one name..)"); DIAGNOSTIC(rp, usernam, EX_UNAVAILABLE, "too many links to file \"%s\"", file); free((void*)file); return; } /* [Edwin Allum] * If we're delivering to a mailbox, and the mail spool directory * is group writable, set our gid to that of the directory. * This allows us to create lock files if need be, without * making the spool dir world-writable. */ if (ismbox && (cp = strrchr(file, '/')) != NULL) { *cp = '\0'; if (stat(file, &s2) == 0 && (s2.st_mode&020)) st.st_gid = s2.st_gid; *cp = '/'; } if (!setupuidgid(rp, st.st_uid, st.st_gid)) { setrootuid(rp); free((void*)file); return; /* setupuidgid sets status */ } if (verboselog) fprintf(verboselog, " ismbox=%d file='%s' usernam='%s'\n", ismbox, file, usernam); if (ismbox) { struct sieve sv; memset(&sv,0,sizeof(sv)); sv.pw = pw; sv.uid = uid; sv.pipeuid = uid; sv.username = usernam; sv.spoolfile = file; rp->notifyflgs |= _DSN__DIAGDELAYMODE; if (sieve_start(&sv) == 0) { for (; sv.state != 0; sieve_iterate(&sv)) { /* XX: SIEVE PROCESSOR/ITERATOR */ int cmd = sieve_command(&sv); switch(cmd) { case SIEVE_NOOP: /* Absolutely NOTHING done with this label; end of iterator */ break; case SIEVE_RUNPIPE: if (program(dp, rp, sv.pipecmdbuf, usernam, timestring, sv.pipeuid) != EX_OK) { /* Possible FALSE negative ?? */ /* Tell also about SUCCESSES! */ rp->notifyflgs |= _DSN_NOTIFY_SUCCESS; } break; case SIEVE_USERSTORE: store_to_file(dp, rp, file, ismbox, usernam, &st, uid, #ifdef HAVE_SOCKET nbp, #endif starttime, timestring ); break; case SIEVE_DISCARD: break; } } /* Cleanup of sieve processor */ sieve_end(&sv); } /* Sieve-filter sets state mode -- keep_or_discard (<=>0) to tell what we should do to the message regarding its storage to the local message store. */ rp->notifyflgs &= ~ _DSN__DIAGDELAYMODE; if (sv.keep_or_discard >= 0) { store_to_file(dp, rp, file, ismbox, usernam, &st, uid, #ifdef HAVE_SOCKET nbp, #endif starttime, timestring ); } else { /* This happens only ONCE per recipient, if ever */ notaryreport(rp->addr->user, s_delivered, "2.2.0 (Discarded successfully)", "x-local; 250 (Discarded successfully)"); DIAGNOSTIC(rp, usernam, EX_OK, "Ok", 0); } } else { store_to_file(dp, rp, file, ismbox, usernam, &st, uid, #ifdef HAVE_SOCKET nbp, #endif starttime, timestring ); } if (file) free((void*)file); /* [Thomas Knott] "Return-Receipt-To:" */ if (do_return_receipt_to) { retrecptaddr = find_return_receipt_hdr(rp); if (retrecptaddr != NULL) return_receipt(dp,retrecptaddr,rp->addr->misc); } } void store_to_file(dp,rp,file,ismbox,usernam,st,uid, #ifdef HAVE_SOCKET nbp, #endif starttime, timestring ) struct ctldesc *dp; struct rcpt *rp; const char *file, *usernam; int ismbox; struct stat *st; uid_t uid; #ifdef HAVE_SOCKET struct biffer *nbp; #endif time_t starttime; const char *timestring; { int fdmail; struct stat s2; Sfio_t *fp = NULL; const char *mboxlocks = getzenv("MBOXLOCKS"); const char *filelocks = NULL; const char *locks = NULL; time_t endtime; if (mboxlocks == NULL || *mboxlocks == 0 ) mboxlocks = MBOXLOCKS_default; if ((filelocks = strchr(mboxlocks,':'))) ++filelocks; else filelocks = ""; if (verboselog) fprintf(verboselog, "To open a file with euid=%d egid=%d ismbox=%d file='%s'\n", (int)geteuid(), (int)getegid(), ismbox, file); fdmail = open(file, O_RDWR|O_APPEND); if (fdmail < 0) { char fmtbuf[512]; int saverrno = errno; sprintf(fmtbuf, "open(\"%%s\") failed: %s", strerror(saverrno)); if (verboselog) fprintf(verboselog, " ... failed, errno = %d (%s)\n", saverrno, strerror(saverrno)); if (TEMPFAIL(saverrno)) rp->status = EX_TEMPFAIL; else if (errno == EACCES) rp->status = EX_NOPERM; else rp->status = EX_UNAVAILABLE; notaryreport(file, s_failed, "5.2.1 (File open for append failed)", "x-local; 500 (File open for append failed)"); DIAGNOSTIC(rp, usernam, rp->status, fmtbuf, file); setrootuid(rp); return; } /* * The mbox may have been removed and symlinked * after the exstat() above, but before the open(), * so verify that we have the same inode. */ if (fstat(fdmail, &s2) < 0) { /* This simply CAN'T HAPPEN! Somebody has broken our fdmail -channel... */ DIAGNOSTIC(rp, usernam, EX_TEMPFAIL, "can't fstat mailbox \"%s\"", file); close(fdmail); setrootuid(rp); return; } if (st->st_ino != s2.st_ino || st->st_dev != s2.st_dev || s2.st_nlink != 1) { notaryreport(file, s_failed, "5.4.5 (Lost race for mailbox file)", "x-local; 550 (Lost race for mailbox file)"); DIAGNOSTIC(rp, usernam, EX_TEMPFAIL, "lost race for mailbox \"%s\"", file); close(fdmail); setrootuid(rp); return; } alarm(180); /* Set an timed interrupt coming to us to break overlengthy file lock acquisition.. */ if (!S_ISREG(st->st_mode)) /* don't lock non-files */; else { int err; if (!ismbox) /* Not mailbox, use file-locks */ mboxlocks = filelocks; locks = mboxlocks; if (verboselog) fprintf(verboselog,"Locking sequence: '%s'\n",locks); err = 0; while (*locks != 0) { switch(*locks) { case '"': /* ZENV variable may have quotes with it.. */ break; case ':': err = -1; /* negative error is no REAL error */ break; case '.': if (ismbox) err = acquire_mboxlock(rp,file,uid); break; case 'N': case 'n': err = acquire_nfsmboxlock(rp,file); break; case 'L': case 'l': err = acquire_lockflock(fdmail,rp,file); break; case 'F': case 'f': err = acquire_flocklock(fdmail,rp,file); break; default: err = 1; break; } if (err) break; ++locks; /* Advance on success only! */ } if (err > 0) { --locks; /* Don't try to revert the failed (= last) lock! */ while (locks >= mboxlocks) { switch (*locks) { case '"': break; case '.': #ifdef HAVE_MAILLOCK if (havemaillock && ismbox) mailunlock(); havemaillock = 0; #endif /* HAVE_MAILLOCK */ #ifdef HAVE_DOTLOCK if (ismbox) dotunlock(file); havedotlock = 0; #endif /*HAVE_DOTLOCK*/ break; case 'N': case 'n': #ifdef USE_NFSMBOX unlock(file); #endif /* USE_NFSMBOX */ break; case 'L': case 'l': #if defined(HAVE_LOCKF) && defined(F_LOCK) /* If one, also the other */ lseek(fdmail,(off_t)0,SEEK_SET); lockf(fdmail, F_ULOCK, 0); #endif /* HAVE_LOCKF */ break; case 'F': case 'f': #ifdef HAVE_FLOCK flock(fdmail, LOCK_UN); #endif break; default: break; } --locks; } close(fdmail); setrootuid(rp); return; } } /* Turn off the alarm */ alarm(0); /* Where is the end of file -- before we write anything */ eofindex = lseek(fdmail, (off_t)0, SEEK_END); #if defined(HAVE_SOCKET) if (nbp != NULL) nbp->offset = eofindex; #endif /* BIFF || RBIFF */ fp = putmail(dp, rp, fdmail, "a+", timestring, file, uid); if (S_ISREG(st->st_mode)) { /* Previously acquired locks need to be released, preferrably in reverse order */ --locks; while (locks >= mboxlocks) { /* if (verboselog) fprintf(verboselog, "Unlock char: '%c'\n",*locks); */ switch (*locks) { case '"': break; case '.': #ifdef HAVE_MAILLOCK if (ismbox && havemaillock) mailunlock(); havemaillock = 0; #endif /* HAVE_MAILLOCK */ #ifdef HAVE_DOTLOCK if (ismbox) dotunlock(file); havedotlock = 0; #endif /*HAVE_DOTLOCK*/ break; case 'N': case 'n': #ifdef USE_NFSMBOX unlock(file); #endif /* USE_NFSMBOX */ break; case 'L': case 'l': #if defined(HAVE_LOCKF) && defined(F_LOCK) lseek(fdmail,(off_t)0,SEEK_SET); lockf(fdmail, F_ULOCK, 0); #endif /* HAVE_LOCKF */ break; case 'F': case 'f': #ifdef HAVE_FLOCK flock(fdmail, LOCK_UN); #endif break; default: break; } --locks; } } setrootuid(rp); time(&endtime); close(fdmail); if (fp != NULL) { /* Dummy marker! */ notary_setxdelay((int)(endtime-starttime)); notaryreport(rp->addr->user, s_delivered, "2.2.0 (Delivered successfully)", "x-local; 250 (Delivered successfully)"); DIAGNOSTIC(rp, usernam, EX_OK, "Ok", 0); } else { #if 0 /* garbage.. */ #if defined(HAVE_SOCKET) if (fp) { if (nbp != NULL) /* putmail() has produced a DIAGNOSTIC */ nbp->offset = -1; } #endif /* BIFF || RBIFF */ #endif } return; } /* * SFIO write discipline which ignores PIPE write errors (EPIPE) * and just claims success at them. Otherwise quite normal * error processing. * */ ssize_t mbox_sfwrite(sfp, vp, len, discp) Sfio_t *sfp; const void * vp; size_t len; Sfdisc_t *discp; { struct wsdisc *wd = (struct wsdisc *)discp; struct writestate *WS = wd->WS; const char * p = (const char *)vp; int outlen = 0; /* If knowingly write to a pipe, and failing with EPIPE, *then* silently ignore it.. */ if (WS->epipe_seen > 0) return len; /* We ignore - fast - the EPIPE */ if (WS->lasterrno != 0) return -1; while (len > 0) { int rc = write(sffileno(sfp), p, len); int e = errno; if (verboselog) fprintf(verboselog, " mbox_sfwrite(ptr, len=%d) rc=%d errno=%d\n", (int)len, rc, e); if (rc < 0) { if (e == EPIPE && WS->epipe_seen >= 0) { WS->epipe_seen = 1; return len + outlen; /* CLAIM success */ } /* Retry on interrupts */ if (e == EINTR) continue; WS->lasterrno = errno = e; /* All other errors, return written amount, or if none, then error! */ if (outlen == 0) return rc; return outlen; } outlen += rc; len -= rc; p += rc; } return outlen; } Sfio_t * putmail(dp, rp, fdmail, fdopmode, timestring, file, uid) struct ctldesc *dp; struct rcpt *rp; int fdmail; const char *fdopmode, *timestring, *file; uid_t uid; { int len, rc, err; Sfio_t *fp; char buf[2]; struct stat st; const char *fromuser; const char *mw = "msg headers"; int lastch = 0xFFF; int topipe = (*(file) == TO_PIPE); int failed = 0; char sfio_buf[1024*16]; struct writestate WS; char **hdrs; fstat(fdmail, &st); fp = sfnew(NULL, sfio_buf, sizeof(sfio_buf), fdmail, SF_READ|SF_WRITE|SF_APPENDWR); if (fp == NULL) { notaryreport(file, s_delayed ,NULL,NULL); DIAGNOSTIC3(rp, file, EX_TEMPFAIL, "cannot fdopen(%d,\"%s\")", fdmail,fdopmode); return NULL; } memset( &WS, 0, sizeof(WS) ); WS.fp = fp; WS.expect = mmdf_mode ? 0 : 'F'; WS.lastch = 256; /* Something no character can be, and not "-1" either.. */ WS.frombuf[0] = 0; WS.fromp = WS.frombuf; WS.epipe_seen = topipe ? 0 : -1; WS.lasterrno = 0; memset(&WS.WSdisc, 0, sizeof(WS.WSdisc)); WS.WSdisc.D.readf = NULL; WS.WSdisc.D.writef = mbox_sfwrite; WS.WSdisc.D.seekf = NULL; WS.WSdisc.D.exceptf = NULL; WS.WSdisc.WS = &WS; sfdisc(WS.fp, &WS.WSdisc.D); if (!topipe && eofindex > 0L) { if (keepatime) { #ifdef HAVE_UTIME struct utimbuf tv; tv.actime = st.st_atime; tv.modtime = st.st_mtime; rc = utime(file, &tv); #else /* HAVE_UTIMES */ struct timeval tv[2]; tv[0].tv_sec = st.st_atime; tv[1].tv_sec = st.st_mtime; tv[0].tv_usec = tv[1].tv_usec = 0; rc = utimes(file, tv); #endif /* HAVE_UTIMES */ } } else if (eofindex > 0L && eofindex < (sizeof "From x\n")) { /* A mail message *cannot* be this small. It must be a corrupted mailbox file. Ignore the trash bytes. */ sfseek(fp, (off_t)0, SEEK_SET); eofindex = 0; } if (!(convert_qp && qp_to_8bit(rp))) convert_qp = 0; /* If malloc failed, clear this too, if it was not set, it doesn't change.. */ /* Begin of the file, and MMDF-style ? */ if (mmdf_mode == 1 /* Values 2 and 3 exist on PIPEs only.. */ ) sfprintf(fp,"\001\001\001\001\n"); fromuser = rp->addr->link->user; if (*fromuser == 0 || STREQ(rp->addr->link->channel, "error")) fromuser = ""; do { hdrs = has_header(rp,"Return-Path:"); if (hdrs) delete_header(rp,hdrs); } while (hdrs); append_header(rp,"Return-Path: <%.999s>", fromuser); if (*fromuser == 0) fromuser = "MAILER-DAEMON"; hdrs = has_header(rp,"To:"); if (!hdrs) { /* No "To:" -header ? Rewrite possible "Apparently-To:" header! */ /* Sendmailism... */ do { hdrs = has_header(rp,"Apparently-To:"); if (hdrs) delete_header(rp,hdrs); } while (hdrs); append_header(rp,"Apparently-To: <%.999s>", rp->addr->link->user); } do { hdrs = has_header(rp,"X-Envelope-To:"); if (hdrs) delete_header(rp,hdrs); } while (hdrs); do { hdrs = has_header(rp,"X-Orcpt:"); if (hdrs) delete_header(rp,hdrs); } while (hdrs); do { /* RFC 2298 section 2.3 */ hdrs = has_header(rp,"Original-Recipient:"); if (hdrs) delete_header(rp,hdrs); } while (hdrs); do { hdrs = has_header(rp,"X-Envid:"); if (hdrs) delete_header(rp,hdrs); } while (hdrs); do { hdrs = has_header(rp,"Envelope-Id:"); if (hdrs) delete_header(rp,hdrs); } while (hdrs); if (do_xuidl) do { hdrs = has_header(rp,"X-UIDL:"); if (hdrs) delete_header(rp,hdrs); } while (hdrs); /* Add the From_ line and print out the header */ sfprintf(fp, "%s%s %s", FROM_, fromuser, timestring); header_received_for_clause(rp, 0, verboselog); swriteheaders(rp, fp, "\n", convert_qp, 0, NULL); sfprintf(fp, "X-Envelope-To: <%s> (uid %d)\n", rp->addr->user, uid); if (rp->orcpt) { sfprintf(fp, "X-Orcpt: "); decodeXtext(fp, rp->orcpt); sfprintf(fp, "\n"); /* RFC 2298: section 2.3 */ sfprintf(fp, "Original-Recipient: "); decodeXtext(fp, rp->orcpt); sfprintf(fp, "\n"); } if (dp->envid) { sfprintf(fp, "X-Envid: "); decodeXtext(fp, dp->envid); sfprintf(fp, "\n"); sfprintf(fp, "Envelope-Id: "); decodeXtext(fp, dp->envid); sfprintf(fp, "\n"); } if (do_xuidl && !topipe) { struct timeval tv; gettimeofday(&tv, NULL); sfprintf(fp, "X-UIDL: %ld.%ld.%d\n", (long)tv.tv_sec, (long)tv.tv_usec, (int)getpid()); } sfprintf(fp, "\n"); sfsync(fp); /* Headers written, sync possible errors here! */ if (sferror(fp) || WS.lasterrno != 0) failed = 1; if (failed) goto write_failure; /* From now on, write errors to PIPE will not cause errors */ mw = "msg body"; if (!failed) lastch = appendlet(dp, rp, &WS, file, is_mime); sfsync(fp); if (!failed && (sferror(fp) || WS.lasterrno)) failed = 1; if (!topipe) { /* We skip this is we are writing to a pipe */ if (lastch < -128 || failed) { write_failure: /* Does the 'errno' really have correct data in all paths ? */ err = WS.lasterrno; rc = EX_IOERR; if (err == EDQUOT && edquot_is_fatal) { rc = EX_UNAVAILABLE; /* Quota-exceeded is instant permanent reject ? */ notaryreport(file, notaryacct(rc, s_delivered), "5.2.2 Mailbox Quota Exceeded", "x-local; 550 (Mailbox Quota Exceeded)"); } else { notaryreport(file, notaryacct(rc, s_delivered), "4.2.0 mailbox write failure", "x-local; 450 (Mailbox write failure)"); } DIAGNOSTIC4(rp, file, rc, "message write[%s] to \"%s\" failed: %s", mw, file, strerror(err)); #ifdef HAVE_FTRUNCATE if (eofindex >= 0) while (ftruncate(sffileno(fp), (off_t)eofindex) < 0) if (errno != EINTR && errno != EAGAIN) break; /* Some syscall traces seem to tell that Solaris 7/8 implements ftruncate() as: > 9497: fcntl(7, F_FREESP, 0xFFBEE5BC) = 0 > 9497: typ=F_WRLCK whence=SEEK_SET start=0 len=0 > 9497: sys=428042295 pid=-4266544 Also notable details being that this moves the read/write cursor into SEEK_SET/start=0 location, which did later cause some trouble.. */ #endif /* HAVE_FTRUNCATE */ #ifdef HAVE_FSYNC while (fsync(sffileno(fp)) < 0) if (errno != EINTR && errno != EAGAIN) break; #endif /* Discard and close! */ zsfclose(fp); eofindex = -1; fp = NULL; goto time_reset; } } /* if (!topipe) */ if (verboselog) fprintf(verboselog," end of putmail(file='%s'), topipe=%d\n", file,topipe); if (!topipe) { /* * Ok, we are NOT writing to a pipe, and thus we can do * fseek(), and play with things... */ /* * Determine how many newlines to append to the previous text. * * If the last characters are: \n\n, append 0 newlines, * [^\n]\n or \n append 1 newline, * else append 2 newlines. * * Optionally preserve the mbox's atime, so * login etc. can distinguish new mail from old. * The mtime will be set to now by the following write() calls. */ err = errno; sfseek(fp, (Sfoff_t)-2LL, SEEK_END); len = sfread(fp, buf, 2); sfseek(fp, (Sfoff_t)0, SEEK_END); /* to end of file, again */ errno = err; if (verboselog) fprintf(verboselog," .. EOF read did yield %d bytes\n", len); if (len == 1 || len == 2) { --len; len = (buf[len]!='\n') + (len == 1 ? buf[0]!='\n' : 1); if (len > 0 && sfwrite(fp, "\n\n", len) != len) failed = 1; if (!failed) sfsync(fp); if (!failed && (sferror(fp) || WS.lasterrno)) failed = 1; if (verboselog) fprintf(verboselog," .. wrote %d newlines to the end%s\n", len, failed ? " FAILED!":""); mw="tail newlines"; if (failed) goto write_failure; } /* End of the file, and MMDF-style ? */ if (mmdf_mode == 1 /* Values 2 and 3 exist on PIPEs only.. */ ) { if (sfprintf(fp,"\001\001\001\001\n") == EOF) { mw="MMDF ending"; goto write_failure; } } } /* !topipe */ /* Flush everything out */ sfsync(fp); /* Raise an error only if it is non-pipe target */ if (!topipe && (sferror(fp) || WS.lasterrno)) { mw="final flushout"; goto write_failure; } #ifdef HAVE_FSYNC if (!topipe) { while (fsync(fdmail) < 0) { if (errno == EINTR || errno == EAGAIN) continue; #if 0 /* No, don't err if fsync() fails. */ mw=5; goto write_failure; #else break; #endif } } #endif time_reset: /* This is actually Linux-thing, we reset the last access time to be that of before we did read the file, last modification into current moment -- At Linux 1.3.8x the system is a bit over-eager to update atime information at O_RDWR -file.. */ time(&st.st_mtime); /* Set the modification time to be now.. */ if (logfp != NULL) { /* [haa@cs.hut.fi] added more info here to catch errors in delivery */ fprintf(logfp, "%s: %d + %d : %s (pid %d user %s)\n", dp->logident, (int)eofindex, (int)((fp ? sftell(fp): 0) - eofindex), file, (int)getpid(), rp->addr->user); fflush(logfp); } if (!topipe && keepatime) { #ifdef HAVE_UTIME struct utimbuf tv; tv.actime = st.st_atime; tv.modtime = st.st_mtime; rc = utime(file, &tv); #else /* !HAVE_UTIMES */ struct timeval tv[2]; tv[0].tv_sec = st.st_atime; tv[1].tv_sec = st.st_mtime; tv[0].tv_usec = tv[1].tv_usec = 0; rc = utimes(file, tv); #endif /* !HAVE_UTIMES */ } if (fp) { /* Discard and close */ zsfclose(fp); /* The pointer is needed later as a marker! (NULL or not) */ } if (WS.buf2) free(WS.buf2); return fp; /* Dummy marker! */ } int program(dp, rp, cmdbuf, user, timestring, uid) struct ctldesc *dp; struct rcpt *rp; const char *cmdbuf; const char *user; const char *timestring; int uid; { int envi, rc, pid, in[2], out[2]; int gid = -1; char *env[40]; const char *s; char *cp, *cpe; int status; struct Zpasswd *pw; Sfio_t *errfp; Sfio_t *fp; time_t starttime, endtime; char safe1[1100]; /* Stack safety buffer zones.. */ char buf[8192]; char safe2[1100]; /* second stack safety buffer */ char errbuf[4*1024]; time(&starttime); cp = safe1; cp = safe2; /* Silence compiler... */ notaryreport(rp->addr->user, NULL, NULL, NULL); envi = 0; env[envi++] = "SHELL=/bin/sh"; env[envi++] = "IFS= \t\n"; while (1) { /* Zone in which: if (cp > cpe) break; statement can be used as buffer overflow safety.. */ cp = buf; *cp = 0; /* Trunc the buf string... */ cpe = buf + sizeof(buf) -2; if ((s = getzenv("PATH")) == NULL) env[envi++] = "PATH=/usr/bin:/bin:/usr/ucb"; else { sprintf(cp, "PATH=%.999s", s); env[envi++] = cp; cp += strlen(cp) + 1; } s = getenv("TZ"); if (s != NULL) { sprintf(cp,"TZ=%.99s", s); env[envi++] = cp; cp += strlen(cp) + 1; } pw = zgetpwuid(uid); if (pw == NULL) { if (verboselog) { fprintf(verboselog,"mailbox: User recipient address privilege code invalid (no user with this uid?): '%s'\n", rp->addr->misc); } sprintf(buf,"x-local; 500 (User recipient address privilege code invalid [no user with this uid?]: '%.200s')", rp->addr->misc); notaryreport(NULL, s_failed, "5.3.0 (User address recipient privilege code invalid)", buf); DIAGNOSTIC(rp, cmdbuf, EX_SOFTWARE, "Bad privilege for a pipe \"%s\"", rp->addr->misc); return EX_SOFTWARE; } else { gid = pw->pw_gid; sprintf(cp, "HOME=%.500s", pw->pw_dir); env[envi++] = cp; cp += strlen(cp) + 1; if (user[0] == 0) sprintf(cp, "USER=%.100s", pw->pw_name); else sprintf(cp, "USER=%.100s", user); env[envi++] = cp; cp += strlen(cp) + 1; } if (STREQ(rp->addr->link->channel,"error")) sprintf(cp, "SENDER=<>"); else sprintf(cp, "SENDER=%.999s", rp->addr->link->user); env[envi++] = cp; cp += strlen(cp) + 1; sprintf(cp, "UID=%d", (int)uid); env[envi++] = cp; cp += strlen(cp) + 1; if (cp > cpe) break; if ((s = getzenv("ZCONFIG")) == NULL) s = ZMAILER_ENV_FILE; sprintf(cp, "ZCONFIG=%.200s", s); env[envi++] = cp; cp += strlen(cp) + 1; if (cp > cpe) break; if ((s = getzenv("MAILBIN")) == NULL) s = MAILBIN; sprintf(cp, "MAILBIN=%.200s", s); env[envi++] = cp; cp += strlen(cp) + 1; if (cp > cpe) break; if ((s = getzenv("MAILSHARE")) == NULL) s = MAILSHARE; env[envi++] = cp; sprintf(cp, "MAILSHARE=%.200s", s); cp += strlen(cp) + 1; if (cp > cpe) break; if (rp->notifyflgs) { char *p = "", *p2 = ","; env[envi++] = cp; sprintf(cp,"NOTIFY="); cp += strlen(cp); if (rp->notifyflgs & _DSN_NOTIFY_NEVER) { strcpy(cp, "NEVER"); p = p2; cp += strlen(cp); } if (rp->notifyflgs & _DSN_NOTIFY_DELAY) { sprintf(cp, "%sDELAY", p); p = p2; cp += strlen(cp); } if (rp->notifyflgs & _DSN_NOTIFY_FAILURE) { sprintf(cp, "%sFAILURE", p); p = p2; cp += strlen(cp); } if (rp->notifyflgs & _DSN_NOTIFY_SUCCESS) { sprintf(cp, "%sSUCCESS", p); p = p2; cp += strlen(cp); } if (rp->notifyflgs & _DSN_NOTIFY_TRACE) { sprintf(cp, "%sTRACE", p); p = p2; cp += strlen(cp); } ++cp; } if (rp->deliverby || rp->deliverbyflgs) { env[envi++] = cp; sprintf(cp,"BY=%ld;",rp->deliverby); cp += strlen(cp); if (rp->deliverbyflgs & _DELIVERBY_R) *cp++ = 'R'; if (rp->deliverbyflgs & _DELIVERBY_N) *cp++ = 'N'; if (rp->deliverbyflgs & _DELIVERBY_T) *cp++ = 'T'; *cp++ = 0; } if (rp->orcpt) { sprintf(cp, "ORCPT=%.999s", rp->orcpt); env[envi++] = cp; cp += strlen(cp) + 1; if (cp > cpe) break; } if (rp->inrcpt) { sprintf(cp, "INRCPT=%.999s", rp->inrcpt); env[envi++] = cp; cp += strlen(cp) + 1; if (cp > cpe) break; } if (rp->infrom) { sprintf(cp, "INFROM=%.999s", rp->infrom); env[envi++] = cp; cp += strlen(cp) + 1; if (cp > cpe) break; } if (rp->ezmlm) { sprintf(cp, "EZMLM=%.999s", rp->ezmlm); env[envi++] = cp; cp += strlen(cp) + 1; if (cp > cpe) break; } if (dp->envid) { sprintf(cp, "ENVID=%.999s", dp->envid); env[envi++] = cp; cp += strlen(cp) + 1; if (cp > cpe) break; } env[envi++] = cp; strcpy(cp, "MSGSPOOLID="); cp += 11; taspoolid(cp, rp->desc->msgmtime, rp->desc->msginonumber); cp += strlen(cp) + 1; if (cp > cpe) break; if (rp->desc->msgfile) { /* Put also the message-id of the message into variables. */ env[envi++] = cp; sprintf(cp, "MESSAGEID=%.199s", rp->desc->msgfile); cp += strlen(cp) + 1; if (cp > cpe) break; } if (verboselog) fprintf(verboselog,"To run a pipe with uid=%d gid=%d cmd='%s'\n", uid, gid, cmdbuf); /* Here the CP should be less than about 5000 bytes into the 8k buffer .. */ break; } env[envi] = NULL; if (cp >= cpe) /* OVERFLOWED THE 8kB BUFFER FOR THE NEW ENVIRONMENT VARIABLES! */ exit(EX_SOFTWARE); /* now we can fork off and run the command... */ if (pipe(in) < 0) { notaryreport(NULL, s_failed, "5.3.0 (out of pipe resources)", "x-local; 500 (out of pipe resources)"); DIAGNOSTIC(rp, cmdbuf, EX_OSERR, "cannot create pipe from \"%s\"", cmdbuf); return EX_OSERR; } if (pipe(out) < 0) { notaryreport(NULL, s_failed, "5.3.0 (out of pipe resources)", "x-local; 500 (out of pipe resources)"); DIAGNOSTIC(rp, cmdbuf, EX_OSERR, "cannot create pipe to \"%s\"", cmdbuf); close(in[0]); close(in[1]); return EX_OSERR; } pid = fork(); if (pid == 0) { /* child */ char * argv[100]; int i; SETGID(gid); SETEGID(gid); SETUID(uid); SETEUID(uid); close(in[0]); close(out[1]); /* its stdout and stderr is the pipe, its stdin is our fdmail */ close(0); dup(out[0]); /* in fd 0 */ close(1); dup(in[1]); /* in fd 1 */ close(2); dup(in[1]); /* in fd 2 */ close(out[0]); close(in[1]); SIGNAL_IGNORE(SIGINT); SIGNAL_IGNORE(SIGHUP); SIGNAL_HANDLE(SIGTERM, SIG_DFL); /* hmm.. must split the command line to inputs for execve(); I have uses for a strict environment without /bin/sh ... [mea] */ cp = (char*)cmdbuf+1; s = strchr(cp,'$'); if (!s) s = strchr(cp,'>'); if (*cp == '/' && s == NULL) { /* Starts with an ABSOLUTE PATH -- at least "/" */ /* ... and does *NOT* contain '$', nor '>' */ i = 0; while (*cp != 0) { while (*cp == ' ') ++cp; if (*cp == '\'') { argv[i] = ++cp; while (*cp != 0 && *cp != '\'') ++cp; if (*cp == '\'') *cp++ = 0; } else if (*cp != 0) argv[i] = cp; ++i; while (*cp != 0 && *cp != ' ') ++cp; if (*cp == ' ') *cp++ = 0; } argv[i] = NULL; if (verboselog) { fprintf(verboselog," argv:"); for (i = 0; argv[i] != NULL; ++i) fprintf(verboselog," [%d]<%s>", i, argv[i]); fprintf(verboselog,"\n"); } execve(argv[0], argv, env); } else { /* Duh, probably something like: "|IFS=' '&&.... " */ /* * Note that argv[0] is set to the command we are running. * That way, we should get some better error messages, at * least more understandable in rejection messages. * Some bourne shells may go into restricted mode if the * stuff to run contains an 'r'. XX: investigate. */ argv[0] = (char*)cmdbuf+1; argv[1] = "-c"; argv[2] = (char*)cmdbuf+1; argv[3] = NULL; execve("/bin/sh", argv, env); /* execle(argv[0], cmdbuf+1,"-c",cmdbuf+1,(char*)NULL,env);*/ execve("/sbin/sh", argv, env); /* execle(argv[0], cmdbuf+1, "-c", cmdbuf+1, (char *)NULL, env); */ } write(2, "Cannot exec '", 13); write(2, argv[0], strlen(argv[0])); write(2, "'\n", 2); _exit(128); } else if (pid < 0) { /* fork failed */ notaryreport(NULL, s_failed, "5.3.0 (fork failure)", "x-local; 500 (fork failure)"); DIAGNOSTIC(rp, cmdbuf, EX_OSERR, "cannot fork", 0); return EX_OSERR; } /* parent */ close(out[0]); close(in[1]); errfp = sfnew(NULL, errbuf, sizeof(errbuf), in[0], SF_READ); /* write the message */ mmdf_mode += 2; eofindex = -1; /* NOT truncatable! */ fp = putmail(dp, rp, out[1], "a", timestring, cmdbuf, uid); /* ``fp'' is dummy marker */ mmdf_mode -= 2; if (fp == NULL) { pid = wait(&status); close(out[1]); zsfclose(errfp); return status; } close(out[1]); /* read any messages from its stdout/err on in[0] */ /* ... having forked and set up the pipe, we quickly continue */ buf[sizeof(buf)-100] = 0; /* Chop it just to make sure */ if (csfgets(buf, (sizeof buf) - 100, errfp) < 0) buf[0] = '\0'; else if ((cp = strchr(buf, '\n')) != NULL) *cp = '\0'; pid = wait(&status); zsfclose(errfp); cp = buf + strlen(buf); /* Union or not, we treat it as if it were an integer.. */ if (status == 0) { rc = EX_OK; if (cp != buf) *cp++ = ' '; strcpy(cp, "[Exit Status 0]"); } else if ((status & 0177) > 0) { if (cp != buf) *cp++ = ' '; sprintf(cp, "[signal %d", status & 0177); if (status & 0200) strcat(cp, " (Core dumped)"); strcat(cp, "]"); rc = EX_TEMPFAIL; } else if ((rc = (status >> 8) & 0377) > 0) { if (cp != buf) *cp++ = ' '; sprintf(cp, "[exit status %d]", rc); /* We report following status codes to the system as is, all the rest are treated as EX_TEMPFAIL, and retried.. */ if (rc != EX_NOPERM && rc != EX_UNAVAILABLE && rc != EX_NOHOST && rc != EX_NOUSER && rc != EX_DATAERR && rc != EX_OK ) rc = EX_TEMPFAIL; } if (verboselog) fprintf(verboselog,"Run result: '%s'\n", buf); time(&endtime); notary_setxdelay((int)(endtime-starttime)); if (rc == EX_OK) { notaryreport(NULL, s_delivered, "2.2.0 (Delivered successfully)", "x-local; 250 (Delivered successfully)"); } else { char buf2[sizeof(buf)+10]; sprintf(buf2,"x-local; 500 (%s)", buf); notaryreport(NULL, s_failed, "5.3.0 (subprogram non-zero termination code)", buf2); } DIAGNOSTIC(rp, cmdbuf, rc, "%s", buf); return rc; } static void mkhashpath __((char *, const char *)); static void mkhashpath(s, uname) char *s; const char *uname; { extern long pjwhash32 __((const char *)); extern long crc32 __((const char *)); if (pjwhashes) { int h = pjwhash32(uname); switch (pjwhashes) { case 1: h %= 26; sprintf(s,"%c/", ('A' + h)); break; default: h %= (26*26); sprintf(s,"%c/%c/", ('A' + (h / 26)), ('A' + (h % 26))); break; } } if (crchashes) { int h = crc32(uname); switch (crchashes) { case 1: h %= 26; sprintf(s,"%c/", ('A' + h)); break; default: h %= (26*26); sprintf(s,"%c/%c/", ('A' + (h / 26)), ('A' + (h % 26))); break; } } if (dirhashes) { switch (dirhashes) { case 1: sprintf(s,"%c/",uname[0]); s += 2; break; case 2: if (uname[1]) sprintf(s,"%c/%c/",uname[0],uname[1]); else /* Err.... One char userid ?? TROUBLE TIME! */ sprintf(s,"%c/%c/",uname[0],uname[0]); s += 4; break; default: break; } } strcat(s, uname); } /* * creatembox - see if we can create the mailbox */ int creatembox(rp, uname, filep, uid, gid, pw) struct rcpt *rp; const char *uname; char **filep; uid_t *uid; gid_t *gid; struct Zpasswd *pw; { const char **maild; int fd = -1; char *s; *uid = pw->pw_uid; *gid = pw->pw_gid; for (maild = &maildirs[0]; *maild != 0; maild++) { if (*filep != NULL) free(*filep); *filep = NULL; if (strchr(*maild,'%')) { *filep = emalloc(2048); if (fmtmbox(*filep,2048,*maild,uname,pw)) { (*filep)[70]='\0'; strcat(*filep,"..."); notaryreport(rp->addr->user, s_failed, "5.3.1 (too long path for user spool mailbox file)", "x-local; 566 (too long path for user spool mailbox file)"); DIAGNOSTIC(rp, *filep, EX_CANTCREAT, "Too long path \"%s\"", *filep); free(*filep); *filep = NULL; return 0; } /* FIXME! Need to create intermediate directories here */ } else { *filep = emalloc(8+2+strlen(*maild)+strlen(uname)); sprintf(*filep, "%s/", *maild); s = *filep + strlen(*filep); mkhashpath(s, uname); } fd = createfile(rp, *filep, *uid, 1); if (fd >= 0) { #ifdef HAVE_FCHOWN fchown(fd, *uid, *gid); #else /* !HAVE_FCHOWN */ chown(*filep, *uid, *gid); #endif /* HAVE_FCHOWN */ close(fd); { struct stat st; #ifdef HAVE_UTIME struct utimbuf tv; stat(*filep,&st); /* This by all probability will not fail.. */ tv.actime = 0; /* never read */ tv.modtime = st.st_mtime; utime(*filep, &tv); #else struct timeval tv[2]; stat(*filep,&st); /* This by all probability will not fail.. */ tv[0].tv_sec = 0; /* never read */ tv[1].tv_sec = st.st_mtime; tv[0].tv_usec = tv[1].tv_usec = 0; utimes(*filep, tv); #endif } return 1; } if (errno == EEXIST) { /* It exists -- probably a race between two file creators caused this */ return 1; } } /* assert *filep != NULL */ if (fd == -1) { notaryreport(rp->addr->user, s_failed, "5.3.1 (can't create user spool mailbox file)", "x-local; 566 (can't create user spool mailbox file)"); DIAGNOSTIC(rp, *filep, EX_CANTCREAT, "can't create \"%s\"", *filep); } /* otherwise the message was printed by createfile() */ free(*filep); *filep = NULL; return 0; } int createfile(rp, file, iuid, ismbox) struct rcpt *rp; const char *file; int iuid, ismbox; { int fd, i = 0, saverrno; struct stat st; char *cp, msg[BUFSIZ]; uid_t uid = iuid; int mailmode = MAILMODE; if (verboselog) fprintf(verboselog, "To create a file with euid=%d egid=%d file='%s', mode=%o\n", (int)geteuid(), (int)getegid(), file, mailmode); fd = open(file, O_RDWR|O_CREAT|O_EXCL, mailmode); if (fd < 0) { saverrno = errno; if (verboselog) fprintf(verboselog, " ... failed, errno = %d (%s)\n", saverrno, strerror(saverrno)); if (errno == EEXIST) return -3; cp = strrchr(file, '/'); if (cp != NULL) { *cp = '\0'; if (exstat(rp, file, &st, stat) < 0) { *cp = '/'; sprintf(msg,"x-local; 566 (*INVOCATION BUG* Can't create user spool mailbox file: \"%s\", Directory stat() error: %s)", file,strerror(errno)); *cp = 0; notaryreport(rp->addr->user, s_failed, "5.3.5 (Something wrong, bad config ?)", msg); DIAGNOSTIC(rp, file, i, "stat failed on %s", file); return -2; } *cp = '/'; if (ismbox && st.st_mode & 020) { /* group writable? */ if (!setupuidgid(rp, uid, st.st_gid)) { notaryreport(rp->addr->user, s_failed, "5.3.5 (The mailbox configuration is faulty)", "x-local; 566 (*INVOCATION BUG* The mailbox directory is group writable, but can't change my gid to it)"); DIAGNOSTIC(rp, file, i, "failed changing group id to create file in \"%s\"", file); return -2; } mailmode += 0060; } } if (verboselog) fprintf(verboselog, "To create a file with euid=%d egid=%d file='%s' mode=%o\n", (int)geteuid(), (int)getegid(), file, mailmode); fd = open(file, O_RDWR|O_CREAT|O_EXCL, mailmode); if (fd < 0) { saverrno = errno; if (verboselog) fprintf(verboselog, " ... failed, errno = %d (%s)\n", saverrno, strerror(saverrno)); setrootuid(rp); errno = saverrno; if (errno == EEXIST) return -3; } else { setrootuid(rp); return fd; } } else return fd; /* No system calls in this spot -- must preserve errno */ if (TEMPFAIL(saverrno)) /* temporary error? */ i = EX_TEMPFAIL; else if (saverrno != EACCES && saverrno != ENOENT) i = EX_UNAVAILABLE; else if (saverrno == EACCES) i = EX_NOPERM; else /* if (saverrno == ENOENT) */ i = EX_CANTCREAT; /* convoluted to maintain 4 arguments to DIAGNOSTIC */ sprintf(msg, "x-local; 566 (error [%s] creating \"%%s\")", strerror(saverrno)); notaryreport(rp->addr->user, s_failed, "5.3.0 (Other mailsystem error)", msg); sprintf(msg, "error (%s) creating \"%%s\"", strerror(saverrno)); DIAGNOSTIC(rp, file, i, msg, file); errno = saverrno; return -2; } /* * setupuidgid - set the euid and gid of the process */ int setupuidgid(rp, uid, gid) struct rcpt *rp; int uid; int gid; { setrootuid(rp); if (gid == -3) { /* MAGIC! Ask GID of the UID of 'uid' */ struct Zpasswd *pw = zgetpwuid(uid); if (pw != NULL) gid = pw->pw_gid; } if (gid >= 0) if (SETGID(gid) < 0) { DIAGNOSTIC(rp, "", EX_OSERR, "can't setgid to %d", (int)gid); return 0; } if (*(rp->addr->user) == TO_FILE || *(rp->addr->user) == TO_PIPE) uid = atol(rp->addr->misc); if (SETEUID(uid) < 0) { if (uid < 0 && atol(rp->addr->misc) < 0) { /* use magic non-sense +ve uid < MAXSHORT */ rp->addr->misc = NONUIDSTR; return setupuidgid(rp, (uid_t)atoi(NONUIDSTR), gid); } DIAGNOSTIC(rp, "", EX_OSERR, "can't seteuid to %d", (int)uid); return 0; } currenteuid = uid; return 1; } /* * exists - see if a mail box exists. Looks at the exit status * to guess whether failure is due to nfs server being * down. */ char * exists(maildir, uname, pw, rp) const char *maildir; const char *uname; struct Zpasswd *pw; struct rcpt *rp; { char *file, *s; if (strchr(maildir, '%') != NULL) { file = emalloc(2048); if (fmtmbox(file, 2048, maildir, uname, pw)) { file[70]='\0'; strcat(file,"..."); DIAGNOSTIC(rp, file, EX_SOFTWARE, "mailbox path does not fit in buffer \"%s\"", file); free(file); return NULL; } } else { file = emalloc(8+strlen(maildir)+strlen(uname)); sprintf(file, "%s/", maildir); s = file + strlen(file); mkhashpath(s, uname); } if (access(file, F_OK) == 0) { /* file exists */ rp->status = EX_OK; return file; } if (TEMPFAIL(errno)) { /* temporary error? */ DIAGNOSTIC(rp, file, EX_TEMPFAIL, "error accessing \"%s\"", file); } else if (errno == ENOENT || errno == EACCES) /* really not there? */ rp->status = EX_OK; else { /* who knows? */ DIAGNOSTIC(rp, file, EX_SOFTWARE, "unexpected error accessing \"%s\"", file); } free(file); return NULL; } /* * setrootuid - set us back to root uid */ void setrootuid(rp) struct rcpt *rp; { if (currenteuid != 0) { if (SETEUID(0) < 0) DIAGNOSTIC(rp, "", EX_OSERR, "can't reset uid to root", 0); } currenteuid = 0; } /* * appendlet - append letter to file pointed at by fd * * Return the last character written.. * */ int appendlet(dp, rp, WS, file, ismime) struct ctldesc *dp; struct rcpt *rp; struct writestate *WS; int ismime; const char *file; { if (ta_use_mmap <= 0) { register int i; register int bufferfull; Sfio_t *mfp; int mfd = dp->msgfd; char sfio_buf[16*1024]; if (ismime) { /* can we use cache of message body data ? */ /* Split it to lines.. */ if (readalready > 0) { int linelen, readidx = 0; const char *s0 = dp->let_buffer, *s; while (readidx < readalready) { s = s0; linelen = 0; while (*s != 0 && *s != '\n' && readidx < readalready) { ++s; ++linelen; ++readidx; } if (*s == '\n' && readidx < readalready) { ++s; ++linelen; ++readidx; } if (writemimeline(WS, s0, linelen) != linelen) { #if 0 DIAGNOSTIC3(rp, file, EX_IOERR, "write to \"%s\" failed(1); %s", file, strerror(WS->lasterrno)); #endif return -256; } s0 = s; } rp->status = EX_OK; return WS->lastch; } lseek(mfd, (off_t)dp->msgbodyoffset, SEEK_SET); mfp = sfnew(NULL, sfio_buf, sizeof(sfio_buf), mfd, SF_READ); #define MFPCLOSE zsfsetfd(mfp,-1); zsfclose(mfp); /* we are assuming to be positioned properly at the start of the message body */ bufferfull = 0; /* We really can't use the 'let_buffer' cache here */ readalready = 0; i = 0; for (;;) { i = csfgets((void*)dp->let_buffer, dp->let_buffer_size, mfp); if (i == EOF) break; /* It MAY be malformed -- if it has a BUFSIZ length line in it, IT CAN'T BE MIME :-/ */ if (i == dp->let_buffer_size && dp->let_buffer[dp->let_buffer_size-1] != '\n') ismime = 0; /* Ok, write the line */ if (writemimeline(WS, dp->let_buffer, i) != i) { #if 0 DIAGNOSTIC3(rp, file, EX_IOERR, "write to \"%s\" failed(2); %s", file, strerror(WS->lasterrno)); #endif MFPCLOSE; return -256; } } if (i == EOF && !sfeof(mfp) && !sferror(mfp)) { DIAGNOSTIC(rp, file, EX_IOERR, "read error from message file", 0); MFPCLOSE; return -256; } MFPCLOSE; } else { /* is NOT MIME message.. */ /* can we use cache of message body data ? */ if (readalready > 0) { if (writebuf(WS, dp->let_buffer, readalready) != readalready) { #if 0 DIAGNOSTIC3(rp, file, EX_IOERR, "write to \"%s\" failed(3); %s", file, strerror(WS->lasterrno)); #endif return -256; } rp->status = EX_OK; return WS->lastch; } /* Make sure we are properly positioned at the start of the message body */ lseek(mfd, dp->msgbodyoffset, SEEK_SET); bufferfull = 0; while (1) { i = read(mfd, (void*)dp->let_buffer, dp->let_buffer_size); if (i == 0) break; if (i < 0) { DIAGNOSTIC(rp, file, EX_IOERR, "read error from message file", 0); readalready = 0; return -256; } if (writebuf(WS, dp->let_buffer, i) != i) { #if 0 DIAGNOSTIC3(rp, file, EX_IOERR, "write to \"%s\" failed(4); %s", file, strerror(WS->lasterrno)); #endif readalready = 0; return -256; } readalready = i; bufferfull++; } if (bufferfull > 1) /* not all in memory, need to reread */ readalready = 0; } } else { /* HAVE_MMAP -- get the input from mmap():ed memory area.. */ const char *s; s = dp->let_buffer + dp->msgbodyoffset; if (ismime) { int i; while (s < dp->let_end) { const char *s2 = s; i = 0; while (s2 < dp->let_end && *s2 != '\n') ++s2, ++i; if (s2 < dp->let_end && *s2 == '\n') ++s2, ++i; if (writemimeline(WS, s, i) != i) { #if 0 DIAGNOSTIC3(rp, file, EX_IOERR, "write to \"%s\" failed(5); %s", file, strerror(WS->lasterrno)); #endif return -256; } s = s2; } } else { if (writebuf(WS, s, dp->let_end - s) != (dp->let_end - s)) { #if 0 DIAGNOSTIC3(rp, file, EX_IOERR, "write to \"%s\" failed(6); %s", file, strerror(WS->lasterrno)); #endif return -256; } } } return WS->lastch; } /* * estat - stat with error checking */ int exstat(rp, file, stbufp, statfcn) struct rcpt *rp; const char *file; struct stat *stbufp; int (*statfcn) __((const char *, struct stat *)); { if (statfcn(file, stbufp) < 0) { if (TEMPFAIL(errno)) rp->status = EX_TEMPFAIL; else rp->status = EX_SOFTWARE; DIAGNOSTIC(rp, file, rp->status, "can't stat \"%s\"", file); return -1; } return 0; } #if defined(HAVE_SOCKET) void biff(hostname, username, offset) const char *hostname, *username; long offset; { int f; spkey_t symid; struct hostent *hp; struct sockaddr_in biffaddr, *bap; struct spblk *spl; static struct sptree *spt_hosts = NULL; char *buf; #define BIFF_PORT 512 /* A well-known UDP port.. */ /* Could do this with getservbyname() - but sometimes (with NIS on loaded Suns) it takes AGES... Hardwiring works as well. */ symid = symbol((const void*)hostname); if (spt_hosts == NULL) spt_hosts = sp_init(); spl = sp_lookup(symid, spt_hosts); if (spl == NULL) { if ((hp = gethostbyname(hostname)) == NULL) return; memset((char *)&biffaddr, 0, sizeof biffaddr); biffaddr.sin_family = hp->h_addrtype; hp_init(hp); if (hp_getaddr() && *hp_getaddr()) memcpy((char *)&biffaddr.sin_addr, *hp_getaddr(), hp->h_length); biffaddr.sin_port = htons(BIFF_PORT); bap = (struct sockaddr_in *)emalloc(sizeof(struct sockaddr_in)); *bap = biffaddr; sp_install(symid, (void*) bap, 0, spt_hosts); } else biffaddr = *(struct sockaddr_in *)spl->data; buf = emalloc(3+strlen(username)+20); sprintf(buf, "%s@%ld\n", username, offset); f = socket(PF_INET, SOCK_DGRAM, 0); sendto(f, buf, strlen(buf)+1, 0, (struct sockaddr *)&biffaddr, sizeof biffaddr); close(f); free(buf); } #endif /* BIFF || RBIFF */ #if defined(HAVE_SOCKET) && defined(HAVE_PROTOCOLS_RWHOD_H) static int readrwho() { struct whod wd; struct outmp outmp; #define NMAX sizeof(outmp.out_name) #define LMAX sizeof(outmp.out_line) int cc, f, n; spkey_t symid; register struct whod *w = &wd; register struct whoent *we; long now; char username[NMAX+1]; DIR *dirp; struct dirent *dp; struct userhost *uhp; struct spblk *spl; if (spt_users == NULL) return 0; now = time(NULL); if (chdir(RWHODIR) || (dirp = opendir(".")) == NULL ) { /* BE SILENT -- failing RBIFF is no fault! */ return 0; /* perror(RWHODIR); exit(EX_OSFILE); */ } while ((dp = readdir(dirp))) { if (dp->d_ino == 0 || strncmp(dp->d_name, "whod.", 5) || (f = open(dp->d_name, 0)) < 0 ) continue; cc = read(f, (char *)&wd, sizeof (struct whod)); if (cc < WHDRSIZE) { close(f); continue; } if (now - w->wd_recvtime > 5 * 60) { close(f); continue; } cc -= WHDRSIZE; we = w->wd_we; for (n = cc / sizeof (struct whoent); n > 0; n--,we++){ /* make sure name null terminated */ strncpy(username, we->we_utmp.out_name, NMAX); username[NMAX] = 0; /* add to data structure */ symid = symbol((void*)username); spl = sp_lookup(symid, spt_users); if (spl == NULL) continue; uhp = (struct userhost *)spl->data; if (uhp != NULL && strcmp(uhp->hostname, w->wd_hostname) == 0) continue; uhp = (struct userhost *)emalloc(sizeof (struct userhost)); uhp->next = (struct userhost *)spl->data; uhp->hostname = strdup(w->wd_hostname); spl->data = (void *)uhp; } close(f); } closedir(dirp); return 1; } void rbiff(nbp) struct biffer *nbp; { struct spblk *spl; struct userhost *uhp; spkey_t symid; symid = symbol((void*)(nbp->user)); if ((spl = sp_lookup(symid, spt_users)) == NULL) return; for (uhp = (struct userhost *)spl->data; uhp != NULL; uhp = uhp->next) biff(uhp->hostname, nbp->user, nbp->offset); } #endif /* RBIFF */ /* * Writebuf() is like write(), except any instances of "From " at the * beginning of a line cause insertion of '>' at that point. * (Except with MMDF_MODE!) */ int writebuf(WS, buf, len) struct writestate *WS; const char *buf; int len; { register const char *cp; register int n; int tlen; register char expect; char *fromp; /* -------------------------------------------- */ /* Non-MIME processing */ expect = WS->expect; fromp = WS->fromp; WS->lastch = buf[len-1]; for (cp = buf, n = len, tlen = 0; n > 0; --n, ++cp) { register int c = *cp; ++tlen; /* This is our error analysis, all SFIO calls below are without any... */ if (sferror(WS->fp) || WS->lasterrno != 0) { tlen = -1; break; } if (c == '\n') { expect = mmdf_mode ? 0 : 'F'; fromp = WS->frombuf; *fromp = 0; sfputc(WS->fp,c); } else if (expect != '\0') { if (c == expect) { *fromp++ = c; *fromp = 0; switch (expect) { case 'F': expect = 'r'; break; case 'r': expect = 'o'; break; case 'o': expect = 'm'; break; case 'm': expect = ' '; break; case ' ': /* Write the separator, and the word.. */ sfwrite(WS->fp, ">From ", 6); /* anticipate future instances */ expect = '\0'; break; } } else { expect = '\0'; if (WS->frombuf[0] != 0) sfprintf(WS->fp, "%s", WS->frombuf); WS->frombuf[0] = 0; fromp = WS->frombuf; sfputc(WS->fp, c); } } else { /* expect == '\0'; */ sfputc(WS->fp, c); } } WS->expect = expect; WS->fromp = fromp; if (tlen >= 0) return len; /* It worked ok */ return -1; /* errno is correct if tlen == -1 ! */ } int writemimeline(WS, buf, len) struct writestate *WS; const char *buf; int len; { register char *s; register const char *cp; register int n; int tlen = 0, i = 0; char *buf2; if (WS->buf2len < len || WS->buf2 == NULL) { WS->buf2 = (char*)realloc(WS->buf2, len+1); WS->buf2len = len; } buf2 = WS->buf2; #if 0 /* Not yet */ /* ------------------------------------------------------------ */ /* MIME format processing, the input is LINE BY LINE */ if (mime_boundary != NULL && len > mime_boundary_len+2 && buf[0] == '-' && buf[1] == '-' && strncmp(buf+2, mime_boundary, mime_boundary_len) == 0) { /* ------------------------------------------------------------ */ /* XX: process MIME boundary! */ WS->lastch = buf[len-1]; return fwrite(buf, len, WS->fp); } else #endif if (convert_qp) { /* ------------------------------------------------------------ */ /* Process Q-P chars of this line */ int qp_hex = 0; int qp_chrs = 0; for (cp = buf, s = buf2, n = len; n > 0; --n, ++cp) { register int c = *cp; /* At first, convert the possible QP character.. */ if (!qp_chrs && c == '=') { qp_chrs = 2; qp_hex = 0; continue; /* '=' starts something */ } if (qp_chrs && c == '\n') { qp_chrs = 0; /* It was "=[ \t]*\n", which was thrown away.*/ continue; } if (qp_chrs == 2 && (c == ' ' || c == '\t')) continue; /* It is "=[ \t]*\n", throw it away */ /* Ok, this may be of =HexHex ? */ if (qp_chrs && ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'))) { qp_hex <<= 4; if (c >= '0' && c <= '9') qp_hex += (c - '0'); if (c >= 'A' && c <= 'F') qp_hex += (c - 'A' + 10); if (c >= 'a' && c <= 'f') qp_hex += (c - 'a' + 10); --qp_chrs; if (!qp_chrs) c = qp_hex; else continue; /* Go collect another digit */ } else if (qp_chrs) qp_chrs = 0; /* Failed a HEX check.. */ /* Now we have a converted character at `c', save it! */ *s++ = c; ++tlen; } /* Uhh... Stored the conversion result into buf2[], length tlen */ if (!mmdf_mode && tlen >= 5 && buf2[0] == 'F' && memcmp(buf2,"From ",5)==0) if (WS->lastch >= 256 || WS->lastch == '\n') if (sfputc(WS->fp, '>') == EOF) return -1; if (tlen > 0) { i = sfwrite(WS->fp, buf2, tlen); WS->lastch = buf2[tlen-1]; } else i = 0; if (i != tlen) return -1; return len; /* Return the incoming length, NOT the true length! */ } /* ------------------------------------------------------------ */ /* Well, no other processings known.. */ if (!mmdf_mode && buf[0] == 'F' && strncmp(buf,"From ",5)==0) if (WS->lastch >= 256 || WS->lastch == '\n') if (sfputc(WS->fp,'>') == EOF) return -1; WS->lastch = buf[len-1]; return sfwrite(WS->fp, buf, len); } /* [mea] See if we have a MIME TEXT/PLAIN message encoded with QUOTED-PRINTABLE... */ int qptext_check(rp) struct rcpt *rp; { /* "Content-Transfer-Encoding: 8BIT" */ const char **hdrs = *((const char ***)rp->newmsgheader); const char *hdr; int cte = 0; is_mime = 0; if (hdrs == NULL) return 0; /* Oh ?? */ while (hdrs && *hdrs && (!is_mime || !cte)) { hdr = *hdrs; if (!cte && cistrncmp(hdr,"Content-Transfer-Encoding:",26)==0) { hdr += 26; while (*hdr == ' ' || *hdr == '\t') ++hdr; if (cistrncmp(hdr,"QUOTED-PRINTABLE",16)==0) cte = 1; } else if (!is_mime && cistrncmp(hdr,"MIME-Version:",13)==0) is_mime = 1; ++hdrs; } if (!is_mime) cte = 0; return cte; } static const char * find_return_receipt_hdr (rp) struct rcpt *rp; { const char **ptr, *hdr; for (ptr = *((const char ***)rp->newmsgheader); *ptr != NULL; ptr++) { if (CISTREQN(*ptr, "Return-Receipt-To:", 18)) break; } if (*ptr == NULL) return (NULL); hdr = *ptr + 18; while (*hdr != '\0' && isspace(*(unsigned char *)hdr)) hdr++; return(hdr); } static void encodeXtext __((Sfio_t *, const char *)); static void encodeXtext(fp,str) Sfio_t *fp; const char *str; { while (*str) { u_char c = *str; if ('!' <= c && c <= '~' && c != '+' && c != '=') sfputc(fp,c); else sfprintf(fp,"+%02X",c); ++str; } } static void decodeXtext(mfp,xtext) Sfio_t *mfp; const char *xtext; { for (;*xtext;++xtext) { if (*xtext == '+') { int c = '?'; sscanf(xtext+1,"%02X",&c); sfputc(mfp,c); if (*xtext) ++xtext; if (*xtext) ++xtext; } else sfputc(mfp,*xtext); } } static const char *dfltform[7] = { "Subject: Returned mail: Return receipt", "MIME-Version: 1.0", "Priority: junk", "Content-Type: multipart/report; report-type=delivery-status;", "", "Your mail message has been delivered properly to the following recipients:", NULL }; static void return_receipt (dp, retrecptaddr, uidstr) struct ctldesc *dp; const char *retrecptaddr; const char *uidstr; { char buf[BUFSIZ]; const char **cpp, *mailshare, *mfpath; Sfio_t *mfp, *efp; struct rcpt *rp; int n; char boundarystr[400]; struct Zpasswd *pw; int uid; struct stat stb; const char *username = "unknown"; char *retaddr = strsave(retrecptaddr); char *s; while ((s = strchr(retaddr, '\n'))) *s = ' '; while ((s = strchr(retaddr, '\r'))) *s = ' '; while ((s = strchr(retaddr, '\t'))) *s = ' '; uid = atoi(uidstr); pw = zgetpwuid(uid); if (pw) username = pw->pw_name; mfp = sfmail_open(MSG_RFC822); if (mfp == NULL) { for (rp = dp->recipients; rp != NULL; rp = rp->next) DIAGNOSTIC(rp, "", EX_TEMPFAIL, "sfmail_open failure", 0); warning("Cannot open mail file!"); return; } sfprintf(mfp, "channel error\n"); rp = dp->recipients; /* copy To: from return-receipt address */ sfprintf(mfp, "todsn NOTIFY=NEVER ORCPT=rfc822;"); encodeXtext(mfp, retaddr); sfprintf(mfp, "\nto %s\n",retaddr); sfprintf(mfp, "env-end\n"); sfprintf(mfp, "To: %s\n", retaddr); sfprintf(mfp, "From: Automatically on behalf of the user <%s>\n", username); free(retaddr); /* copy error message file itself */ mailshare = getzenv("MAILSHARE"); if (mailshare == NULL) mailshare = MAILSHARE; mfpath = emalloc(3+strlen(mailshare)+strlen(FORMSDIR) +strlen(RETURN_RECEIPT_FORM)); sprintf((char*)mfpath, "%s/%s/%s", mailshare, FORMSDIR, RETURN_RECEIPT_FORM); efp = sfopen(NULL, mfpath, "r"); free((void*)mfpath); if (efp) { char *dom = mydomain(); /* transports/libta/buildbndry.c */ struct stat stbuf; fstat(sffileno(mfp),&stbuf); taspoolid(boundarystr, stbuf.st_ctime, (long)stbuf.st_ino); strcat(boundarystr, "=_/return-receipt/"); strcat(boundarystr, dom); } if (efp) { int inhdr = 1; buf[sizeof(buf)-1] = 0; while (csfgets(buf,sizeof(buf)-1,efp) >= 0) { if (strncmp(buf,"HDR",3)==0) { sfprintf(mfp, "%s", buf+4); } else if (strncmp(buf,"SUB",3)==0) { sfprintf(mfp, "%s", buf+4); } else { if (inhdr) { inhdr = 0; sfprintf(mfp,"MIME-Version: 1.0\n"); sfprintf(mfp,"Content-Type: multipart/report; report-type=delivery-status;\n"); sfprintf(mfp,"\tboundary=\"%s\"\n\n\n",boundarystr); sfprintf(mfp, "--%s\n", boundarystr); sfprintf(mfp, "Content-Type: text/plain\n"); } sfprintf(mfp, "%s", buf); } } /* ... while() ends.. */ sfclose(efp); } else { for (cpp = dfltform; *cpp != NULL; ++cpp) if (*cpp[0] == 0) { sfprintf(mfp, "\tboundary=\"%s\"\n\n", boundarystr); sfprintf(mfp, "--%s\n", boundarystr); sfprintf(mfp, "Content-Type: text/plain\n\n"); } else sfprintf(mfp, "%s\n", *cpp); } /* print out errors in standard format */ for (rp = dp->recipients; rp != NULL; rp = rp->next) { sfprintf(mfp, "\t%s\n", rp->addr->user); } sfprintf(mfp, "\n--%s\n", boundarystr); sfprintf(mfp, "Content-Type: message/delivery-status\n\n"); if (mydomain() != NULL) { sfprintf(mfp, "Reporting-MTA: dns;%s\n", mydomain() ); } else { sfprintf(mfp, "Reporting-MTA: x-local-hostname; -unknown-\n"); } if (dp->envid != NULL) { sfprintf(mfp, "Original-Envelope-Id: "); decodeXtext(mfp,dp->envid); sfputc(mfp, '\n'); } /* rfc822date() returns a string with trailing newline! */ fstat(dp->msgfd,&stb); sfprintf(mfp, "Arrival-Date: %s", rfc822date(&stb.st_mtime)); sfprintf(mfp, "\n"); for (rp = dp->recipients; rp != NULL; rp = rp->next) { if (rp->orcpt != NULL) { sfprintf(mfp, "Original-Recipient: "); decodeXtext(mfp,rp->orcpt); sfprintf(mfp, "\n"); } sfprintf(mfp, "Final-Recipient: X-LOCAL;%s\n", rp->addr->user); sfprintf(mfp, "Action: delivered\n"); sfprintf(mfp, "Status: 2.2.0 (delivered successfully)\n"); sfprintf(mfp, "Diagnostic-Code: smtp; 250 ('%s' delivered)\n", rp->addr->user ); sfprintf(mfp, "\n"); } sfprintf(mfp, "--%s\n", boundarystr); sfprintf(mfp, "Content-Type: message/rfc822\n\n"); rp = dp->recipients; /* write the (new) headers with local "Received:"-line.. */ swriteheaders(rp, mfp, "\n", 0, 0, NULL); sfprintf(mfp, "\n"); sfprintf(mfp, "--%s--\n", boundarystr); if (sferror(mfp)) { sfmail_abort(mfp); n = EX_IOERR; } else if (sfmail_close(mfp) == EOF) n = EX_IOERR; else n = EX_OK; for (rp = dp->recipients; rp != NULL; rp = rp->next) DIAGNOSTIC(rp, "", n, (char *)NULL, 0); }