/* * Copyright 1988 by Rayan S. Zachariassen, all rights reserved. * This will be free software, but only when it is finished. */ /* * Several extensive changes by Matti Aarnio * Copyright 1991-2003. */ /* * Zmailer SMTP-server divided into bits * * The basic commands: * * - DATA (RFC 821) * - BDAT (RFC 1830) * */ /* XX: for anti-spam hack */ /* #define USE_ANTISPAM_HACKS */ /* #define USE_STRICT_MSGID_FREEZING */ #define FREEZE__X_ADVERTISEMENT_FOUND 951 #ifdef USE_ANTISPAM_HACKS #define FREEZE__X_UIDL_FOUND 952 #define FREEZE__IMPROBABLE_RECEIVED_HEADER_FOUND 952 #define FREEZE__MALFORMED_MESSAGE_ID_HEADER 953 #endif #include "smtpserver.h" #ifdef USE_TRANSLATION #include #endif /* USE_TRANSLATION */ #define SKIPSPACE(Y) while (*Y == ' ' || *Y == '\t') ++Y #define SKIPDIGIT(Y) while ('0' <= *Y && *Y <= '9') ++Y #define SKIPTEXT(Y) while (*Y && *Y != ' ' && *Y != '\t') ++Y static int mvdata __((SmtpState *, char *)); static int mvbdata __((SmtpState *, char *, long)); static int parsestatcode __((const char **ss, const char **statcode)); static int parsestatcode(ssp, statcodep) const char **ssp; const char **statcodep; { int code = -1; const char *ss = *ssp; static char statcodebuf[6]; *statcodep = NULL; for (;'0' <= *ss && *ss <= '9'; ++ss) { if (code < 0) code = 0; code = code * 10 + (*ss - '0'); } SKIPSPACE(ss); if (isdigit(ss[0]) && ss[1] == '.' && isdigit(ss[2]) && ss[3] == '.' && isdigit(ss[4])) { memcpy(statcodebuf, ss, 5); statcodebuf[5] = 0; *statcodep = statcodebuf; ss += 5; } SKIPSPACE(ss); *ssp = ss; if (code < 200 || code > 599) code = 0; return code; } int smtp_data(SS, buf, cp) SmtpState *SS; const char *buf, *cp; { int filsiz; long tell = 0; int i, j; char msg[2048]; struct stat stbuf; char *fname; char taspid[30]; MIBMtaEntry->ss.ReceivedMessagesSs += 1; MIBMtaEntry->ss.ReceivedRecipientsSs += SS->ok_rcpt_count; MIBMtaEntry->ss.IncomingSMTP_DATA += 1; while (!strict_protocol && (*cp == ' ' || *cp == '\t')) ++cp; if (strict_protocol && *cp != 0) { MIBMtaEntry->ss.IncomingSMTP_DATA_bad += 1; type(SS, 501, m554, "Extra junk after 'DATA' verb"); return 0; } if (SS->state != RecipientOrData) { switch (SS->state) { case Hello: cp = "Waiting for HELO command"; break; case Mail: case MailOrHello: cp = "Waiting for MAIL command"; break; case Recipient: cp = "Waiting for RCPT command"; break; case BData: cp = "Must not intermix BDAT and DATA in same transaction!"; break; default: cp = NULL; break; } MIBMtaEntry->ss.IncomingSMTP_DATA_bad += 1; type(SS, 503, m552, cp); typeflush(SS); return 0; } if (ferror(SS->mfp)) { type(SS, 452, m430, (char *) NULL); typeflush(SS); clearerr(SS->mfp); mail_abort(SS->mfp); MIBMtaEntry->ss.IncomingSMTP_DATA_bad += 1; SS->mfp = NULL; reporterr(SS, tell, "message file error"); return 0; } if (SS->sender_ok == 0) { type(SS, 550, "5.1.7", "No valid sender, rejecting all recipients"); typeflush(SS); SS->state = MailOrHello; if (SS->mfp) mail_abort(SS->mfp); MIBMtaEntry->ss.IncomingSMTP_DATA_bad += 1; SS->mfp = NULL; return 0; } if (SS->rcpt_count == 0) { /* No valid recipients! */ type(SS, 550, "5.1.3", "No valid recipients at RCPT addresses, or no RCPT addresses at all"); typeflush(SS); SS->state = MailOrHello; mail_abort(SS->mfp); MIBMtaEntry->ss.IncomingSMTP_DATA_bad += 1; SS->mfp = NULL; return 0; } if ((SS->from_box != 0) && (SS->rcpt_count > MaxErrorRecipients)) { /* Too many recipients for a "MAIL FROM:<>" */ type(SS, 550, "5.7.1", "SPAM trap -- too many recipients for an empty source address!"); typeflush(SS); SS->state = MailOrHello; mail_abort(SS->mfp); MIBMtaEntry->ss.IncomingSMTP_DATA_bad += 1; SS->mfp = NULL; return 0; } SS->rcpt_count = 0; /* now we can zero it.. */ type(SS, 354, NULL, (char *) NULL); typeflush(SS); fputs("env-end\n", SS->mfp); if (msa_mode && SS->authuser != NULL ) { fprintf(SS->mfp, "X-Comment: RFC 2476 MSA function at %s logged sender identity as: %s\n", SS->myhostname, SS->authuser); } /* We set alarm()s inside the mvdata() */ *msg = 0; filsiz = mvdata(SS, msg); SS->messagesize = filsiz; fflush(SS->mfp); fname = mail_fname(SS->mfp); fstat(FILENO(SS->mfp), &stbuf); taspoolid(taspid, stbuf.st_mtime, stbuf.st_ino); tell = stbuf.st_size, report(SS, "Got '.'; tell=%ld", tell); availspace = used_fd_statfs(FILENO(SS->mfp)); if (availspace >= 0) MIBMtaEntry->sys.SpoolUsedSpace = availspace; availspace = free_fd_statfs(FILENO(SS->mfp)); if (availspace < 0) availspace = LONG_MAX / 1024; /* Over 2G ? */ if (availspace >= 0) MIBMtaEntry->sys.SpoolFreeSpace = availspace; availspace -= minimum_availspace; if (availspace > (LONG_MAX / 1024)) availspace = LONG_MAX / 1024; availspace *= 1024; if (*msg != 0) { mail_abort(SS->mfp); MIBMtaEntry->ss.IncomingSMTP_DATA_bad += 1; SS->mfp = NULL; type(SS, 452, m430, "%s", msg); if (lmtp_mode) for(i = 1; i < SS->ok_rcpt_count; ++i) type(SS, 452, m430, "%s", msg); typeflush(SS); } else if (s_feof(SS)) { if (STYLE(SS->cfinfo,'D')) { /* Says: DON'T DISCARD -- aka DEBUG ERRORS! */ mail_close_alternate(SS->mfp,"public",".DATA-EOF"); } else mail_abort(SS->mfp); MIBMtaEntry->ss.IncomingSMTP_DATA_bad += 1; SS->mfp = NULL; reporterr(SS, tell, "premature EOF on DATA input"); typeflush(SS); return -1; } else if (availspace < 0 || ferror(SS->mfp)) { type(SS, 452, m430, NULL); /* insufficient system storage */ if (lmtp_mode) for(i = 1; i < SS->ok_rcpt_count; ++i) type(SS, 452, m430, NULL); /* insufficient system storage */ typeflush(SS); reporterr(SS, tell, ferror(SS->mfp) ? "write to spool file failed" : "system free storage under limit"); clearerr(SS->mfp); mail_abort(SS->mfp); MIBMtaEntry->ss.IncomingSMTP_DATA_bad += 1; SS->mfp = NULL; } else if (maxsize > 0 && filsiz > maxsize) { mail_abort(SS->mfp); MIBMtaEntry->ss.IncomingSMTP_DATA_bad += 1; SS->mfp = NULL; type(SS, 552, "5.3.4", "Size of this message exceeds the fixed maximum size of %ld chars for received email ", maxsize); if (lmtp_mode) for(i = 1; i < SS->ok_rcpt_count; ++i) type(SS, 552, "5.3.4", "Size of this message exceeds the fixed maximum size of %ld chars for received email ", maxsize); typeflush(SS); } else { /* Things have been good thus far, now we store the resulting file into router spool area; pending a few things we do at first.. */ const char *statcode = NULL, *ss, *ss0; int code = 0; const char *sslines[20]; int sslinecnt = 0; /* Lets see what the content-policy will tell now ? */ if (debug) typeflush(SS); SS->policyresult = contentpolicy(policydb, &SS->policystate, fname); ss0 = ss = policymsg(policydb, &SS->policystate); if (ss) type(NULL,0,NULL, "Content-policy analysis ordered message %s. (code=%d); msg='%s'", (SS->policyresult < 0 ? "rejection" : (SS->policyresult > 0 ? "freezing" : "acceptance")), SS->policyresult, ss); if (ss) { char *p, *s; code = parsestatcode(&ss,&statcode); s = (char *)ss; p = strchr(s,'\r'); sslinecnt = 0; if (p) { /* Multiline! CRs in message text... */ while (p && sslinecnt < 17) { /* Arbitrary fixed limit.. */ *p++ = '\0'; sslines[sslinecnt++] = s; sslines[sslinecnt+0] = p; sslines[sslinecnt+1] = NULL; s = p; p = strchr(s,'\r'); } } } if (!ss || *ss == 0) { if (SS->policyresult < 0) sslines[0] = ss = "rejected, no further explanations"; else if (SS->policyresult == 0) sslines[0] = ss = "accepted"; else sslines[0] = ss = "accepted into freezer, no explanations"; sslines[1] = NULL; } if (SS->policyresult < 0) { if (!statcode) statcode = m571; if (!code) code = 552; type(SS, -code, statcode, "Content-Policy msg: %s; %s", ss, taspid); for (j= 1; j <= sslinecnt; ++j) type(SS, -code, statcode, "msg: %s", sslines[j]); type(SS, code, statcode, "Content-Policy analysis rejected this message"); if (lmtp_mode) for(i = 1; i < SS->ok_rcpt_count; ++i) { type(SS, -code, statcode, "Content-Policy msg: %s; %s", ss, taspid); for (j= 1; j <= sslinecnt; ++j) type(SS, -code, statcode, "msg: %s", sslines[j]); type(SS, code, statcode, "Content-Policy analysis rejected this message"); } mail_abort(SS->mfp); MIBMtaEntry->ss.IncomingSMTP_DATA_bad += 1; SS->mfp = NULL; } else if (SS->policyresult > 0) { char polbuf[20]; runasrootuser(); sprintf(polbuf,"policy-%d",SS->policyresult); if (mail_close_alternate(SS->mfp, FREEZERDIR, polbuf) != 0) { type(NULL,0,NULL, "mail_close_alternate(..'FREEZER','%s') failed, errno=%d (%s)", polbuf, errno, strerror(errno)); type(SS, 452, m430, "Message file disposition failed; %s", taspid); if (lmtp_mode) for(i = 1; i < SS->ok_rcpt_count; ++i) type(SS, 452, m430, "Message file disposition failed; %s", taspid); typeflush(SS); SS->mfp = NULL; reporterr(SS, tell, "message file close failed"); } else { smtp_tarpit(SS); if (!statcode) statcode = "2.7.1"; if (!code) code = 250; type(SS, -code, statcode, "%s; %s", ss, taspid); for (j= 1; j <= sslinecnt; ++j) type(SS, -code, statcode, "%s", sslines[j]); type(SS, code, statcode, "Content-Policy accepted this message into freezer-%d; %s", SS->policyresult, taspid); if (lmtp_mode) for(i = 1; i < SS->ok_rcpt_count; ++i) { type(SS, -code, statcode, "%s; %s", ss, taspid); for (j= 1; j <= sslinecnt; ++j) type(SS, -code, statcode, "%s", sslines[j]); type(SS, code, statcode, "Content-Policy accepted this message into freezer-%d; %s", SS->policyresult, taspid); } typeflush(SS); SS->mfp = NULL; zsyslog((LOG_INFO, "accepted %s (%ldc) from %s/%d into freeze[%d]", taspid, tell, SS->rhostname, SS->rport, SS->policyresult)); } MIBMtaEntry->ss.IncomingSMTP_DATA_bad += 1; runastrusteduser(); } else { /* Ok, we didn't have smtp-policy defined freezer action, lets see if we do it some other way.. */ if (mail_close(SS->mfp) == EOF) { type(SS, 452, m430, (char *) NULL); if (lmtp_mode) for(i = 1; i < SS->ok_rcpt_count; ++i) type(SS, 452, m430, (char *) NULL); typeflush(SS); SS->mfp = NULL; reporterr(SS, tell, "message file close failed"); MIBMtaEntry->ss.IncomingSMTP_DATA_bad += 1; } else { /* Ok, build response with proper "spoolid" */ SS->mfp = NULL; if (!ss || *ss == 0) { type(SS, 250, "2.0.0", "Message accepted; %s", taspid); if (lmtp_mode) for(i = 1; i < SS->ok_rcpt_count; ++i) type(SS, 250, "2.0.0", "Message accepted; %s", taspid); } else { if (!statcode) statcode = "2.0.0"; if (!code) code = 250; if (sslinecnt < 1) type(SS, code, statcode, "%s; %s", ss, taspid); else type(SS, -code, statcode, "%s; %s", ss, taspid); for (j= 1; j <= sslinecnt; ++j) type(SS, -code, statcode, "%s", sslines[j]); if (sslinecnt >= 1) type(SS, code, statcode, "Content-Policy accepted this message; %s", taspid); if (lmtp_mode) for(i = 1; i < SS->ok_rcpt_count; ++i) { if (sslinecnt < 1) type(SS, code, statcode, "%s; %s", ss, taspid); else type(SS, -code, statcode, "%s; %s", ss, taspid); for (j= 1; j <= sslinecnt; ++j) type(SS, -code, statcode, "%s", sslines[j]); if (sslinecnt >= 1) type(SS, code, statcode, "Content-Policy accepted this message; %s", taspid); } } typeflush(SS); if (smtp_syslog) zsyslog((LOG_INFO, "%s: (%ldc) accepted from %s/%d", taspid, tell, SS->rhostname, SS->rport)); MIBMtaEntry->ss.IncomingSMTP_DATA_ok += 1; MIBMtaEntry->ss.TransmittedMessagesSs += 1; MIBMtaEntry->ss.TransmittedRecipientsSs += SS->ok_rcpt_count; MIBMtaEntry->ss.IncomingSMTP_DATA_KBYTES += (SS->messagesize+1023)/1024; MIBMtaEntry->ss.IncomingSMTP_spool_KBYTES += (tell + 1023)/1024; type(NULL,0,NULL,"%s: %ld bytes", taspid, tell); if (logfp) fflush(logfp); } } } SS->state = MailOrHello; typeflush(SS); return 0; } int smtp_bdata(SS, buf, cp) SmtpState *SS; const char *buf, *cp; { int filsiz, rc; long tell; char msg[2048]; long bdata_chunksize; int bdata_last, i, j; struct stat stbuf; char *fname; char taspid[30]; MIBMtaEntry->ss.ReceivedMessagesSs += 1; MIBMtaEntry->ss.ReceivedRecipientsSs += SS->ok_rcpt_count; MIBMtaEntry->ss.IncomingSMTP_BDAT += 1; if (SS->state == RecipientOrData) { SS->state = BData; SS->bdata_blocknum = 0; SS->mvbstate = -1; } *msg = 0; rc = sscanf(cp, "%ld %7s %7s", &bdata_chunksize, msg, msg + 20); SS->bdata_blocknum += 1; bdata_last = CISTREQ(msg, "LAST"); if (!(bdata_chunksize >= 0L && (rc == 1 || (rc == 2 && bdata_last)))) { type(SS, 501, m552, NULL); typeflush(SS); MIBMtaEntry->ss.IncomingSMTP_BDAT_bad += 1; return 0; } if (SS->bdata_blocknum == 1 && SS->mfp) { fputs("env-end\n", SS->mfp); if (msa_mode && SS->authuser != NULL ) { fprintf(SS->mfp, "X-Comment: RFC 2476 MSA function at %s logged sender identity as: %s\n", SS->myhostname, SS->authuser); } } /* We set alarm()s inside the mvbdata() */ *msg = 0; filsiz = mvbdata(SS, msg, bdata_chunksize); SS->messagesize += filsiz; tell = 0; if (SS->mfp) { fflush(SS->mfp); fname = mail_fname(SS->mfp); fstat(FILENO(SS->mfp), &stbuf); taspoolid(taspid, stbuf.st_mtime, stbuf.st_ino); tell = stbuf.st_size; } else { tell = 0; fname = ""; strcpy(taspid, ""); } report(SS, "BDAT %ld%s; tell=%ld", bdata_chunksize, bdata_last ? " LAST":"", tell); if (SS->state != BData) { switch (SS->state) { case Hello: cp = "Waiting for HELO command"; break; case Mail: case MailOrHello: cp = "Waiting for MAIL command"; break; case Recipient: cp = "Waiting for RCPT command"; break; default: cp = NULL; break; } type(SS, 503, m552, cp); if (lmtp_mode && bdata_last) for(i = 1; i < SS->ok_rcpt_count; ++i) type(SS, 503, m552, cp); typeflush(SS); if (SS->mfp) mail_abort(SS->mfp); MIBMtaEntry->ss.IncomingSMTP_BDAT_bad += 1; SS->mfp = NULL; return 0; } if (SS->bdata_blocknum == 1) { if (SS->sender_ok == 0 || SS->rcpt_count == 0) { cp = "No valid sender, rejecting all recipients"; if (SS->sender_ok != 0) cp = "No valid recipient at RCPT addresses, or no RCPT addresses at all"; type(SS, 550, "5.1.3", cp); if (lmtp_mode && bdata_last) for(i = 1; i < SS->ok_rcpt_count; ++i) type(SS, 550, "5.1.3", cp); typeflush(SS); SS->state = MailOrHello; if (SS->mfp) mail_abort(SS->mfp); MIBMtaEntry->ss.IncomingSMTP_BDAT_bad += 1; SS->mfp = NULL; return 0; } if ((SS->from_box != 0) && (SS->rcpt_count > MaxErrorRecipients)) { /* Too many recipients for a "MAIL FROM:<>" */ type(SS, 550, "5.7.1", "SPAM trap -- too many recipients for an empty source address!"); if (lmtp_mode && bdata_last) for(i = 1; i < SS->ok_rcpt_count; ++i) type(SS, 550, "5.7.1", "SPAM trap -- too many recipients for an empty source address!"); typeflush(SS); SS->state = MailOrHello; mail_abort(SS->mfp); MIBMtaEntry->ss.IncomingSMTP_BDAT_bad += 1; SS->mfp = NULL; return 0; } SS->rcpt_count = 0; /* now we can zero them.. */ SS->sender_ok = 0; } availspace = used_fd_statfs(FILENO(SS->mfp)); if (availspace >= 0) MIBMtaEntry->sys.SpoolUsedSpace = availspace; availspace = free_fd_statfs(FILENO(SS->mfp)); if (availspace < 0) availspace = LONG_MAX / 1024; /* Over 2G ? */ if (availspace >= 0) MIBMtaEntry->sys.SpoolFreeSpace = availspace; availspace -= minimum_availspace; if (availspace > (LONG_MAX / 1024)) availspace = LONG_MAX / 1024; availspace *= 1024; /* The common typeflush() is at the end... */ if (SS->mfp == NULL) { type(SS, 452, m430, "BDAT block discarded due to earlier error"); if (lmtp_mode && bdata_last) for(i = 1; i < SS->ok_rcpt_count; ++i) type(SS, 452, m430, "BDAT block discarded due to earlier error"); MIBMtaEntry->ss.IncomingSMTP_BDAT_bad += 1; } else if (*msg != 0) { mail_abort(SS->mfp); SS->mfp = NULL; type(SS, 452, "%s", msg); if (lmtp_mode && bdata_last) for(i = 1; i < SS->ok_rcpt_count; ++i) type(SS, 452, "%s", msg); MIBMtaEntry->ss.IncomingSMTP_BDAT_bad += 1; } else if (s_feof(SS)) { /* [mea@utu.fi] says this can happen */ if (STYLE(SS->cfinfo,'D')) { /* Says: DON'T DISCARD -- aka DEBUG ERRORS! */ mail_close_alternate(SS->mfp,"public",".BDAT-EOF"); } else mail_abort(SS->mfp); MIBMtaEntry->ss.IncomingSMTP_BDAT_bad += 1; SS->mfp = NULL; reporterr(SS, tell, "premature EOF on BDAT input"); typeflush(SS); /* Pointless ?? */ return -1; } else if (availspace < 0 || ferror(SS->mfp)) { type(SS, 452, m400, (char *) NULL); if (lmtp_mode && bdata_last) for(i = 1; i < SS->ok_rcpt_count; ++i) type(SS, 452, m400, (char *) NULL); reporterr(SS, tell, ferror(SS->mfp) ? "write to spool file failed" : "system free storage under limit"); clearerr(SS->mfp); mail_abort(SS->mfp); MIBMtaEntry->ss.IncomingSMTP_BDAT_bad += 1; SS->mfp = NULL; } else if (maxsize > 0 && tell > maxsize) { mail_abort(SS->mfp); MIBMtaEntry->ss.IncomingSMTP_BDAT_bad += 1; SS->mfp = NULL; type(SS, 552, "5.3.4", "Size of this message exceeds the fixed maximum size of %ld chars for received email ", maxsize); if (lmtp_mode && bdata_last) for(i = 1; i < SS->ok_rcpt_count; ++i) type(SS, 552, "5.3.4", "Size of this message exceeds the fixed maximum size of %ld chars for received email ", maxsize); } else if (bdata_last) { /* Things have been good thus far, now we store the resulting file into router spool area; pending a few things we do at first.. */ const char *statcode = NULL, *ss, *ss0; int code = 0; const char *sslines[20]; int sslinecnt = 0; /* Lets see what the content-policy will tell now ? */ if (debug) typeflush(SS); SS->policyresult = contentpolicy(policydb, &SS->policystate, fname); ss0 = ss = policymsg(policydb, &SS->policystate); if (ss) type(NULL,0,NULL, "Content-policy analysis ordered message %s. (code=%d); msg='%s'", (SS->policyresult < 0 ? "rejection" : (SS->policyresult > 0 ? "freezing" : "acceptance")), SS->policyresult, ss); if (ss) { char *p, *s; code = parsestatcode(&ss,&statcode); s = (char *) ss; p = strchr(s,'\r'); sslinecnt = 0; if (p) { /* Multiline! CRs in message text... */ while (p && sslinecnt < 17) { /* Arbitrary fixed limit.. */ *p++ = '\0'; sslines[sslinecnt++] = s; sslines[sslinecnt+0] = p; sslines[sslinecnt+1] = NULL; s = p; p = strchr(s,'\r'); } } } if (!ss || *ss == 0) { if (SS->policyresult < 0) sslines[0] = ss = "rejected, no further explanations"; else if (SS->policyresult == 0) sslines[0] = ss = "accepted"; else sslines[0] = ss = "accepted into freezer, no explanations"; sslines[1] = NULL; } if (SS->policyresult < 0) { if (!statcode) statcode = m571; if (!code) code = 552; type(SS, -code, statcode, "Content-Policy msg: %s; %s", ss, taspid); for (j= 1; j <= sslinecnt; ++j) type(SS, -code, statcode, "msg: %s", sslines[j]); type(SS, code, statcode, "Content-Policy analysis rejected this message"); if (lmtp_mode) for(i = 1; i < SS->ok_rcpt_count; ++i) { type(SS, -code, statcode, "Content-Policy msg: %s; %s", ss, taspid); for (j= 1; j <= sslinecnt; ++j) type(SS, -code, statcode, "msg: %s", sslines[j]); type(SS, code, statcode, "Content-Policy analysis rejected this message"); } mail_abort(SS->mfp); MIBMtaEntry->ss.IncomingSMTP_BDAT_bad += 1; SS->mfp = NULL; } else if (SS->policyresult > 0) { runasrootuser(); if (mail_close_alternate(SS->mfp, FREEZERDIR, "policy") != 0) { type(NULL,0,NULL, "mail_close_alternate(..'FREEZER','%s') failed, errno=%d (%s)", "policy", errno, strerror(errno)); if (logfp) fflush(logfp); type(SS, 452, m430, "Message file disposition failed; %s", taspid); if (lmtp_mode) for(i = 0; i < SS->ok_rcpt_count; ++i) type(SS, 452, m430, "Message file disposition failed; %s",taspid); SS->mfp = NULL; reporterr(SS, tell, "message file close failed"); } else { smtp_tarpit(SS); if (!statcode) statcode = "2.7.1"; if (!code) code = 250; type(SS, -code, statcode, "%s; %s", ss, taspid); for (j= 1; j <= sslinecnt; ++j) type(SS, -code, statcode, "%s", sslines[j]); type(SS, code, statcode, "Content-Policy accepted this message into freezer-%d; %s", SS->policyresult, taspid); if (lmtp_mode) for(i = 1; i < SS->ok_rcpt_count; ++i) { type(SS, -code, statcode, "%s; %s", ss, taspid); for (j= 1; j <= sslinecnt; ++j) type(SS, -code, statcode, "%s", sslines[j]); type(SS, code, statcode, "Content-Policy accepted this message into freezer-%d; %s", SS->policyresult, taspid); } typeflush(SS); SS->mfp = NULL; zsyslog((LOG_INFO, "accepted %s (%ldc) from %s/%d into freeze[%d]", taspid, tell, SS->rhostname, SS->rport, SS->policyresult)); } MIBMtaEntry->ss.IncomingSMTP_BDAT_bad += 1; runastrusteduser(); } else if (mail_close(SS->mfp) == EOF) { type(SS, 452, m400, (char *) NULL); if (lmtp_mode) for(i = 1; i < SS->ok_rcpt_count; ++i) type(SS, 452, m400, (char *) NULL); SS->mfp = NULL; reporterr(SS, tell, "message file close failed"); MIBMtaEntry->ss.IncomingSMTP_BDAT_bad += 1; } else { /* Ok, build response with proper "spoolid" */ SS->mfp = NULL; #if 1 type(SS, 250, "2.0.0", "%s Roger, got %ld bytes in the last chunk, stored %ld bytes into spool", taspid, bdata_chunksize, (long) tell); if (lmtp_mode) for(i = 1; i < SS->ok_rcpt_count; ++i) type(SS, 250, "2.0.0", "%s Roger, got %ld bytes in the last chunk, stored %ld bytes into spool", taspid, bdata_chunksize, (long) tell); type(NULL,0,NULL,"-- pipeline input: %d bytes",s_hasinput(SS)); #else if (!statcode) statcode = "2.0.0"; if (!code) code = 250; if (sslinecnt < 1) type(SS, code, statcode, "%s; %s", ss, taspid); else type(SS, -code, statcode, "%s; %s", ss, taspid); for (j= 1; j <= sslinecnt; ++j) type(SS, -code, statcode, "%s", sslines[j]); if (sslinecnt >= 1) type(SS, code, statcode, "Content-Policy accepted this message; %s", taspid); if (lmtp_mode) for(i = 1; i < SS->ok_rcpt_count; ++i) { if (sslinecnt < 1) type(SS, code, statcode, "%s; %s", ss, taspid); else type(SS, -code, statcode, "%s; %s", ss, taspid); for (j= 1; j <= sslinecnt; ++j) type(SS, -code, statcode, "%s", sslines[j]); if (sslinecnt >= 1) type(SS, code, statcode, "Content-Policy accepted this message; %s", taspid); } #endif MIBMtaEntry->ss.IncomingSMTP_BDAT_ok += 1; MIBMtaEntry->ss.TransmittedMessagesSs += 1; MIBMtaEntry->ss.TransmittedRecipientsSs += SS->ok_rcpt_count; MIBMtaEntry->ss.IncomingSMTP_BDAT_KBYTES += (SS->messagesize+1023)/1024; MIBMtaEntry->ss.IncomingSMTP_spool_KBYTES += (tell + 1023)/1024; if (smtp_syslog) zsyslog((LOG_INFO, "%s: (%ldc) accepted from %s/%d", taspid, tell, SS->rhostname, SS->rport)); type(NULL,0,NULL,"%s: %ld bytes", taspid, tell); if (logfp) fflush(logfp); } } else { /* Not last chunk! */ type(SS, 250, "2.0.0", "Received %ld bytes", bdata_chunksize); if (lmtp_mode && bdata_last) for(i = 1; i < SS->ok_rcpt_count; ++i) type(SS, 250, "2.0.0", "Received %ld bytes", bdata_chunksize); } if (bdata_last) { SS->state = MailOrHello; } typeflush(SS); return 0; } /* Implement SMTP DATA filter */ /* * The state table is indexed by the current character (across), and * the current state (down). Column 0 is for any character that is not * a '\r', '\n', or '.'. The table entries encode the next state, and * what to output. An entry of EOF means exit. The next state is encoded * in the l.s.byte, and what to output is encoded in the next-l.s.byte. * If the next-l.s.byte is null, the current input character is output, * if the high bit is set, nothing is output, else the entire byte is * output followed by the current character. */ #define O_ (0200 << 8) /* don't print anything flag */ #define N_ ('\r' << 8) /* print '\r' then current input */ #define X_ ~0 /* exit. must have (X_&O_) != 0 */ static int states[] = { /* current input character */ /* * '\r' '\n' '.' EOF states */ 0, O_ | 15, 10, 0, X_, /* 0: during line */ 0, O_ | 20, X_, 0, X_, /* 5: "^." */ 0, O_ | 15, 10, O_ | 5, X_, /* 10: "^" (start state) */ N_ | 0, 15, 10, N_ | 0, X_, /* 15: seen a \r */ N_ | 0, 15, X_, N_ | 0, X_, /* 20: "^.\r" */ }; /* * Quick way of getting the column number of the state table, * that corresponds to the input character. */ static char indexnum[256 + 1] = { /* #if 0 idxnum['\r'] = 1; idxnum['\n'] = 2; idxnum['.'] = 3; #if EOF == -1 idxnum[EOF] = 4; #endif #endif */ 4, /* EOF */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 1, 0, 0, /* ...'\n'..'\r'.. */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, /* ... '.' .. */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; /* * BASE64 DECODER index table, and ENCODER map array... */ static int base64decode_index[128] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1 }; #define decodechar64(c) (((c) < 0 || (c) > 127) ? -1 : base64decode_index[(c)]) static char base64encode_array[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; int decodebase64string(instr,inlen,outstr,outspc,inleftover) const char * instr; char * outstr; const char ** inleftover; int inlen, outspc; { int b64decoding = 1, outlen = 0; int b64eod = 0, b64i = 0, i, c; char b64c[4]; while (inlen > 0 && outlen < outspc && !b64eod) { c = *instr++; --inlen; if (c == '=' || !b64decoding) { b64eod = 1; continue; } i = decodechar64(c); if (i < 0) continue; b64c[b64i++] = i; if (b64i < 2) continue; if (b64i == 2) { c = (b64c[0] << 2) | ((b64c[1] & 0x30) >> 4); } else if (b64i == 3) { c = ((b64c[1] & 0x0f) << 4) | ((b64c[2] & 0x3c) >> 2); } else { c = (b64c[2] << 6) | b64c[3]; b64i = 0; if (b64eod) b64decoding = 0; } outstr[outlen] = c; ++outlen; } if (outlen < outspc) outstr[outlen] = 0; while (*instr == '=') ++instr; if (inleftover) *inleftover = instr; return outlen; } int encodebase64string(instr,inlen,outstr,outspc) const char * instr; char *outstr; int inlen, outspc; /* Always positive values .. */ { /* Build groups of 3 bytes, encode them in 4 chars; if some byte is not filled, mark the incomplete byte with '=' */ u_char b64out[4]; int b64i, outlen = 0, b64val; while (inlen > 0 && outlen < outspc) { b64i = inlen > 3 ? 3 : inlen; b64val = ((unsigned char) instr[0]) << 16; if (b64i > 1) b64val |= ((unsigned char) instr[1]) << 8; if (b64i > 2) b64val |= ((unsigned char) instr[2]); b64out[3] = base64encode_array[ b64val & 63 ]; b64val >>= 6; b64out[2] = base64encode_array[ b64val & 63 ]; b64val >>= 6; b64out[1] = base64encode_array[ b64val & 63 ]; b64val >>= 6; b64out[0] = base64encode_array[ b64val & 63 ]; switch(b64i) { case 1: b64out[2] = '='; case 2: b64out[3] = '='; } instr += b64i; inlen -= b64i; b64i = (b64i == 1) ? 3 : 4; if ((outlen + b64i) < outspc) { /* Can fit in .. */ memcpy(outstr+outlen, b64out, b64i); outlen += b64i; } else { /* Can't fit in :-( */ memcpy(outstr+outlen, b64out, outspc-outlen); outlen = outspc; } } return outlen; } /* * Copy bytes from stdin to out, obeying sensible SMTP DATA input heuristics. * * Rayan back in 1988: * "If you can improve on this heavily optimized routine, I'd like to see it. * This version goes at better than 100kB/cpu-sec on a Sun 3/180." * (with 68030 CPU running at 33 MHz -- about 10-15 MIPS) */ static int /* count of bytes */ mvdata(SS, msg) SmtpState *SS; char *msg; { register int c, state, *sts, endstate, cnt; register char *idxnum; #ifdef NO_INCOMING_HEADER_PROCESSING idxnum = indexnum + 1; state = 10; endstate = X_; sts = states; cnt = 0; SS->read_alarm_ival = SMTP_DATA_TIME_PER_LINE; #else char linebuf[4000], *s, *eol; int col; int insubject = 0; int has8bit = 0; /* In headers */ int has8bitsum = 0; /* int from__err = 0; */ int linecnt = 0; #ifdef USE_TRANSLATION int wi; char hdr_cte[4000], hdr_ct[4000]; int delay_cte = 0, delay_ct = 0, append_hdr_ct = 0, append_hdr_cte = 0; int ct_is_text = 1; #define CTE_8BIT 0 #define CTE_BASE64 1 #define CTE_QP 2 int cte = CTE_8BIT; int do_decode = 0, do_translate = 0; int qp_chars = 0, qp_hex = 0; int b64decoding = 1, b64eod = 0, b64i = 0; char b64c[4]; #endif /* USE_TRANSLATION */ typeflush(SS); idxnum = indexnum + 1; state = 10; endstate = X_; sts = states; cnt = 0; col = 0; SS->read_alarm_ival = SMTP_DATA_TIME_PER_LINE; /* ================ Input the email headers ================ */ /* and analyze them a bit (Precedence:) */ /* ... that only "Subject:" has 8-bit chars ... */ mail_priority = _MAILPRIO_NORMAL; for (;;) { c = s_getc(SS, 1); /* An EOF in here is an error! */ #if EOF != -1 if (c == EOF) return EOF; #else if (c < 0) return EOF; #endif ++cnt; state = sts[state + idxnum[c]]; if (state & ~0xff) { if (state & O_) { if (state == endstate) { if (col > 0) { #ifdef USE_TRANSLATION if (has8bit && !X_8bit) header_to_mime(linebuf, &col, sizeof(linebuf)); else header_from_mime(linebuf, &col, sizeof(linebuf)); for (wi = 0; wi < col; ++wi) fputc(TR_IN(linebuf[wi]), SS->mfp); #else /* USE_TRANSLATION */ if (has8bit) header_to_mime(linebuf, &col, sizeof(linebuf)); fwrite(linebuf, 1, col, SS->mfp); #endif /* USE_TRANSLATION */ } return cnt; } state = state & 0xFF; continue; } if (col < (sizeof(linebuf) - 1)) linebuf[col++] = (state >> 8); state = state & 0xFF; } if (0x80 & c) has8bit = 1; if (col < (sizeof(linebuf) - 1)) linebuf[col++] = c; /* LF, or something else ? Here the else.. */ if (c != '\n') continue; if (col < sizeof(linebuf)) linebuf[col] = 0; /* We have a string - header line - in ``col'' first char positions of the ``linebuf'' array */ /* See what this header is about -- or if the body starts.. */ if (col > sizeof(linebuf) - 1) col = sizeof(linebuf) - 1; eol = linebuf + col; *eol = 0; /* See if the line is all white-space: */ for (s = linebuf; s < eol; ++s) if (*s != ' ' && *s != '\t' && *s != '\r' && *s != '\n') break; if (s == eol) { /* All-blank line */ #ifdef USE_TRANSLATION /* We have all info from the headers, time to make decision */ if (X_8bit && ct_is_text && cte) do_decode = cte; if (X_translation && X_8bit && ct_is_text && (X_settrrc == 0)) do_translate = 1; type(NULL,0,NULL,"(8bit decode: %s, translate: %s) [%s%s,%s]", do_decode ? "YES" : "NO", do_translate ? "YES" : "NO", X_translation ? "-X " : "", X_8bit ? "-8" : "", ct_is_text ? "text" : "non-text"); /* write out content-type and content-transfer-encoding */ if (delay_ct) { if (do_translate) { /* Remove "charset=xxx". It is mismatching anyway */ char *p, *q, *r; p = hdr_ct; while (*p && (*p != ';')) p++; /* skip to attrs */ q = p; while (*p == ';') { /* check attributes */ r = p + 1; while (*r && ((*r == ' ') || (*r == '\t'))) r++; if (CISTREQN(r, "CHARSET=", 8)) { /* skip it */ p++; while (*p && (*p != ';') && (*p != '\n')) p++; #ifdef USE_TRANSLATION /* if forced charset specified, insert it. */ r = "; CHARSET="; while (*r) *(q++) = *(r++); r = USE_TRANSLATION; while (*r) *(q++) = *(r++); #endif /* USE_TRANSLATION */ } else { /* copy it */ *(q++) = *(p++); while (*p && (*p != ';')) *(q++) = *(p++); } } while (*p) *(q++) = *(p++); *q = '\0'; } fwrite(hdr_ct, 1, strlen(hdr_ct), SS->mfp); } if (delay_cte) { if (do_decode) { /* Transfer-encoding changed to 8bit */ fputs("Content-Transfer-Encoding: 8bit\n", SS->mfp); } else { fwrite(hdr_cte, 1, strlen(hdr_cte), SS->mfp); } } #endif /* USE_TRANSLATION */ if (col > 0) fwrite(linebuf, 1, col, SS->mfp); break; /* Into the body processing */ } ++linecnt; if (*linebuf == ' ' || *linebuf == '\t') { #ifdef USE_TRANSLATION if (append_hdr_ct) { strcat(hdr_ct, linebuf); col = 0; continue; } if (append_hdr_cte) { strcat(hdr_cte, linebuf); col = 0; continue; } if (has8bit && !X_8bit) header_to_mime(linebuf, &col, sizeof(linebuf)); else header_from_mime(linebuf, &col, sizeof(linebuf)); if (col > 0) for (wi = 0; wi < col; wi++) fputc(TR_IN(linebuf[wi]), SS->mfp); #else /* USE_TRANSLATION */ if (has8bit) header_to_mime(linebuf, &col, sizeof(linebuf)); if (col > 0) fwrite(linebuf, 1, col, SS->mfp); #endif /* USE_TRANSLATION */ has8bit = 0; col = 0; continue; /* continuation line.. */ } #ifdef USE_TRANSLATION append_hdr_ct = 0; append_hdr_cte = 0; #endif /* USE_TRANSLATION */ /* ================ PROCESS THE HEADERS! ================ */ if (CISTREQN(linebuf, "Subject:", 8)) { insubject = 1; } else { if (!insubject) { #if 0 if (has8bit) sprintf(msg, "Header line \"%.200s\" contains illegal 8-bit chars", linebuf); #endif has8bitsum += has8bit; } } /* XX: The anti-spam hacks. To differentiate between the auto-freeze of * allegedly-spam messages and freezes resulting from user-specified * smtp-policy.src, we do not use 1 to freeze the messages. */ if (CISTREQN(linebuf, "X-Advertisement:",16)) { /* Gee... Only SPAMmers (Cyberpromo!) use this .. (I hope..) */ SS->policyresult = FREEZE__X_ADVERTISEMENT_FOUND; type(NULL,0,NULL,"Found X-Advertisement header"); } if (CISTREQN(linebuf, "X-Advertisment:",15)) { /* Gee... Only SPAMmers (Cyberpromo!) use this .. (I hope..) */ SS->policyresult = FREEZE__X_ADVERTISEMENT_FOUND; type(NULL,0,NULL,"Found X-Advertisment header"); } #ifdef USE_ANTISPAM_HACKS if (strncmp(linebuf, "X-UIDL:", 7)==0) { /* Sigh... SPAMmers use this .. but it is valid AOL too.. */ SS->policyresult = FREEZE__X_UIDL_FOUND; } #endif #ifdef USE_ANTISPAM_HACKS /* This test probably doesn't work */ if (CISTREQN(linebuf, "Received:", 9)) { /* Scan for "(really ". Anything with this string in the Received * header is highly likely to have a forged Received header * characteristic of spams. Unfortunately this string may * also be a result of pure coincidence. */ s = linebuf + 11; while (*s) { if (CISTREQN(s, "(really ", 8)) { SS->policyresult = FREEZE__IMPROBABLE_RECEIVED_HEADER_FOUND; type(NULL,0,NULL,"Improbable Received: header"); break; } ++s; } } #endif #ifdef USE_ANTISPAM_HACKS if (CISTREQN(linebuf, "Message-ID:", 11)) { /* Freeze any mail with no message id in the message-id header, * or a message id with obvious syntax errors, or message id * with junk after it. These are highly likely to be spam, though * they might only be a result of buggy software. (MS Exchange?) */ s = linebuf + 11; while (*s == ' ' || *s == '\t') ++s; if (*s != '<') { SS->policyresult = FREEZE__MALFORMED_MESSAGE_ID_HEADER; type(NULL,0,NULL,"No <> around Message-Id"); } else if (s[1] == '@') { SS->policyresult = FREEZE__MALFORMED_MESSAGE_ID_HEADER; type(NULL,0,NULL,"Source route in Message-Id:"); } else if (s[1] == '>') { SS->policyresult = FREEZE__MALFORMED_MESSAGE_ID_HEADER; type(NULL,0,NULL,"Empty Message-Id:"); } else { const char *t = rfc821_path(s, 1); if (s == t) { /* error */ #ifdef USE_STRICT_MSGID_FREEZING SS->policyresult = FREEZE__MALFORMED_MESSAGE_ID_HEADER; type(NULL,0,NULL,"Message-Id: syntax error"); #endif } else { while (*t == ' ' || *t == '\t' || *t == '\r' || *t == '\n') ++t; if (*t) { SS->policyresult = FREEZE__MALFORMED_MESSAGE_ID_HEADER; type(NULL,0,NULL,"Spurious junk after Message-Id:"); } } } } #endif if (CISTREQN(linebuf, "Precedence:", 11)) { s = linebuf + 11; while (*s == ' ' || *s == '\t') ++s; if ((eol - s) < 4) continue; /* Hmm.. */ if (CISTREQN("high", s, 4)) mail_priority = _MAILPRIO_HIGH; else if (CISTREQN("junk", s, 4)) mail_priority = _MAILPRIO_JUNK; else if (CISTREQN("bulk", s, 4)) mail_priority = _MAILPRIO_BULK; else if (((eol - s) >= 6) && CISTREQN("normal", s, 6)) mail_priority = _MAILPRIO_NORMAL; } #if 0 /* Nice in theory - impractical in reality */ if (msa_mode && CISTREQN(linebuf, "Sender:", 7)) { if ( SS->authuser != NULL ) { fprintf(SS->mfp, "Sender: %s@%s\n", SS->authuser, SS->myhostname); fprintf(SS->mfp, "Old-"); } } #endif #ifdef USE_TRANSLATION if (X_translation && (X_settrrc == 0)) { if (CISTREQN(linebuf, "Content-Transfer-Encoding:", 26)) { if (1) { strcpy(hdr_cte, linebuf); delay_cte = 1; append_hdr_cte = 1; col = 0; has8bit = 0; s = linebuf + 26; while (*s == ' ' || *s == '\t') ++s; if ((eol - s) < 4) continue; /* Hmm.. */ if (CISTREQN("8bit", s, 4)) cte = CTE_8BIT; else if (CISTREQN("base64", s, 6)) cte = CTE_BASE64; else if (CISTREQN("quoted-printable", s, 16)) cte = CTE_QP; continue; /* do not write out this one */ } } else if (CISTREQN(linebuf, "Content-Type:", 13)) { if (1) { strcpy(hdr_ct, linebuf); delay_ct = 1; append_hdr_ct = 1; col = 0; has8bit = 0; s = linebuf + 13; while (*s == ' ' || *s == '\t') ++s; if ((eol - s) < 10) continue; /* Hmm.. */ /* Must ALWAYS check for C-T: TEXT/any ! */ /* #ifdef PARANOID_TRANSLATION */ if (CISTREQN("text", s, 4)) ct_is_text = 1; else ct_is_text = 0; /* #endif */ /* PARANOID_TRANSLATION */ continue; /* do not write out this one */ } } } #endif /* USE_TRANSLATION */ if (linecnt == 1 && (strncmp("From ", linebuf, 5) == 0 || strncmp(">From ", linebuf, 6) == 0)) { #if 0 from__err = 1; sprintf(msg, "Message starts with illegal \"%.200s\" line", linebuf); #endif /* DO NOT WRITE THIS LINE OUT! */ col = 0; has8bit = 0; continue; } #ifdef USE_TRANSLATION if (has8bit && !X_8bit) header_to_mime(linebuf, &col, sizeof(linebuf)); else header_from_mime(linebuf, &col, sizeof(linebuf)); /* Write the line out */ if (col > 0) for (wi = 0; wi < col; ++wi) fputc(TR_IN(linebuf[wi]), SS->mfp); #else /* USE_TRANSLATION */ if (has8bit) header_to_mime(linebuf, &col, sizeof(linebuf)); /* Write the line out */ if (col > 0) fwrite(linebuf, 1, col, SS->mfp); #endif /* USE_TRANSLATION */ has8bit = 0; col = 0; } if (verbose) type(NULL,0,NULL,"(mail_priority=%d)", mail_priority); #endif /* ================ Normal email BODY input.. ================ */ for (;;) { c = s_getc(SS, 1); #if EOF != -1 if (c == EOF) /* a little slower... */ break; #endif ++cnt; state = sts[state + idxnum[c]]; if (state & ~0xff) { if (state & O_) { if (state == endstate) break; state = (char) state; continue; } if (!ferror(SS->mfp)) fputc((state >> 8), SS->mfp); state = state & 0xFF; } if (!ferror(SS->mfp)) { #ifdef USE_TRANSLATION if (do_decode == CTE_QP) { if (!qp_chars && c == '=') { qp_chars = 2; qp_hex = 0; continue; } if (qp_chars && c == '\n') { qp_chars = 0; continue; } if (qp_chars == 2 && (c == ' ' || c == '\t')) { continue; } if (qp_chars && ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'))) { qp_hex <<= 4; if (c >= '0' && c <= '9') qp_hex += (c - '0'); if (c >= 'A' && c <= 'F') qp_hex += (c - 'A' + 10); if (c >= 'a' && c <= 'f') qp_hex += (c - 'a' + 10); --qp_chars; if (!qp_chars) c = qp_hex; else continue; } else if (qp_chars) qp_chars = 0; } else if (do_decode == CTE_BASE64) { if (b64decoding) { if ((c == ' ') || (c == '\t') || (c == '\n') || (c == '\r')) continue; b64c[b64i++] = decodechar64(c); if (c == '=') { b64eod = 1; continue; } if (b64i < 2) continue; if (b64i == 2) { c = (b64c[0] << 2) | ((b64c[1] & 0x30) >> 4); } else if (b64i == 3) { c = ((b64c[1] & 0x0f) << 4) | ((b64c[2] & 0x3c) >> 2); } else { c = (b64c[2] << 6) | b64c[3]; b64i = 0; if (b64eod) b64decoding = 0; } } } if (do_translate) fputc(TR_IN(c), SS->mfp); else #endif /* USE_TRANSLATION */ fputc(c, SS->mfp); } } typeflush(SS); return cnt; } /* * BDAT -- For ESMTP CHUNKING extension (rfc 1830) */ static int /* count of bytes */ mvbdata(SS, msg, incount) SmtpState *SS; char *msg; register long incount; { register int c, cnt; cnt = 0; /* XX: header processing REMOVED from BDAT processing */ SS->read_alarm_ival = SMTP_DATA_TIME_PER_LINE; /* ================ Normal email BODY input.. ================ */ for (; incount > 0; --incount) { c = s_getc(SS, 1); if (c == EOF) break; ++cnt; /* Canonize CR+LF --> LF (UNIX style) */ if (c == '\r') { /* Suspend sending, this is our 'mvbstate' */ /* do nothing, 'mvbstate = c' is done after this if-else */ } else if (SS->mvbstate == '\r') { if (c != '\n') { /* Suspended lone CR */ if (SS->mfp) { /* We just discard it, if no output stream */ if (!ferror(SS->mfp)) fputc(SS->mvbstate, SS->mfp); if (!ferror(SS->mfp)) fputc(c, SS->mfp); } } else { /* CR + LF -- forget the CR */ if (SS->mfp && !ferror(SS->mfp)) fputc(c, SS->mfp); } } else { /* Anything else, just output it! */ if (SS->mfp && !ferror(SS->mfp)) fputc(c, SS->mfp); } SS->mvbstate = c; } return cnt; }