Synchronet now requires the libarchive development package (e.g. libarchive-dev on Debian-based Linux distros, libarchive.org for more info) to build successfully.

mailsrvr.c 209 KB
Newer Older
1 2 3 4 5 6
/* 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)		*
 *																			*
7
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
8 9 10 11 12 13 14 15 16 17 18 19 20 21
 *																			*
 * 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
22
/* ANSI C Library headers */
deuce's avatar
deuce committed
23
#include <limits.h>			/* UINT_MAX */
rswindell's avatar
rswindell committed
24
#include <stdio.h>
25 26 27 28 29
#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
30 31

/* Synchronet-specific headers */
32
#undef SBBS	/* this shouldn't be defined unless building sbbs.dll/libsbbs.so */
33
#include "sbbs.h"
rswindell's avatar
rswindell committed
34
#include "mailsrvr.h"
35
#include "utf8.h"
36
#include "mime.h"
37
#include "md5.h"
38
#include "crc32.h"
39
#include "base64.h"
40
#include "ini_file.h"
41
#include "netwrap.h"	/* getNameServerList() */
42
#include "xpendian.h"
43
#include "js_rtpool.h"
44
#include "js_request.h"
deuce's avatar
deuce committed
45
#include "multisock.h"
46 47
#include "ssl.h"
#include "cryptlib.h"
48
#include "ver.h"
49

rswindell's avatar
rswindell committed
50
/* Constants */
51
static const char*	server_name="Synchronet Mail Server";
52 53
#define FORWARD			"forward:"
#define NO_FORWARD		"local:"
54
#define NO_SPAM			"#nospam"
55

56 57
int dns_getmx(char* name, char* mx, char* mx2
			  ,DWORD intf, DWORD ip_addr, BOOL use_tcp, int timeout);
58

59 60
#define pop_error		"-ERR System Error: %s, try again later"
#define pop_auth_error	"-ERR Authentication failure"
61 62
#define ok_rsp			"250 OK"
#define auth_ok			"235 User Authenticated"
63
#define smtp_error		"421 System Error: %s, try again later"
64 65 66 67 68
#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 ...\""
69

70
#define TIMEOUT_THREAD_WAIT		60		/* Seconds */
71
#define DNSBL_THROTTLE_VALUE	1000	/* Milliseconds */
72
#define SMTP_MAX_BAD_CMDS		9
73 74 75 76 77

#define STATUS_WFC	"Listening"

static mail_startup_t* startup=NULL;
static scfg_t	scfg;
78
static char*	text[TOTAL_TEXT];
deuce's avatar
deuce committed
79 80
static struct xpms_set	*mail_set=NULL;
static BOOL terminated=FALSE;
81 82
static protected_uint32_t active_clients;
static protected_uint32_t thread_count;
83 84 85 86
static volatile int		active_sendmail=0;
static volatile BOOL	sendmail_running=FALSE;
static volatile BOOL	terminate_server=FALSE;
static volatile BOOL	terminate_sendmail=FALSE;
87
static sem_t	sendmail_wakeup_sem;
88
static volatile time_t	uptime;
89 90
static str_list_t recycle_semfiles;
static str_list_t shutdown_semfiles;
91
static int		mailproc_count;
92
static js_server_props_t js_server_props;
93
static link_list_t current_logins;
94
static link_list_t current_connections;
95
static bool savemsg_mutex_created = false;
96
static pthread_mutex_t savemsg_mutex;
97 98 99 100 101 102

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";
103

104
struct {
105 106 107
	volatile ulong	sockets;
	volatile ulong	errors;
	volatile ulong	crit_errors;
108
	volatile ulong	connections_exceeded;
109 110 111 112 113
	volatile ulong	connections_ignored;
	volatile ulong	connections_refused;
	volatile ulong	connections_served;
	volatile ulong	pop3_served;
	volatile ulong	smtp_served;
114
	/* SMTP: */
115 116 117 118
	volatile ulong	sessions_refused;
	volatile ulong	msgs_ignored;
	volatile ulong	msgs_refused;
	volatile ulong	msgs_received;
119 120
} stats;

121
struct mailproc {
rswindell's avatar
rswindell committed
122
	char		name[INI_MAX_VALUE_LEN];
123
	char		cmdline[INI_MAX_VALUE_LEN];
rswindell's avatar
rswindell committed
124
	char		eval[INI_MAX_VALUE_LEN];
125
	str_list_t	to;
126
	str_list_t	from;
127 128
	BOOL		passthru;
	BOOL		native;
129
	BOOL		ignore_on_error;	/* Ignore mail message if cmdline fails */
130
	BOOL		disabled;
131 132
	BOOL		process_spam;
	BOOL		process_dnsbl;
133
	uint8_t*	ar;
134
	ulong		handled;			/* counter (for stats display) */
135
} *mailproc_list;
136 137 138

typedef struct {
	SOCKET			socket;
deuce's avatar
deuce committed
139 140
	union xp_sockaddr	client_addr;
	socklen_t		client_addr_len;
141
	BOOL			tls_port;
142 143
} smtp_t,pop3_t;

deuce's avatar
deuce committed
144
#define GCES(status, server, sock, sess, action) do {                             \
deuce's avatar
deuce committed
145 146
	char *GCES_estr;                                                               \
	int GCES_level;                                                                 \
deuce's avatar
deuce committed
147
	get_crypt_error_string(status, sess, &GCES_estr, action, &GCES_level);  \
deuce's avatar
deuce committed
148
	if (GCES_estr) {                                                                  \
149
		lprintf(GCES_level, "%04d %s %s", sock, server, GCES_estr);                     \
deuce's avatar
deuce committed
150
		free_crypt_attrstr(GCES_estr);                                                  \
151 152 153
	}                                                                                    \
} while(0)

deuce's avatar
deuce committed
154
#define GCESH(status, server, sock, host, sess, action) do {                      \
deuce's avatar
deuce committed
155 156
	char *GCES_estr;                                                               \
	int GCES_level;                                                                 \
deuce's avatar
deuce committed
157
	get_crypt_error_string(status, sess, &GCES_estr, action, &GCES_level);  \
deuce's avatar
deuce committed
158
	if (GCES_estr) {                                                                  \
159 160 161 162 163 164 165 166 167 168 169
		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, "%04d %s [%s] %s", sock, server, host, GCES_estr);         \
deuce's avatar
deuce committed
170
		free_crypt_attrstr(GCES_estr);                                                  \
171 172 173
	}                                                                                    \
} while(0)

174 175 176
#if defined(__GNUC__)   // Catch printf-format errors with lprintf
static int lprintf(int level, const char *fmt, ...) __attribute__ ((format (printf, 2, 3)));
#endif
177
static int lprintf(int level, const char *fmt, ...)
178 179 180 181 182
{
	va_list argptr;
	char sbuf[1024];

	va_start(argptr,fmt);
183 184
    vsnprintf(sbuf,sizeof(sbuf),fmt,argptr);
	sbuf[sizeof(sbuf)-1]=0;
185
    va_end(argptr);
186

187
	if(level <= LOG_ERR) {
188 189
		char errmsg[sizeof(sbuf)+16];
		SAFEPRINTF(errmsg, "mail %s", sbuf);
190
		errorlog(&scfg, level, startup==NULL ? NULL:startup->host_name,errmsg), stats.errors++;
191
		if(startup!=NULL && startup->errormsg!=NULL)
192
			startup->errormsg(startup->cbdata,level,errmsg);
193
	}
194 195 196

	if(level <= LOG_CRIT)
		stats.crit_errors++;
197 198 199 200 201 202 203 204 205

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

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

206
    return(startup->lputs(startup->cbdata, level, condense_whitespace(sbuf)));
207 208 209 210 211
}

#ifdef _WINSOCKAPI_

static WSADATA WSAData;
212
#define SOCKLIB_DESC WSAData.szDescription
213
static BOOL WSAInitialized=FALSE;
214 215 216 217 218 219

static BOOL winsock_startup(void)
{
	int		status;             /* Status Code */

    if((status = WSAStartup(MAKEWORD(1,1), &WSAData))==0) {
220
		lprintf(LOG_DEBUG,"%s %s",WSAData.szDescription, WSAData.szSystemStatus);
221
		WSAInitialized=TRUE;
222 223 224
		return (TRUE);
	}

225
    lprintf(LOG_CRIT,"!WinSock startup ERROR %d", status);
226 227 228 229 230
	return (FALSE);
}

#else /* No WINSOCK */

rswindell's avatar
rswindell committed
231
#define winsock_startup()	(TRUE)
232
#define SOCKLIB_DESC NULL
233 234 235

#endif

236 237 238 239 240
static char* server_host_name(void)
{
	return startup->host_name[0] ? startup->host_name : scfg.sys_inetaddr;
}

241 242
static void update_clients(void)
{
243
	if(startup!=NULL && startup->clients!=NULL)
244
		startup->clients(startup->cbdata,protected_uint32_value(active_clients)+active_sendmail);
245 246
}

247
static void client_on(SOCKET sock, client_t* client, BOOL update)
248
{
249 250
	if(!update)
		listAddNodeData(&current_connections, client->addr, strlen(client->addr)+1, sock, LAST_NODE);
251
	if(startup!=NULL && startup->client_on!=NULL)
252
		startup->client_on(startup->cbdata,TRUE,sock,client,update);
253 254 255 256
}

static void client_off(SOCKET sock)
{
257
	listRemoveTaggedNode(&current_connections, sock, /* free_data */TRUE);
258
	if(startup!=NULL && startup->client_on!=NULL)
259
		startup->client_on(startup->cbdata,FALSE,sock,NULL,FALSE);
260 261
}

262
static void thread_up(BOOL setuid)
263 264
{
	if(startup!=NULL && startup->thread_up!=NULL)
265
		startup->thread_up(startup->cbdata,TRUE,setuid);
266 267
}

268
static int32_t thread_down(void)
269
{
270
	int32_t count = protected_uint32_adjust_fetch(&thread_count,-1);
271
	if(startup!=NULL && startup->thread_up!=NULL)
272
		startup->thread_up(startup->cbdata,FALSE,FALSE);
273
	return count;
274 275
}

deuce's avatar
deuce committed
276
void mail_open_socket(SOCKET sock, void* cb_protocol)
277
{
deuce's avatar
deuce committed
278
	char	*protocol=(char *)cb_protocol;
279
	char	error[256];
280
	char	section[128];
281

deuce's avatar
deuce committed
282
	if(startup!=NULL && startup->socket_open!=NULL)
283
		startup->socket_open(startup->cbdata,TRUE);
deuce's avatar
deuce committed
284 285
	SAFEPRINTF(section,"mail|%s",protocol);
	if(set_socket_options(&scfg, sock, section, error, sizeof(error)))
286
		lprintf(LOG_ERR,"%04d %s !ERROR %s", sock, protocol, error);
287

deuce's avatar
deuce committed
288 289 290 291 292 293 294 295
	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);
	stats.sockets--;
296 297
}

298
int mail_close_socket(SOCKET *sock, int *sess)
299 300 301
{
	int		result;

302 303 304 305 306
	if (*sess != -1) {
		cryptDestroySession(*sess);
		*sess = -1;
	}
	if(*sock==INVALID_SOCKET)
307 308
		return(-1);

309 310
	shutdown(*sock,SHUT_RDWR);	/* required on Unix */
	result=closesocket(*sock);
311
	if(startup!=NULL && startup->socket_open!=NULL)
312
		startup->socket_open(startup->cbdata,FALSE);
313
	stats.sockets--;
314 315
	if(result!=0) {
		if(ERROR_VALUE!=ENOTSOCK)
316
			lprintf(LOG_WARNING,"%04d !ERROR %d closing socket",*sock, ERROR_VALUE);
317
	}
318
#if 0 /*def _DEBUG */
319
	else 
320
		lprintf(LOG_DEBUG,"%04d Socket closed (%d sockets in use)",*sock,stats.sockets);
321 322
#endif

323 324
	*sock = -1;

325 326 327 328 329
	return(result);
}

static void status(char* str)
{
330
	if(startup!=NULL && startup->status!=NULL)
331
	    startup->status(startup->cbdata,str);
332 333
}

334
int sockprintf(SOCKET sock, const char* prot, CRYPT_SESSION sess, char *fmt, ...)
335 336
{
	int		len;
337
	int		maxlen;
338 339 340
	int		result;
	va_list argptr;
	char	sbuf[1024];
341 342
	fd_set	socket_set;
	struct timeval tv;
343 344

    va_start(argptr,fmt);
345 346 347
    len=vsnprintf(sbuf,maxlen=sizeof(sbuf)-2,fmt,argptr);
    va_end(argptr);

348
	if(len<0 || len > maxlen) /* format error or output truncated */
349
		len=maxlen;
350
	if(startup->options&MAIL_OPT_DEBUG_TX)
351
		lprintf(LOG_DEBUG,"%04d %s TX: %.*s", sock, prot, len, sbuf);
352
	memcpy(sbuf+len,"\r\n",2);
353
	len+=2;
354

355
	if(sock==INVALID_SOCKET) {
356
		lprintf(LOG_WARNING,"%s !INVALID SOCKET in call to sockprintf", prot);
357 358 359
		return(0);
	}

360
	/* Check socket for writability (using select) */
361
	tv.tv_sec=300;
362 363 364 365 366 367
	tv.tv_usec=0;

	FD_ZERO(&socket_set);
	FD_SET(sock,&socket_set);

	if((result=select(sock+1,NULL,&socket_set,NULL,&tv))<1) {
368
		if(result==0)
369 370
			lprintf(LOG_NOTICE,"%04d %s !TIMEOUT selecting socket for send"
				,sock, prot);
371
		else
372 373
			lprintf(LOG_NOTICE,"%04d %s !ERROR %d selecting socket for send"
				,sock, prot, ERROR_VALUE);
374 375 376
		return(0);
	}

377 378 379 380 381 382 383
	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;
384
			else {
385
				GCES(result, prot, sock, sess, "pushing data");
386
				return 0;
387
			}
388
		}
389
		if ((result = cryptFlushData(sess)) != CRYPT_OK) {
390
			GCES(result, prot, sock, sess, "flushing data");
391
			return 0;
392
		}
393 394 395 396 397 398 399 400 401 402
	}
	else {
		// It looks like this could stutter on partial sends -- Deuce
		while((result=sendsocket(sock,sbuf,len))!=len) {
			if(result==SOCKET_ERROR) {
				if(ERROR_VALUE==EWOULDBLOCK) {
					YIELD();
					continue;
				}
				if(ERROR_VALUE==ECONNRESET) 
403
					lprintf(LOG_NOTICE,"%04d %s Connection reset by peer on send",sock,prot);
404
				else if(ERROR_VALUE==ECONNABORTED) 
405
					lprintf(LOG_NOTICE,"%04d %s Connection aborted by peer on send",sock, prot);
406
				else
407
					lprintf(LOG_NOTICE,"%04d %s !ERROR %d sending on socket",sock,prot,ERROR_VALUE);
408 409
				return(0);
			}
410
			lprintf(LOG_WARNING,"%04d %s !ERROR: short send on socket: %d instead of %d",sock,prot,result,len);
411 412 413 414 415
		}
	}
	return(len);
}

416
static void sockerror(SOCKET socket, const char* prot, int rd, const char* action)
417 418
{
	if(rd==0) 
419 420
		lprintf(LOG_NOTICE,"%04d %s Socket closed by peer on %s"
			,socket, prot, action);
421
	else if(rd==SOCKET_ERROR) {
rswindell's avatar
rswindell committed
422
		if(ERROR_VALUE==ECONNRESET) 
423 424
			lprintf(LOG_NOTICE,"%04d %s Connection reset by peer on %s"
				,socket, prot, action);
425
		else if(ERROR_VALUE==ECONNABORTED) 
426 427
			lprintf(LOG_NOTICE,"%04d %s Connection aborted by peer on %s"
				,socket, prot, action);
428
		else
429 430
			lprintf(LOG_NOTICE,"%04d %s !SOCKET ERROR %d on %s"
				,socket, prot, ERROR_VALUE, action);
431
	} else
432 433
		lprintf(LOG_WARNING,"%04d %s !SOCKET ERROR: unexpected return value %d from %s"
			,socket, prot, rd, action);
434 435
}

436
static int sock_recvbyte(SOCKET sock, const char* prot, CRYPT_SESSION sess, char *buf, time_t start)
437 438 439 440 441 442 443 444 445 446
{
	int len=0;
	fd_set	socket_set;
	struct	timeval	tv;
	int ret;
	int i;

	if (sess > -1) {
		while (1) {
			ret = cryptPopData(sess, buf, 1, &len);
447
			GCES(ret, prot, sock, sess, "popping data");
448 449 450 451 452 453 454 455 456 457 458 459 460 461 462
			switch(ret) {
				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) {
463 464
				lprintf(LOG_WARNING,"%04d %s !TIMEOUT in sock_recvbyte (%u seconds):  INACTIVE SOCKET"
					,sock, prot, startup->max_inactivity);
465 466 467 468 469 470 471 472 473 474 475 476
				return(-1);
			}
		}
	}
	else {
		tv.tv_sec=startup->max_inactivity;
		tv.tv_usec=0;

		FD_ZERO(&socket_set);
		FD_SET(sock,&socket_set);

		i=select(sock+1,&socket_set,NULL,NULL,&tv);
477

478 479 480
		if(i<1) {
			if(i==0) {
				if(startup->max_inactivity && (time(NULL)-start)>startup->max_inactivity) {
481 482
					lprintf(LOG_WARNING,"%04d %s !TIMEOUT in sock_recvbyte (%u seconds):  INACTIVE SOCKET"
						,sock, prot, startup->max_inactivity);
483 484 485 486
					return(-1);
				}
				return 0;
			}
487
			sockerror(sock,prot,i,"select");
488 489 490 491
			return 0;
		}
		i=recv(sock, buf, 1, 0);
		if(i<1)
492
			sockerror(sock,prot,i,"receive");
493 494 495 496
		return i;
	}
}

497
static int sockreadline(SOCKET socket, const char* prot, CRYPT_SESSION sess, char* buf, int len)
498 499 500 501 502
{
	char	ch;
	int		i,rd=0;
	time_t	start;

503 504
	buf[0]=0;

505
	start=time(NULL);
506 507

	if(socket==INVALID_SOCKET) {
508
		lprintf(LOG_WARNING,"%s !INVALID SOCKET in call to sockreadline", prot);
509
		return(-1);
510
	}
511 512
	
	while(rd<len-1) {
513

deuce's avatar
deuce committed
514
		if(terminated || terminate_server) {
515
			lprintf(LOG_WARNING,"%04d %s !ABORTING sockreadline",socket, prot);
516 517 518
			return(-1);
		}

519
		i = sock_recvbyte(socket, prot, sess, &ch, start);
520

521
		if(i<1)
522
			return(-1);
523

524
		if(ch=='\n' /* && rd>=1 */ ) { /* Mar-9-2003: terminate on sole LF */
525
			break;
526
		}
527 528
		buf[rd++]=ch;
	}
529
	if(rd>0 && buf[rd-1]=='\r')
530 531
		rd--;
	buf[rd]=0;
532 533 534 535
	
	return(rd);
}

536
static BOOL sockgetrsp(SOCKET socket, const char* prot, CRYPT_SESSION sess, char* rsp, char *buf, int len)
537 538 539 540
{
	int rd;

	while(1) {
541
		rd = sockreadline(socket, prot, sess, buf, len);
542
		if(rd<1) {
543
			if(rd==0 && rsp != NULL)
544
				lprintf(LOG_WARNING,"%04d %s !RECEIVED BLANK RESPONSE, Expected '%s'", socket, prot, rsp);
545
			return(FALSE);
546
		}
547 548
		if(buf[3]=='-')	{ /* Multi-line response */
			if(startup->options&MAIL_OPT_DEBUG_RX_RSP) 
549
				lprintf(LOG_DEBUG,"%04d %s RX: %s",socket, prot, buf);
550 551
			continue;
		}
552
		if(rsp!=NULL && strnicmp(buf,rsp,strlen(rsp))) {
553
			lprintf(LOG_WARNING,"%04d %s !INVALID RESPONSE: '%s' Expected: '%s'", socket, prot, buf, rsp);
554 555 556 557 558
			return(FALSE);
		}
		break;
	}
	if(startup->options&MAIL_OPT_DEBUG_RX_RSP) 
559
		lprintf(LOG_DEBUG,"%04d %s RX: %s",socket, prot, buf);
560 561 562
	return(TRUE);
}

563
static int sockgetrsp_opt(SOCKET socket, const char* prot, CRYPT_SESSION sess, char* rsp, char *opt, char *buf, int len)
564 565 566 567 568 569
{
	int rd;
	int ret = 0;
	size_t moptlen;
	char *mopt;

570
	moptlen = strlen(rsp)+strlen(opt) + 1;
571 572 573 574 575
	mopt = malloc(moptlen+1);
	if (mopt == NULL)
		return -1;
	sprintf(mopt, "%s-%s", rsp, opt);
	while(1) {
576
		rd = sockreadline(socket, prot, sess, buf, len);
577 578
		if(rd<1) {
			if(rd==0)
579
				lprintf(LOG_WARNING,"%04d %s !RECEIVED BLANK RESPONSE, Expected '%s'", socket, prot, rsp);
580 581 582 583 584 585 586
			free(mopt);
			return(-1);
		}
		if(buf[3]=='-')	{ /* Multi-line response */
			if (strncmp(buf, mopt, moptlen) == 0)
				ret = 1;
			if(startup->options&MAIL_OPT_DEBUG_RX_RSP) 
587
				lprintf(LOG_DEBUG,"%04d %s RX: %s",socket, prot, buf);
588 589 590
			continue;
		}
		if(rsp!=NULL && strnicmp(buf,rsp,strlen(rsp))) {
591
			lprintf(LOG_WARNING,"%04d %s !INVALID RESPONSE: '%s' Expected: '%s'", socket, prot, buf, rsp);
592 593 594 595 596
			free(mopt);
			return(-1);
		}
		break;
	}
597
	mopt[strlen(rsp)] = ' ';
598
	if (strncmp(buf, mopt, moptlen) == 0)
599
		ret = 1;
600 601
	free(mopt);
	if(startup->options&MAIL_OPT_DEBUG_RX_RSP)
602
		lprintf(LOG_DEBUG,"%04d %s RX: %s",socket, prot, buf);
603 604 605
	return(ret);
}

rswindell's avatar
rswindell committed
606
/* non-standard, but documented (mostly) in draft-newman-msgheader-originfo-05 */
607
void originator_info(SOCKET socket, const char* protocol, CRYPT_SESSION sess, smbmsg_t* msg)
rswindell's avatar
rswindell committed
608 609 610 611 612 613 614 615 616 617 618
{
	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)
619
		sockprintf(socket, protocol, sess
rswindell's avatar
rswindell committed
620 621 622 623 624 625 626 627 628 629 630 631
			,"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
			);
}

632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648
char* angle_bracket(char* buf, size_t max, const char* addr)
{
	if(*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));
}

649 650 651 652 653 654 655
/* RFC822: The maximum total length of a text line including the
   <CRLF> is 1000 characters (but not counting the leading
   dot duplicated for transparency). 

   POP3 (RFC1939) actually calls for a 512 byte line length limit!
*/
#define MAX_LINE_LEN	998		
656

657
static ulong sockmimetext(SOCKET socket, const char* prot, CRYPT_SESSION sess, smbmsg_t* msg, char* msgtxt, ulong maxlines
658
						  ,str_list_t file_list, char* mime_boundary)
659
{
660 661
	char		toaddr[256]="";
	char		fromaddr[256]="";
662
	char		fromhost[256];
rswindell's avatar
rswindell committed
663
	char		msgid[256];
664
	char		tmp[256];
665
	char		date[64];
rswindell's avatar
rswindell committed
666
	char*		p;
667
	char*		np;
668
	int			i;
669
	int			s;
670
	ulong		lines;
671
	int			len,tlen;
672

673
	/* HEADERS (in recommended order per RFC822 4.1) */
674 675

	if(msg->reverse_path!=NULL)
676
		if(!sockprintf(socket,prot,sess,"Return-Path: %s", msg->reverse_path))
677 678
			return(0);

679 680
	for(i=0;i<msg->total_hfields;i++)
		if(msg->hfield[i].type == SMTPRECEIVED && msg->hfield_dat[i]!=NULL) 
681
			if(!sockprintf(socket,prot,sess,"Received: %s", (char*)msg->hfield_dat[i]))
682 683
				return(0);

684
	if(!sockprintf(socket,prot,sess,"Date: %s",msgdate(msg->hdr.when_written,date)))
685
		return(0);
686 687

	if((p=smb_get_hfield(msg,RFC822FROM,NULL))!=NULL)
688
		s=sockprintf(socket,prot,sess,"From: %s",p);	/* use original RFC822 header field */
689
	else {
rswindell's avatar
rswindell committed
690
		char fromname[256];
rswindell's avatar
rswindell committed
691
		SAFEPRINTF(fromname, "\"%s\"", msg->from);
692
		if(msg->from_net.type==NET_QWK && msg->from_net.addr!=NULL)
693
			SAFEPRINTF2(fromaddr,"%s!%s"
694 695
				,(char*)msg->from_net.addr
				,usermailaddr(&scfg,fromhost,msg->from));
rswindell's avatar
rswindell committed
696 697 698
		else if(msg->from_net.type==NET_FIDO && msg->from_net.addr!=NULL) {
			faddr_t* faddr = (faddr_t *)msg->from_net.addr;
			char faddrstr[128];
rswindell's avatar
rswindell committed
699
			SAFEPRINTF2(fromname,"\"%s\" (%s)", msg->from, smb_faddrtoa(faddr, NULL));
rswindell's avatar
rswindell committed
700 701 702 703 704 705 706 707
			if(faddr->point)
				SAFEPRINTF4(faddrstr,"p%hu.f%hu.n%hu.z%hu"FIDO_TLD
					,faddr->point, faddr->node, faddr->net, faddr->zone);
			else
				SAFEPRINTF3(faddrstr,"f%hu.n%hu.z%hu"FIDO_TLD
					,faddr->node, faddr->net, faddr->zone);
			SAFEPRINTF2(fromaddr,"%s@%s", usermailaddr(NULL,fromhost,msg->from), faddrstr);
		} else if(msg->from_net.type!=NET_NONE && msg->from_net.addr!=NULL)
708
			SAFECOPY(fromaddr,(char*)msg->from_net.addr);
709 710
		else 
			usermailaddr(&scfg,fromaddr,msg->from);
711
		s = sockprintf(socket,prot,sess,"From: %s %s", fromname, angle_bracket(tmp, sizeof(tmp), fromaddr));
712
	}
713 714
	if(!s)
		return(0);
715

716 717
	if((p = smb_get_hfield(msg, RFC822ORG, NULL)) != NULL) {
		if(!sockprintf(socket,prot,sess,"Organization: %s", p))
718
			return(0);
719 720 721 722 723 724
	} else {
		if(msg->from_org!=NULL || msg->from_net.type==NET_NONE)
			if(!sockprintf(socket,prot,sess,"Organization: %s"
				,msg->from_org==NULL ? scfg.sys_name : msg->from_org))
				return(0);
	}
725

726 727
	p = smb_get_hfield(msg, RFC822SUBJECT, NULL);
	if(!sockprintf(socket,prot,sess,"Subject: %s", p == NULL ? msg->subj : p))
728
		return(0);
729

730
	if((p = smb_get_hfield(msg, RFC822TO, NULL)) != NULL)
731 732
		s=sockprintf(socket,prot,sess,"To: %s", p);	/* use original RFC822 header field (MIME-Encoded) */
	else if((p = msg->to_list) != NULL)
733
		s=sockprintf(socket,prot,sess,"To: %s", p);	/* use original RFC822 header field */
734 735
	else {
		if(strchr(msg->to,'@')!=NULL || msg->to_net.addr==NULL)
736
			s=sockprintf(socket,prot,sess,"To: %s",msg->to);	/* Avoid double-@ */
737 738
		else if(msg->to_net.type==NET_INTERNET || msg->to_net.type==NET_QWK) {
			if(strchr((char*)msg->to_net.addr,'<')!=NULL)
739
				s=sockprintf(socket,prot,sess,"To: %s",(char*)msg->to_net.addr);
740
			else
741
				s=sockprintf(socket,prot,sess,"To: \"%s\" <%s>",msg->to,(char*)msg->to_net.addr);
rswindell's avatar
rswindell committed
742
		} else if(msg->to_net.type==NET_FIDO) {
743
			s=sockprintf(socket,prot,sess,"To: \"%s\" (%s)",msg->to, smb_faddrtoa((fidoaddr_t*)msg->to_net.addr, NULL));
744 745
		} else {
			usermailaddr(&scfg,toaddr,msg->to);
746
			s=sockprintf(socket,prot,sess,"To: \"%s\" <%s>",msg->to,toaddr);
747
		}
748
	}
749 750
	if(!s)
		return(0);
751 752
	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))
753
			return(0);
754
	np=NULL;
755 756
	p = smb_get_hfield(msg, RFC822REPLYTO, NULL);
	if(p == NULL && (p = msg->replyto_list) == NULL) {
757
		np=msg->replyto;
758
		if(msg->replyto_net.type==NET_INTERNET)
rswindell's avatar
rswindell committed
759
			p=msg->replyto_net.addr;
760
	}
rswindell's avatar
rswindell committed
761
	if(p!=NULL && strchr((char*)p, '@') != NULL) {
762
		if(np!=NULL)
763
			s=sockprintf(socket,prot,sess,"Reply-To: \"%s\" <%s>",np,p);
764
		else 
765
			s=sockprintf(socket,prot,sess,"Reply-To: %s",p);
766
	}
767 768
	if(!s)
		return(0);
769
	if(!sockprintf(socket,prot,sess,"Message-ID: %s",get_msgid(&scfg,INVALID_SUB,msg,msgid,sizeof(msgid))))
770
		return(0);
771
	if(msg->reply_id!=NULL)
772
		if(!sockprintf(socket,prot,sess,"In-Reply-To: %s",msg->reply_id))
773
			return(0);
774

775 776 777 778
	if(msg->hdr.priority != SMB_PRIORITY_UNSPECIFIED)
		if(!sockprintf(socket,prot,sess,"X-Priority: %u", (uint)msg->hdr.priority))
			return(0);

779
	originator_info(socket, prot, sess, msg);
780

781 782 783 784 785 786 787 788 789 790 791 792
	/* 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:
793
				if(!sockprintf(socket, prot, sess, "%s: %s", smb_hfieldtype(msg->hfield[i].type), (char*)msg->hfield_dat[i]))
794 795 796 797 798
					return(0);
				break;
		}
	}
	for(i=0;i<msg->total_hfields;i++) { 
799
		if(msg->hfield[i].type==RFC822HEADER) { 
800
			if(!sockprintf(socket,prot, sess,"%s",(char*)msg->hfield_dat[i]))
801
				return(0);
802
        }
803
    }
804 805 806 807 808 809 810 811 812 813
	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";
	}

814
	/* Default MIME Content-Type for non-Internet messages */
815 816 817
	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");
818 819
	}

820
	if(strListCount(file_list)) {	/* File attachments */
821 822 823 824
        mimeheaders(socket,prot,sess,mime_boundary);
        sockprintf(socket,prot,sess,"");
        mimeblurb(socket,prot,sess,mime_boundary);
        sockprintf(socket,prot,sess,"");
825
        mimetextpartheader(socket,prot,sess,mime_boundary, msg->text_subtype, charset);
826
	}
827
	if(!sockprintf(socket,prot,sess,""))	/* Header Terminator */
828
		return(0);
829

830
	/* MESSAGE BODY */
831
	lines=0;
832 833
	np=msgtxt;
	while(*np && lines<maxlines) {
834
		len=0;
835
		while(len<MAX_LINE_LEN && *(np+len)!=0 && *(np+len)!='\n')
836 837 838
			len++;

		tlen=len;
839
		while(tlen && *(np+(tlen-1))<=' ') /* Takes care of '\r' or spaces */
840 841
			tlen--;

842
		if(!sockprintf(socket,prot,sess, "%s%.*s", *np=='.' ? ".":"", tlen, np))
843
			break;
rswindell's avatar
rswindell committed
844
		lines++;
845
		if(*(np+len)=='\r')
846
			len++;
847
		if(*(np+len)=='\n')
848
			len++;
849
		np+=len;
850 851 852
		/* release time-slices every x lines */
		if(startup->lines_per_yield
			&& !(lines%startup->lines_per_yield))	
853
			YIELD();
854
		if((lines%100) == 0)
rswindell's avatar
rswindell committed
855 856
			lprintf(LOG_DEBUG,"%04d %s sent %lu lines (%ld bytes) of body text"
				,socket, prot, lines, (long)(np-msgtxt));
857
	}
rswindell's avatar
rswindell committed
858 859
	lprintf(LOG_DEBUG,"%04d %s sent %lu lines (%ld bytes) of body text"
		,socket, prot, lines, (long)(np-msgtxt));
860 861
	if(file_list!=NULL) {
		for(i=0;file_list[i];i++) { 
862 863 864 865
			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]))
				lprintf(LOG_ERR,"%04u %s !ERROR opening/encoding/sending %s", socket, prot, file_list[i]);
866 867 868
			else {
				if(msg->hdr.auxattr&MSG_KILLFILE)
					if(remove(file_list[i])!=0)
rswindell's avatar
rswindell committed
869
						lprintf(LOG_WARNING,"%04u %s !ERROR %d (%s) removing %s", socket, prot, errno, strerror(errno), file_list[i]);
870
			}
871
			endmime(socket,prot,sess,mime_boundary);
872
		}
873
	}