/* * mailer * * modified for Illinois NCP * return undeliverable mail as a message * fixed write length in returned mail * Modified by BBN(dan) July 12, 79: to change call to wait() to give * it an argument. * Add immediate fork so it gets adopted by init jsq BBN 21July79 * Modified by BBN(josh) Aug 10, 79: exec sndmsg to return mail to sender. * Modified to do logging of everything received over net as it is read, * give reason if net open failed, timeout on net reads, * diagnose errors when trying to return mail, * close stdin before calling sndmsg to ensure that it completes. * BBN:dan May 7, 1980 * Modified to delete printing of host number and host_hash value: * BBN:dan May 12, 1980 * Modified to use either mtp (the new mail transfer protocol - rfc780) * or ftp. Removed code dealing with old style return addresses * Arranged to have messages going from an internal network * out to an external network pass through a filter that changes * names of hosts on the internal net to the name of the forwarder. * Also reformatted: * BBN: Eric Shienbrood 27 April 1981 * Changed to stdio for all I/O except network writes. Didn't change * this because it would have been too hard too check for write errors. * Also generally cleaned up code. * BBN: Eric Shienbrood 29 May 1981 * Modified to work in either NCP or TCP environment. To get a TCP mailer, * compile with TCP #defined. Otherwise you get an NCP mailer. * BBN: Eric Shienbrood 1 June 1981 * Modified to use new network library. Installed new host-hashing package. * BBN: Eric Shienbrood 15 Sept 1981 */ #include <stdio.h> # include <con.h> #include <signal.h> #include <errno.h> #include <sys/types.h> #include <sys/stat.h> #include <netlib.h> #ifndef NETMAILDIR #define NETMAILDIR "/usr/spool/netmail" #endif /* * The following macro returns TRUE if * <hostname> is on the network whose number * is <netnum>, FALSE otherwise */ #define is_on_net(hostname,netnum) (!isbadhost(getnhost(hostname,netnum))) #define TMPFILE "/tmp/mailertmp" #define HOSTFILTER "/etc/net/hostfilter" # define NETFILE "/dev/net/tcp" #define SLPTIME 600 /* default sleep time */ #define RETDAYS 1 /* return mail which is undelivered after */ /* this many days */ #define DOWNRET ((long)(24L*60L*60L*RETDAYS)) #define NTBYPASS 3 /* default bypass count */ #define OPEN_TIMEOUT 60 /* Timeout on network open call */ #define READ_TIMEOUT 300 /* Timeout on network read calls */ #define WRITE_TIMEOUT 30 /* Minimum timeout on network write calls */ #define MTPSOCK 57 /* MTP socket number */ #define FTPSOCK 3 /* FTP socket number */ #define TCP_CAP 1 /* Code used in host map to indicate host */ /* has TCP capability */ #define NCP_CAP 2 /* Same for NCP capability */ /* Codes returned by ship_it_off */ #define NO_OPEN 0 #define DID_OPEN 1 #define SENT_MAIL 2 #define FALSE 0 #define TRUE 1 /* * Following structure tells us how many times we should * skip a host because it is down (h_bypass) and * some information that we know about a host * (h_flags). Currently, we only keep track of * whether or not each host speaks MTP (the default) * or only FTP. */ struct hostinfo { struct hostinfo *h_next; netaddr h_addr; char h_flags; char h_bypass; }; char sndhnm[60]; /* destination host name */ char *dest_host; /* host to which we are currently talking */ char mailcomd[256]; /* buffer into which mtp or ftp */ /* mail command is constructed */ char recipient[60]; /* name of destination mailbox */ char sender[60]; /* name of sending mailbox */ char linebuf[256]; char mailbuf[512]; char *ourname; /* the name of this host */ netaddr ourhostaddr; /* net address of this host */ FILE *dirf; int nflag; int timed_out; int slptime; int ntbypass; char *getline(), *getlc(), *ctime(); char *net_getline(); struct hostinfo *host_lookup(); char filename[16]; #define NO_MTP 1 #define OTIMEOUT OPEN_TIMEOUT struct con openparam; int netfd = -1; /* file descriptor of network connection */ FILE *mailfile, *netin; /* -------------------------- M A I L D A E M O N ------------------------- */ /* * See if there is any mail waiting delivery to some other * site on the network, and try to deliver it. * Calling sequence is * * mailer [slptime] [npass] [logflag] * * If slptime is present, it is the number of seconds to sleep * between passes through the mail directory. If it is absent * or 0, a default value is used(SLPTIME). * If npass is present, it is the number of passes through * which to bypass mail for hosts which are not responding. * If logflag is not present or is non-zero, messages will be * written out indicating the progress of the mailer. */ main(argc, argv) int argc; char **argv; { time_t tvec; /* Dup 1 into 2 so all error messages go to logfile: BBN(dan) */ /* Make log output unbuffered: BBN(shienbrood) */ dup2 (1, 2); setbuf(stdout, NULL); setbuf(stderr, NULL); signal(SIGHUP, SIG_IGN); signal(SIGINT, SIG_IGN); signal(SIGQUIT, SIG_IGN); setsig(0); /* will be changed while sleeping between passes */ if (geteuid() != 0) ecmderr (0, "Mailer not running as root.\n"); ourname = thisname(); ourhostaddr = thishost(); getlc(ourname); if (argc < 2 || (slptime = atoi(argv[1])) <= 0) slptime = SLPTIME; if (argc < 3 || (ntbypass = atoi(argv[2])) <= 0) ntbypass = NTBYPASS; if (argc > 3) { nflag = atoi(argv[3]); if (nflag < 0) nflag = 1; } else nflag = 1; if (!loadnetmap()) ecmderr(0, "Error loading network map\n"); if (chdir(NETMAILDIR) == -1) ecmderr(-1, "Cannot change current directory to \"%s\".\n", NETMAILDIR); dirf = fopen(".", "r"); if (dirf == NULL) ecmderr(-1, "Unable to open \"%s\".\n", NETMAILDIR); loop: if (nflag) { time(&tvec); printf("\nStarting at %s", ctime(&tvec)); printf("sleep time = %d, bypass = %d\n", slptime, ntbypass); } fseek(dirf, 0L, 0); while (get_next_file()) { if (ship_it_off() != NO_OPEN) sleep(30); } if (nflag) { time(&tvec); printf("Ending at %s", ctime(&tvec)); } dec_bypass_cnt(); setsig(1); /* let signal 11 wake us up during wait between passes */ xsleep(slptime); setsig(0); /* jsq BBN 11Jul19 */ goto loop; } /* -------------------------- X S L E E P --------------------------------- */ /* * This routine is like the sleep library routine, but it returns * if interrupted by a signal, instead of going back to sleep. */ xsleep(n) unsigned n; { int sleepx(); if (n==0) return; signal(SIGALRM, sleepx); alarm(n); pause(); alarm(0); signal(SIGALRM, SIG_IGN); } sleepx() {} dosig11() { /* jsq BBN 11Jul19 */ setsig(0); if (nflag) printf("\nAwakened by signal 11\n"); } setsig(aflag) /* jsq BBN 11Jul19 */ { if (aflag) signal(11, dosig11); else signal(11, SIG_IGN); } /* -------------------------- T I M E O U T ------------------------------- */ /* * Set/reset timeout. timeout(period) [period > 0] causes a reset in * period seconds. timeout(0) turns it off. Check the global variable timed_out * to see if it happened. */ timeout(period) int period; { extern int timed_out; int clock_int(); if (period > 0) { /* Turn on */ signal(SIGALRM, clock_int); alarm(period); timed_out = 0; } else { /* Turn off */ alarm(0); signal(SIGALRM, SIG_IGN); } } /* -------------------------- C L O C K _ I N T --------------------------- */ /* * Called on a SIGALRM signal. */ clock_int() { extern int timed_out; timed_out++; } /* ----------------------- G E T _ N E X T _ F I L E --------------------- */ /* * Find get the next file in NETMAILDIR and set up all * of the parameters for the mailing. Return TRUE if * a file was found, FALSE if no more files in directory. */ get_next_file() { register char *p; register char *s; char tempf[16]; extern char *errmsg(); extern char *getdirent(); char *scopyto(); loop: if ((s = getdirent (dirf, NULL)) == NULL) return(FALSE); scopy(s, filename, 0); mailfile = fopen (filename, "r"); if (mailfile == NULL) { printf("Could not open mailfile \"%s\": %s\n", filename, errmsg(-1)); return(FALSE); } p = getline (mailfile); if (p == 0) { printf("No characters in mailfile \"%s\".\n", filename); goto badfile; } /* * The first line of the mailfile contains: * the hostname * the destination at that host * the 'return-to' field, which is the name of * the person to return the mail to. */ /* Copy the hostname into sndhnm. */ p = scopyto(p, sndhnm, ":", sizeof(sndhnm)); if (*p == '\0') { printf("no end to host name\n"); goto badfile; } getlc (sndhnm); /* Put hostname in lowercase */ p++; /* Copy the addressee into the right place in the mail command. */ p = scopyto(p, recipient, ":", sizeof(recipient)); if (*p == '\0') { printf("no end to addressee\n"); goto badfile; } p++; /* Copy the return address into sender. */ if (*p == ':') /* Null return address */ scopy ("root", sender, 0); else { p = scopyto(p, sender, ":", sizeof(sender)); if (*p == '\0') { printf("no return address\n"); goto badfile; } } if (nflag) printf("Mail found in file %s for %s at %s\n", filename, recipient, sndhnm); return(TRUE); badfile: printf("bad file found and removed:%s\n", filename); if (mailfile != NULL) fclose (mailfile); unlink(filename); goto loop; } /* ------------------------- S H I P _ I T _ O F F ------------------------ */ /* * Carry out the protocol to mail off a file over the network. * Tries using MTP first, FTP if that fails. * A TCP mailer will use only MTP. * If there is a permanent failure, the message is returned to * the sender. * Returns one of 3 values for the following 3 cases: * a) Didn't even get as far as trying to open * a connection (destination host unknown, or * host bypassed because it has been down). * b) Opened or tried to open a connection, * but didn't succeed in sending mail. * c) Successfully transmitted mail. */ ship_it_off() { netaddr hnum; time_t life; register char *p; register char *datestr; register int i, j; register FILE *rtnfile; int proto, wantreply, fd, errnum, retcode, downdays; static char netbuf[512]; char from[60]; extern int errno; extern char *sfind(); extern char *errmsg(); struct stat filestat; struct hostinfo *hp; retcode = NO_OPEN; dest_host = sndhnm; hnum = gethost(sndhnm); if (isbadhost(hnum)) { p = "Destination host is unknown."; goto fail; } if ((hp = host_lookup(hnum))->h_bypass) { if (nflag) printf("Message bypassed because host has been down.\n"); goto nogood; } if (*(sfind (sender, "@")) == '\0') sprintf (from, "%s@%s", sender, ourname); else scopy (sender, from, 0); /* * The first time we connect to each host, * we assume that it speaks mtp. If we * time out waiting for a connection on the * mtp socket, we decide the host doesn't speak * mtp, and from then on we only try that host * on the ftp socket. The TCP mailer only speaks * on the mtp port. */ # define FTP 0 # define MTP 1 proto = MTP; sprintf (mailcomd, "mail from:<%s> to:<%s@%s>", from, recipient, sndhnm); setup_open (&openparam, hnum, MTPSOCK, OTIMEOUT); top: if (nflag) printf("Attempting connection to %s on %s socket.\n", dest_host, proto == FTP ? "FTP" : "MTP"); retcode = DID_OPEN; netfd = open(NETFILE, &openparam); if (netfd < 0) { errnum = errno; if (nflag) printf("Unable to open connection: %s\n", errmsg(errnum)); /* * Record fact that host was down. */ host_lookup(hnum)->h_bypass = ntbypass; stat(filename, &filestat); if ((life = filestat.st_atime - filestat.st_mtime) > DOWNRET) { if (nflag) printf("\tmessage will be returned to sender -- host down for more\n\tthan %ld secs\n", DOWNRET); p = "Host has been down for over 1 day"; goto fail; } goto nogood; } ioctl (netfd, NETSETE, NULL); netin = fdopen (netfd, "r"); i = 0; wantreply = proto == FTP ? 300 : 220; while (i != wantreply) { p = net_getline(netin, nflag); if (p == 0) goto tempfail; i = reply(p); if (i >= 400) goto tempfail; } i = net_putline (netfd, mailcomd); if (i < 0) goto tempfail; i = 0; wantreply = proto == FTP ? 350 : 354; while (i != wantreply) { p = net_getline(netin, nflag); if (p == 0) goto tempfail; i = reply(p); if (proto == FTP) { if (i == 331) goto fail; if (i == 504) switch(loginfirst()) { case -1: goto fail; case 0: goto tempfail; case 1: continue; } /* * "user unknown" codes from various places * jsq BBN 10Jul79 */ if (i == 450 || i == 451 || i == 431) goto fail; } if (i >= 400 && i < 500) goto tempfail; if (i >= 500 && !(proto == FTP && i == 951)) goto fail; } while (p = getline(mailfile)) { if (p[0] == '.' && proto == MTP) *--p = '.'; /* Double leading period */ i = net_putline (netfd, p); if (i < 0) goto tempfail; } i = net_putline (netfd, "."); printf("Finished sending mail.\n"); if (i < 0) goto tempfail; i = 0; wantreply = proto == FTP ? 256 : 250; while (i != wantreply) { p = net_getline(netin, nflag); if (p == 0) goto tempfail; i = reply(p); if (i > 300) goto fail; } net_putline (netfd, proto == FTP ? "bye" : "quit"); /* keep NCP happy until 'flush user' fixed */ net_getline(netin, nflag); fclose(netin); fclose(mailfile); unlink(filename); if (nflag) printf("Transmission completed\n"); return(SENT_MAIL); fail: if (nflag) printf("Unrecoverable error\n"); /* Return the message to the sender */ close (creat( TMPFILE, 0666)); if ((rtnfile = fopen (TMPFILE, "w")) == NULL) { cmderr(-1, "Cannot create \"%s\".\n", TMPFILE); goto done; } if (nflag) printf("Returning mail to %s\n", sender); time(&life); datestr = ctime(&life); datestr[3] = datestr[7] = datestr[10] = datestr[19] = datestr[24] = '\0'; fprintf (rtnfile, "Date: %s %s %s %s (%s)\n", &datestr[8], &datestr[4], &datestr[20], &datestr[11], &datestr[0]); fprintf (rtnfile, "From: ~MAILER~DAEMON at %s\n", thisname()); fprintf (rtnfile, "Subject: Undeliverable mail\n"); fprintf (rtnfile, "To: %s\n\n", sender); fprintf (rtnfile, "Mail addressed to %s at %s could not be sent.\n", recipient, sndhnm); fprintf(rtnfile, "%s\n------- Unsent message is below -------\n\n", p); fclose(mailfile); if ((mailfile = fopen(filename, "r")) == NULL) { cmderr (-1, "Couldn't open mail file.\n"); goto done; } getline (mailfile); while ((i = fread(mailbuf, 1, 512, mailfile)) > 0) fwrite(mailbuf, 1, i, rtnfile); fclose(rtnfile); close(0); /* Make sure sndmsg reads EOF */ open(TMPFILE, 0); execute("/etc/delivermail", "delivermail", "-f", "~MAILER~DAEMON", sender, 0); close(0); unlink(TMPFILE); done: unlink(filename); tempfail: if (nflag) printf("Failure during transmission: %s\n", p); if (netfd >= 0) { net_putline (netfd, proto == FTP ? "bye" : "quit"); fclose(netin); } nogood: fclose(mailfile); return(retcode); } /* * Setup_open: set up an open structure to be used in opening a network * connection. Conditionally compiled (TCP flag) to set up for * either a TCP or NCP open. */ setup_open (open_struct, hostn, socket, otimeout) struct con *open_struct; netaddr hostn; int socket; int otimeout; { open_struct->c_mode = CONACT | CONTCP; *((long *) &(open_struct->c_con)) = *((long *)&hostn); open_struct->c_fport = socket; open_struct->c_timeo = otimeout; } /* * Execute: invoke another program and wait for it. Args: as to execl. */ static execute(name, args) char *name, *args[]; { int chpid, waitres, status; int fork(), wait(); chpid = fork(); if (chpid == -1) { /* could not fork */ if (nflag) printf("Cant fork\n"); return (-1); } else if (chpid == 0) { /* if we are the child */ execvp(name, &args); if (nflag) printf("Exec failed\n"); exit(-1); } else { /* if we are the parent */ do waitres = wait(&status); /* wait for death */ while (waitres != chpid && waitres != -1); } return (status); } /* * Getline - read a line into a buffer, and replace the newline * with a null character. Leave some extra room at the beginning * of the line buffer in case we have to prepend a character later. */ char * getline (inp) register FILE *inp; { register int ch; register char *p; p = &mailbuf[2]; while ((ch = getc (inp)) != '\n' && ch != EOF && p < &mailbuf[(sizeof mailbuf) - 3]) *p++ = ch; *p = '\0'; return (ch == EOF || ferror(inp) ? NULL : &mailbuf[2]); } /* -------------------------- N E T _ G E T L I N E ----------------------- */ /* * Read a line from the specified stream, performing timeouts and logging, * if requested. Return 0 on EOF or error, address of line otherwise. * The returned line is null-terminated, with no newline at the end. * Carriage returns are ignored. * If the last line of the stream does not end in a newline, one is appended. * The line is always put in the global static array 'linebuf'. */ char * net_getline (infile, lflag) register FILE *infile; int lflag; /* Log if on */ { register char *nextout; register int ch; extern int errno; extern char *errmsg(); nextout = linebuf; timeout(READ_TIMEOUT); while ((ch = getc (infile)) != '\n') { if (ch == EOF) { if (nextout > linebuf) *nextout++ = ch = '\n'; break; } if (ch != '\r') *nextout++ = ch; } timeout(0); *nextout = '\0'; if (lflag) { if (ch == '\n') printf("%s|%s\n", dest_host, linebuf); else { printf("%s returned %s", dest_host, feof(infile) ? "EOF" : errmsg(-1)); if (errno == EINTR && timed_out) printf(" (timed out)\n"); else printf("\n"); } } if (ch == EOF) return(NULL); return(linebuf); } /* -------------------------- N E T _ P U T L I N E ----------------------- */ /* * Write a null terminated string out to the network */ net_putline (fd, str) int fd; char *str; { register char *s, *p; register int i; register int j; char wbuf[BUFSIZ]; p = wbuf; for (s = str; *s; ) *p++ = *s++; *p++ = '\r'; *p++ = '\n'; *p++ = '\0'; i = p - wbuf - 1; timeout(i > WRITE_TIMEOUT ? i : WRITE_TIMEOUT); j = write (fd, wbuf, i); timeout(0); if (nflag && j < 0) { printf("write failed\n"); printf("line was '%s'.\n", str); if (errno == EINTR && timed_out) printf(" (timed out)\n"); } return(j); } /* ------------------------------- R E P L Y ------------------------------ */ /* * Find the reply number from a network message. * Converts the first three characters into an integer. * If any are non numeric, return a zero. */ reply(ap) register char *ap; { register char c; int i; register int j; while ((*ap < '0') || (*ap > '9')) if (*ap++ == '\0') return(0); /* multics */ j = 0; for (i = 3; i; i--) { c = *ap++; if ((c < '0') || (c > '9')) return(0); j *= 10; j += c; j -= '0'; } return(j); } /* -------------------------- L O G I N F I R S T ------------------------- */ /* * Login to a bastard host who insists on it * * send a "user NETML\npassNETML\n"; * wait for the 330 230 * resend the mail command * * if a transmission failure return 0 * if a bad reply return -1 * else return 1 */ loginfirst() { } /* -------------------------- S C O P Y T O ------------------------------- */ /* * Copy from source to destination until one of specified chars in source * reached. Return pointer to ending place in source. */ char * scopyto(s, d, cl, max) char *s; char *d; char *cl; int max; /* Size of destination array */ { char * end; int size; extern char * sfind(); end = sfind(s, cl); size = end - s + 1; /* Including null at end */ if (size > max) size = max; scopy(s, d, d + size - 1); return(end); } /* -------------------------- H O S T _ L O O K U P ----------------------- */ /* * Return a pointer to a per-host information entry, * creating it if one doesn't already exist. */ #define HSHSIZE 257 struct hostinfo *host_status[HSHSIZE]; struct hostinfo * host_lookup(hostaddr) netaddr hostaddr; { register char *cp; register struct hostinfo *hp; register int index; int i; index = hashost(hostaddr); for (hp = host_status[index]; hp != NULL; hp = hp->h_next) if (iseqhost(hp->h_addr, hostaddr)) return (hp); if ((hp = (struct hostinfo *)calloc(1, sizeof(struct hostinfo))) == NULL) ecmderr(-1, "No room for host info.\n"); hp->h_addr = hostaddr; hp->h_next = host_status[index]; host_status[index] = hp; return (hp); } /* * Lowercase a string in place. */ char * getlc(s) char *s; { register char *p, c; for (p = s; c = *p; p++) { if (c <= 'Z' && c >= 'A') *p += ('a' - 'A'); } return(s); } /* * Decrement the number-of-times-to-bypass count * for each host that is marked as being down. */ dec_bypass_cnt() { register int i; register struct hostinfo *hp; for (i = 0; i < HSHSIZE; i++) for (hp = host_status[i]; hp != NULL; hp = hp->h_next) if (hp->h_bypass) hp->h_bypass--; }