V10/cmd/upas/smtp/converse.c
/*
* 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);
}