/* * Copyright 1988 by Rayan S. Zachariassen, all rights reserved. * This will be free software, but only when it is finished. */ #include "hostenv.h" #include #include #include "zmalloc.h" #include #include #include #include #include #include "mail.h" #include "zmsignal.h" #include "ta.h" #include "fuzzy.h" #ifndef SEEK_SET #define SEEK_SET 0 #endif #define PROGNAME "fuzzyalias" #define CHANNEL "fuzzy" /* the default channel name we deliver for */ char *dfltform[] = { "From: The Post Office ", "Subject: Unknown user", "MIME-Version: 1.0", "Content-Type: multipart/report; report-type=delivery-status;", "Precedence: junk", /* BSD sendmail */ "", "Processing your mail message caused the following errors:", "The given recipient is unknown.", NULL }; char *progname; char *logfile; FILE *logfp; extern char *optarg; extern int optind; extern int emptyline(); extern void prversion(); extern void process(); #ifndef strchr extern char *strrchr(); extern char *strchr(); #endif extern char *mydomain(); #ifndef MAXPATHLEN #define MAXPATHLEN 1024 #endif /* MAXPATHLEN */ int D_alloc = 0; /* For tmalloc() from libz.a ... */ int main(argc, argv) int argc; char *argv[]; { char *channel, msgfilename[MAXPATHLEN+1]; int errflg, c; struct ctldesc *dp; RETSIGTYPE (*oldsig)(); NAMELIST *answer; int old_dbm, thresh, pw; struct rcpt *rp; int fd; char **files, **ptr; SIGNAL_HANDLESAVE(SIGINT, SIG_IGN, oldsig); if (oldsig != SIG_IGN) SIGNAL_HANDLE(SIGINT, wantout); SIGNAL_HANDLESAVE(SIGHUP, SIG_IGN, oldsig); if (oldsig != SIG_IGN) SIGNAL_HANDLE(SIGHUP, 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_IGNORE(SIGPIPE); if ((progname = strrchr(argv[0], '/')) == NULL) progname = argv[0]; else ++progname; errflg = 0; channel = CHANNEL; pw = 0; thresh = 50; logfile = NULL; while ((c = getopt(argc, argv, "c:l:pt:V")) != EOF) { switch (c) { case 'c': /* specify channel scanned for */ channel = optarg; break; case 'l': /* log file */ logfile = emalloc(strlen(optarg)+1); strcpy(logfile, optarg); break; case 'p': /* scan passwd file for user */ pw++; break; case 't': thresh = atoi(optarg); if (thresh<10 || thresh>90) ++errflg; break; case 'V': prversion(PROGNAME); exit(EX_OK); break; default: ++errflg; break; } } if (errflg || optind != argc) { fprintf(stderr, "Usage: %s [-V] [-c channel] [-l logfile] [-p] [-t thresh] file [file ...]\n", progname); exit(EX_USAGE); } old_dbm = (strcmp("dbm", getzenv("DBTYPE")) == 0); if (logfile != NULL) { if ((fd = open(logfile, O_CREAT|O_APPEND|O_WRONLY, 0644)) < 0) { fprintf(stderr, "%s: cannot open logfile \"%s\"!\n", progname, logfile); } else { logfp = (FILE *)fdopen(fd, "a"); } } else { logfp = NULL; } files = (char **) emalloc((argc-optind+1)*sizeof(char *)); for (ptr=files; optind < argc; optind++, *++ptr) *ptr = argv[optind]; *ptr = NULL; while (!getout) { char *s; printf("#hungry\n"); fflush(stdout); if (fgets(msgfilename, sizeof msgfilename, stdin) == NULL) break; if (strchr(msgfilename, '\n') == NULL) break; /* No ending '\n' ! Must have been partial input! */ if (strcmp(msgfilename, "#idle\n") == 0) continue; /* Ah well, we can stay idle.. */ /* Input: spool/file/name [ \t host.info ] \n */ if (emptyline(msgfilename, sizeof msgfilename)) break; s = strchr(msgfilename,'\t'); if (s != NULL) *s = 0; /* Ignore the host-selector */ dp = ctlopen(msgfilename, channel, (char *)NULL, &getout, NULL, NULL, ctlsticky, NULL); if (dp != NULL) { rp = dp->recipients; answer = fuzzy(rp->addr->user, thresh, pw, old_dbm, files); process(dp, answer); ctlclose(dp); } else { printf("#resync %s\n",msgfilename); fflush(stdout); } } if (logfp != NULL) fclose(logfp); return 0; } /* #ifndef MALLOC_TRACE univptr_t tmalloc(n) size_t n; { return emalloc((u_int)n); } #endif */ /* Pick recipient address from the input line. EXTREMELY Simple minded parsing.. */ static void pick_env_addr(buf,mfp) char *buf; FILE *mfp; { char *s = buf; while (*s != 0 && *s != ' ' && *s != '\t' && *s != ':') ++s; if (*s != ':') return; /* BAD INPUT! */ buf = ++s; /* We have skipped the initial header.. */ s = strchr(buf,'\n'); if (s) *s = 0; s = strchr(buf,'<'); if (s != NULL) { /* Cc: The Postoffice managers */ buf = ++s; s = strrchr(buf,'>'); if (s) *s = 0; else return; /* No trailing '>' ? BAD BAD! */ fprintf(mfp,"to <%s>\n",buf); } else { /* Cc: some-address */ fprintf(mfp,"to <%s>\n",buf); } } void process(dp, answer) struct ctldesc *dp; NAMELIST *answer; { char buf[BUFSIZ]; char **cpp, *mailshare, *mfpath; FILE *mfp, *efp; int n; struct rcpt *rp; char boundarystr[400]; char lastchar; int reportcnt = 0; struct stat stbuf; char *formname; NAMELIST *ptr; if (fstat(dp->msgfd, &stbuf) != 0) abort(); /* This is a "CAN'T FAIL" case.. */ /* recipient host field is the error message file name in FORMSDIR */ /* recipient user field is the address causing the error */ if (dp->senders == NULL) { /* * If there was no error return address * it might be because this message was * an error message being bounced back. * We do NOT want to bounce this, but * instead just drop it on the floor. */ for (rp = dp->recipients; rp != NULL; rp = rp->next) diagnostic(rp, EX_OK, 0, "error bounce dropped"); return; } if ((mfp = mail_open(MSG_RFC822)) == NULL) { for (rp = dp->recipients; rp != NULL; rp = rp->next) diagnostic(rp, EX_TEMPFAIL, 0, "mail_open failure"); warning("Cannot open mail file!"); return; } { char *dom = mydomain(); /* transports/libta/buildbndry.c */ char fname[20]; struct stat stbuf; fstat(FILENO(mfp),&stbuf); taspoolid(boundarystr, stbuf.st_ctime, (long)stbuf.st_ino); strcat(boundarystr, "=_/fuzzy/"); strcat(boundarystr, dom); } fprintf(mfp, "channel error\n"); rp = dp->recipients; /* [Thomas Knott] * Wenn der "Errors-To"-Header in der Mail angegeben war, so * wird diese E-Mail-Adresse zur Ruecksendung der Fehlermeldung * benutzt. */ fprintf(mfp, "to <%s>\n", rp->addr->link->user); /* copy error message file itself */ if ((mailshare = getzenv("MAILSHARE")) == NULL) mailshare = MAILSHARE; formname = (char *)((answer == NULL) ? (rp->addr->host) : progname); mfpath = emalloc(3 + strlen(mailshare) + strlen(FORMSDIR) + strlen(formname)); sprintf(mfpath, "%s/%s/%s", mailshare, FORMSDIR, formname); if ((efp = fopen(mfpath, "r")) != NULL) { int inhdr = 1; buf[sizeof(buf)-1] = 0; while (fgets(buf,sizeof(buf)-1,efp) != NULL) { if (strncmp(buf,"HDR",3)==0) continue; else if (strncmp(buf,"ADR",3)==0) pick_env_addr(buf+4,mfp); else if (strncmp(buf,"SUB",3)==0) continue; else break; } fseek(efp,0,0); /* Rewind! */ fputs("env-end\n",mfp); /* copy To: from error return address */ /* [Thomas Knott] * Wenn der "Errors-To"-Header in der Mail angegeben war, so * wird diese E-Mail-Adresse zur Ruecksendung der Fehlermeldung * benutzt, ansonsten die in der "from"-Enveloppe angegebenen * Adresse. */ fprintf(mfp, "To: %s\n", (*rp->addr->link->user != '\0') ? rp->addr->link->user : "postmaster"); while (fgets(buf,sizeof(buf)-1,efp) != NULL) { if (strncmp(buf,"HDR",3)==0) { fputs(buf+4,mfp); } else if (strncmp(buf,"ADR",3)==0) { fputs(buf+4,mfp); } else if (strncmp(buf,"SUB",3)==0) { fputs(buf+4,mfp); } else { if (inhdr) { inhdr = 0; fprintf(mfp,"MIME-Version: 1.0\n"); fprintf(mfp,"Content-Type: multipart/report; report-type=delivery-status;\n"); fprintf(mfp,"\tboundary=\"%s\"\n\n\n",boundarystr); fprintf(mfp, "--%s\n", boundarystr); fprintf(mfp, "Content-Type: text/plain\n"); } fputs(buf,mfp); } } /* ... while() ends.. */ fclose(efp); } else { for (cpp = &dfltform[0]; *cpp != NULL; ++cpp) if (*cpp[0] == 0) { fprintf(mfp, "\tboundary=\"%s\"\n\n\n",boundarystr); fprintf(mfp, "--%s\n", boundarystr); fprintf(mfp, "Content-Type: text/plain\n"); } else fprintf(mfp, "%s\n", *cpp); } /* print out fuzzy matches in standard format */ fputc('\n', mfp); if (answer != NULL) { fprintf(mfp, "The following addresses are the nearest matches of known recipients:\n"); if (logfp != NULL) { fprintf(logfp, "%s: %s ->", dp->logident, rp->addr->user); } for (ptr=answer; ptr!=NULL; ptr=ptr->next) { fprintf(mfp, "\t%s\n", ptr->name); if (logfp != NULL) { fprintf(logfp, " %s", ptr->name); } } ++reportcnt; } else { if (logfp != NULL) { fprintf(logfp, "%s: no fuzzy matches found", dp->logident); } /* print out errors in standard format */ for (rp = dp->recipients; rp != NULL; rp = rp->next) { /* If not prohibited, print it! */ if (rp->notify == NULL || !CISTREQN(rp->notify,"NEVER",5)) { fprintf(mfp, "error: %s: %s\n", rp->addr->host, rp->addr->user); ++reportcnt; } } } if (logfp != NULL) { rp = dp->recipients; fprintf(logfp, "\n%s: Sent to %s\n", dp->logident, (*rp->addr->link->user == '\0') ? "postmaster" : rp->addr->link->user); } /* Did we report anything ? */ if (reportcnt == 0){ /* No, throw it away and ack success. */ mail_abort(mfp); diagnostic(rp, EX_OK, 0, NULL); return; } fprintf(mfp, "\n--%s\n", boundarystr); fprintf(mfp, "Content-Type: message/delivery-status\n\n"); /* Print out errors in IETF-NOTARY format as well! */ if (mydomain() != NULL) { fprintf(mfp, "Reporting-MTA: dns; %s\n", mydomain() ); } else { fprintf(mfp, "Reporting-MTA: x-local-hostname; -unknown-\n"); } if (dp->envid != NULL) fprintf(mfp, "Original-Envelope-Id: %s\n",dp->envid); /* rfc822date() returns a string with trailing newline! */ fprintf(mfp, "Arrival-Date: %s", rfc822date(&stbuf.st_ctime)); fprintf(mfp, "\n"); for (rp = dp->recipients; rp != NULL; rp = rp->next) { /* If not prohibited, print it! */ const char *typetag; const char *rcpt; static char const *type_rfc = "RFC822"; static char const *type_local = "X-LOCAL"; if (rp->notify != NULL || CISTREQN(rp->notify,"NEVER",5)) continue; rcpt = rp->addr->user; if (strchr(rcpt,'@') != NULL) { typetag = type_rfc; if (strncmp(rcpt,"ns:",3)==0) /* 'hold'-channel stuff */ typetag = type_local; } else typetag = type_local; fprintf(mfp, "Final-Recipient: %s; %s\n", typetag, rcpt); fprintf(mfp, "Action: failed\n"); fprintf(mfp, "Diagnostic-Code: X-LOCAL; 500 (%s)\n", rp->addr->host ); if (rp->orcpt != NULL) fprintf(mfp, "Original-Rcpt: %s\n",rp->orcpt); fprintf(mfp, "\n"); } fprintf(mfp, "--%s\n", boundarystr); fprintf(mfp, "Content-Type: message/rfc822\n\n"); /* Skip over the delivery envelope lines! */ rp = dp->recipients; /* copy original message file */ /* seek to message body -- try it anyway */ lseek(dp->msgfd, dp->msgbodyoffset, SEEK_SET); /* write the (new) headers with local "Received:"-line.. */ writeheaders(rp,mfp,"\n",0,0); fprintf(mfp,"\n"); /* If the DSN RET=NO is in effect, don't copy the msg body! */ if (!dp->dsnretmode || CISTREQN(dp->dsnretmode,"FULL",4)) { /* Copy out the rest with somewhat more efficient method */ lastchar = 0; while ((n = read(dp->msgfd, buf, sizeof(buf))) > 0) { fwrite(buf, sizeof buf[0], n, mfp); lastchar = buf[n-1]; } if (lastchar != '\n') fputs("\n",mfp); } fprintf(mfp, "--%s--\n", boundarystr); if (ferror(mfp)) { mail_abort(mfp); n = EX_IOERR; } else if (mail_close(mfp) == EOF) n = EX_IOERR; else n = EX_OK; for (rp = dp->recipients; rp != NULL; rp = rp->next) { diagnostic(rp, n, 0, (char *)NULL); } }