/* * Copyright 1988 by Rayan S. Zachariassen, all rights reserved. * This will be free software, but only when it is finished. * Copyright 1994-2000,2002-2003 by Matti Aarnio -- MIME processings, * etc. */ #define DefCharset "ISO-8859-1" #include "hostenv.h" #include #include #include "zmsignal.h" #include #include #include #include #include "ta.h" #include "mail.h" #include "zmalloc.h" #include "zsyslog.h" #ifdef HAVE_UNISTD_H #include #endif #include #ifdef HAVE_LOCALE_H #include #endif #include "libz.h" #include "libc.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 #ifndef WEXITSTATUS # define WEXITSTATUS(s) (((s) >> 8) & 0377) #endif #ifndef WSIGNALSTATUS # define WSIGNALSTATUS(s) ((s) & 0177) #endif #ifndef SEEK_SET #define SEEK_SET 0 #endif /* !SEEK_SET */ /* as in: SKIPWHILE(isascii,cp) */ #define SKIPSPACE(Y) while (*Y == ' ' || *Y == '\t' || *Y == '\n') ++Y #define SKIPTEXT(Y) while (*Y && *Y != ' ' && *Y != '\t' && *Y != '\n') ++Y #define FROM_ "From " #ifndef MAXHOSTNAMELEN #define MAXHOSTNAMELEN 64 #endif /* MAXHOSTNAMELEN */ char uucpname[MAXHOSTNAMELEN+1]; const char *progname; int readalready = 0; /* does buffer contain valid message data? */ int mimeqpnarrow = 0; /* Can't send TAB thru without MIME-QP */ FILE *logfp = NULL; int maxwidth = 0; int can_8bit = 0; /* Can do 8-bit stuff! */ int decode_qp = 0; int keep_header8 = 0; /* Don't do "MIME-2" to the headers */ int D_alloc = 0; /* Memory debugging */ const char *defcharset; extern RETSIGTYPE sigpipe(); int gotsigpipe = 0; RETSIGTYPE sigpipe(sig) int sig; { gotsigpipe = 1; SIGNAL_HANDLE(SIGPIPE, sigpipe); } struct maildesc { char *name; long flags; char *command; #define MD_ARGVMAX 20 char *argv[MD_ARGVMAX]; }; extern RETSIGTYPE wantout(); #ifndef MALLOC_TRACE extern univptr_t emalloc(); extern univptr_t erealloc(); #endif extern char *optarg; extern int optind; extern int getmyuucpname(); extern struct maildesc *readsmcf __((char *file, char *mailer)); extern void prversion(); extern void process __((struct ctldesc *dp, struct maildesc *mp, FILE *verboselog)); extern void deliver __((struct ctldesc *dp, struct maildesc *mp, struct rcpt *startrp, struct rcpt *endrp, FILE *verboselog)); extern int appendlet __((struct ctldesc *dp, struct maildesc *mp, FILE *fp, FILE *verboselog, int convertmode)); extern int writebuf __((struct maildesc *mp, FILE *fp, const char *buf, int len)); extern time_t time(); #ifndef strchr extern char *strchr(), *strrchr(); #endif 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 decodeXtext(mfp,xtext) FILE *mfp; const char *xtext; { for (;*xtext;++xtext) { if (*xtext == '+') { int c = '?'; sscanf(xtext+1,"%02X",&c); fputc(c, mfp); if (*xtext) ++xtext; if (*xtext) ++xtext; } else fputc(*xtext, mfp); } } static void MIBcountCleanup __((void)) { MIBMtaEntry->tasmcm.TaProcCountG -= 1; } static void SM_MIB_diag(rc) const int rc; { switch (rc) { case EX_OK: /* OK */ MIBMtaEntry->tasmcm.TaRcptsOk ++; break; case EX_TEMPFAIL: case EX_IOERR: case EX_OSERR: case EX_CANTCREAT: case EX_SOFTWARE: case EX_DEFERALL: /* DEFER */ MIBMtaEntry->tasmcm.TaRcptsRetry ++; break; case EX_NOPERM: case EX_PROTOCOL: case EX_USAGE: case EX_NOUSER: case EX_NOHOST: case EX_UNAVAILABLE: default: /* FAIL */ MIBMtaEntry->tasmcm.TaRcptsFail ++; break; } } extern int check_7bit_cleanness __((struct ctldesc *dp)); extern int writemimeline __(( struct maildesc *mp, FILE *fp, const char *buf, int len, int convertmode)); #define MO_FFROMFLAG 0x00001 #define MO_RFROMFLAG 0x00002 #define MO_NORESETUID 0x00004 #define MO_STRIPQUOTES 0x00008 #define MO_MANYUSERS 0x00010 #define MO_RETURNPATH 0x00020 #define MO_UNIXFROM 0x00040 #define MO_HIDDENDOT 0x00080 /* SMTP dot-duplication */ #define MO_ESCAPEFROM 0x00100 #define MO_STRIPHIBIT 0x00200 #define MO_REMOTEFROM 0x00400 #define MO_CRLF 0x00800 #define MO_BSMTP 0x01000 /* BSMTP-wrapping -- with HIDDENDOT.. */ #define MO_BESMTP 0x02000 /* Extended BSMTP -- SIZE+8BITMIME */ #define MO_BEDSMTP 0x04000 /* EBSMTP + DSN */ #define MO_BEBSMTP 0x04000 /* ESMTP + DELIVERBY */ #define MO_WANTSDATE 0x08000 /* Wants "Date:" -header */ #define MO_WANTSFROM 0x10000 /* Wants "From:" -header */ #define MO_BSMTPHELO 0x20000 /* Add HELO/EHLO to the BSMTP */ #define MO_XENVELOPES 0x40000 /* Write various X-Envelope-*: headers to mesage */ #define MO_XORCPT 0x80000 /* Write X-Orcpt : header to message */ struct exmapinfo { int origstatus; const char *statusmsg; int newstatus; const char *dsnstatus; const char *dsndiags; }; struct exmapinfo exmap[] = { { EX_USAGE, "command line usage error", EX_TEMPFAIL, "5.3.0", "x-local; 500 (Command line usage error)" }, { EX_DATAERR, "data format error", EX_DATAERR, "5.3.0", "x-local; 500 (Data format error)" }, { EX_NOINPUT, "cannot open input", EX_TEMPFAIL, "5.3.0", "x-local; 530 (Cannot open input)" }, { EX_NOUSER, "addressee unknown", EX_NOUSER, "5.1.1", "x-local; 521 (No such target user)" }, { EX_NOHOST, "host name unknown", EX_NOHOST, "5.3.0", "x-local; 500 (Target host unknown)" }, { EX_UNAVAILABLE, "service unavailable", EX_UNAVAILABLE, "5.3.0", "x-local; 500 (Service unavailable)" }, { EX_SOFTWARE, "internal software error", EX_TEMPFAIL, "5.3.0", "x-local; 500 (Internal software error)" }, { EX_OSERR, "system error", EX_TEMPFAIL, "5.3.0", "x-local; 500 (System error)" }, { EX_OSFILE, "critical OS file missing", EX_TEMPFAIL, "5.3.0", "x-local; 500 (Critical OS file missing)" }, { EX_CANTCREAT, "can't create output file", EX_TEMPFAIL, "5.2.1", "x-local; 500 (Can't create output file)" }, { EX_IOERR, "input/output error", EX_TEMPFAIL, "5.2.2", "x-local; 500 (Input/Output error)" }, { EX_TEMPFAIL, "temporary failure", EX_TEMPFAIL, "5.3.0", "x-local; 500 (Temporary failure)" }, { EX_PROTOCOL, "remote error in protocol", EX_TEMPFAIL, "5.3.0", "x-local; 500 (Remote error in protocol)" }, { EX_NOPERM, "permission denied", EX_NOPERM, "5.2.0", "x-local; 520 (Permission denied)" }, { 0, NULL, EX_TEMPFAIL, NULL, NULL } }; char myhostname[MAXHOSTNAMELEN+1]; #ifdef lint #undef putc #define putc fputc #endif /* lint */ #ifndef MAXPATHLEN #define MAXPATHLEN 1024 #endif /* MAXPATHLEN */ int main(argc, argv) int argc; char *argv[]; { char file[MAXPATHLEN+1]; char *channel, *host = NULL, *mailer, *cf; struct ctldesc *dp; int errflg, c; struct maildesc *mp; RETSIGTYPE (*oldsig)(); FILE *verboselog = NULL; 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(SIGPIPE, 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->tasmcm.TaProcessStarts += 1; MIBMtaEntry->tasmcm.TaProcCountG += 1; atexit(MIBcountCleanup); if ((progname = strrchr(argv[0], '/')) == NULL) progname = argv[0]; else ++progname; errflg = 0; host = channel = NULL; myhostname[0] = 0; cf = NULL; while (1) { c = getopt(argc, argv, "f:c:h:HQvVw:8"); if (c == EOF) break; switch (c) { case 'f': cf = optarg; break; case 'c': /* remote hostname */ channel = optarg; break; case 'h': /* remote hostname */ host = strdup(optarg); break; case 'Q': mimeqpnarrow = 1; break; case 'V': prversion("sm"); exit(0); break; case 'v': verboselog = stdout; break; case '8': can_8bit = decode_qp = 1; break; case 'H': keep_header8 = 1; break; case 'w': maxwidth = atoi(optarg); if (maxwidth < 0) maxwidth = 0; default: ++errflg; break; } } if (errflg || optind != argc - 1 || host == channel) { fprintf(stderr, "Usage: %s [-V][-v][-H][-8 | -Q][-f cfgfile][-w maxwidth][-c channel | -h host] mailer\n", argv[0]); exit(EX_USAGE); } mailer = argv[optind]; /* We need this later on .. */ zopenlog("sm", LOG_PID, LOG_MAIL); defcharset = getzenv("DEFCHARSET"); if (!defcharset) defcharset = DefCharset; if ((mp = readsmcf(cf, mailer)) == NULL) exit(EX_OSFILE); if (mp->flags & MO_REMOTEFROM) getmyuucpname(uucpname, sizeof uucpname); /*XX*/ while (!getout) { char *s; /* Input: spool/file/name [ \t host.info ] \n */ printf("#hungry\n"); fflush(stdout); if (fgets(file, sizeof file, stdin) == NULL) break; if (strchr(file, '\n') == NULL) break; /* No ending '\n' ! Must have been partial input! */ if (strcmp(file, "#idle\n") == 0) { MIBMtaEntry->tasmcm.TaIdleStates += 1; continue; /* Ah well, we can stay idle.. */ } if (emptyline(file, sizeof file)) break; MIBMtaEntry->tasmcm.TaMessages += 1; s = strchr(file,'\t'); if (s != NULL) { if (host) free(host); host = strdup(s+1); *s = 0; } ctlsticky(NULL, NULL, NULL); /* reset */ dp = ctlopen(file, channel, host, &getout, ctlsticky, NULL); if (dp == NULL) { printf("#resync %s\n",file); fflush(stdout); continue; } if (verboselog != stdout && verboselog != NULL) { fclose(verboselog); verboselog = NULL; } if (verboselog != stdout && dp->verbose) { verboselog = fopen(dp->verbose,"a"); if (verboselog) setbuf(verboselog,NULL); } process(dp, mp, verboselog); ctlclose(dp); } if (verboselog != NULL) fclose(verboselog); if (logfp != NULL) fclose(logfp); exit(EX_OK); /* NOTREACHED */ return 0; } void process(dp, mp, verboselog) struct ctldesc *dp; struct maildesc *mp; FILE *verboselog; { struct rcpt *rp, *rphead; readalready = 0; /* ignore any previous message data cache */ if (mp->flags & MO_MANYUSERS) { for (rp = rphead = dp->recipients; rp != NULL; rp = rp->next) { if (rp->next == NULL || rp->addr->link != rp->next->addr->link || rp->newmsgheader != rp->next->newmsgheader) { deliver(dp, mp, rphead, rp->next, verboselog); rphead = rp->next; } } } else { for (rp = dp->recipients; rp != NULL; rp = rp->next) { deliver(dp, mp, rp, rp->next, verboselog); } } } /* * deliver - deliver the letter in to user's mail box. Return * errors and requests for further processing in the structure */ void deliver(dp, mp, startrp, endrp, verboselog) struct ctldesc *dp; struct maildesc *mp; struct rcpt *startrp, *endrp; FILE *verboselog; { struct rcpt *rp = NULL; struct exmapinfo *exp; const char *exs, *exd; int i, j, pid = 0, in[2], out[2], ii = 0; unsigned int avsize; FILE *tafp = NULL, *errfp = NULL; char *cp = NULL, buf[BUFSIZ], buf2[BUFSIZ]; char *ws = NULL, *we = NULL; const char *ds, **av, *s; int status; int content_kind, conversion_prohibited, ascii_clean = 0; time_t now; char *timestring; CONVERTMODE convertmode = _CONVERT_NONE; char *lineendseq = "\n"; MIBMtaEntry->tasmcm.TaDeliveryStarts += 1; now = time((time_t *)0); timestring = ctime(&now); *(timestring+strlen(timestring)-1) = '\0'; if (lseek(dp->msgfd, (off_t)(dp->msgbodyoffset), SEEK_SET) < 0L) warning("Cannot seek to message body! (%m)", (char *)NULL); i = 0; avsize = 5; av = (const char **)emalloc(sizeof av[0] * avsize); av[i++] = mp->argv[0]; if (mp->flags & MO_FFROMFLAG) { av[i++] = "-f"; if (strcmp(startrp->addr->link->channel,"error")==0) av[i++] = "<>"; else av[i++] = startrp->addr->link->user; } else if (mp->flags & MO_RFROMFLAG) { av[i++] = "-r"; if (strcmp(startrp->addr->link->channel,"error")==0) av[i++] = "<>"; else av[i++] = startrp->addr->link->user; } for (j = 0; mp->argv[j] != NULL; ++j) { while (i+2 >= avsize) { avsize *= 2; av = (const char **)erealloc((char *)av, sizeof av[0] * avsize); } if (strchr(mp->argv[j], '$') == NULL) { if (j > 0) av[i++] = mp->argv[j]; continue; } rp = startrp; do { /* Even argv[0] MAY have $-expansions.. */ ii = i; if (j == 0) ii = 0; while (i+2 >= avsize) { avsize <<= 1; av = (const char **)erealloc((char *)av, sizeof av[0] * avsize); } ws = buf; we = buf + sizeof(buf); for (cp = mp->argv[j]; *cp != '\0'; ++cp) { if (*cp == '$') { switch (*++cp) { case 'g': ds = rp->addr->link->user; break; case 'h': ds = rp->addr->host; break; case 'u': ds = rp->addr->user; rp = rp->next; break; case 'U': strncpy(buf2, rp->addr->user, sizeof(buf2)); buf2[sizeof(buf2)-1] = 0; strlower(buf2); ds = buf2; rp = rp->next; break; case '{': s = ++cp; while (*cp != 0 && *cp != '}') ++cp; if (*cp != 0) { *cp = 0; ds = getzenv(s); *cp = '}'; } else { ds = getzenv(s); } break; default: ds = NULL; break; } if (ds == NULL || *ds == '\0') { char msg[BUFSIZ]; sprintf(msg, "Null value for $%c (%%s) (msgfile: %s)!", *cp, dp->msgfile); warning(msg, mp->name); } else { int len = strlen(ds); if (ws + len >= we) break; /* D'uh :-( */ memcpy(ws, ds, len+1); ws += len; } } else if (ws < we) *ws++ = *cp; } if (ws < we) *ws = '\0'; else we[-1] = '\0'; /* Trunk in all cases */ /* not worth freeing this stuff */ av[ii] = strdup(buf); if (j > 0) ++i; } while (rp != startrp && rp != endrp); /* End of: "do {" */ } /* End of: "for (j = ...) {" */ av[i] = NULL; gotsigpipe = 0; /* now we can fork off and run the command... */ if (pipe(out) < 0) { for (rp = startrp; rp != endrp; rp = rp->next) { notaryreport(rp->addr->user,"failed", "5.3.0 (Out of system resources, pipe creation failed)", "x-local; 500 (pipe creation error, out of system resources ?)"); diagnostic(verboselog, rp, EX_OSERR, 0, "cannot create pipe from \"%s\"", mp->command); SM_MIB_diag(EX_OSERR); } return; } if (pipe(in) < 0) { for (rp = startrp; rp != endrp; rp = rp->next) { notaryreport(rp->addr->user,"failed", "5.3.0 (Out of system resources, pipe creation failed)", "x-local; 500 (pipe creation error, out of system resources ?)"); diagnostic(verboselog, rp, EX_OSERR, 0, "cannot create pipe to \"%s\"", mp->command); SM_MIB_diag(EX_OSERR); } return; } if (verboselog) { const char **p = av; fprintf(verboselog,"To run UID=%d GID=%d ARGV[] =", (int)getuid(), (int)getgid()); for ( ;*p != NULL; ++p) { fprintf(verboselog," '%s'", *p); } fprintf(verboselog,"\n"); fflush(verboselog); } if ((pid = fork()) == 0) { /* child, run the command */ if (!(mp->flags & MO_NORESETUID)) { /* struct passwd *pw = getpwuid(); */ setuid(getuid()); } if (in[1] > 0) { /* its stdout and stderr is the pipe, its stdin is our tafp */ if (out[0] > 0) dup2(out[0], 0); if (in[1] > 1) dup2(in[1], 1); dup2(1, 2); if (in[0] > 2) close(in[0]); if (in[1] > 2) close(in[1]); if (out[0] > 2) close(out[0]); if (out[1] > 2) close(out[1]); } else { close(in[0]); close(out[1]); /* its stdout and stderr is the pipe, its stdin is our tafp */ close(0); close(1); close(2); dup2(out[0], 0); close(out[0]); dup2(in[1], 1); dup2(in[1], 2); close(in[1]); } execv(mp->command, (char**) av); _exit(254); } else if (pid < 0) { /* couldn't fork, complain */ for (rp = startrp; rp != endrp; rp = rp->next) { notaryreport(rp->addr->user,"failed", "5.3.0 (Out of system resources, fork failed)", "x-local; 500 (fork failure, out of system resources ?)"); diagnostic(verboselog, rp, EX_OSERR, 0, "cannot fork"); SM_MIB_diag(EX_OSERR); } return; } close(out[0]); /* child ends.. */ close(in[1]); tafp = fdopen(out[1], "w"); /* parent ends .. */ errfp = fdopen(in[0], "r"); /* read any messages from its stdout/err on in[0] */ if (verboselog) { fprintf(verboselog,"%s\n\t", mp->command); for (i = 0; av[i] != NULL; ++i) fprintf(verboselog,"%s ", av[i]); fprintf(verboselog,"\n"); } free((char *)av); /* ... having forked and set up the pipe, we quickly continue */ /* BSMTP et.al. envelope formation here! */ if (mp->flags & MO_BSMTP) { if (mp->flags & MO_BSMTPHELO) { if (mp->flags & MO_BESMTP) fprintf(tafp,"EHLO %s",myhostname); else fprintf(tafp,"HELO %s",myhostname); if (mp->flags & MO_CRLF) putc('\r',tafp); putc('\n',tafp); } if (strcmp(startrp->addr->link->channel,"error")==0) fprintf(tafp,"MAIL From:<>"); else fprintf(tafp,"MAIL From:<%s>", startrp->addr->link->user); if (mp->flags & MO_BESMTP) { fprintf(tafp," SIZE=%ld",startrp->desc->msgsizeestimate); if (can_8bit) fprintf(tafp," BODY=8BITMIME"); } if (mp->flags & MO_BEDSMTP) { if (startrp->desc->envid != NULL) fprintf(tafp," ENVID=%s",startrp->desc->envid); if (startrp->desc->dsnretmode != NULL) fprintf(tafp, " RET=%s", startrp->desc->dsnretmode); } if (mp->flags & MO_CRLF) lineendseq = "\r\n"; fputs(lineendseq, tafp); for (rp = startrp; rp != endrp; rp = rp->next) { fprintf(tafp,"RCPT TO:<%s>",rp->addr->user); /* if (mp->flags & MO_BESMTP) { } */ if (mp->flags & MO_BEDSMTP) { if (rp->notifyflgs) { char *s = ""; fprintf(tafp," NOTIFY="); if (rp->notifyflgs & _DSN_NOTIFY_NEVER) { fprintf(tafp,"NEVER"); } if (rp->notifyflgs & _DSN_NOTIFY_SUCCESS) { fprintf(tafp,"SUCCESS"); s = ","; } if (rp->notifyflgs & _DSN_NOTIFY_FAILURE) { fprintf(tafp,"%sFAILURE",s); s = ","; } if (rp->notifyflgs & _DSN_NOTIFY_DELAY) { fprintf(tafp,"%sDELAY",s); } } if (rp->orcpt) fprintf(tafp," ORCPT=%s",rp->orcpt); } if (mp->flags & MO_BEBSMTP) { if (rp->deliverby) { fprintf(tafp," BY=%ld;", (long)(rp->deliverby - now)); if (rp->deliverbyflgs & _DELIVERBY_R) fputc('R',tafp); if (rp->deliverbyflgs & _DELIVERBY_N) fputc('N',tafp); if (rp->deliverbyflgs & _DELIVERBY_T) fputc('T',tafp); } } fputs(lineendseq, tafp); } fprintf(tafp,"DATA"); fputs(lineendseq, tafp); } /* Now continue with inside stuff -- well, normal UUCP stuff */ if (mp->flags & (MO_UNIXFROM|MO_REMOTEFROM)) { const char *uu = startrp->addr->link->user; if (strcmp(startrp->addr->link->channel,"error")==0) uu = "<>"; fprintf(tafp, "%s%s %s", FROM_, uu, timestring); if (mp->flags & MO_REMOTEFROM) fprintf(tafp, " remote from %s", uucpname); if (verboselog) { fprintf(verboselog, "%s%s %s", FROM_, uu, timestring); if (mp->flags & MO_REMOTEFROM) fprintf(verboselog, " remote from %s", uucpname); putc('\n',verboselog); } putc('\n', tafp); } conversion_prohibited = check_conv_prohibit(startrp); /* Content-Transfer-Encoding: 8BIT ? */ content_kind = cte_check(startrp); /* If the header says '8BIT' and ISO-8859-* something, but body is plain 7-bit, turn it to '7BIT', and US-ASCII */ ascii_clean = check_7bit_cleanness(dp); if (!conversion_prohibited && ascii_clean && content_kind == 8) { if (downgrade_charset(startrp, verboselog)) content_kind = 7; } convertmode = _CONVERT_NONE; if (!conversion_prohibited) { switch (content_kind) { case 0: /* No MIME headers defined */ if (!can_8bit && !ascii_clean) { convertmode = _CONVERT_UNKNOWN; downgrade_headers(startrp, convertmode, verboselog); } break; case 2: /* MIME, but no C-T-E: ? */ case 1: /* MIME BASE64 ? some MIME anyway.. */ case 7: /* 7BIT */ convertmode = _CONVERT_NONE; break; case 8: /* 8BIT */ if (!can_8bit && !ascii_clean) { convertmode = _CONVERT_QP; if (!downgrade_headers(startrp, convertmode, verboselog)) convertmode = _CONVERT_NONE; } break; case 9: /* QUOTED-PRINTABLE */ if (decode_qp) { /* Force(d) to decode Q-P while transfer.. */ convertmode = _CONVERT_8BIT; /* UPGRADE TO 8BIT ! */ if (!qp_to_8bit(startrp)) convertmode = _CONVERT_NONE; content_kind = 10; ascii_clean = 0; } break; default: /* ?? should not happen.. */ break; } if (!keep_header8 && headers_need_mime2(startrp)) { headers_to_mime2(startrp,defcharset,verboselog); } } /* Snub the "Return-Path:" header, if it exists.. */ if (1) { char **hdrs; do { hdrs = has_header(startrp,"Return-Path:"); if (hdrs) delete_header(startrp, hdrs); } while (hdrs); } /* Snub stuff that we add later below.. */ if (mp->flags & MO_XENVELOPES) { char **hdrs; do { hdrs = has_header(startrp,"X-Envelope-To:"); if (hdrs) delete_header(startrp, hdrs); } while (hdrs); do { hdrs = has_header(startrp,"X-Envid:"); if (hdrs) delete_header(rp,hdrs); } while (hdrs); do { hdrs = has_header(startrp,"Envelope-Id:"); if (hdrs) delete_header(rp,hdrs); } while (hdrs); } /* PERT-NB X-Orcpt if O option */ if (mp->flags & (MO_XORCPT|MO_XENVELOPES)) { char **hdrs; do { hdrs = has_header(startrp,"X-Orcpt:"); if (hdrs) delete_header(startrp, hdrs); } while (hdrs); do { /* RFC 2298 section 2.3 */ hdrs = has_header(startrp,"Original-Recipient:"); if (hdrs) delete_header(startrp, hdrs); } while (hdrs); } /* PERT-NB END */ /* Write headers: */ header_received_for_clause(startrp, 0, verboselog); fwriteheaders(startrp, tafp, lineendseq, convertmode, maxwidth, NULL); if (verboselog) fwriteheaders(startrp, verboselog, "\n", convertmode, maxwidth, NULL); /* * NOTE: Following header writers make sense only for SINGLE * RECIPIENT DELIVERIES! * */ if (mp->flags & MO_XENVELOPES) { if (dp->envid) { fprintf(tafp, "Envelope-Id: "); decodeXtext(tafp, dp->envid); fputs(lineendseq, tafp); if (verboselog) { fprintf(verboselog, "Envelope-Id: "); decodeXtext(verboselog, dp->envid); fputs("\n", verboselog); } } } if (mp->flags & (MO_XORCPT|MO_XENVELOPES)) { /* Put out a sequence of X-Envelope-To: and Original-Recipient: headers -- in order.. */ for (rp = startrp; rp != endrp; rp = rp->next) { const char *uu = rp->addr->user; if (strcmp(rp->addr->link->channel,"error")==0) uu = ""; fprintf(tafp, "X-Envelope-To: <%s> (uid %s)%s", uu, rp->addr->misc, lineendseq); if (rp->orcpt) { /* RFC 2298: section 2.3 */ fprintf(tafp, "Original-Recipient: "); decodeXtext(tafp, rp->orcpt); fputs(lineendseq, tafp); } if (verboselog) { fprintf(verboselog, "X-Envelope-To: <%s> (uid %s)\n", uu, rp->addr->misc); if (rp->orcpt) { /* RFC 2298: section 2.3 */ fprintf(verboselog, "Original-Recipient: "); decodeXtext(verboselog, rp->orcpt); fputs("\n", verboselog); } } } } if (mp->flags & MO_RETURNPATH) { const char *uu = startrp->addr->link->user; if (strcmp(startrp->addr->link->channel,"error")==0) uu = ""; fprintf(tafp, "Return-Path: <%s>%s", uu, lineendseq); } /* Header-Body separator: */ fputs(lineendseq, tafp); if (verboselog) fprintf(verboselog, "\n"); /* Append message body itself */ i = appendlet(dp, mp, tafp, verboselog, convertmode); if (i != EX_OK && !gotsigpipe) { for (rp = startrp; rp != endrp; rp = rp->next) { notaryreport(rp->addr->user,"failed", /* Could indicate: 4.3.1 - mail system full ?? */ "5.3.0 (Write to target failed for some reason)", "x-local; 500 (Write to target failed for some reason)"); diagnostic(verboselog, rp, i, 0, "write error"); SM_MIB_diag(i); } /* just to make sure nothing will get delivered */ kill(pid, SIGTERM); sleep(1); kill(pid, SIGKILL); wait(NULL); fclose(tafp); /* FP/pipe cleanups */ fclose(errfp); return; } if (mp->flags & MO_BSMTP) { fputs(lineendseq, tafp); } fclose(tafp); close(out[1]); /* paranoia */ if (fgets(buf, sizeof buf, errfp) == NULL) buf[0] = '\0'; else if ((cp = strchr(buf, '\n')) != NULL) *cp = '\0'; fclose(errfp); close(in[0]); /* more paranoia */ cp = buf + strlen(buf); exd = exs = NULL; pid = wait(&status); if (WSIGNALSTATUS(status) != 0) { if (cp != buf) *cp++ = ' '; sprintf(cp, "[signal %d", WSIGNALSTATUS(status)); if (status&0200) strcat(cp, " (Core dumped)"); strcat(cp, "]"); i = EX_TEMPFAIL; exd = "x-local; 500 (failed on signal)"; exs = "5.3.0"; } else if (WEXITSTATUS(status) == 0 #if EX_OK != 0 || WEXITSTATUS(status) == EX_OK #endif ) { i = EX_OK; } else { i = WEXITSTATUS(status); s = NULL; for (exp = & exmap[0]; exp->origstatus != 0; ++exp) if (exp->origstatus == i) { s = exp->statusmsg; i = exp->newstatus; exs = exp->dsnstatus; exd = exp->dsndiags; break; } sprintf(cp, "[exit status %d/%d", WEXITSTATUS(status), i); if (s) sprintf(cp+strlen(cp), " (%s)", s); /* sprintf(cp+strlen(cp), " of command: %s", mp->command); */ strcat(cp, "]"); } for (rp = startrp; rp != endrp; rp = rp->next) { if (i == EX_OK) notaryreport(rp->addr->user, "relayed", "2.5.0", "smtp;250 (Delivered)"); else notaryreport(rp->addr->user, "failed", exs, exd); diagnostic(verboselog, rp, i, 0, "%s", buf); SM_MIB_diag(i); } /* XX: still need to deal with MO_STRIPQUOTES */ } /* * appendlet - append letter to file pointed at by fd */ int appendlet(dp, mp, fp, verboselog, convertmode) struct ctldesc *dp; struct maildesc *mp; FILE *fp; FILE *verboselog; int convertmode; { /* `convertmode' controls the behaviour of the message conversion: _CONVERT_NONE (0): send as is _CONVERT_QP (1): Convert 8-bit chars to QUOTED-PRINTABLE _CONVERT_8BIT (2): Convert QP-encoded chars to 8-bit _CONVERT_UNKNOWN (3): Turn message to charset=UNKNOWN-8BIT, Q-P.. */ register int i; int lastch, rc; register int bufferfull; int mfd = dp->msgfd; writebuf(mp, fp, (char *)NULL, 0); /* magic initialization */ lastch = -1; if (convertmode == _CONVERT_NONE) { if (ta_use_mmap <= 0) { /* can we use cache of message body data */ if (readalready > 0) { lastch = dp->let_buffer[readalready-1]; if (writebuf(mp, fp, dp->let_buffer, readalready) != readalready) return EX_IOERR; if (lastch != '\n') if (writebuf(mp, fp, "\n", 1) != 1) return EX_IOERR; return EX_OK; } bufferfull = 0; readalready = 0; lastch = -256; lseek(mfd, dp->msgbodyoffset, SEEK_SET); for (;;) { i = read(mfd, (void*)(dp->let_buffer), dp->let_buffer_size); if (i == 0) break; /* EOF */ if (i < 0 && (errno == EINTR || errno == EAGAIN)) continue; if (i < 0) return EX_IOERR; lastch = dp->let_buffer[i-1]; if (writebuf(mp, fp, dp->let_buffer, i) != i) return EX_IOERR; readalready = i; bufferfull++; } if (lastch != '\n') if (writebuf(mp, fp, "\n", 1) != 1) return EX_IOERR; if (bufferfull > 1) /* not all in memory, need to reread */ readalready = 0; } else { /* mmap()ed message buffer */ const char *p = dp->let_buffer + dp->msgbodyoffset; i = dp->let_end - p; if (i < 0) return EX_IOERR; lastch = dp->let_end[-1]; if (writebuf(mp, fp, p, i) != i) return EX_IOERR; if (lastch != '\n') if (writebuf(mp, fp, "\n", 1) != 1) return EX_IOERR; } rc = EX_OK; } else { /* convertmode something else, than _CONVERT_NONE */ /* Various esoteric conversion modes.. We are better to feed writemimeline() with LINES instead of blocks of data.. */ writemimeline(mp, fp, (char *)NULL, 0, 0); /* reset */ /* if(verboselog) fprintf(verboselog, "sm: Convert mode: %d, fd=%d, fdoffset=%d, bodyoffset=%d\n", convertmode, mfd, (int)lseek(mfd, (off_t)0, SEEK_CUR), dp->msgbodyoffset); */ /* we are assuming to be positioned properly at the start of the message body */ lastch = -1; i = 0; if (ta_use_mmap <= 0) { /* Locally read, line by line */ char sfio_buf[16*1024]; Sfio_t *mfp = NULL; mfp = sfnew(NULL, sfio_buf, sizeof(sfio_buf), mfd, SF_READ); sfseek(mfp, dp->msgbodyoffset, SEEK_SET); readalready = 0; for (;;) { i = csfgets((void*)(dp->let_buffer), dp->let_buffer_size, mfp); if (i == EOF) break; lastch = dp->let_buffer[i-1]; /* It MAY be malformed -- if it has a BUFSIZ length line in it, IT CAN'T BE MIME :-/ */ /* Ok, write the line */ if (writemimeline(mp, fp, dp->let_buffer, i, convertmode) != i) return EX_IOERR; } if (mfp) { if (i == EOF && !sfeof(mfp) && !sferror(mfp)) { rc = EX_IOERR; } zsfsetfd(mfp, -1); sfclose(mfp); } rc = EX_OK; } else { /* MMAP()ED buffer */ const char *s = dp->let_buffer + dp->msgbodyoffset; for (;;) { const char *s2 = s; i = 0; if (s >= dp->let_end) break; /* "EOF" */ while (s2 < dp->let_end && *s2 != '\n') ++s2, ++i; if ((lastch = *s2) == '\n') ++s2, ++i; /* Ok, write the line */ if (writemimeline(mp, fp, s, i, convertmode) != i) return EX_IOERR; s = s2; } } rc = EX_OK; } /* we must make sure the last thing we transmit is a CRLF sequence */ if (lastch != '\n') writebuf(mp, fp, "\n", 1); /* Failure in that last CRLF writing does not affect our return code -- should it ?? */ return rc; } /* * Writebuf() is like write(), except all '\n' are converted to "\r\n" * (CRLF), and the sequence "\n.\n" is converted to "\r\n..\r\n". */ int writebuf(mp, fp, buf, len) struct maildesc *mp; FILE *fp; const char *buf; int len; { register const char *cp; register int n; int tlen; register char expect; static char save = '\0'; static char frombuf[8]; static char *fromp; if (buf == NULL) { /* magic initialization */ save = '.'; frombuf[0] = 0; fromp = frombuf; return 0; } expect = save; for (cp = buf, n = len, tlen = 0; n > 0; --n, ++cp) { int c = (*cp) & 0xFF; if (mp->flags & MO_STRIPHIBIT) c &= 0x7F; ++tlen; if (c == '\n') { frombuf[0] = 0; fromp = frombuf; if (expect == '\n' && (mp->flags & MO_HIDDENDOT)) /* "\n.\n" sequence */ if (putc('.', fp) == EOF) { tlen = -1; break; } if (mp->flags & MO_CRLF) if (putc('\r', fp) == EOF) { tlen = -1; break; } if (putc(c,fp) == EOF) { tlen = -1; break; } expect = '.'; } else if (expect != '\0') { if (expect == '.') { if ((mp->flags & MO_ESCAPEFROM) && c == 'F') expect = 'F'; else if (c == '.' && (mp->flags & MO_HIDDENDOT)) { if (putc('.', fp) == EOF || putc('.', fp) == EOF) { tlen = -1; break; } expect = '\0'; continue; } else { if (putc(c, fp) == EOF) { tlen = -1; break; } expect = '\0'; continue; } } 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.. */ if (fwrite(">From ", 6, 1, fp) == 0) { tlen = -1; break; } /* anticipate future instances */ expect = '\0'; break; } } else { expect = '\0'; fromp = frombuf; while (*fromp) { if (putc(*fromp,fp) == EOF) { tlen = -1; break; } ++fromp; } frombuf[0] = 0; if (putc(c,fp) == EOF) { tlen = -1; break; } } } else { /* expect == 0 */ if (putc(c,fp) == EOF) { tlen = -1; break; } } } save = expect; return tlen; } int writemimeline(mp, fp, buf, len, convertmode) struct maildesc *mp; FILE *fp; const char *buf; int len, convertmode; { /* `convertmode' controls the behaviour of the message conversion: _CONVERT_NONE (0): send as is _CONVERT_QP (1): Convert 8-bit chars to QUOTED-PRINTABLE _CONVERT_8BIT (2): Convert QP-encoded chars to 8-bit _CONVERT_UNKNOWN (3): Turn message to charset=UNKNOWN-8BIT, Q-P.. */ register const char *cp; register int n; static int column; register int qp_conv; register int qp_chrs = 0; if (buf == NULL) { column = -1; return 0; } qp_conv = (convertmode == _CONVERT_QP || convertmode == _CONVERT_UNKNOWN); for (cp = buf, n = len; n > 0; --n, ++cp) { int c = (*cp) & 0xFF; ++column; if (qp_conv) { /* ENCODE to QUOTED-PRINTABLE ... */ if (column > 70 && c != '\n') { putc('=',fp); if (mp->flags & MO_CRLF) putc('\r', fp); putc('\n',fp); column = 0; } if (column == 0 && (mp->flags & MO_HIDDENDOT) && c == '.') { /* Duplicate the line initial dot.. */ if (putc(c,fp)==EOF) return EOF; } else if (column == 0 && (mp->flags & MO_ESCAPEFROM) && c == 'F' && n >= 4 && strncmp(cp,"From",4)==0) { /* We Q-P encode the leading 'F'.. */ if (fputs("=46",fp) != 3) return EOF; column += 2; } else if ((n < 3 || mimeqpnarrow) && c != '\n' && (c <= 32 || c > 126 || c == '=')) { /* Downgrade it by translating it to Quoted-Printable.. */ /* Translate also trailing spaces/TABs */ if (fprintf(fp,"=%02X",c) != 3) return EOF; column += 2; } else if (c != '\n' && c != '\t' && (c < 32 || c > 126 || c == '=')) { /* Downgrade it by translating it to Quoted-Printable.. */ /* SPACE and TAB are left untranslated */ if (fprintf(fp,"=%02X",c) != 3) return EOF; column += 2; buf = cp; } else if (c == '\n') { /* This is most likely the LAST char */ if (mp->flags & MO_CRLF) if (putc('\r', fp) == EOF) return EOF; if (putc(c,fp) == EOF) return EOF; column = -1; } else { if (putc(c, fp) == EOF) return EOF; } } else if (convertmode == _CONVERT_8BIT) { /* DECODE from QUOTED-PRINTABLE text.. */ static int qp_val = 0; if (!qp_chrs && c == '=') { /* Q-P -prefix */ qp_chrs = 2; qp_val = 0; continue; } else if (qp_chrs) { --column; if (c == ' ' || c == '\t' || c == '\n' || c == '\r') break; /* Done with it, it was soft end-of-line */ if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')) { /* A HEX digit ? QP char coming up ? */ if (c >= 'a') c -= ('a' - 'A'); if (c >= 'A') c -= ('A' - '9' -1); qp_val <<= 4; qp_val |= (c & 0x0F); if (--qp_chrs) continue; /* Not yet last.. */ else c = qp_val; /* The second (=last) hex digit */ } else qp_chrs = 0; /* While in this mode, NOT QP-hex-digit! */ } /* Ok, decoded possible Q-P chars. Now normal processing.. */ if (column == 0 && c == '.' && (mp->flags & MO_HIDDENDOT)) { if (putc(c,fp)==EOF) return EOF; } else if (column == 0 && (mp->flags & MO_ESCAPEFROM) && c == 'F' && strncmp(cp+1,"rom",3)==0) { if (putc('>',fp)==EOF) return EOF; ++column; } else if (c == '\n') { if (mp->flags & MO_CRLF) if (putc('\r',fp)==EOF) return EOF; column = -1; } /* And output the char.. */ if (putc(c,fp)==EOF) return EOF; } else abort(); /* WOO! We should not be called for '_CONVERT_NONE'! */ } if (feof(fp) || ferror(fp)) return EOF; return len; } struct maildesc * readsmcf(file, mailer) char *file, *mailer; { char *entry, buf[BUFSIZ]; unsigned char *cp; FILE *fp; int i; static struct maildesc m; if (file == NULL) { const char *mailshare = getzenv("MAILSHARE"); if (mailshare == NULL) mailshare = MAILSHARE; sprintf(buf, "%s/%s.conf", mailshare, progname); file = buf; } if ((fp = fopen(file, "r")) == NULL) { fprintf(stderr, "%s: cannot open ", progname); perror(file); exit(EX_OSFILE); } entry = NULL; cp = NULL; while (fgets(buf, sizeof buf, fp) != NULL) { if (buf[0] == '#' || buf[0] == '\n') continue; if ((cp = emalloc(strlen(buf)+1)) == NULL) { fprintf(stderr, "%s: Out of Virtual Memory!\n", progname); exit(EX_OSERR); } entry = (char*)cp; strcpy(entry, buf); SKIPTEXT(cp); if (isascii(*cp) && isspace(*cp)) { if (*cp == '\n') { fprintf(stderr, "%s: %s: bad entry: %s", progname, file, entry); } else *cp = '\0'; } else { fprintf(stderr, "%s: %s: bad entry: %s", progname, file, entry); } if (strcmp(entry, mailer) == 0) break; free(entry); entry = NULL; } fclose(fp); if (entry == NULL) return NULL; m.name = entry; m.flags = MO_UNIXFROM; ++cp; SKIPSPACE(cp); /* process mailer option flags */ for (;*cp && *cp != ' ' && *cp != '\t' && *cp != '\n'; ++cp) { int no = 0; switch (*cp) { case '7': m.flags |= MO_STRIPHIBIT; break; case '8': can_8bit = 1; break; case '9': decode_qp = 1; break; case 'A': no=*cp; break; /* arpanet-compatibility */ case 'b': m.flags |= (MO_BSMTP|MO_HIDDENDOT); break; case 'B': if (m.flags & MO_BESMTP) /* -BB */ m.flags |= MO_BEDSMTP; else m.flags |= MO_BESMTP|MO_BSMTP|MO_HIDDENDOT; break; case 'C': no=*cp; break; /* canonicalize remote hostnames */ case 'D': /* this mailer wants a Date: line */ m.flags |= MO_WANTSDATE; break; case 'e': m.flags |= MO_XENVELOPES; break; /* PERT-NB O option for X-Orcpt */ case 'O': m.flags |= MO_XORCPT; break; case 'E': m.flags |= MO_ESCAPEFROM; break; case 'f': m.flags |= MO_FFROMFLAG; break; case 'F': /* this mailer wants a From: line */ m.flags |= MO_WANTSFROM; break; case 'h': no=*cp; break; /* preserve upper case in host names */ case 'H': m.flags |= MO_BSMTPHELO; break; case 'I': no=*cp; break; /* talking to a clone of I */ case 'l': no=*cp; break; /* this is a local mailer */ case 'L': no=*cp; break; /* limit line length */ case 'm': m.flags |= MO_MANYUSERS; break; case 'M': no=*cp; break; /* this mailer wants a Message-Id: line */ case 'n': m.flags &= ~MO_UNIXFROM; break; case 'p': no=*cp; break; /* use SMTP return path */ case 'P': m.flags |= MO_RETURNPATH; break; case 'r': m.flags |= MO_RFROMFLAG; break; case 'R': m.flags |= MO_CRLF; break; case 's': m.flags |= MO_STRIPQUOTES; break; case 'S': m.flags |= MO_NORESETUID; break; case 'u': no=*cp; break; /* preserve upper case in user names */ case 'U': m.flags |= MO_REMOTEFROM; break; case 'x': no=*cp; break; /* this mailer wants a Full-Name: line */ case 'X': m.flags |= MO_HIDDENDOT; break; case '-': break; /* ignore */ default: fprintf(stderr, "%s: unknown sendmail mailer option '%c'\n", progname, *cp); break; } if (no) { fprintf(stderr, "%s: the '%c' sendmail mailer option does not make sense in this environment\n", progname,no); } } SKIPSPACE(cp); m.command = (char*) cp; SKIPTEXT(cp); if ((char*)cp == m.command) { fprintf(stderr,"%s: bad entry for %s\n",progname, m.name); return NULL; } *cp++ = '\0'; if (*m.command != '/') { char *nmc; const char *mailbin = getzenv("MAILBIN"); if (mailbin == NULL) mailbin = MAILBIN; nmc = emalloc(strlen(mailbin)+1+strlen(m.command)+1); sprintf(nmc, "%s/%s", mailbin, m.command); m.command = nmc; } SKIPSPACE(cp); i = 0; while (isascii(*cp) && !isspace(*cp) && i < MD_ARGVMAX) { if (*cp == '\0') break; m.argv[i++] = (char*) cp; SKIPTEXT(cp); if (*cp) { *cp++ = '\0'; SKIPSPACE(cp); } } if (i == 0) { fprintf(stderr, "%s: bad command for %s\n", progname, m.name); return NULL; } m.argv[i] = NULL; return &m; } /* When data is clean 7-BIT, return 1.. (zero == non-clean) */ int check_7bit_cleanness(dp) struct ctldesc *dp; { if (ta_use_mmap > 0) { /* With MMAP()ed spool file it is sweet and simple.. */ register const char *s = dp->let_buffer + dp->msgbodyoffset; while (s < dp->let_end) if (128 & *s) { /* if (verboselog) fprintf(verboselog, "check_7bit_cleanness() non-clean byte on offset %d, str=\"%-8s\"\n", s-(dp->let_buffer + dp->msgbodyoffset), s); */ return 0; /* Not clean ! */ } else ++s; } else { /* Without MMAP() we work... */ register int i; register int bufferfull; int lastwasnl; int mfd = dp->msgfd; /* can we use cache of message body data */ if (readalready != 0) { for (i=0; ilet_buffer[i])) return 0; /* Not clean ! */ } /* we are assumed to be positioned properly at start of message body */ bufferfull = 0; for (;;) { i = read(mfd, (void*)(dp->let_buffer), dp->let_buffer_size); if (i == 0) break; /* EOF */ if (i < 0) { /* ERROR ?!?!? */ if (errno == EINTR || errno == EAGAIN) continue; readalready = 0; lseek(mfd, dp->msgbodyoffset, SEEK_SET); return 0; } lastwasnl = (dp->let_buffer[i-1] == '\n'); readalready = i; bufferfull++; for (i=0; i < readalready; ++i) if (128 & (dp->let_buffer[i])) { lseek(mfd, dp->msgbodyoffset, SEEK_SET); /* We probably have not read everything of the file! */ readalready = 0; return 0; /* Not clean ! */ } } /* Got to EOF, and still it is clean 7-BIT! */ lseek(mfd, dp->msgbodyoffset, SEEK_SET); if (bufferfull > 1) /* not all in memory, need to reread */ readalready = 0; } return 1; }