Skip to content
Snippets Groups Projects
mailsrvr.c 230 KiB
Newer Older
/* Synchronet Mail (SMTP/POP3) server and sendmail threads */

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
 *																			*
 * This program is free software; you can redistribute it and/or			*
 * modify it under the terms of the GNU General Public License				*
 * as published by the Free Software Foundation; either version 2			*
 * of the License, or (at your option) any later version.					*
 * See the GNU General Public License for more details: gpl.txt or			*
 * http://www.fsf.org/copyleft/gpl.html										*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * Note: If this box doesn't appear square, then you need to fix your tabs.	*
 ****************************************************************************/

rswindell's avatar
rswindell committed
/* ANSI C Library headers */
#include <limits.h>         /* UINT_MAX */
rswindell's avatar
rswindell committed
#include <stdio.h>
#include <stdlib.h>         /* ltoa in GNU C lib */
#include <stdarg.h>         /* va_list */
#include <string.h>         /* strrchr */
#include <fcntl.h>          /* Open flags */
#include <errno.h>          /* errno */
rswindell's avatar
rswindell committed

/* Synchronet-specific headers */
#undef SBBS /* this shouldn't be defined unless building sbbs.dll/libsbbs.so */
rswindell's avatar
rswindell committed
#include "mailsrvr.h"
#include "crc32.h"
#include "netwrap.h"    /* getNameServerList() */
deuce's avatar
deuce committed
#include "multisock.h"
#include "ssl.h"
#include "cryptlib.h"
#include "git_branch.h"
#include "git_hash.h"
rswindell's avatar
rswindell committed
/* Constants */
static const char* server_name = "Synchronet Mail Server";
static const char* server_abbrev = "mail";
#define FORWARD         "forward:"
#define NO_FORWARD      "local:"
#define NO_SPAM         "#nospam"
              , DWORD intf, DWORD ip_addr, BOOL use_tcp, int timeout);

#define pop_error       "-ERR System Error: %s, try again later"
#define pop_auth_error  "-ERR Authentication failure"
#define ok_rsp          "250 OK"
#define auth_ok         "235 User Authenticated"
#define smtp_error      "421 System Error: %s, try again later"
#define insuf_stor      "452 Insufficient system storage"
#define badarg_rsp      "501 Bad argument"
#define badseq_rsp      "503 Bad sequence of commands"
#define badauth_rsp     "535 Authentication failure"
#define badrsp_err      "%s replied with:\r\n\"%s\"\r\ninstead of the expected reply:\r\n\"%s ...\""

#define TIMEOUT_THREAD_WAIT     60      /* Seconds */
#define DNSBL_THROTTLE_VALUE    1000    /* Milliseconds */
#define SMTP_MAX_BAD_CMDS       9
#define SMTP_MAX_CMD_LEN        510     /* Excluding CRLF */
#define RFC822_MAX_LINE_LEN     998     /* Excluding CRLF */

static mail_startup_t*    startup = NULL;
static scfg_t             scfg;
static char*              text[TOTAL_TEXT];
static struct xpms_set *  mail_set = NULL;
static BOOL               terminated = FALSE;
static protected_uint32_t active_clients;
static protected_uint32_t thread_count;
static volatile uint32_t  client_highwater = 0;
static volatile int       active_sendmail = 0;
static volatile BOOL      sendmail_running = FALSE;
static volatile BOOL      terminate_sendmail = FALSE;
static sem_t              sendmail_wakeup_sem;
static volatile time_t    uptime;
static str_list_t         pause_semfiles;
static str_list_t         recycle_semfiles;
static str_list_t         shutdown_semfiles;
static int                mailproc_count;
static js_server_props_t  js_server_props;
static link_list_t        current_logins;
static link_list_t        current_connections;
static bool               savemsg_mutex_created = false;
static pthread_mutex_t    savemsg_mutex;
static struct mqtt        mqtt;

static const char*        servprot_smtp = "SMTP";
static const char*        servprot_submission = "SMTP";
static const char*        servprot_submissions = "SMTPS";
static const char*        servprot_pop3 = "POP3";
static const char*        servprot_pop3s = "POP3S";
	volatile ulong sockets;
	volatile ulong errors;
	volatile ulong crit_errors;
	volatile ulong connections_exceeded;
	volatile ulong connections_ignored;
	volatile ulong connections_refused;
	volatile ulong connections_served;
	volatile ulong pop3_served;
	volatile ulong smtp_served;
	volatile ulong sessions_refused;
	volatile ulong msgs_ignored;
	volatile ulong msgs_refused;
	volatile ulong msgs_received;
	char name[INI_MAX_VALUE_LEN];
	char cmdline[INI_MAX_VALUE_LEN];
	char eval[INI_MAX_VALUE_LEN];
	str_list_t to;
	str_list_t from;
	BOOL passthru;
	BOOL native;
	BOOL ignore_on_error;           /* Ignore mail message if cmdline fails */
	BOOL disabled;
	BOOL process_spam;
	BOOL process_dnsbl;
	uint8_t* ar;
	ulong handled;                  /* counter (for stats display) */
	SOCKET socket;
	union xp_sockaddr client_addr;
	socklen_t client_addr_len;
	BOOL tls_port;
	CRYPT_SESSION session;
} smtp_t, pop3_t;
deuce's avatar
deuce committed
#define GCES(status, server, sock, sess, action) do {                             \
			char *GCES_estr;                                                               \
			int   GCES_level;                                                                 \
			get_crypt_error_string(status, sess, &GCES_estr, action, &GCES_level);  \
			if (GCES_estr) {                                                                  \
				if (GCES_level < startup->tls_error_level)                                     \
				GCES_level = startup->tls_error_level;                                     \
				if (GCES_level > LOG_INFO)                                                      \
				GCES_level = LOG_INFO;                                                      \
				lprintf(GCES_level, "%04d %s %s", sock, server, GCES_estr);                     \
				free_crypt_attrstr(GCES_estr);                                                  \
			}                                                                                    \
} while (0)
deuce's avatar
deuce committed
#define GCESH(status, server, sock, host, sess, action) do {                      \
			char *GCES_estr;                                                               \
			int   GCES_level;                                                                 \
			get_crypt_error_string(status, sess, &GCES_estr, action, &GCES_level);  \
			if (GCES_estr) {                                                                  \
				if (GCES_level < startup->tls_error_level)                                     \
				GCES_level = startup->tls_error_level;                                      \
				if (GCES_level > LOG_INFO)                                                      \
				GCES_level = LOG_INFO;                                                      \
				lprintf(GCES_level, "%04d %s [%s] %s", sock, server, host, GCES_estr);         \
				free_crypt_attrstr(GCES_estr);                                                  \
			}                                                                                    \
} while (0)

#define GCESHL(status, server, sock, host, log_level, sess, action) do {                        \
			char *GCES_estr;                                                                            \
			int   GCES_level;                                                                             \
			get_crypt_error_string(status, sess, &GCES_estr, action, &GCES_level);                      \
			if (GCES_estr) {                                                                            \
				lprintf((log_level < startup->tls_error_level) ? startup->tls_error_level : log_level   \
						, "%04d %s [%s] %s", sock, server, host, GCES_estr);                                \
				free_crypt_attrstr(GCES_estr);                                                          \
			}                                                                                           \
} while (0)
static int lputs(int level, const char* str)
{
	mqtt_lputs(&mqtt, TOPIC_SERVER, level, str);
	if (level <= LOG_ERR) {
		char errmsg[1024];
		stats.errors++;
		SAFEPRINTF2(errmsg, "%-4s %s", server_abbrev, str);
		errorlog(&scfg, &mqtt, level, startup == NULL ? NULL : startup->host_name, errmsg);
		if (startup != NULL && startup->errormsg != NULL)
			startup->errormsg(startup->cbdata, level, errmsg);
	}

	if (level <= LOG_CRIT)
		stats.crit_errors++;

	if (startup == NULL || startup->lputs == NULL || str == NULL || level > startup->log_level)
		return 0;

#if defined(_WIN32)
	if (IsBadCodePtr((FARPROC)startup->lputs))
		return 0;
#endif

	return startup->lputs(startup->cbdata, level, str);
#if defined(__GNUC__)   // Catch printf-format errors with lprintf
static int lprintf(int level, const char *fmt, ...) __attribute__ ((format (printf, 2, 3)));
#endif
static int lprintf(int level, const char *fmt, ...)
	va_start(argptr, fmt);
	vsnprintf(sbuf, sizeof(sbuf), fmt, argptr);
	sbuf[sizeof(sbuf) - 1] = 0;
	va_end(argptr);
	return lputs(level, condense_whitespace(sbuf));
#if defined(__GNUC__)   // Catch printf-format errors with lprintf
static int errprintf(int level, int line, const char* function, const char* file, const char *fmt, ...) __attribute__ ((format (printf, 5, 6)));
#endif
static int errprintf(int level, int line, const char* function, const char* file, const char *fmt, ...)
{
	va_list argptr;
	va_start(argptr, fmt);
	vsnprintf(sbuf, sizeof(sbuf), fmt, argptr);
	sbuf[sizeof(sbuf) - 1] = 0;
	if (repeated_error(line, function)) {
		if (level < LOG_WARNING)
			level = LOG_WARNING;
	}
	return lputs(level, condense_whitespace(sbuf));
}

#ifdef _WINSOCKAPI_

static WSADATA WSAData;
#define SOCKLIB_DESC WSAData.szDescription
static BOOL    WSAInitialized = FALSE;

static BOOL winsock_startup(void)
{
	int status;                 /* Status Code */
	if ((status = WSAStartup(MAKEWORD(1, 1), &WSAData)) == 0) {
		lprintf(LOG_DEBUG, "%s %s", WSAData.szDescription, WSAData.szSystemStatus);
		WSAInitialized = TRUE;
	lprintf(LOG_CRIT, "!WinSock startup ERROR %d", status);
#define winsock_startup()   (TRUE)
static char* server_host_name(void)
{
	return startup->host_name[0] ? startup->host_name : scfg.sys_inetaddr;
}

static void set_state(enum server_state state)
{
	if (state == curr_state)
	if (startup != NULL) {
		if (startup->set_state != NULL)
			startup->set_state(startup->cbdata, state);
		mqtt_server_state(&mqtt, state);
static void update_clients(void)
{
	if (startup != NULL) {
		if (startup->clients != NULL)
			startup->clients(startup->cbdata, protected_uint32_value(active_clients) + active_sendmail);
static void client_on(SOCKET sock, client_t* client, BOOL update)
	if (!update)
		listAddNodeData(&current_connections, client->addr, strlen(client->addr) + 1, sock, LAST_NODE);
	if (startup != NULL && startup->client_on != NULL)
		startup->client_on(startup->cbdata, TRUE, sock, client, update);
	mqtt_client_on(&mqtt, TRUE, sock, client, update);
}

static void client_off(SOCKET sock)
{
	listRemoveTaggedNode(&current_connections, sock, /* free_data */ TRUE);
	if (startup != NULL && startup->client_on != NULL)
		startup->client_on(startup->cbdata, FALSE, sock, NULL, FALSE);
	mqtt_client_on(&mqtt, FALSE, sock, NULL, FALSE);
static void thread_up(BOOL setuid)
	if (startup != NULL) {
		if (startup->thread_up != NULL)
			startup->thread_up(startup->cbdata, TRUE, setuid);
static int32_t thread_down(void)
	int32_t count = protected_uint32_adjust_fetch(&thread_count, -1);
	if (startup != NULL) {
		if (startup->thread_up != NULL)
			startup->thread_up(startup->cbdata, FALSE, FALSE);
deuce's avatar
deuce committed
void mail_open_socket(SOCKET sock, void* cb_protocol)
	char *protocol = (char *)cb_protocol;
	char  error[256];
	char  section[128];
	if (startup != NULL && startup->socket_open != NULL)
		startup->socket_open(startup->cbdata, TRUE);
	SAFEPRINTF(section, "mail|%s", protocol);
	if (set_socket_options(&scfg, sock, section, error, sizeof(error)))
		lprintf(LOG_ERR, "%04d %s !ERROR %s", sock, protocol, error);
deuce's avatar
deuce committed
	stats.sockets++;
}

void mail_close_socket_cb(SOCKET sock, void* cb_protocol)
{
	if (startup != NULL && startup->socket_open != NULL)
		startup->socket_open(startup->cbdata, FALSE);
deuce's avatar
deuce committed
	stats.sockets--;
int mail_close_socket(SOCKET *sock, int *sess)
	if (*sock == INVALID_SOCKET)
	shutdown(*sock, SHUT_RDWR);  /* required on Unix */
	result = closesocket(*sock);
	if (startup != NULL && startup->socket_open != NULL)
		startup->socket_open(startup->cbdata, FALSE);
	if (result != 0) {
		if (SOCKET_ERRNO != ENOTSOCK)
			lprintf(LOG_WARNING, "%04d !ERROR %d closing socket", *sock, SOCKET_ERRNO);
#if 0 /*def _DEBUG */
		lprintf(LOG_DEBUG, "%04d Socket closed (%d sockets in use)", *sock, stats.sockets);
int sockprintf(SOCKET sock, const char* prot, CRYPT_SESSION sess, char *fmt, ...)
	va_list argptr;
	if (sock == INVALID_SOCKET) {
		lprintf(LOG_WARNING, "%s !INVALID SOCKET in call to sockprintf", prot);
Deucе's avatar
Deucе committed
	/* Check socket for writability */
	if (!socket_writable(sock, 300000)) {
		lprintf(LOG_NOTICE, "%04d %s !NOTICE socket did not become writable"
		        , sock, prot);
	va_start(argptr, fmt);
	len = vasprintf(&sbuf, fmt, argptr);
	va_end(argptr);
	if (len < 0 || sbuf == NULL) { /* format error or allocation error */
		errprintf(LOG_CRIT, WHERE, "%04d %s %s error (%d) formatting string: '%s'", sock, prot, __FUNCTION__, len, fmt);
	if (startup->options & MAIL_OPT_DEBUG_TX)
		lprintf(LOG_DEBUG, "%04d %s TX: %.*s", sock, prot, len, sbuf);
	char* newp = realloc(sbuf, len + 2); // "\r\n"
	if (newp == NULL) { /* format error or allocation error */
		errprintf(LOG_CRIT, WHERE, "%04d %s %s re-allocation failure of %d bytes", sock, prot, __FUNCTION__, len + 2);
	memcpy(sbuf + len, "\r\n", 2);
	len += 2;
	if (sess != -1) {
		int tls_sent;
		int sent = 0;
		while (sent < len) {
			result = cryptPushData(sess, sbuf + sent, len - sent, &tls_sent);
			if (result == CRYPT_OK)
				sent += tls_sent;
				GCES(result, prot, sock, sess, "pushing data");
		if ((result = cryptFlushData(sess)) != CRYPT_OK) {
			GCES(result, prot, sock, sess, "flushing data");
	}
	else {
		// It looks like this could stutter on partial sends -- Deuce
		while ((result = sendsocket(sock, sbuf, len)) != len) {
			if (result == SOCKET_ERROR) {
				if (SOCKET_ERRNO == EWOULDBLOCK) {
					YIELD();
					continue;
				}
				if (SOCKET_ERRNO == ECONNRESET)
					lprintf(LOG_NOTICE, "%04d %s Connection reset by peer on send", sock, prot);
				else if (SOCKET_ERRNO == ECONNABORTED)
					lprintf(LOG_NOTICE, "%04d %s Connection aborted by peer on send", sock, prot);
					lprintf(LOG_NOTICE, "%04d %s !ERROR %d sending on socket", sock, prot, SOCKET_ERRNO);
			lprintf(LOG_WARNING, "%04d %s !ERROR: short send on socket: %d instead of %d", sock, prot, result, len);
static void sockerror(SOCKET socket, const char* prot, int rd, const char* action)
	if (rd == 0)
		lprintf(LOG_NOTICE, "%04d %s Socket closed by peer on %s"
		        , socket, prot, action);
	else if (rd == SOCKET_ERROR) {
		if (SOCKET_ERRNO == ECONNRESET)
			lprintf(LOG_NOTICE, "%04d %s Connection reset by peer on %s"
			        , socket, prot, action);
		else if (SOCKET_ERRNO == ECONNABORTED)
			lprintf(LOG_NOTICE, "%04d %s Connection aborted by peer on %s"
			        , socket, prot, action);
			lprintf(LOG_NOTICE, "%04d %s !SOCKET ERROR %d on %s"
			        , socket, prot, SOCKET_ERRNO, action);
		lprintf(LOG_WARNING, "%04d %s !SOCKET ERROR: unexpected return value %d from %s"
		        , socket, prot, rd, action);
static int sock_recvbyte(SOCKET sock, const char* prot, CRYPT_SESSION sess, char *buf, time_t start)
	int ret;
	int i;

	if (sess > -1) {
		while (1) {
			ret = cryptPopData(sess, buf, 1, &len);
			GCES(ret, prot, sock, sess, "popping data");
				case CRYPT_OK:
					break;
				case CRYPT_ERROR_TIMEOUT:
					return -1;
				case CRYPT_ERROR_COMPLETE:
					return 0;
				default:
					if (ret < -1)
						return ret;
					return -2;
			}
			if (len)
				return len;
			if (startup->max_inactivity && (time(NULL) - start) > startup->max_inactivity) {
				lprintf(LOG_WARNING, "%04d %s !TIMEOUT in sock_recvbyte (%u seconds):  INACTIVE SOCKET"
				        , sock, prot, startup->max_inactivity);
Deucе's avatar
Deucе committed
		if (!socket_readable(sock, startup->max_inactivity * 1000)) {
			if (startup->max_inactivity && (time(NULL) - start) > startup->max_inactivity) {
				lprintf(LOG_WARNING, "%04d %s !TIMEOUT in sock_recvbyte (%u seconds):  INACTIVE SOCKET"
				        , sock, prot, startup->max_inactivity);
		i = recv(sock, buf, 1, 0);
		if (i < 1)
			sockerror(sock, prot, i, "receive");
static int sockreadline(SOCKET socket, const char* prot, CRYPT_SESSION sess, char* buf, int len)
	char   ch;
	int    i, rd = 0;
	time_t start;
	start = time(NULL);
	if (socket == INVALID_SOCKET) {
		lprintf(LOG_WARNING, "%s !INVALID SOCKET in call to sockreadline", prot);
	while (rd < len - 1) {
		if (terminated || terminate_server) {
			lprintf(LOG_WARNING, "%04d %s !ABORTING sockreadline", socket, prot);
		i = sock_recvbyte(socket, prot, sess, &ch, start);
		if (ch == '\n' /* && rd>=1 */) { /* Mar-9-2003: terminate on sole LF */
	if (rd > 0 && buf[rd - 1] == '\r')
static BOOL sockgetrsp(SOCKET socket, const char* prot, CRYPT_SESSION sess, char* rsp, char *buf, int len)
		rd = sockreadline(socket, prot, sess, buf, len);
		if (rd < 1) {
			if (rd == 0 && rsp != NULL)
				lprintf(LOG_NOTICE, "%04d %s !RECEIVED BLANK RESPONSE, Expected '%s'", socket, prot, rsp);
		if (buf[3] == '-') { /* Multi-line response */
			if (startup->options & MAIL_OPT_DEBUG_RX_RSP)
				lprintf(LOG_DEBUG, "%04d %s RX: %s", socket, prot, buf);
		if (rsp != NULL && strnicmp(buf, rsp, strlen(rsp))) {
			lprintf(LOG_NOTICE, "%04d %s !INVALID RESPONSE: '%s' Expected: '%s'", socket, prot, buf, rsp);
	if (startup->options & MAIL_OPT_DEBUG_RX_RSP)
		lprintf(LOG_DEBUG, "%04d %s RX: %s", socket, prot, buf);
static int sockgetrsp_opt(SOCKET socket, const char* prot, CRYPT_SESSION sess, char* rsp, char *opt, char *buf, int len)
	size_t moptlen;
	moptlen = strlen(rsp) + strlen(opt) + 1;
	mopt = malloc(moptlen + 1);
	if (mopt == NULL)
		return -1;
	sprintf(mopt, "%s-%s", rsp, opt);
		rd = sockreadline(socket, prot, sess, buf, len);
		if (rd < 1) {
			if (rd == 0)
				lprintf(LOG_NOTICE, "%04d %s !RECEIVED BLANK RESPONSE, Expected '%s'", socket, prot, rsp);
			free(mopt);
		if (buf[3] == '-') { /* Multi-line response */
			if (strncmp(buf, mopt, moptlen) == 0)
				ret = 1;
			if (startup->options & MAIL_OPT_DEBUG_RX_RSP)
				lprintf(LOG_DEBUG, "%04d %s RX: %s", socket, prot, buf);
		if (strnicmp(buf, rsp, strlen(rsp))) {
			lprintf(LOG_NOTICE, "%04d %s !INVALID RESPONSE: '%s' Expected: '%s'", socket, prot, buf, rsp);
			free(mopt);
	if (strncmp(buf, mopt, moptlen) == 0)
	free(mopt);
	if (startup->options & MAIL_OPT_DEBUG_RX_RSP)
		lprintf(LOG_DEBUG, "%04d %s RX: %s", socket, prot, buf);
rswindell's avatar
rswindell committed
/* non-standard, but documented (mostly) in draft-newman-msgheader-originfo-05 */
void originator_info(SOCKET socket, const char* protocol, CRYPT_SESSION sess, smbmsg_t* msg)
rswindell's avatar
rswindell committed
{
	char* user      = msg->from_ext;
	char* login     = smb_get_hfield(msg, SENDERUSERID, NULL);
	char* server    = smb_get_hfield(msg, SENDERSERVER, NULL);
	char* client    = smb_get_hfield(msg, SENDERHOSTNAME, NULL);
	char* addr      = smb_get_hfield(msg, SENDERIPADDR, NULL);
	char* prot      = smb_get_hfield(msg, SENDERPROTOCOL, NULL);
	char* port      = smb_get_hfield(msg, SENDERPORT, NULL);
	char* time      = smb_get_hfield(msg, SENDERTIME, NULL);

	if (user || login || server || client || addr || prot || port || time)
		           , "X-Originator-Info: account=%s; login-id=%s; server=%s; client=%s; addr=%s; prot=%s; port=%s; time=%s"
		           , user
		           , login
		           , server
		           , client
		           , addr
		           , prot
		           , port
		           , time
		           );
char* angle_bracket(char* buf, size_t max, const char* addr)
{
		safe_snprintf(buf, max, "%s", addr);
	else
		safe_snprintf(buf, max, "<%s>", addr);
	return buf;
}

int compare_addrs(const char* addr1, const char* addr2)
{
	char tmp1[256];
	char tmp2[256];

	return strcmp(angle_bracket(tmp1, sizeof(tmp1), addr1), angle_bracket(tmp2, sizeof(tmp2), addr2));
}

// MIME-Encode a word (Q-encoded UTF-8, only)
size_t mime_encode_word(char* buf, size_t size, const char* src)
{
	size_t len = snprintf(buf, size - 1, "=?utf-8?q?");
	// RFC 2047: "An 'encoded-word' may not be more than 75 characters long"
	for (const char* p = src; *p != '\0' && len < (size - 1) && len < 71; ++p) {
		if (*p >= 0 && *p != '=' && *p != ' ') { // US-ASCII char, represented without Q-encoding
			buf[len++] = *p;
			continue;
		}
		len += snprintf(buf + len, size - (len + 1), "=%02X", (unsigned char)*p);
	}
	len += snprintf(buf + len, size - (len + 1), "?=");
	return len;
}

// MIME-Encode words in string, when necessary
static char* encode_header_field(const char* src, char* buf, size_t size, const smbmsg_t* msg)
{
	if (str_is_ascii(src))
	if (!(msg->hdr.auxattr & MSG_HFIELDS_UTF8))
		cp437_to_utf8_str(src, tmp, sizeof tmp, /* min-char-val: */ '\x80');
	char*  state = NULL;
	for (char* p = strtok_r(tmp, " \t", &state); p != NULL && len < (size - 1); p = strtok_r(NULL, " \t", &state)) {
		if (len)
		if (str_is_ascii(p))
			len += snprintf(buf + len, size - (len + 1), "%s", p);
		else
			len += mime_encode_word(buf + len, size - (len + 1), p);
	}
	buf[len] = '\0';
	return buf;
}

static ulong sockmimetext(SOCKET socket, const char* prot, CRYPT_SESSION sess, smbmsg_t* msg, char* msgtxt, ulong maxlines
                          , str_list_t file_list, char* mime_boundary)
	char  toaddr[256] = "";
	char  msgid[256];
	char  tmp[256];
	char  date[64];
	char  encoded_text[256];
	char* p;
	char* np;
	int   i;
	int   s;
	ulong lines;
	int   len, tlen;
	/* HEADERS (in recommended order per RFC822 4.1) */
	if (msg->reverse_path != NULL)
		if (!sockprintf(socket, prot, sess, "Return-Path: %s", msg->reverse_path))
	for (i = 0; i < msg->total_hfields; i++)
		if (msg->hfield[i].type == SMTPRECEIVED && msg->hfield_dat[i] != NULL)
			if (!sockprintf(socket, prot, sess, "Received: %s", (char*)msg->hfield_dat[i]))
	if (!sockprintf(socket, prot, sess, "Date: %s", msgdate(msg->hdr.when_written, date)))
	if ((p = smb_get_hfield(msg, RFC822FROM, NULL)) != NULL)
		s = sockprintf(socket, prot, sess, "From: %s", p); /* use original RFC822 header field */
rswindell's avatar
rswindell committed
		char fromname[256];
		char fromaddr[256];
		smtp_netmailaddr(&scfg, msg, fromname, sizeof(fromname), fromaddr, sizeof(fromaddr));
		s = sockprintf(socket, prot, sess, "From: %s %s"
		               , encode_header_field(fromname, encoded_text, sizeof encoded_text, msg)
		               , angle_bracket(tmp, sizeof(tmp), fromaddr));
	if ((p = smb_get_hfield(msg, RFC822ORG, NULL)) != NULL) {
		if (!sockprintf(socket, prot, sess, "Organization: %s", p))
		if (msg->from_org != NULL || msg->from_net.type == NET_NONE)
			if (!sockprintf(socket, prot, sess, "Organization: %s"
			                , encode_header_field(msg->from_org == NULL ? scfg.sys_name : msg->from_org, encoded_text, sizeof encoded_text, msg)))
	p = smb_get_hfield(msg, RFC822SUBJECT, NULL);
	if (!sockprintf(socket, prot, sess, "Subject: %s", p == NULL ? encode_header_field(msg->subj, encoded_text, sizeof encoded_text, msg) : p))
	if ((p = smb_get_hfield(msg, RFC822TO, NULL)) != NULL)
		s = sockprintf(socket, prot, sess, "To: %s", p); /* use original RFC822 header field (MIME-Encoded) */
	else if ((p = msg->to_list) != NULL)
		s = sockprintf(socket, prot, sess, "To: %s", p); /* use original RFC822 header field */
		const char* to = encode_header_field(msg->to, encoded_text, sizeof encoded_text, msg);
		if (strchr(msg->to, '@') != NULL || msg->to_net.addr == NULL)
			s = sockprintf(socket, prot, sess, "To: %s", to); /* Avoid double-@ */
		else if (msg->to_net.type == NET_INTERNET || msg->to_net.type == NET_QWK) {
			if (strchr((char*)msg->to_net.addr, '<') != NULL)
				s = sockprintf(socket, prot, sess, "To: %s", (char*)msg->to_net.addr);
				s = sockprintf(socket, prot, sess, "To: \"%s\" <%s>", to, (char*)msg->to_net.addr);
		} else if (msg->to_net.type == NET_FIDO) {
			s = sockprintf(socket, prot, sess, "To: \"%s\" (%s)", to, smb_faddrtoa((fidoaddr_t*)msg->to_net.addr, faddrbuf));
			usermailaddr(&scfg, toaddr, msg->to);
			s = sockprintf(socket, prot, sess, "To: \"%s\" <%s>", to, toaddr);
	if ((p = smb_get_hfield(msg, RFC822CC, NULL)) != NULL || msg->cc_list != NULL)
		if (!sockprintf(socket, prot, sess, "Cc: %s", p == NULL ? msg->cc_list : p))
	p = smb_get_hfield(msg, RFC822REPLYTO, NULL);
	if (p == NULL && (p = msg->replyto_list) == NULL) {
		np = msg->replyto;
		if (msg->replyto_net.type == NET_INTERNET)
			p = msg->replyto_net.addr;
	if (p != NULL && strchr((char*)p, '@') != NULL) {
		if (np != NULL)
			s = sockprintf(socket, prot, sess, "Reply-To: \"%s\" <%s>", np, p);
			s = sockprintf(socket, prot, sess, "Reply-To: %s", p);
	if (!sockprintf(socket, prot, sess, "Message-ID: %s", get_msgid(&scfg, INVALID_SUB, msg, msgid, sizeof(msgid))))
	if (msg->reply_id != NULL)
		if (!sockprintf(socket, prot, sess, "In-Reply-To: %s", msg->reply_id))
	if (msg->hdr.priority != SMB_PRIORITY_UNSPECIFIED)
		if (!sockprintf(socket, prot, sess, "X-Priority: %u", (uint)msg->hdr.priority))
	originator_info(socket, prot, sess, msg);
	/* Include all possible FidoNet header fields here */
	for (i = 0; i < msg->total_hfields; i++) {
		switch (msg->hfield[i].type) {
			case FIDOSEENBY:
			case FIDOPATH:
			case FIDOMSGID:
			case FIDOREPLYID:
			case FIDOPID:
			case FIDOFLAGS:
			case FIDOTID:
				if (!sockprintf(socket, prot, sess, "%s: %s", smb_hfieldtype(msg->hfield[i].type), (char*)msg->hfield_dat[i]))
	for (i = 0; i < msg->total_hfields; i++) {
		if (msg->hfield[i].type == RFC822HEADER) {
			if (!sockprintf(socket, prot, sess, "%s", (char*)msg->hfield_dat[i]))
	const char* charset = msg->text_charset;
	if (charset == NULL) {
		if (smb_msg_is_utf8(msg) || (msg->hdr.auxattr & MSG_HFIELDS_UTF8))
			charset = "UTF-8";
		else if (str_is_ascii(msgtxt))
			charset = "US-ASCII";
		else
			charset = "IBM437";
	}

	if (strListCount(file_list)) {   /* File attachments */
		mimeheaders(socket, prot, sess, mime_boundary);
		sockprintf(socket, prot, sess, "");
		mimeblurb(socket, prot, sess, mime_boundary);
		sockprintf(socket, prot, sess, "");
		mimetextpartheader(socket, prot, sess, mime_boundary, msg->text_subtype, charset);
	} else {
		/* Default MIME Content-Type for non-Internet messages */
		if (msg->from_net.type != NET_INTERNET && msg->content_type == NULL) {
			sockprintf(socket, prot, sess, "Content-Type: text/plain; charset=%s", charset);
			sockprintf(socket, prot, sess, "Content-Transfer-Encoding: 8bit");
	if (!sockprintf(socket, prot, sess, ""))    /* Header Terminator */
	/* MESSAGE BODY */
	lines = 0;
	if (*msgtxt == '\0')
		np = "\r\n"; // Send at least one line of message text (issue #822)
	else
		np = msgtxt;
	long bytes = 0;
	while (*np && lines < maxlines) {
		len = 0;
		while (len < RFC822_MAX_LINE_LEN && *(np + len) != 0 && *(np + len) != '\n')
		tlen = len;
		while (tlen && *(np + (tlen - 1)) <= ' ') /* Takes care of '\r' or spaces */
		if (!sockprintf(socket, prot, sess, "%s%.*s", *np == '.' ? ".":"", tlen, np))
rswindell's avatar
rswindell committed
		lines++;
		if (*(np + len) == '\r')
		if (*(np + len) == '\n')
		/* release time-slices every x lines */
		if (startup->lines_per_yield
		    && !(lines % startup->lines_per_yield))
		if ((lines % 100) == 0)
			lprintf(LOG_DEBUG, "%04d %s sent %lu lines (%ld bytes) of body text"
			        , socket, prot, lines, bytes);
	lprintf(LOG_DEBUG, "%04d %s sent %lu lines (%ld bytes) of body text"
	        , socket, prot, lines, bytes);
	if (file_list != NULL) {
		for (i = 0; file_list[i]; i++) {
			sockprintf(socket, prot, sess, "");
			lprintf(LOG_INFO, "%04u %s MIME Encoding and sending %s", socket, prot, file_list[i]);
			if (!mimeattach(socket, prot, sess, mime_boundary, file_list[i]))
				errprintf(LOG_ERR, WHERE, "%04u %s !ERROR opening/encoding/sending %s", socket, prot, file_list[i]);
				if (msg->hdr.auxattr & MSG_KILLFILE)
					if (remove(file_list[i]) != 0)
						lprintf(LOG_WARNING, "%04u %s !ERROR %d (%s) removing %s", socket, prot, errno, strerror(errno), file_list[i]);
			endmime(socket, prot, sess, mime_boundary);
	sockprintf(socket, prot, sess, ".");   /* End of text */
static ulong sockmsgtxt(SOCKET socket, const char* prot, CRYPT_SESSION sess, smbmsg_t* msg, char* msgtxt, ulong maxlines)
	char       dirname[MAX_PATH + 1];
	char       filepath[MAX_PATH + 1];
	ulong      retval;
	char*      boundary = NULL;
	str_list_t file_list = NULL;

	if (msg->hdr.auxattr & MSG_FILEATTACH) {
		if (msg->idx.to != 0)
			SAFEPRINTF2(dirname, "%sfile/%04u.in", scfg.data_dir, msg->idx.to);
		else
			SAFEPRINTF2(dirname, "%sfile/%04u.out", scfg.data_dir, msg->idx.from);

		boundary = mimegetboundary();
		file_list = strListInit();

		/* filename(s) in subject */
		char* p = msg->subj;
		SKIP_WHITESPACE(p);
		while (p != NULL && *p != '\0') {
			char delim = ' ';
			char* tp = strchr(p, delim);
			if (tp == NULL) {
				if (delim != ' ')
			char* fname = getfname(truncsp(p));
			if (strcspn(fname, ILLEGAL_FILENAME_CHARS) == strlen(fname)) {
				SAFEPRINTF2(filepath, "%s/%s", dirname, fname);
				strListPush(&file_list, filepath);
			}
				break;
			p = tp + 1;
			SKIP_WHITESPACE(p);
	retval = sockmimetext(socket, prot, sess, msg, msgtxt, maxlines, file_list, boundary);
	if (boundary != NULL)