[mod.sources] SMTP SEND command for Sendmail

sources-request@panda.UUCP (05/23/86)

Mod.sources:  Volume 5, Issue 13
Submitted by: Greg Satz <talcott!mojave.stanford.edu:satz>


Since I saw a request for this on Unix-Wizards and someone asked me for
it, I thought I would send it here for others who may be interested.

The following shell archive contains context diffs that support the
SMTP send command.  These diffs were extracted from the 4.3beta
sendmail and extraneous patches removed, so don't believe the line
numbers at all.

The only change to the configuration files is in localm.m4 where a TTY
mailer needs to be defined. I included an example as localm.m4.

Also included is the program we use to send messages, to.  It is a
simple program that was originally written by Stuart Cracraft.  I
hacked it up to use Sendmail/SMTP.  It isn't much but it will deliver
tty messages.

This code has been in use here at Stanford for over a year.  It is
known to work with the TOPS-20 implementation for sending and receiving
messages.

If anyone makes any changes to these things, I would appreciate it if
you could send them back to me.  I have always wanted to add a reply
command and "what are the last N messages" command, but have never
found the time.  Manual pages are needed too.

Enjoy!

Greg Satz	satz@mojave.stanford.edu
		Shasta!satz

: Run this shell script with "sh" not "csh"
PATH=:/bin:/usr/bin:/usr/ucb
export PATH
all=FALSE
if [ $1x = -ax ]; then
	all=TRUE
fi
/bin/echo 'Extracting sendmail.diffs'
sed 's/^X//' <<'//go.sysin dd *' >sendmail.diffs
*** /tmp/,RCSt1001511	Thu May  1 09:59:09 1986
--- collect.c	Wed Mar  5 10:45:21 1986
***************
*** 197,203 ****
  	*/
  
  	if (hvalue("to") == NULL && hvalue("cc") == NULL &&
! 	    hvalue("bcc") == NULL && hvalue("apparently-to") == NULL)
  	{
  		register ADDRESS *q;
  
--- 197,204 ----
  	*/
  
  	if (hvalue("to") == NULL && hvalue("cc") == NULL &&
! 	    hvalue("bcc") == NULL && hvalue("apparently-to") == NULL &&
! 	    !bitset(EF_TTYSEND, CurEnv->e_flags))
  	{
  		register ADDRESS *q;
  

*** /tmp/,RCSt1001521	Thu May  1 09:59:18 1986
--- deliver.c	Wed Mar  5 22:52:23 1986
***************
*** 1286,1292 ****
  	{
  		extern bool shouldqueue();
  
! 		if (shouldqueue(e->e_msgpriority))
  			mode = SM_QUEUE;
  		else
  			mode = SendMode;
--- 1288,1296 ----
  	{
  		extern bool shouldqueue();
  
! 		if (bitset(EF_TTYSEND, e->e_flags))
! 			mode = SM_QUICKD;
! 		else if (shouldqueue(e->e_msgpriority))
  			mode = SM_QUEUE;
  		else
  			mode = SendMode;

*** /tmp/,RCSt1001526	Thu May  1 09:59:31 1986
--- main.c	Fri Mar 21 13:44:49 1986
***************
*** 451,457 ****
  	expand("\001j", jbuf, &jbuf[sizeof jbuf - 1], CurEnv);
  	MyHostName = jbuf;
  
! 	/* the indices of local and program mailers */
  	st = stab("local", ST_MAILER, ST_FIND);
  	if (st == NULL)
  		syserr("No local mailer defined");
--- 485,491 ----
  	expand("\001j", jbuf, &jbuf[sizeof jbuf - 1], CurEnv);
  	MyHostName = jbuf;
  
! 	/* the indices of local and program and tty mailers */
  	st = stab("local", ST_MAILER, ST_FIND);
  	if (st == NULL)
  		syserr("No local mailer defined");
***************
*** 462,467 ****
--- 496,506 ----
  		syserr("No prog mailer defined");
  	else
  		ProgMailer = st->s_mailer;
+ 	st = stab("tty", ST_MAILER, ST_FIND);
+ 	if (st == NULL)
+ 		syserr("No tty mailer defined");
+ 	else
+ 		TTYMailer = st->s_mailer;
  
  	/* operate in queue directory */
  	if (chdir(QueueDir) < 0)

*** /tmp/,RCSt1001545	Thu May  1 09:59:49 1986
--- recipient.c	Wed Mar  5 14:48:16 1986
***************
*** 192,199 ****
  	stripquotes(buf, TRUE);
  
  	/* do sickly crude mapping for program mailing, etc. */
! 	if (m == LocalMailer && buf[0] == '|')
! 	{
  		a->q_mailer = m = ProgMailer;
  		a->q_user++;
  		if (a->q_alias == NULL && !tTd(0, 1) && !QueueRun && !ForceMail)
--- 193,200 ----
  	stripquotes(buf, TRUE);
  
  	/* do sickly crude mapping for program mailing, etc. */
! 	if (m == LocalMailer)
! 	    if (buf[0] == '|') {
  		a->q_mailer = m = ProgMailer;
  		a->q_user++;
  		if (a->q_alias == NULL && !tTd(0, 1) && !QueueRun && !ForceMail)
***************
*** 201,207 ****
  			usrerr("Cannot mail directly to programs");
  			a->q_flags |= QDONTSEND;
  		}
! 	}
  
  	/*
  	**  Look up this person in the recipient list.
--- 202,211 ----
  			usrerr("Cannot mail directly to programs");
  			a->q_flags |= QDONTSEND;
  		}
! 	} else
! 		if (bitset(EF_TTYSEND, CurEnv->e_flags))
! 			a->q_mailer = m = TTYMailer;
! 	
  
  	/*
  	**  Look up this person in the recipient list.
***************
*** 265,291 ****
  	**  the user (which is probably correct anyway).
  	*/
  
! 	if (!bitset(QDONTSEND, a->q_flags) && m == LocalMailer)
  	{
  		struct stat stb;
  		extern bool writable();

--- 269,309 ----
  	**  the user (which is probably correct anyway).
  	*/
  
! 	if (!bitset(QDONTSEND, a->q_flags) && (m == LocalMailer ||
! 					       m == TTYMailer))
  	{
  		struct stat stb;
  		extern bool writable();
  
*** /tmp/,RCSt1001550	Thu May  1 09:59:53 1986
--- sendmail.h	Wed Mar  5 14:51:58 1986
***************
*** 153,158 ****
--- 153,159 ----
  
  EXTERN MAILER	*LocalMailer;		/* ptr to local mailer */
  EXTERN MAILER	*ProgMailer;		/* ptr to program mailer */
+ EXTERN MAILER	*TTYMailer;		/* ptr to tty mailer */
  /*
  **  Header structure.
  **	This structure is used internally to store header items.
***************
*** 243,248 ****
--- 244,250 ----
  #define EF_KEEPQUEUE	000100		/* keep queue files always */
  #define EF_RESPONSE	000200		/* this is an error or return receipt */
  #define EF_RESENT	000400		/* this message is being forwarded */
+ #define	EF_TTYSEND	001000		/* this message go to tty mailer */
  
  EXTERN ENVELOPE	*CurEnv;	/* envelope currently being processed */
  /*

*** /tmp/,RCSt1001555	Thu May  1 09:59:58 1986
--- srvrsmtp.c	Wed Mar 12 22:49:18 1986
***************
*** 60,69 ****
--- 60,71 ----
  # define CMDDBGKILL	13	/* kill -- kill sendmail */
  # define CMDDBGWIZ	14	/* wiz -- become a wizard */
  # define CMDONEX	15	/* onex -- sending one transaction only */
+ # define CMDSEND	17	/* send -- designate sender of a send */
  
  static struct cmd	CmdTab[] =
  {
  	"mail",		CMDMAIL,
+ 	"send",		CMDSEND,
  	"rcpt",		CMDRCPT,
  	"data",		CMDDATA,
  	"rset",		CMDRSET,
***************
*** 209,219 ****
--- 211,226 ----
  
+ 		  case CMDSEND:
  		  case CMDMAIL:		/* mail -- designate sender */
  			SmtpPhase = "MAIL";
  
***************
*** 245,250 ****
--- 252,259 ----
  			if (p == NULL)
  				break;
  			setsender(p);
+ 			if (c->cmdcode == CMDSEND)
+ 				CurEnv->e_flags |= EF_TTYSEND;
  			if (Errors == 0)
  			{
  				message("250", "Sender ok");
***************
*** 640,645 ****
--- 650,656 ----
  	char *label;
  {
  	int childpid;
+ 	char *s;
  
  	if (!OneXact)
  	{
***************
*** 669,675 ****
--- 680,689 ----
  			/* child */
  			InChild = TRUE;
  			QuickAbort = FALSE;
+ 			s = macvalue('s', CurEnv);
  			clearenvelope(CurEnv, FALSE);
+ 			if (s)
+ 				define('s', s, CurEnv);
  		}
  	}
  
//go.sysin dd *
made=TRUE
if [ $made = TRUE ]; then
	/bin/chmod 644 sendmail.diffs
	/bin/echo -n '	'; /bin/ls -ld sendmail.diffs
fi
/bin/echo 'Extracting localm.m4'
sed 's/^X//' <<'//go.sysin dd *' >localm.m4

Mlocal,	P=/bin/mail, F=rlsDFMmn, S=10, R=20, A=mail -d $u
Mprog,	P=/bin/sh,   F=lsDFMe,   S=10, R=20, A=sh -c $u
Mtty,	P=/usr/stanford/bin/to, F=rlsn, S=10, R=20, A=to $u, M=5000

S10
R@			$n			errors to mailer-daemon
//go.sysin dd *
made=TRUE
if [ $made = TRUE ]; then
	/bin/chmod 644 localm.m4
	/bin/echo -n '	'; /bin/ls -ld localm.m4
fi
/bin/echo 'Extracting to.c'
sed 's/^X//' <<'//go.sysin dd *' >to.c
X/*
 * Send terminal messages locally and over the Internet
 */

#include <sys/types.h>
#include <stdio.h>
#include <ctype.h>
#include <signal.h>
#include <utmp.h>
#include <pwd.h>
#include <sysexits.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

#define	MSG_SIZE 5000

struct	sockaddr_in  hisctladdr;
struct	utmp utmp, *utmpp;
struct	stat statb;
char    msg[MSG_SIZE], buffer[BUFSIZ];
char	tpath[] = "/dev/";
char	term[sizeof(tpath)+sizeof(utmp.ut_line)];
int	netfd, msglen;

char	*from,			  /* Remote user@host */
	*mytimestr,		  /* time string */
	*myname,		  /* Local username */
	*mytty,			  /* Local TTY */
	myhost[100];		  /* Local host name */

int	alarmed;
int	sendmail;
time_t	mytime;

char	*getlogin(), *index(), *ctime(), *malloc(), *ttyname();
int	nothing();
time_t	time();

main (argc, argv)
int argc;
char **argv;
{
    register char *p, *q;
    int ufd;
    char person[80];
    struct hostent *hp;

    argc--, argv++;
    while (argc > 0 && **argv == '-')
	switch (*++*argv) {

	case 'r':
		argc--, argv++;
		if (argc > 0)
		    from = *argv;
		else
		    goto usage;
		argc--, argv++;
		break;

	case 'd':
		sendmail++;
	case NULL:
		argc--, argv++;
		break;			/* we can ignore this */

	default:
		goto usage;
	}

    if (argc < 1) {
usage:
	fprintf (stderr, "usage: to address[,...,address] [message]\n");
	exit (EX_USAGE);
    }
    signal(SIGALRM, nothing);
    if (stat("/etc/utmp", &statb) < 0) {
	perror("to: /etc/utmp");
	exit(EX_OSFILE);
    }
    utmpp = (struct utmp *) malloc((unsigned)statb.st_size);
    if ((ufd = open("/etc/utmp", 0)) < 0) {
	perror("to: /etc/utmp");
	exit(EX_OSFILE);
    }
    read(ufd, (char *)utmpp, (unsigned) statb.st_size);
    close(ufd);
    if ((myname = getlogin()) == NULL)
	myname =  "UNKNOWN";
    if ((mytty = ttyname(0)) != NULL)
	mytty += 5;
    mytime = time((time_t *) 0);
    mytimestr = ctime(&mytime);
    gethostname(myhost, sizeof myhost);
    if ((hp = gethostbyname(myhost)) != NULL)
	strcpy(myhost, hp->h_name);
    if (argc == 1) {
	fputs ("Msg:\n", stdout);
	p = msg;
	while (p < &msg[MSG_SIZE] && gets(p) != NULL) {
	    p += strlen(p);
	    *p++ = '\r'; *p++ = '\n';
	}
	msglen = p - msg;
	p = *argv;
    } else {
	p = *argv;
	q = msg;
	while (--argc && q < &msg[MSG_SIZE]) {
	    strcpy(q, *++argv);
	    q += strlen(*argv);
	    *q++ = ' ';
	}
	*q++ = '\r';
	*q++ = '\n';
	msglen = q - msg;
    }
    for (; *p;) {
	for(q = person; *p && *p != ',';)
		*q++ = *p++;
	*q = '\0';
	if ((q = index(person, '@')) != NULL) {
	    *q++ = '\0';
	    netsend(person, q);
	} else
	    if (!locsend(person)) {	/* user not logged in */
		if (!sendmail)
			fprintf(stderr, "to: %s: not logged in\n", person);
		exit(EX_UNAVAILABLE);
	    }
    }
    exit(EX_OK);
}

X/*
 * Send a message to a remote user
 */
netsend (person, host)
char *person, *host;
{
    register int n;
    char newhost[100];
    struct servent *sp;
    struct hostent *hp;

#ifdef DEBUG
    printf("net user = %s@%s\n",person, host);
#endif
    hp = gethostbyname(host);
    if (hp == NULL) {
	fprintf(stderr, "to: %s: no such host\n", host);
	exit(EX_NOHOST);
    }
    strcpy(newhost, hp->h_name);
    sp = getservbyname("smtp", "tcp");
    if (sp == NULL) {
	fprintf(stderr, "to: smtp/tcp: service not found\n");
	exit(EX_UNAVAILABLE);
    }
    if ((netfd = socket(hp->h_addrtype, SOCK_STREAM, 0, 0)) < 0) {
	perror("to: socket");
	exit(EX_UNAVAILABLE);
    }
    if (bind(netfd, &hisctladdr, sizeof hisctladdr, 0) < 0) {
	perror("to: bind");
	exit(EX_UNAVAILABLE);
    }
    bcopy(hp->h_addr, (caddr_t)&hisctladdr.sin_addr, hp->h_length);
    hisctladdr.sin_family = hp->h_addrtype;
    hisctladdr.sin_port = sp->s_port;
    if (connect(netfd, &hisctladdr, sizeof hisctladdr, 0) < 0) {
        perror("to: connect");
	exit (EX_UNAVAILABLE);
    }
    n = getrply ();
    if (n != 220)		/* Got the site */
	goto error;
    sprintf (buffer, "HELO %s\r\n",myhost);
#ifdef DEBUG
    printf("buffer = %s\n",buffer);
#endif
    write(netfd, buffer, strlen (buffer));
    n = getrply ();
    if (n != 250)
	goto error;
    sprintf (buffer, "SEND FROM:<%s@%s>\r\n", myname, myhost);
#ifdef DEBUG
    printf("buffer = %s\n",buffer);
#endif
    write (netfd, buffer, strlen (buffer));
    n = getrply ();
    if (n != 250)
	goto error;
    sprintf (buffer, "RCPT TO:<%s@%s>\r\n", person, newhost);
#ifdef DEBUG
    printf("buffer = %s\n",buffer);
#endif
    write (netfd, buffer, strlen (buffer));
    n = getrply ();
    if (n != 250)
	goto error;
#ifdef DEBUG
    printf("buffer = DATA\n");
#endif
    write (netfd, "DATA\r\n", 6);
    n = getrply ();
    if (n != 354)
	goto error;
    write (netfd, msg, msglen);
#ifdef DEBUG
    printf(".\n");
#endif
    write (netfd, ".\r\n", 3);
    n = getrply ();
    if (n != 250)
	goto error;
#ifdef DEBUG
    printf("buffer = QUIT\n");
#endif
    write (netfd, "QUIT\r\n",6);
    n = getrply ();
    if (n != 221 && n != 220)
	goto error;
done:
    disconnect ();
    return;
error:
    fprintf(stderr, "to: network error: %s\n", buffer);
    goto done;
}

X/*
 * Send a message to a local user
 */
locsend(person)
char   *person;
{
    char tbuf[MSG_SIZE+BUFSIZ];
    int count, found;
    FILE *tf;
    register struct utmp *up;

    count = statb.st_size / sizeof(struct utmp);
    found = 0;
    for (up = utmpp; up < &utmpp[count]; up++) {
	if (up->ut_name[0] == '\0' || strncmp(person, up->ut_name,
							sizeof(utmp.ut_name)))
	    continue;
	strcpy(term, tpath);
	strncat(term, up->ut_line, sizeof(utmp.ut_line));
	alarmed = 0;
	alarm(3);
	if ((tf = fopen(term, "w")) != NULL) {
	    alarm(0);
	    setbuf(tf, tbuf);
	    fprintf(tf, "\r\n\007%s,", from ? from : myname);
	    if (mytty)
		fprintf(tf, " %s,", mytty);
	    fprintf(tf, " %.7s%.4s%.9s\r\n%s",
		&mytimestr[4],
		&mytimestr[20],
		&mytimestr[10],
		msg);
	    alarm(5);
	    fflush(tf);
	    fclose(tf);
	    alarm(0);
	    if (!alarmed)
		found++;
	}
    }
    return(found);
}

X/*
 * Disconnect from SMTP server
 */
disconnect ()
{
    write(netfd, "QUIT\r\n", 6);
    close(netfd);
}

X/*
 * Read reply code from SMTP server
 */
getrply ()
{
    char temp[BUFSIZ];
    register int i, n;

    while((i = read(netfd, temp, sizeof temp)) == 0)
	;
    temp[i] = NULL;
#ifdef DEBUG
    printf("temp=\"%s\"\n",temp);
#endif
    for (i = 0, n = 0; temp[i] != NULL; i++)
	if (temp[i] != '\n' && temp[i] != '\r')	{
	    buffer[n++] = temp[i];
	}
    buffer[n] = NULL;
    n = 0;
    for (i = 0; i < strlen (buffer); i++) {
	if (isdigit (buffer[i]))
	    n = (n * 10) + (buffer[i] - '0');
	else
	    break;
    }
#ifdef DEBUG
    printf("n=%d temp=\"%s\"\n",n, temp);
#endif
    return (n);
}

nothing()
{
    alarmed++;
}
//go.sysin dd *
made=TRUE
if [ $made = TRUE ]; then
	/bin/chmod 644 to.c
	/bin/echo -n '	'; /bin/ls -ld to.c
fi