/* ** Deliver.c ** ** Routines to effect delivery of mail for rmail/smail. ** */ /* ** Patched for MS-DOS compatibility by Stephen Trier ** March, 1990 - Version 0.1 alpha ** Mid-April, 1990 - Version 0.1 beta ** May, 1990 - Version 1.0 beta */ #ifndef lint static char *sccsid="@(#)deliver.c 2.5 (smail) 9/15/87"; #endif # include # include # include # include # include # include "defs.h" #ifdef MSDOS # include # include # include #endif extern int exitstat; /* set if a forked mailer fails */ extern enum edebug debug; /* how verbose we are */ extern char hostname[]; /* our uucp hostname */ extern char hostdomain[]; /* our host's domain */ extern enum ehandle handle; /* what we handle */ extern enum erouting routing; /* how we're routing addresses */ extern char *uuxargs; /* arguments given to uux */ extern int queuecost; /* threshold for queueing mail */ extern int maxnoqueue; /* max number of uucico's */ extern char *spoolfile; /* file name of spooled message */ extern FILE *spoolfp; /* file ptr to spooled message */ extern int spoolmaster; /* set if creator of spoolfile */ extern char nows[]; /* local time in ctime(3) format*/ extern char arpanows[]; /* local time in arpadate format*/ #ifndef MSDOS char stderrfile[20]; /* error file for stderr traping*/ #else char stderrfile[80]; /* error file for stderr traping*/ #endif /* ** ** deliver(): hand the letter to the proper mail programs. ** ** Issues one command for each different host of , ** constructing the proper command for LOCAL or UUCP mail. ** Note that LOCAL mail has blank host names. ** ** The names for each host are arguments to the command. ** ** Prepends a "From" line to the letter just before going ** out, with a "remote from " if it is a UUCP letter. ** */ deliver(argc, hostv, userv, formv, costv) int argc; /* number of addresses */ char *hostv[]; /* host names */ char *userv[]; /* user names */ enum eform formv[]; /* form for each address */ int costv[]; /* cost vector */ { FILE *out; /* pipe to mailer */ FILE *popen(); /* to fork a mailer */ #ifdef RECORD void record(); /* record all transactions */ #endif #ifdef LOG void log(); #endif char *mktemp(); char from[SMLBUF]; /* accumulated from argument */ char lcommand[SMLBUF]; /* local command issued */ char rcommand[SMLBUF]; /* remote command issued */ char scommand[SMLBUF]; /* retry command issued */ char *command; /* actual command */ char buf[SMLBUF]; /* copying rest of the letter */ enum eform form; /* holds form[i] for speed */ long size; /* number of bytes of message */ char *flags; /* flags for uux */ char *sflag; /* flag for smail */ int i, j, status, retrying; char *c, *postmaster(); #ifndef MSDOS int failcount = 0; #else int k, stderrfd; #endif int noqcnt = 0; /* number of uucico's started */ char *uux_noqueue = UUX_NOQUEUE;/* uucico starts immediately */ char *uux_queue = UUX_QUEUE; /* uucico job gets queued */ off_t message; struct stat st; /* ** rewind the spool file and read the collapsed From_ line */ (void) fseek(spoolfp, 0L, 0); (void) fgets(from, sizeof(from), spoolfp); if((c = index(from, '\n')) != 0) *c = '\0'; message = ftell(spoolfp); /* ** We pass through the list of addresses. */ stderrfile[0] = '\0'; for(i = 0; i < argc; i++) { char *lend = lcommand; char *rend = rcommand; char *send = scommand; /* ** If we don't have sendmail, arrange to trap standard error ** for inclusion in the message that is returned with failed mail. */ (void) unlink(stderrfile); #ifndef MSDOS (void) strcpy(stderrfile, "/tmp/stderrXXXXXX"); (void) mktemp(stderrfile); (void) freopen(stderrfile, "w", stderr); if(debug != YES) { (void) freopen(stderrfile, "w", stdout); } #else /* MSDOS */ (void) sprintf(stderrfile, "%s/stderrXXXXXX", ms_tmpdir); (void) mktemp(stderrfile); stderrfd = open(stderrfile, O_CREAT|O_APPEND, S_IREAD|S_IWRITE); dup2(stderrfd, 2); if (debug != NO) dup2(stderrfd, 2); #endif /* !MSDOS */ *lend = *rend = *send = '\0'; /* ** If form == ERROR, the address was bad ** If form == SENT, it has been sent on a previous pass. */ form = formv[i]; if (form == SENT) { continue; } /* ** Build the command based on whether this is local mail or uucp mail. ** By default, don't allow more than 'maxnoqueue' uucico commands to ** be started by a single invocation of 'smail'. */ if(uuxargs == NULL) { /* flags not set on command line */ if(noqcnt < maxnoqueue && costv[i] <= queuecost) { flags = uux_noqueue; } else { flags = uux_queue; } } else { flags = uuxargs; } retrying = 0; if(routing == JUSTDOMAIN) { sflag = "-r"; } else if(routing == ALWAYS) { sflag = "-R"; } else { sflag = ""; } (void) sprintf(lcommand, LMAIL(from, hostv[i])); (void) sprintf(rcommand, RMAIL(flags, from, hostv[i])); /* ** For each address with the same host name and form, append the user ** name to the command line, and set form = ERROR so we skip this address ** on later passes. */ /* we initialized lend (rend) to point at the * beginning of its buffer, so that at * least one address will be used regardless * of the length of lcommand (rcommand). */ for (j = i; j < argc; j++) { if ((formv[j] != form) || (strcmpic(hostv[i], hostv[j]) != 0) || ((lend - lcommand) > MAXCLEN) || ((rend - rcommand) > MAXCLEN)) { continue; } /* ** seek to the end of scommand ** and add on a 'smail' command ** multiple commands are separated by ';' */ send += strlen(send); if(send != scommand) { *send++ = ';' ; } (void) sprintf(send, RETRY(sflag)); send += strlen(send); lend += strlen(lend); rend += strlen(rend); if (form == LOCAL) { (void) sprintf(lend, LARG(userv[j])); (void) sprintf(send, LARG(userv[j])); } else { (void) sprintf(lend, RLARG(hostv[i], userv[j])); (void) sprintf(send, RLARG(hostv[i], userv[j])); } (void) sprintf(rend, RARG(userv[j])); formv[j] = SENT; } retry: /* ** rewind the spool file and read the collapsed From_ line */ (void) fseek(spoolfp, message, 0); /* if the address was in a bogus form (usually DOMAIN), ** then don't bother trying the uux. ** ** Rather, go straight to the next smail routing level. */ if(form == ERROR) { static char errbuf[SMLBUF]; (void) sprintf(errbuf, "address resolution ('%s' @ '%s') failed", userv[i], hostv[i]); command = errbuf; size = 0; goto form_error; } if (retrying) { command = scommand; } else if (form == LOCAL) { command = lcommand; } else { command = rcommand; if(flags == uux_noqueue) { noqcnt++; } } ADVISE("COMMAND: %s\n", command); /* ** Fork the mailer and set it up for writing so we can send the mail to it, ** or for debugging divert the output to stdout. */ /* ** We may try to write on a broken pipe, if the uux'd host ** is unknown to us. Ignore this signal, since we can use the ** return value of the pclose() as our indication of failure. */ #ifndef MSDOS (void) signal(SIGPIPE, SIG_IGN); #endif if (debug == YES) { out = stdout; } else { #ifndef MSDOS failcount = 0; do { out = popen(command, "w"); if (out) break; /* * Fork failed. System probably overloaded. * Wait awhile and try again 10 times. * If it keeps failing, probably some * other problem, like no uux or smail. */ (void) sleep(60); } while (++failcount < 10); #else /* MSDOS */ out = popen(command, "w"); /* * A failed fork under MS-DOS is just that. Don't * bother retrying it; it won't get any better. */ #endif /* !MSDOS */ } if(out == NULL) { exitstat = EX_UNAVAILABLE; (void) printf("couldn't execute %s.\n", command); continue; } size = 0; if(fstat(fileno(spoolfp), &st) >= 0) { size = st.st_size - message; } /* ** Output our From_ line. */ if (form == LOCAL) { #ifdef SENDMAIL (void) sprintf(buf, LFROM(from, nows, hostname)); size += strlen(buf); (void) fputs(buf, out); #else char *p; if((p=index(from, '!')) == NULL) { (void) sprintf(buf, LFROM(from, nows, hostname)); size += strlen(buf); (void) fputs(buf, out); } else { *p = NULL; (void) sprintf(buf, RFROM(p+1, nows, from)); size += strlen(buf); (void) fputs(buf, out); *p = '!'; } #endif } else { (void) sprintf(buf, RFROM(from, nows, hostname)); size += strlen(buf); (void) fputs(buf, out); } #ifdef SENDMAIL /* ** If using sendmail, insert a Received: line only for mail ** that is being passed to uux. If not using sendmail, always ** insert the received line, since sendmail isn't there to do it. */ if(command == rcommand && handle != ALL) #endif { (void) sprintf(buf, "Received: by %s (%s)\n\tid AA%05d; %s\n", hostdomain, VERSION, getpid(), arpanows); size += strlen(buf); (void) fputs(buf, out); } /* ** Copy input. */ while(fgets(buf, sizeof(buf), spoolfp) != NULL) { (void) fputs(buf, out); } /* ** Get exit status and if non-zero, set global exitstat so when we exit ** we can indicate an error. */ form_error: if (debug != YES) { if(form == ERROR) { exitstat = EX_NOHOST; } else if (status = pclose(out)) { exitstat = status >> 8; } /* * The 'retrying' check prevents a smail loop. */ if(exitstat != 0) { /* ** the mail failed, probably because the host ** being uux'ed isn't in L.sys or local user ** is unknown. */ if((retrying == 0) /* first pass */ && (routing != REROUTE) /* have higher level */ && (form != LOCAL)) { /* can't route local */ /* ** Try again using a higher ** level of routing. */ ADVISE("%s failed (%d)\ntrying %s\n", command, exitstat, scommand); exitstat = 0; retrying = 1; form = SENT; #ifndef MSDOS goto retry; #else for (k = i; k < argc; k++) { if ((formv[k] != form) || (strcmpic(hostv[i], hostv[k]) != 0) || ((lend - lcommand) > MAXCLEN) || ((rend - rcommand) > MAXCLEN)) { continue; } formv[k] = ROUTE; /* Try again */ } #endif } /* ** if we have no other routing possibilities ** see that the mail is returned to sender. */ if((routing == REROUTE) || (form == LOCAL)) { /* ** if this was our last chance, ** return the mail to the sender. */ ADVISE("%s failed (%d)\n", command, exitstat); (void) fseek(spoolfp, message, 0); #ifdef SENDMAIL /* if we have sendmail, then it ** was handed the mail, which failed. ** sendmail returns the failed mail ** for us, so we need not do it again. */ if(form != LOCAL) #endif { return_mail(from, command); } exitstat = 0; } } # ifdef LOG else { if(retrying == 0) log(command, from, size); /* */ } # endif } } /* ** Update logs and records. */ # ifdef RECORD (void) fseek(spoolfp, message, 0); record(command, from, size); # endif /* ** close spool file pointer. ** if we created it, then unlink file. */ #ifndef MSDOS (void) fclose(spoolfp); if(spoolmaster) { (void) unlink(spoolfile); } #else /* MSDOS */ /* * Close and unlink the spool file in the main program; we might need * it again. */ close(stderrfd); fclose(stderr); #endif /* !MSDOS */ (void) unlink(stderrfile); } /* ** return mail to sender, as determined by From_ line. */ return_mail(from, fcommand) char *from, *fcommand; { char buf[SMLBUF]; char domain[SMLBUF], user[SMLBUF]; char *r; FILE *fp, *out, *popen(); #ifndef MSDOS int i = 0; #endif r = buf; (void) sprintf(r, "%s %s", SMAIL, VFLAG); r += strlen(r); if(islocal(from, domain, user)) { (void) sprintf(r, LARG(user)); } else { (void) sprintf(r, RLARG(domain, user)); } #ifndef MSDOS i = 0; do { out = popen(buf, "w"); if (out) break; /* * Fork failed. System probably overloaded. * Wait awhile and try again 10 times. * If it keeps failing, probably some * other problem, like no uux or smail. */ (void) sleep(60); } while (++i < 10); #else /* !MSDOS */ out = popen(buf, "w"); /* * Don't bother retrying it if the fork failed. After * all, it's awfully hard for an MS-DOS machine to be * running too many processes at once... :-) */ #endif if(out == NULL) { (void) printf("couldn't execute %s.\n", buf); return; } (void) fprintf(out, "Date: %s\n", arpanows); (void) fprintf(out, "From: MAILER-DAEMON@%s\n", hostdomain); (void) fprintf(out, "Subject: failed mail\n"); (void) fprintf(out, "To: %s\n", from); (void) fprintf(out, "\n"); (void) fprintf(out, "======= command failed =======\n\n"); (void) fprintf(out, " COMMAND: %s\n\n", fcommand); (void) fprintf(out, "======= standard error follows =======\n"); (void) fflush(stderr); if((fp = fopen(stderrfile, "r")) != NULL) { while(fgets(buf, sizeof(buf), fp) != NULL) { (void) fputs(buf, out); } } (void) fclose(fp); (void) fprintf(out, "\n"); (void) fprintf(out, "======= text of message follows =======\n"); /* ** Copy input. */ (void) fprintf(out, "From %s\n", from); while(fgets(buf, sizeof(buf), spoolfp) != NULL) { (void) fputs(buf, out); } (void) pclose(out); }