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. *
****************************************************************************/
#include <limits.h> /* UINT_MAX */
#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 */
#undef SBBS /* this shouldn't be defined unless building sbbs.dll/libsbbs.so */
#include "sbbs.h"
#include "utf8.h"
#include "mime.h"
#include "md5.h"
#include "base64.h"
#include "ini_file.h"
#include "netwrap.h" /* getNameServerList() */
#include "xpendian.h"
#include "xpprintf.h"
#include "js_rtpool.h"
#include "js_request.h"
#include "ssl.h"
#include "cryptlib.h"
#include "git_branch.h"
#include "git_hash.h"
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"

rswindell
committed
int dns_getmx(char* name, char* mx, char* mx2
, 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;

Rob Swindell
committed
static bool terminate_server = 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";
struct {
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;
/* SMTP: */
volatile ulong sessions_refused;
volatile ulong msgs_ignored;
volatile ulong msgs_refused;
volatile ulong msgs_received;
} stats;
struct mailproc {
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) */
} *mailproc_list;
SOCKET socket;
union xp_sockaddr client_addr;
socklen_t client_addr_len;
BOOL tls_port;
CRYPT_SESSION session;
} smtp_t, pop3_t;
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)
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) {
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)
if (startup == NULL || startup->lputs == NULL || str == NULL || level > startup->log_level)
return 0;
if (IsBadCodePtr((FARPROC)startup->lputs))
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;
va_end(argptr);
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);
}
#else /* No WINSOCK */
#define winsock_startup() (TRUE)
#define SOCKLIB_DESC NULL
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)
{
static int curr_state;
if (state == curr_state)
return;
if (startup != NULL) {
if (startup->set_state != NULL)
startup->set_state(startup->cbdata, state);
curr_state = 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(¤t_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(¤t_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);
return count;
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);
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);
int mail_close_socket(SOCKET *sock, int *sess)
if (*sess != -1) {
destroy_session(lprintf, *sess);
*sess = -1;
}
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);
stats.sockets--;
if (result != 0) {
if (SOCKET_ERRNO != ENOTSOCK)
lprintf(LOG_WARNING, "%04d !ERROR %d closing socket", *sock, SOCKET_ERRNO);
else
lprintf(LOG_DEBUG, "%04d Socket closed (%d sockets in use)", *sock, stats.sockets);
*sock = -1;
int sockprintf(SOCKET sock, const char* prot, CRYPT_SESSION sess, char *fmt, ...)
int len;
int result;
if (sock == INVALID_SOCKET) {
lprintf(LOG_WARNING, "%s !INVALID SOCKET in call to sockprintf", prot);
/* 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);
free(sbuf);
return 0;
}
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);
free(sbuf);
return 0;
}
sbuf = newp;
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");
free(sbuf);
if ((result = cryptFlushData(sess)) != CRYPT_OK) {
GCES(result, prot, sock, sess, "flushing data");
free(sbuf);
}
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) {
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);
free(sbuf);
lprintf(LOG_WARNING, "%04d %s !ERROR: short send on socket: %d instead of %d", sock, prot, result, len);
free(sbuf);
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);
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;
if (socket == INVALID_SOCKET) {
lprintf(LOG_WARNING, "%s !INVALID SOCKET in call to sockreadline", prot);
while (rd < len - 1) {

rswindell
committed
if (terminated || terminate_server) {
lprintf(LOG_WARNING, "%04d %s !ABORTING sockreadline", socket, prot);
i = sock_recvbyte(socket, prot, sess, &ch, start);

rswindell
committed
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)
int rd;
int ret = 0;
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);
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);
mopt[strlen(rsp)] = ' ';
if (strncmp(buf, mopt, moptlen) == 0)
if (startup->options & MAIL_OPT_DEBUG_RX_RSP)
lprintf(LOG_DEBUG, "%04d %s RX: %s", socket, prot, buf);
/* 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)
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)
sockprintf(socket, protocol, sess
, "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))
return (char*)src;
char tmp[256];
if (!(msg->hdr.auxattr & MSG_HFIELDS_UTF8))
cp437_to_utf8_str(src, tmp, sizeof tmp, /* min-char-val: */ '\x80');
else
SAFECOPY(tmp, src);
size_t len = 0;
char* state = NULL;
for (char* p = strtok_r(tmp, " \t", &state); p != NULL && len < (size - 1); p = strtok_r(NULL, " \t", &state)) {
if (len)
buf[len++] = ' ';
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 */
else {

Rob Swindell
committed
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))
} else {
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 */
else {
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);
else
s = sockprintf(socket, prot, sess, "To: \"%s\" <%s>", to, (char*)msg->to_net.addr);
} else if (msg->to_net.type == NET_FIDO) {

Rob Swindell
committed
char faddrbuf[64];
s = sockprintf(socket, prot, sess, "To: \"%s\" (%s)", to, smb_faddrtoa((fidoaddr_t*)msg->to_net.addr, faddrbuf));
} else {
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);
else
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 FIDOCTRL:
case FIDOAREA:
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]))
break;
}
}
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))
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 */
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')
len++;
tlen = len;
while (tlen && *(np + (tlen - 1)) <= ' ') /* Takes care of '\r' or spaces */
tlen--;
if (!sockprintf(socket, prot, sess, "%s%.*s", *np == '.' ? ".":"", tlen, np))
if (*(np + len) == '\r')
len++;
if (*(np + len) == '\n')
len++;
/* release time-slices every x lines */
if (startup->lines_per_yield
&& !(lines % startup->lines_per_yield))
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);
char* tp = strchr(p, delim);
if (tp == NULL) {
if (delim != ' ')
break;
} else
*tp = '\0';
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);
strListFree(&file_list);
free(boundary);