BBN-Vax-TCP/src/mtp/mailer.c
/*
* 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--;
}