/*
* Copyright 1988 by Rayan S. Zachariassen, all rights reserved.
* This will be free software, but only when it is finished.
* Copyright 1991-2003 by Matti Aarnio -- modifications, including MIME
*/
#include "smtp.h"
#define ALARM_BLOCKSIZE 2000 /* Not alarm() thing, but more for reports.. */
/*
* appendlet - append letter to file pointed at by fd
*/
int
appendlet(SS, dp, convertmode, CT)
SmtpState *SS;
struct ctldesc *dp;
CONVERTMODE convertmode;
struct ct_data *CT;
{
/* `convertmode' controls the behaviour of the message conversion:
_CONVERT_NONE (0): send as is
_CONVERT_QP (1): Convert 8-bit chars to QUOTED-PRINTABLE
_CONVERT_MULTIPARTQP (2): Convert substructures to QP
_CONVERT_8BIT (3): Convert QP-encoded chars to 8-bit
_CONVERT_UNKNOWN (4): Turn message to charset=UNKNOWN-8BIT, Q-P..
*/
register int i, rc;
int lastwasnl = 0;
int ct_boundary_len = 999999;
volatile int bufferfull = 0;
char iobuf[ZBUFSIZ];
Sfio_t *mfp = NULL;
if (CT && CT->boundary)
ct_boundary_len = strlen(CT->boundary);
SS->state = 1;
SS->column = -1;
SS->alarmcnt = ALARM_BLOCKSIZE;
SS->lastch = '\n'; /* WriteMIMELine() can decode-QP and then the
"lastwasnl" is no longer valid .. */
/* AlarmJmp wraps the appendlet() to an all encompasing
wrapping which breaks out of the lower levels if the
timer does not get reset.. */
gotalarm = 0;
#define MFPCLOSE if (mfp != NULL) { \
zsfsetfd(mfp,-1); /* keep the fd */ \
sfclose(mfp); /* close the stream */ \
}
if (statusreport)
report(SS,"DATA %d/%d (%d%%)",
SS->hsize, SS->msize, (SS->hsize*100+SS->msize/2)/SS->msize);
#if 0 /* No, we aren't SYNCin now.. */
sfsync(SS->smtpfp);
if (gotalarm) {
sprintf(SS->remotemsg,"smtp; 500 (msgbuffer write timeout! DATA %d/%d [%d%%])",
SS->hsize, SS->msize, (SS->hsize*100+SS->msize/2)/SS->msize);
return EX_IOERR;
}
#endif
if (ta_use_mmap <= 0) {
/* Using MALLOC()ed memory block */
/* Makeing sure we are properly positioned
at the begin of the message body */
if (lseek(dp->msgfd, (off_t)dp->msgbodyoffset, SEEK_SET) < 0L)
warning("Cannot seek to message body! (%m)", (char *)NULL);
lastwasnl = 1; /* we are guaranteed to have a \n after
the header */
if (convertmode == _CONVERT_NONE) {
bufferfull = 0;
if (readalready > 0) {
i = readalready;
lastwasnl = (dp->let_buffer[i-1] == '\n');
rc = writebuf(SS, dp->let_buffer, i);
if (statusreport)
report(SS,"DATA %d/%d (%d%%)",
SS->hsize, SS->msize, (SS->hsize*100+SS->msize/2)/SS->msize);
/* We NEVER get timeouts here.. We get anything else.. */
if (rc != i) {
goto write_error_processing;
}
goto lastch_processing;
}
for (;;) {
/* Optimization: If the buffer has stuff in it due to
earlier read in some of the check algorithms, we use
it straight away: */
i = read(dp->msgfd, (void*)(dp->let_buffer), dp->let_buffer_size);
if (i == 0) /*EOF*/
break;
if (i < 0) {
if (errno == EINTR || errno == EAGAIN)
continue; /* Restart in good POSIX style.. */
strcpy(SS->remotemsg,
"smtp; 500 (Read error from message file!?)");
return EX_IOERR;
}
readalready = i;
++bufferfull;
lastwasnl = (dp->let_buffer[i-1] == '\n');
rc = writebuf(SS, dp->let_buffer, i);
if (statusreport)
report(SS,"DATA %d/%d (%d%%)",
SS->hsize, SS->msize, (SS->hsize*100+SS->msize/2)/SS->msize);
/* We NEVER get timeouts here.. We get anything else.. */
if (rc != i) {
write_error_processing:;
if (gotalarm) {
sprintf(SS->remotemsg,"smtp; 500 (msgbuffer write timeout! DATA %d/%d [%d%%])",
SS->hsize, SS->msize, (SS->hsize*100+SS->msize/2)/SS->msize);
return EX_IOERR;
}
sprintf(SS->remotemsg,
"smtp; 500 (msgbuffer write IO-error[1]! [%s] DATA %d/%d [%d%%])",
strerror(errno),
SS->hsize, SS->msize, (SS->hsize*100+SS->msize/2)/SS->msize);
return EX_IOERR;
}
} /* .. end for() */
if (bufferfull > 1)
readalready = 0; /* More than one bufferfull read.. */
/* End of "NO CONVERSIONS" mode, then ... */
} else {
/* ... various esoteric conversion modes:
We are better to feed writemimeline() with
LINES instead of blocks of data.. */
/* Classical way to read in things, one line at the time! */
mfp = sfnew(NULL, iobuf, sizeof(iobuf), dp->msgfd, SF_READ);
readalready = 0;
/* we are assuming to be positioned properly
at the start of the message body */
lastwasnl = 0;
for (;;) {
i = csfgets((void*)(dp->let_buffer), dp->let_buffer_size, mfp);
if (i <= 0)
break; /* EOF, or such.. */
/* It MAY be malformed -- if it has a ZBUFSIZ*8 length
line in it, IT CAN'T BE STANDARD CONFORMANT MIME :-/ */
lastwasnl = (dp->let_buffer[i-1] == '\n');
/* XX: Detect multiparts !! */
if (CT && CT->boundary /* defined at all! */ &&
dp->let_buffer[0] == '-' &&
dp->let_buffer[1] == '-' &&
i > ct_boundary_len &&
memcmp(dp->let_buffer+2, CT->boundary, ct_boundary_len)==0){
/* Begin/intermediate/end boundary line of something */
/* XXX: Multipart part-switching line detected ? */
}
/* Ok, write the line -- decoding QP can alter
the "lastwasnl" */
rc = writemimeline(SS, dp->let_buffer, i, convertmode);
/* We NEVER get timeouts here.. We get anything else.. */
if (rc != i) {
sprintf(SS->remotemsg,
"500 (msgbuffer write IO-error[2]! [%s] DATA %d/%d [%d%%])",
strerror(errno),
SS->hsize, SS->msize, (SS->hsize*100+SS->msize/2)/SS->msize);
MFPCLOSE;
return EX_IOERR;
}
} /* End of line loop */
if (i == EOF && !sfeof(mfp)) {
strcpy(SS->remotemsg, "500 (Read error from message file!?)");
MFPCLOSE;
return EX_IOERR;
}
MFPCLOSE;
} /* ... end of conversion modes */
} else { /* WITH MMAP()ED MEMORY BLOCK */
if (convertmode == _CONVERT_NONE) {
const char *p = dp->let_buffer + dp->msgbodyoffset;
i = dp->let_end - dp->let_buffer - dp->msgbodyoffset;
lastwasnl = (p[i-1] == '\n');
rc = writebuf(SS, p, i);
if (statusreport)
report(SS,"DATA %d/%d (%d%%)",
SS->hsize, SS->msize, (SS->hsize*100+SS->msize/2)/SS->msize);
/* We NEVER get timeouts here.. We get anything else.. */
if (rc != i) {
if (gotalarm) {
sprintf(SS->remotemsg,"smtp; 500 (msgbuffer write timeout! DATA %d/%d [%d%%])",
SS->hsize, SS->msize, (SS->hsize*100+SS->msize/2)/SS->msize);
return EX_IOERR;
}
sprintf(SS->remotemsg,
"smtp; 500 (msgbuffer write IO-error[1]! [%s] DATA %d/%d [%d%%])",
strerror(errno),
SS->hsize, SS->msize, (SS->hsize*100+SS->msize/2)/SS->msize);
return EX_IOERR;
}
/* End of "NO CONVERSIONS" mode, then ... */
} else {
/* ... various esoteric conversion modes:
We are better to feed writemimeline() with
LINES instead of blocks of data.. */
const char *s = dp->let_buffer + dp->msgbodyoffset;
/* we are assuming to be positioned properly
at the start of the message body */
lastwasnl = 0;
for (;;) {
const char *p = s, *s2 = s;
if (s >= dp->let_end) break; /* EOF */
i = 0;
while (s2 < dp->let_end && *s2 != '\n')
++s2, ++i;
if ((lastwasnl = (*s2 == '\n')))
++s2, ++i;
s = s2;
/* XX: Detect multiparts !! */
if (CT && CT->boundary /* defined at all! */ &&
p[0] == '-' &&
p[1] == '-' &&
i > ct_boundary_len &&
memcmp(p+2, CT->boundary, ct_boundary_len)==0) {
/* Begin/intermediate/end boundary line of something */
/* XXX: Multipart part-switching line detected ? */
}
/* Ok, write the line -- decoding QP can alter the "lastwasnl" */
rc = writemimeline(SS, p, i, convertmode);
/* We NEVER get timeouts here.. We get anything else.. */
if (rc != i) {
sprintf(SS->remotemsg,
"500 (msgbuffer write IO-error[2]! [%s] DATA %d/%d [%d%%])",
strerror(errno),
SS->hsize, SS->msize, (SS->hsize*100+SS->msize/2)/SS->msize);
return EX_IOERR;
}
s = s2; /* Advance one linefull.. */
} /* End of line loop */
} /* ... end of conversion modes */
}
lastch_processing:;
/* we must make sure the last thing we transmit is a CRLF sequence */
if (!lastwasnl || SS->lastch != '\n') {
if (writebuf(SS, "\n", 1) != 1) {
if (gotalarm) {
sprintf(SS->remotemsg,"500 (msgbuffer write timeout! DATA %d/%d [%d%%])",
SS->hsize, SS->msize, (SS->hsize*100+SS->msize/2)/SS->msize);
return EX_IOERR;
}
sprintf(SS->remotemsg,
"500 (msgbuffer write IO-error[3]! [%s] DATA %d/%d [%d%%])",
strerror(errno),
SS->hsize, SS->msize, (SS->hsize*100+SS->msize/2)/SS->msize);
if (bufferfull > 1) readalready = 0;
return EX_IOERR;
}
}
if (bufferfull > 1) /* not all in memory, need to reread */
readalready = 0;
#if 0
if (sfsync(SS->smtpfp) != 0) {
return EX_IOERR;
}
#endif
return EX_OK;
}
#ifdef DO_CHUNKING
extern int ssputc __(( SmtpState *, int, Sfio_t * ));
int
ssputc(SS, ch, fp)
SmtpState *SS;
int ch;
Sfio_t *fp;
{
if (SS->chunkbuf == NULL) {
if (sferror(fp)) return EOF;
if (sfputc(fp, ch) < 0) return EOF;
return 0;
}
if (SS->chunksize >= CHUNK_MAX_SIZE) {
if (bdat_flush(SS, 0) != EX_OK) /* Not yet the last one! */
return EOF;
}
if (SS->chunksize >= SS->chunkspace) {
SS->chunkspace <<= 1; /* Double the size */
SS->chunkbuf = realloc(SS->chunkbuf, SS->chunkspace);
if (SS->chunkbuf == NULL)
return EOF;
}
SS->chunkbuf[SS->chunksize] = ch;
SS->chunksize += 1;
return 0;
}
#else
#define ssputc(SS,ch,fp) sfputc((fp),(ch))
#endif
#if 0
# define VLSFPRINTF(x) if(SS->verboselog)fprintf x
#else
# define VLSFPRINTF(x)
#endif
/*
* Writebuf() is like write(), except all '\n' are converted to "\r\n"
* (CRLF), and the sequence "\n." is converted to "\r\n..".
* writebuf() has a cousin: writemimeline(), which does some more esoteric
* conversions on flight..
*/
int
writebuf(SS, buf, len)
SmtpState *SS;
const char *buf;
int len;
{
Sfio_t *fp = SS->smtpfp;
register const char *cp;
register int n;
register int state;
int alarmcnt;
state = SS->state;
alarmcnt = SS->alarmcnt;
for (cp = buf, n = len; n > 0 && !gotalarm; --n, ++cp) {
register char c = (*cp) & 0xFF;
++SS->hsize;
if (--alarmcnt <= 0) {
alarmcnt = ALARM_BLOCKSIZE;
/* sfsync(fp); */
if (statusreport)
report(SS,"DATA %d/%d (%d%%)",
SS->hsize, SS->msize,
(SS->hsize*100+SS->msize/2)/SS->msize);
}
if (state && c != '\n') {
state = 0;
if (c == '.' && !SS->chunking) {
if (ssputc(SS, c, fp) == EOF || ssputc(SS, c, fp) == EOF) {
time(&endtime);
notary_setxdelay((int)(endtime-starttime));
notaryreport(NULL,FAILED,"5.4.2 (body write error, 1)",
"smtp; 500 (body write error, 1)");
strcpy(SS->remotemsg, "write error 1");
return EOF;
}
VLSFPRINTF((SS->verboselog,".."));
} else {
if (ssputc(SS, c, fp) == EOF) {
time(&endtime);
notary_setxdelay((int)(endtime-starttime));
notaryreport(NULL,FAILED,"5.4.2 (body write error, 2)",
"smtp; 500 (body write error, 2)");
strcpy(SS->remotemsg, "write error 2");
return EOF;
}
VLSFPRINTF((SS->verboselog,"%c",c));
}
} else if (c == '\n') {
if (ssputc(SS, '\r', fp) == EOF || ssputc(SS, c, fp) == EOF) {
time(&endtime);
notary_setxdelay((int)(endtime-starttime));
notaryreport(NULL,FAILED,"5.4.2 (body write error, 3)",
"smtp; 500 (body write error, 3)");
strcpy(SS->remotemsg, "write error 3");
return EOF;
}
VLSFPRINTF((SS->verboselog,"\r\n"));
state = 1;
} else {
if (ssputc(SS, c, fp) == EOF) {
time(&endtime);
notary_setxdelay((int)(endtime-starttime));
notaryreport(NULL,FAILED,"5.4.2 (body write error, 4)",
"smtp; 500 (body write error, 4)");
strcpy(SS->remotemsg, "write error 4");
return EOF;
}
VLSFPRINTF((SS->verboselog,"%c",c));
}
}
SS->state = state;
SS->alarmcnt = alarmcnt;
return len;
}
int
writemimeline(SS, buf, len, convertmode)
SmtpState *SS;
const char *buf;
int len;
CONVERTMODE convertmode;
{
Sfio_t *fp = SS->smtpfp;
register const char *cp;
register int n;
char *i2h = "0123456789ABCDEF";
int qp_chrs = 0;
int qp_val = 0;
int qp_conv;
int column;
int alarmcnt;
/* `convertmode' controls the behaviour of the message conversion:
_CONVERT_NONE (0): send as is
_CONVERT_QP (1): Convert 8-bit chars to QUOTED-PRINTABLE
_CONVERT_MULTIPARTQP (2): Convert substructures to QP
_CONVERT_8BIT (3): Convert QP-encoded chars to 8-bit
_CONVERT_UNKNOWN (4): Turn message to charset=UNKNOWN-8BIT, Q-P..
*/
alarmcnt = SS->alarmcnt;
column = SS->column;
qp_conv = (convertmode == _CONVERT_QP ||
convertmode == _CONVERT_UNKNOWN);
if (buf == NULL) { /* magic initialization */
/* No magics here.. we are linemode.. */
return 0;
}
SS->lastch = -1;
for (cp = buf, n = len; n > 0; --n, ++cp) {
register int c = (*cp) & 0xFF;
++column;
++SS->hsize;
if (--alarmcnt <= 0) {
alarmcnt = ALARM_BLOCKSIZE;
/* sfsync(fp); */
if (statusreport)
report(SS,"DATA %d/%d (%d%%)",
SS->hsize, SS->msize,
(SS->hsize*100+SS->msize/2)/SS->msize);
}
if (convertmode == _CONVERT_8BIT) {
if (c == '=' && qp_chrs == 0) {
qp_val = 0;
qp_chrs = 2;
continue;
}
if (qp_chrs != 0) {
if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
n = 0; /* We have the line-end wrapper mode */
continue; /* It should NEVER be present except at
the end of the line, thus we are safe
to do this ? */
}
--column;
if ((c >= '0' && c <= '9') ||
(c >= 'a' && c <= 'f') ||
(c >= 'A' && c <= 'F')) {
/* The first char was HEX digit, assume the second one
to be also, and convert all three (=XX) to be a char
of given value.. */
if (c >= 'a') c -= ('a' - 'A');
if (c > '9') c -= ('A' - '9' - 1);
qp_val <<= 4;
qp_val |= (c & 0x0F);
}
--qp_chrs;
if (qp_chrs == 0)
c = qp_val;
else
continue;
}
SS->lastch = c;
} else if (qp_conv) {
if (column > 70 && c != '\n') {
ssputc(SS, '=', fp);
ssputc(SS, '\r', fp);
ssputc(SS, '\n', fp);
SS->lastch = '\n';
VLSFPRINTF((SS->verboselog,"=\r\n"));
column = 0;
}
/* Trailing SPACE/TAB ? */
if (n < 3 && (c == ' ' || c == '\t')) {
ssputc(SS, '=', fp);
ssputc(SS, i2h[(c >> 4) & 15], fp);
ssputc(SS, i2h[(c) & 15], fp);
SS->lastch = i2h[(c) & 15];
column += 2;
VLSFPRINTF((SS->verboselog,"=%02X",c));
continue;
}
/* Any other char which needs quoting ? */
if (c == '=' || c > 126 ||
(column == 0 && (c == 'F' || c == '.')) ||
(c != '\n' && c != '\t' && c < 32)) {
ssputc(SS, '=', fp);
ssputc(SS, i2h[(c >> 4) & 15], fp);
ssputc(SS, i2h[(c) & 15], fp);
SS->lastch = i2h[(c) & 15];
column += 2;
VLSFPRINTF((SS->verboselog,"=%02X",c));
continue;
}
} /* .... end convertmode */
if (column == 0 && c == '.' && !SS->chunking) {
if (ssputc(SS, c, fp) == EOF) {
time(&endtime);
notary_setxdelay((int)(endtime-starttime));
notaryreport(NULL,FAILED,"5.4.2 (body write error, 5)",
"smtp; 500 (body write error, 5)");
strcpy(SS->remotemsg, "write error 5");
return EOF;
}
VLSFPRINTF((SS->verboselog,".."));
}
if (c == '\n') {
if (ssputc(SS, '\r', fp) == EOF) {
time(&endtime);
notary_setxdelay((int)(endtime-starttime));
notaryreport(NULL,FAILED,"5.4.2 (body write error, 6)",
"smtp; 500 (body write error, 6)");
strcpy(SS->remotemsg, "write error 6");
return EOF;
}
VLSFPRINTF((SS->verboselog,"\r"));
column = -1;
}
if (ssputc(SS, c, fp) == EOF) {
time(&endtime);
notary_setxdelay((int)(endtime-starttime));
notaryreport(NULL,FAILED,"5.4.2 (body write error, 7)",
"smtp; 500 (body write error, 7)");
strcpy(SS->remotemsg, "write error 7");
return EOF;
}
SS->lastch = c;
VLSFPRINTF((SS->verboselog,"%c",c));
}
SS->column = column;
SS->alarmcnt = alarmcnt;
return len;
}
/* When data is clean 7-BIT, do: *flag_ptr = (*flag_ptr) << 1 */
int
check_7bit_cleanness(dp)
struct ctldesc *dp;
{
if (ta_use_mmap > 0) {
/* With MMAP()ed spool file this is sweet and simple.. */
const register char *s = dp->let_buffer + dp->msgbodyoffset;
while (s < dp->let_end)
if (128 & *s)
return 0;
else
++s;
return 1;
} else {
register int i;
register int bufferfull;
int lastwasnl;
off_t mfd_pos;
int mfd = dp->msgfd;
/* can we use cache of message body data ? */
if (readalready != 0) {
for (i=0; i<readalready; ++i)
if (128 & (dp->let_buffer[i]))
return 0; /* Not clean ! */
}
/* make sure we are properly positioned at the start
of the message body */
bufferfull = 0;
mfd_pos = lseek(mfd, (off_t)dp->msgbodyoffset, SEEK_SET);
while (1) {
i = read(mfd, (void*)(dp->let_buffer), dp->let_buffer_size);
if (i == 0)
break;
if (i < 0) {
/* ERROR ?!?!? */
if (errno == EINTR)
continue; /* Hickup... */
readalready = 0;
return 0;
}
lastwasnl = (dp->let_buffer[i-1] == '\n');
readalready = i;
bufferfull++;
for (i = 0; i < readalready; ++i)
if (128 & (dp->let_buffer[i])) {
lseek(mfd, mfd_pos, SEEK_SET);
readalready = 0;
return 0; /* Not clean ! */
}
}
/* Got to EOF, and still it is clean 7-BIT! */
lseek(mfd, mfd_pos, SEEK_SET);
if (bufferfull > 1) /* not all in memory, need to reread */
readalready = 0;
/* Set indication! */
return 1;
}
}
syntax highlighted by Code2HTML, v. 0.9.1