V10/cmd/upas/smtp/converse.c

Compare this file to the similar file:
Show the results in this format:

/*
 * Do the necessary commands for a smtp transfer.  Start by waiting for the
 * connection to open, then send HELO, MAIL, RCPT, and DATA.  Check the
 * reply codes and give up if needed.
 * 
 * This code modified from the MIT UNIX TCP implementation:
 * Copyright 1984 Massachusetts Institute of Technology
 * 
 * Permission to use, copy, modify, and distribute this file
 * for any purpose and without fee is hereby granted, provided
 * that this copyright and permission notice appear on all copies
 * and supporting documentation, the name of M.I.T. not be used
 * in advertising or publicity pertaining to distribution of the
 * program without specific prior permission, and notice be given
 * in supporting documentation that copying and distribution is
 * by permission of M.I.T.  M.I.T. makes no representations about
 * the suitability of this software for any purpose.  It is provided
 * "as is" without express or implied warranty.
 */

#include <stdio.h>
#include <signal.h>
#include <sysexits.h>
#include "mail.h"
#include "smtp.h"
#include "string.h"

#define TRUE 1
#define FALSE 0
#define MINUTES * 60
#define HOURS * 60 MINUTES
#define DAYS * 24 HOURS

char *strcat(), *strcpy();

SIGRETURN ignore_signal();
SIGRETURN death();
char	*timemsg;
int	timelim;
int	timeerror;
long 	nbytes;
extern int debug;
FILE	*fi;
FILE	*fo;

converse(unixformat, from, rcpts, domain, sfi, sfo, mlfd)
char	*from;				/* from address */
namelist *rcpts;			/* to addresses */
char	*domain;
FILE	*sfi;				/* smtp input */
FILE	*sfo;				/* smtp output */
FILE	*mlfd;				/* mail file descriptor */
{
	extern char *helohost;
	char buf[MAXSTR];
	namelist *np;
	extern int kludgepause;

	fi = sfi;  fo = sfo;

	(void) signal(SIGALRM, death);

	setalarm(5 MINUTES, "initial handshake", EX_TEMPFAIL);
	expect(220, sfi, sfo, EX_PROTOCOL);			/* expect a service ready msg */

	/*
	 * This pause is needed when an smtp call gets redialed
	 * (the research version of the Datakit splice.)  Because of
	 * a race condition inherent in both features, -inet- must
	 * not be too eager to send HELO until the connection is set up
	 * completely.  Unfortunately, the other guy's 220 response
	 * (above) can arrive before the redialed connection
	 * is completely processed locally.  Hence, the brief delay.
	 * We can't fix this without changing (read ``fixing'') Datakit
	 * everywhere.  This kludge is slightly easier.
	 */
	if (kludgepause)
		sleep(kludgepause);

	sprintf(buf, "HELO %s\n", helohost);
	csend(buf);
	expect(250, sfi, sfo, EX_PROTOCOL);			/* expect an OK */

	strcpy(buf, "MAIL FROM:<");
	strcat(buf, from);
	strcat(buf, ">\n");
	csend(buf);
	setalarm(10 MINUTES, "response to MAIL FROM/RCPT TO", EX_TEMPFAIL);
	expect(250, sfi, sfo, EX_PROTOCOL);			/* expect OK */

	for (np=rcpts; np!=NULL; np=np->next) {
		strcpy(buf, "RCPT TO:<");
		strcat(buf, np->name);
		strcat(buf, ">\n");
		csend(buf);
		expect(250, sfi, sfo, EX_NOUSER);		/* expect OK */
	}
	setalarm(10 MINUTES, "response to DATA", EX_TEMPFAIL);
	csend("DATA\n");
	expect(354, sfi, sfo, EX_PROTOCOL);
	setalarm(10 MINUTES, "sending mail data", EX_TEMPFAIL);
	do_data(unixformat, mlfd, sfo, from, rcpts, domain);
	setalarm(1 HOURS, "expecting delivery ack", EX_TEMPFAIL);
	expect(250, sfi, sfo, EX_PROTOCOL);

	setalarm(5 MINUTES, "response to QUIT", 0);
	csend("QUIT\n");
	expect(221, sfi, sfo, 0);			/* who cares? (Some do)*/
	exit(0);
}

/*
 *  escape '.'s at the beginning of lines and turn newlines into
 *  /r/n's.
 */
static char smlastchar;

smfputs(str, fp)
	char *str;
	FILE *fp;
{
	register char *cp;

	/*
	 *  escape a leading dot
	 */
	if(smlastchar=='\n' && str[0]=='.') {
		nbytes++;
		fputc('.', fp);
	}

	/*
	 *  output the line
	 */
	for(cp=str; *cp; cp++){
		if(*cp=='\n') {
			nbytes++;
			putc('\r', fp);
		}
		nbytes++;
 		putc(*cp, fp);
	}
	if(cp!=str)
		smlastchar = *(cp-1);
}


/*
 * Send the data from the specified mail file out on the current smtp
 * connection.  Do the appropriate netascii conversion and hidden '.'
 * padding.  Send the <CRLF>.<CRLF> at completion.
 */
do_data(unixformat, sfi, sfo, from, rcpts, domain)
	register FILE *sfi;		/* mail file descriptor */
	register FILE *sfo;		/* smtp files */
	char *from;
	namelist *rcpts;
	char *domain;
{
	static string *rcvr;
	char buf[4096];
	namelist *p;
	long nchars;

	/*
	 *  turn rcpts into a , list of receivers
	 */
	rcvr = s_reset(rcvr);
	for(p = rcpts; p; p = p->next){
		s_append(rcvr, p->name);
		if(p->next)
			s_append(rcvr, ", ");
	}

	/*
	 *  send data to output
	 */
	setalarm(5 MINUTES, "start sending mail data", EX_TEMPFAIL);
	nbytes = 0;
	smlastchar = '\n';
	if(unixformat){
		nchars = 0;
		while(fgets(buf, sizeof(buf), sfi)!=NULL) {
			smfputs(buf, sfo);
			nchars += strlen(buf)+1;
			if (nchars>1024) {
				if (debug)
					fprintf(stderr, ".");
				nchars -= 1024;
				setalarm(5 MINUTES, "sending mail data", EX_TEMPFAIL);
			}
		}
	} else {
		if(to822(smfputs, sfi, sfo, from, domain, s_to_c(rcvr))<0){
			Syslog(LOG_INFO, "bad input file to %s\n",
				s_to_c(rcvr));
			bomb(EX_DATAERR);
		}
	}

	/*
	 *  terminate the DATA command with \r\n.\r\n
	 */
	if(smlastchar != '\n'){
		fputs("\r\n", sfo);
		nbytes += 2;
	}
	fputs(".\r\n", sfo);
	nbytes += 3;
	Syslog(LOG_INFO, "%s  sent %d bytes to  %s\n",
		from, nbytes, s_to_c(rcvr));

	/*
	 *  see if we screwed up
	 */
	setalarm(30 MINUTES, "finishing data", EX_TEMPFAIL);
	fflush(sfo);
	if (ferror(sfo)) {
		Syslog(LOG_INFO, "write error finishing data to %s",
			s_to_c(rcvr));
		bomb(EX_IOERR);
	}
}

/*
 * Expect a reply message with the specified code.  If the specified code
 * is received return TRUE; otherwise print the error message out on the
 * standard output and give up.  Note that the reply can be a multiline
 * message.
 */
expect(code, sfi, sfo, error)
int	code;
FILE	*sfi, *sfo;
int	error;
{
	int retcd;
	char cmdbuf[BUFSIZ];
	char cbuf[6144];
	extern int debug;

	cbuf[0] = '\0';

	/* get whole reply */
more:
	while (tgets(cmdbuf, sizeof cmdbuf, sfi) > 0) {
		cmdbuf[131] = '\n';
		cmdbuf[132] = '\0';		/* not too long */
		Syslog(LOG_DEBUG, "---> %s", cmdbuf);
		if (cmdbuf[3] != '-')	/* continuation line? */
			break;		/* no, last line */
		else if (strlen(cmdbuf) + strlen(cbuf) + 4 < sizeof(cbuf)) {
			strcat(cbuf, "<<< ");
			strcat(cbuf, cmdbuf);
		}
	}
	if (sscanf(cmdbuf, "%d", &retcd) != 1 ){
		int l=strlen(cmdbuf)-1;
		if (l>=0 && cmdbuf[l]=='\n')
			cmdbuf[l]='\0';
		(void) fprintf(stderr, "non-numeric command reply (%s)\n", cmdbuf);
		if (error)
			bomb(EX_PROTOCOL);
		else
			return 1;
	}
	if (retcd == code) {
		return 1;
	}
	if (retcd/100 == code/100) {
		return 1;
	}
	Syslog(LOG_NOTICE, "Failed, expecting %d, got %d\n", code, retcd);
	/* print the error line */
	(void) fprintf(stderr, "%s<<< %s", cbuf, cmdbuf);
	csend("QUIT\n");
	if (error)
		if (retcd/100 == 4)
			bomb(EX_TEMPFAIL); /* 4xx make temp fails, no matter what*/
		else
			bomb(error);	/* map smtp errors to mailsys errors */
	return 1;
}

setalarm(limit, message, error)
	char *message;
{
	timelim = limit;
	timemsg = message;
	timeerror = error;
	alarm(limit);
}

/* Maximum time to live elapsed.  Die right now. */
SIGRETURN
death()
{
	Syslog(LOG_NOTICE, "Timer (%d sec) expired: %s.\n", timelim, timemsg);
	exit(timeerror);
}

SIGRETURN
ignore_signal(){}

csend(buf)
char *buf;
{
	Syslog(LOG_DEBUG, "<--- %s", buf);
	tputs(buf, fo);
}