mailsrvr.c 143 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
/* mailsrvr.c */

/* Synchronet Mail (SMTP/POP3) server and sendmail threads */

/* $Id$ */

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
11
 * Copyright 2009 Rob Swindell - http://www.synchro.net/copyright.html		*
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
 *																			*
 * 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										*
 *																			*
 * Anonymous FTP access to the most recent released source is available at	*
 * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net	*
 *																			*
 * Anonymous CVS access to the development source and modification history	*
 * is available at cvs.synchro.net:/cvsroot/sbbs, example:					*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login			*
 *     (just hit return, no password is necessary)							*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src		*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * You are encouraged to submit any modifications (preferably in Unix diff	*
 * format) via e-mail to mods@synchro.net									*
 *																			*
 * Note: If this box doesn't appear square, then you need to fix your tabs.	*
 ****************************************************************************/

rswindell's avatar
rswindell committed
38
/* ANSI C Library headers */
deuce's avatar
deuce committed
39
#include <limits.h>			/* UINT_MAX */
rswindell's avatar
rswindell committed
40
#include <stdio.h>
41
42
43
44
45
46
#include <stdlib.h>			/* ltoa in GNU C lib */
#include <stdarg.h>			/* va_list */
#include <string.h>			/* strrchr */
#include <ctype.h>			/* isdigit */
#include <fcntl.h>			/* Open flags */
#include <errno.h>			/* errno */
rswindell's avatar
rswindell committed
47
48

/* Synchronet-specific headers */
49
#undef SBBS	/* this shouldn't be defined unless building sbbs.dll/libsbbs.so */
50
#include "sbbs.h"
rswindell's avatar
rswindell committed
51
#include "mailsrvr.h"
52
#include "mime.h"
53
#include "md5.h"
54
#include "crc32.h"
55
#include "base64.h"
56
#include "ini_file.h"
57
#include "netwrap.h"	/* getNameServerList() */
58
#include "xpendian.h"
59
#include "js_rtpool.h"
60
#include "js_request.h"
61

rswindell's avatar
rswindell committed
62
/* Constants */
63
static const char*	server_name="Synchronet Mail Server";
64
65
#define FORWARD			"forward:"
#define NO_FORWARD		"local:"
66

67
68
int dns_getmx(char* name, char* mx, char* mx2
			  ,DWORD intf, DWORD ip_addr, BOOL use_tcp, int timeout);
69

70
static char* pop_err	=	"-ERR";
71
static char* ok_rsp		=	"250 OK";
72
static char* auth_ok	=	"235 User Authenticated";
73
static char* sys_error	=	"421 System error";
74
static char* sys_unavail=	"421 System unavailable, try again later";
75
static char* insuf_stor =	"452 Insufficient system storage";
76
static char* badarg_rsp =	"501 Bad argument";
77
static char* badseq_rsp	=	"503 Bad sequence of commands";
78
static char* badauth_rsp=	"535 Authentication failure";
79
80
static char* badrsp_err	=	"%s replied with:\r\n\"%s\"\r\n"
							"instead of the expected reply:\r\n\"%s ...\"";
81

82
#define TIMEOUT_THREAD_WAIT		60		/* Seconds */
83
#define DNSBL_THROTTLE_VALUE	5000	/* Milliseconds */
84
85
86
87
88
89

#define STATUS_WFC	"Listening"

static mail_startup_t* startup=NULL;
static scfg_t	scfg;
static SOCKET	server_socket=INVALID_SOCKET;
90
static SOCKET	submission_socket=INVALID_SOCKET;
91
static SOCKET	pop3_socket=INVALID_SOCKET;
92
static DWORD	active_clients=0;
93
static int		active_sendmail=0;
94
static DWORD	thread_count=0;
95
96
static BOOL		sendmail_running=FALSE;
static DWORD	sockets=0;
97
static DWORD	served=0;
98
99
static BOOL		terminate_server=FALSE;
static BOOL		terminate_sendmail=FALSE;
100
static sem_t	sendmail_wakeup_sem;
101
static char		revision[16];
102
static time_t	uptime;
103
104
static str_list_t recycle_semfiles;
static str_list_t shutdown_semfiles;
105
static int		mailproc_count;
106
static js_server_props_t js_server_props;
107
108

struct mailproc {
rswindell's avatar
rswindell committed
109
	char		name[INI_MAX_VALUE_LEN];
110
	char		cmdline[INI_MAX_VALUE_LEN];
rswindell's avatar
rswindell committed
111
	char		eval[INI_MAX_VALUE_LEN];
112
	str_list_t	to;
113
	str_list_t	from;
114
115
	BOOL		passthru;
	BOOL		native;
116
	BOOL		ignore_on_error;	/* Ignore mail message if cmdline fails */
117
	BOOL		disabled;
118
	uint8_t*	ar;
119
} *mailproc_list;
120
121
122
123
124
125

typedef struct {
	SOCKET			socket;
	SOCKADDR_IN		client_addr;
} smtp_t,pop3_t;

126
static int lprintf(int level, const char *fmt, ...)
127
128
129
130
{
	va_list argptr;
	char sbuf[1024];

131
    if(startup==NULL || startup->lputs==NULL || level > startup->log_level)
132
133
134
        return(0);

	va_start(argptr,fmt);
135
136
    vsnprintf(sbuf,sizeof(sbuf),fmt,argptr);
	sbuf[sizeof(sbuf)-1]=0;
137
    va_end(argptr);
138
    return(startup->lputs(startup->cbdata,level,sbuf));
139
140
141
142
143
}

#ifdef _WINSOCKAPI_

static WSADATA WSAData;
144
#define SOCKLIB_DESC WSAData.szDescription
145
static BOOL WSAInitialized=FALSE;
146
147
148
149
150
151

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

    if((status = WSAStartup(MAKEWORD(1,1), &WSAData))==0) {
152
		lprintf(LOG_INFO,"%s %s",WSAData.szDescription, WSAData.szSystemStatus);
153
		WSAInitialized=TRUE;
154
155
156
		return (TRUE);
	}

157
    lprintf(LOG_CRIT,"!WinSock startup ERROR %d", status);
158
159
160
161
162
	return (FALSE);
}

#else /* No WINSOCK */

rswindell's avatar
rswindell committed
163
#define winsock_startup()	(TRUE)
164
#define SOCKLIB_DESC NULL
165
166
167
168
169

#endif

static void update_clients(void)
{
170
	if(startup!=NULL && startup->clients!=NULL)
171
		startup->clients(startup->cbdata,active_clients+active_sendmail);
172
173
}

174
static void client_on(SOCKET sock, client_t* client, BOOL update)
175
{
176
	if(startup!=NULL && startup->client_on!=NULL)
177
		startup->client_on(startup->cbdata,TRUE,sock,client,update);
178
179
180
181
}

static void client_off(SOCKET sock)
{
182
	if(startup!=NULL && startup->client_on!=NULL)
183
		startup->client_on(startup->cbdata,FALSE,sock,NULL,FALSE);
184
185
}

186
static void thread_up(BOOL setuid)
187
{
188
	thread_count++;
189
	if(startup!=NULL && startup->thread_up!=NULL)
190
		startup->thread_up(startup->cbdata,TRUE,setuid);
191
192
193
194
}

static void thread_down(void)
{
195
196
	if(thread_count>0)
		thread_count--;
197
	if(startup!=NULL && startup->thread_up!=NULL)
198
		startup->thread_up(startup->cbdata,FALSE,FALSE);
199
200
}

201
SOCKET mail_open_socket(int type, const char* protocol)
202
{
203
	char	error[256];
204
	char	section[128];
205
	SOCKET	sock;
206
207
208

	sock=socket(AF_INET, type, IPPROTO_IP);
	if(sock!=INVALID_SOCKET && startup!=NULL && startup->socket_open!=NULL) 
209
		startup->socket_open(startup->cbdata,TRUE);
210
	if(sock!=INVALID_SOCKET) {
211
212
		SAFEPRINTF(section,"mail|%s",protocol);
		if(set_socket_options(&scfg, sock, section, error, sizeof(error)))
213
			lprintf(LOG_ERR,"%04d !ERROR %s",sock,error);
214

215
216
		sockets++;
#if 0 /*def _DEBUG */
217
		lprintf(LOG_DEBUG,"%04d Socket opened (%d sockets in use)",sock,sockets);
218
219
220
221
222
#endif
	}
	return(sock);
}

223
int mail_close_socket(SOCKET sock)
224
225
226
{
	int		result;

227
228
229
	if(sock==INVALID_SOCKET)
		return(-1);

230
	shutdown(sock,SHUT_RDWR);	/* required on Unix */
231
	result=closesocket(sock);
232
	if(startup!=NULL && startup->socket_open!=NULL)
233
		startup->socket_open(startup->cbdata,FALSE);
234
	sockets--;
235
236
	if(result!=0) {
		if(ERROR_VALUE!=ENOTSOCK)
237
			lprintf(LOG_ERR,"%04d !ERROR %d closing socket",sock, ERROR_VALUE);
238
	}
239
#if 0 /*def _DEBUG */
240
	else 
241
		lprintf(LOG_DEBUG,"%04d Socket closed (%d sockets in use)",sock,sockets);
242
243
244
245
246
247
248
#endif

	return(result);
}

static void status(char* str)
{
249
	if(startup!=NULL && startup->status!=NULL)
250
	    startup->status(startup->cbdata,str);
251
252
}

253
int sockprintf(SOCKET sock, char *fmt, ...)
254
255
{
	int		len;
256
	int		maxlen;
257
258
259
	int		result;
	va_list argptr;
	char	sbuf[1024];
260
261
	fd_set	socket_set;
	struct timeval tv;
262
263

    va_start(argptr,fmt);
264
265
266
    len=vsnprintf(sbuf,maxlen=sizeof(sbuf)-2,fmt,argptr);
    va_end(argptr);

267
	if(len<0 || len > maxlen) /* format error or output truncated */
268
		len=maxlen;
269
	if(startup->options&MAIL_OPT_DEBUG_TX)
270
271
		lprintf(LOG_DEBUG,"%04d TX: %.*s", sock, len, sbuf);
	memcpy(sbuf+len,"\r\n",2);
272
	len+=2;
273

274
	if(sock==INVALID_SOCKET) {
275
		lprintf(LOG_WARNING,"!INVALID SOCKET in call to sockprintf");
276
277
278
		return(0);
	}

279
	/* Check socket for writability (using select) */
280
	tv.tv_sec=300;
281
282
283
284
285
286
	tv.tv_usec=0;

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

	if((result=select(sock+1,NULL,&socket_set,NULL,&tv))<1) {
287
		if(result==0)
288
			lprintf(LOG_NOTICE,"%04d !TIMEOUT selecting socket for send"
289
290
				,sock);
		else
291
			lprintf(LOG_NOTICE,"%04d !ERROR %d selecting socket for send"
292
				,sock, ERROR_VALUE);
293
294
295
		return(0);
	}

296
	while((result=sendsocket(sock,sbuf,len))!=len) {
297
		if(result==SOCKET_ERROR) {
rswindell's avatar
rswindell committed
298
			if(ERROR_VALUE==EWOULDBLOCK) {
299
				YIELD();
300
301
				continue;
			}
rswindell's avatar
rswindell committed
302
			if(ERROR_VALUE==ECONNRESET) 
303
				lprintf(LOG_NOTICE,"%04d Connection reset by peer on send",sock);
304
			else if(ERROR_VALUE==ECONNABORTED) 
305
				lprintf(LOG_NOTICE,"%04d Connection aborted by peer on send",sock);
306
			else
307
				lprintf(LOG_NOTICE,"%04d !ERROR %d sending on socket",sock,ERROR_VALUE);
308
309
			return(0);
		}
310
		lprintf(LOG_WARNING,"%04d !ERROR: short send on socket: %d instead of %d",sock,result,len);
311
312
313
314
	}
	return(len);
}

315
static void recverror(SOCKET socket, int rd, int line)
316
317
{
	if(rd==0) 
318
		lprintf(LOG_NOTICE,"%04d Socket closed by peer on receive (line %d)"
319
			,socket, line);
320
	else if(rd==SOCKET_ERROR) {
rswindell's avatar
rswindell committed
321
		if(ERROR_VALUE==ECONNRESET) 
322
			lprintf(LOG_NOTICE,"%04d Connection reset by peer on receive (line %d)"
323
				,socket, line);
324
		else if(ERROR_VALUE==ECONNABORTED) 
325
			lprintf(LOG_NOTICE,"%04d Connection aborted by peer on receive (line %d)"
326
				,socket, line);
327
		else
328
			lprintf(LOG_NOTICE,"%04d !ERROR %d receiving on socket (line %d)"
329
				,socket, ERROR_VALUE, line);
330
	} else
331
		lprintf(LOG_WARNING,"%04d !ERROR: recv on socket returned unexpected value: %d (line %d)"
332
			,socket, rd, line);
333
334
}

335

336
337
338
339
static int sockreadline(SOCKET socket, char* buf, int len)
{
	char	ch;
	int		i,rd=0;
340
341
	fd_set	socket_set;
	struct	timeval	tv;
342
343
	time_t	start;

344
345
	buf[0]=0;

346
	start=time(NULL);
347
348

	if(socket==INVALID_SOCKET) {
349
		lprintf(LOG_WARNING,"!INVALID SOCKET in call to sockreadline");
350
		return(-1);
351
	}
352
353
	
	while(rd<len-1) {
354

355
		if(server_socket==INVALID_SOCKET || terminate_server) {
356
			lprintf(LOG_WARNING,"%04d !ABORTING sockreadline",socket);
357
358
359
			return(-1);
		}

360
		tv.tv_sec=startup->max_inactivity;
361
362
363
364
365
366
367
		tv.tv_usec=0;

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

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

368
		if(i<1) {
369
			if(i==0) {
370
				if((time(NULL)-start)>startup->max_inactivity) {
371
					lprintf(LOG_WARNING,"%04d !SOCKET INACTIVE",socket);
372
					return(-1);
373
374
375
				}
				continue;
			}
376
			recverror(socket,i,__LINE__);
377
			return(-1);
378
379
380
381
		}
		i=recv(socket, &ch, 1, 0);
		if(i<1) {
			recverror(socket,i,__LINE__);
382
			return(-1);
383
		}
384
		if(ch=='\n' /* && rd>=1 */ ) { /* Mar-9-2003: terminate on sole LF */
385
386
387
388
			break;
		}	
		buf[rd++]=ch;
	}
389
	if(rd>0 && buf[rd-1]=='\r')
390
391
		rd--;
	buf[rd]=0;
392
393
394
395
396
397
398
399
400
401
402
403
404
405
	
	return(rd);
}

static BOOL sockgetrsp(SOCKET socket, char* rsp, char *buf, int len)
{
	int rd;

	while(1) {
		rd = sockreadline(socket, buf, len);
		if(rd<1) 
			return(FALSE);
		if(buf[3]=='-')	{ /* Multi-line response */
			if(startup->options&MAIL_OPT_DEBUG_RX_RSP) 
406
				lprintf(LOG_DEBUG,"%04d RX: %s",socket,buf);
407
408
			continue;
		}
409
		if(rsp!=NULL && strnicmp(buf,rsp,strlen(rsp))) {
410
			lprintf(LOG_WARNING,"%04d !INVALID RESPONSE: '%s' Expected: '%s'", socket, buf, rsp);
411
412
413
414
415
			return(FALSE);
		}
		break;
	}
	if(startup->options&MAIL_OPT_DEBUG_RX_RSP) 
416
		lprintf(LOG_DEBUG,"%04d RX: %s",socket,buf);
417
418
419
420
421
	return(TRUE);
}

#define MAX_LINE_LEN	1000

422
423
static ulong sockmimetext(SOCKET socket, smbmsg_t* msg, char* msgtxt, ulong maxlines
						  ,str_list_t file_list, char* mime_boundary)
424
{
425
426
	char		toaddr[256]="";
	char		fromaddr[256]="";
427
	char		fromhost[256];
rswindell's avatar
rswindell committed
428
	char		msgid[256];
429
430
431
	char		date[64];
	char*		p;
	char*		tp;
432
	char*		content_type=NULL;
433
	int			i;
434
	int			s;
435
	ulong		lines;
436

437
	/* HEADERS (in recommended order per RFC822 4.1) */
438
439
440
441
442

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

443
444
	if(!sockprintf(socket,"Date: %s",msgdate(msg->hdr.when_written,date)))
		return(0);
445
446
447
448

	if((p=smb_get_hfield(msg,RFC822FROM,NULL))!=NULL)
		s=sockprintf(socket,"From: %s",p);	/* use original RFC822 header field */
	else {
449
		if(msg->from_net.type==NET_QWK && msg->from_net.addr!=NULL)
450
			SAFEPRINTF2(fromaddr,"%s!%s"
451
452
				,(char*)msg->from_net.addr
				,usermailaddr(&scfg,fromhost,msg->from));
453
454
455
456
		else if(msg->from_net.type==NET_FIDO && msg->from_net.addr!=NULL)
			SAFECOPY(fromaddr,smb_faddrtoa((faddr_t *)msg->from_net.addr,NULL));
		else if(msg->from_net.type!=NET_NONE && msg->from_net.addr!=NULL)
			SAFECOPY(fromaddr,(char*)msg->from_net.addr);
457
458
459
460
461
462
463
		else 
			usermailaddr(&scfg,fromaddr,msg->from);
		if(fromaddr[0]=='<')
			s=sockprintf(socket,"From: \"%s\" %s",msg->from,fromaddr);
		else
			s=sockprintf(socket,"From: \"%s\" <%s>",msg->from,fromaddr);
	}
464
465
	if(!s)
		return(0);
466

467
468
469
470
	if(msg->from_org!=NULL || msg->from_net.type==NET_NONE)
		if(!sockprintf(socket,"Organization: %s"
			,msg->from_org==NULL ? scfg.sys_name : msg->from_org))
			return(0);
471

472
473
	if(!sockprintf(socket,"Subject: %s",msg->subj))
		return(0);
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488

	if((p=smb_get_hfield(msg,RFC822TO,NULL))!=NULL)
		s=sockprintf(socket,"To: %s",p);	/* use original RFC822 header field */
	else {
		if(strchr(msg->to,'@')!=NULL || msg->to_net.addr==NULL)
			s=sockprintf(socket,"To: %s",msg->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,"To: %s",(char*)msg->to_net.addr);
			else
				s=sockprintf(socket,"To: \"%s\" <%s>",msg->to,(char*)msg->to_net.addr);
		} else {
			usermailaddr(&scfg,toaddr,msg->to);
			s=sockprintf(socket,"To: \"%s\" <%s>",msg->to,toaddr);
		}
489
	}
490
491
	if(!s)
		return(0);
492
493
494
	if((p=smb_get_hfield(msg,SMB_CARBONCOPY,NULL))!=NULL)
		if(!sockprintf(socket,"CC: %s",p))
			return(0);
495
496
	if((p=smb_get_hfield(msg,RFC822REPLYTO,NULL))==NULL) {
		if(msg->replyto_net.type==NET_INTERNET)
rswindell's avatar
rswindell committed
497
			p=msg->replyto_net.addr;
498
499
500
501
		else if(msg->replyto!=NULL)
			p=msg->replyto;
	}
	if(p!=NULL)
502
		s=sockprintf(socket,"Reply-To: %s",p);	/* use original RFC822 header field */
503
504
	if(!s)
		return(0);
rswindell's avatar
rswindell committed
505
	if(!sockprintf(socket,"Message-ID: %s",get_msgid(&scfg,INVALID_SUB,msg,msgid,sizeof(msgid))))
506
		return(0);
507
	if(msg->reply_id!=NULL)
508
509
		if(!sockprintf(socket,"In-Reply-To: %s",msg->reply_id))
			return(0);
510

511
    for(i=0;i<msg->total_hfields;i++) { 
512
		if(msg->hfield[i].type==RFC822HEADER) { 
513
514
			if(strnicmp((char*)msg->hfield_dat[i],"Content-Type:",13)==0)
				content_type=msg->hfield_dat[i];
515
516
			if(!sockprintf(socket,"%s",(char*)msg->hfield_dat[i]))
				return(0);
517
        }
518
    }
519
	/* Default MIME Content-Type for non-Internet messages */
520
	if(msg->from_net.type!=NET_INTERNET && content_type==NULL && startup->default_charset[0]) {
521
		/* No content-type specified, so assume IBM code-page 437 (full ex-ASCII) */
522
		sockprintf(socket,"Content-Type: text/plain; charset=%s", startup->default_charset);
523
524
525
		sockprintf(socket,"Content-Transfer-Encoding: 8bit");
	}

526
527
	if(strListCount(file_list)) {	/* File attachments */
        mimeheaders(socket,mime_boundary);
528
        sockprintf(socket,"");
529
        mimeblurb(socket,mime_boundary);
530
        sockprintf(socket,"");
531
        mimetextpartheader(socket,mime_boundary);
532
	}
533
534
	if(!sockprintf(socket,""))	/* Header Terminator */
		return(0);
535

536
	/* MESSAGE BODY */
537
	lines=0;
538
#if 0	/* This is now handled in smb_getmsgtxt() */
539
    for(i=0;i<msg->total_hfields;i++) {			/* delivery failure notification? */
540
		if(msg->hfield[i].type==SMTPSYSMSG || msg->hfield[i].type==SMB_COMMENT) { 
541
542
543
544
545
			if(!sockprintf(socket,"%s",(char*)msg->hfield_dat[i]))
				return(0);
			lines++;
		}
    }
546
#endif
547
	p=msgtxt;
548
	while(*p && lines<maxlines) {
549
550
551
552
553
554
555
556
557
558
559
560
561
		tp=strchr(p,'\n');
		if(tp) {
			if(tp-p>MAX_LINE_LEN)
				tp=p+MAX_LINE_LEN;
			*tp=0;
		}
		truncsp(p);	/* Takes care of '\r' or spaces */
		if(*p=='.')
			i=sockprintf(socket,".%.*s",MAX_LINE_LEN,p);
		else
			i=sockprintf(socket,"%.*s",MAX_LINE_LEN,p);
		if(!i)
			break;
rswindell's avatar
rswindell committed
562
		lines++;
563
564
565
		if(tp==NULL)
			break;
		p=tp+1;
566
567
568
		/* release time-slices every x lines */
		if(startup->lines_per_yield
			&& !(lines%startup->lines_per_yield))	
569
			YIELD();
570
	}
571
572
573
574
575
576
577
578
579
580
581
582
	if(file_list!=NULL) {
		for(i=0;file_list[i];i++) { 
			sockprintf(socket,"");
			lprintf(LOG_INFO,"%04u MIME Encoding and sending %s",socket,file_list[i]);
			if(!mimeattach(socket,mime_boundary,file_list[i]))
				lprintf(LOG_ERR,"%04u !ERROR opening/encoding/sending %s",socket,file_list[i]);
			else {
				endmime(socket,mime_boundary);
				if(msg->hdr.auxattr&MSG_KILLFILE)
					if(remove(file_list[i])!=0)
						lprintf(LOG_WARNING,"%04u !ERROR %d removing %s",socket,errno,file_list[i]);
			}
583
		}
584
	}
585
    sockprintf(socket,".");	/* End of text */
586
	return(lines);
587
588
}

589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
static ulong sockmsgtxt(SOCKET socket, smbmsg_t* msg, char* msgtxt, ulong maxlines)
{
	char		filepath[MAX_PATH+1];
	ulong		retval;
	char*		boundary=NULL;
	unsigned	i;
	str_list_t	file_list=NULL;
	str_list_t	split;

	if(msg->hdr.auxattr&MSG_FILEATTACH) {

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

		/* Parse header fields */
		for(i=0;i<msg->total_hfields;i++)
	        if(msg->hfield[i].type==FILEATTACH) 
				strListPush(&file_list,(char*)msg->hfield_dat[i]);

		/* Parse subject (if necessary) */
		if(!strListCount(file_list)) {	/* filename(s) stored in subject */
			split=strListSplitCopy(NULL,msg->subj," ");
			if(split!=NULL) {
				for(i=0;split[i];i++) {
					if(msg->idx.to!=0)
						SAFEPRINTF3(filepath,"%sfile/%04u.in/%s"
615
							,scfg.data_dir,msg->idx.to,getfname(truncsp(split[i])));
616
617
					else
						SAFEPRINTF3(filepath,"%sfile/%04u.out/%s"
618
							,scfg.data_dir,msg->idx.from,getfname(truncsp(split[i])));
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
					strListPush(&file_list,filepath);
				}
				strListFree(&split);
			}
		}
    }

	retval = sockmimetext(socket,msg,msgtxt,maxlines,file_list,boundary);

	strListFree(&file_list);

	if(boundary!=NULL)
		free(boundary);

	return(retval);
}

636
static u_long resolve_ip(char *inaddr)
637
{
638
	char*		p;
639
640
	char*		addr;
	char		buf[128];
641
	HOSTENT*	host;
642

643
644
	SAFECOPY(buf,inaddr);
	addr=buf;
645
	if(*addr=='[' && *(p=lastchar(addr))==']') { /* Support [ip_address] notation */
646
647
648
649
		addr++;
		*p=0;
	}

650
	if(*addr==0)
651
		return((u_long)INADDR_NONE);
652

653
654
655
656
	for(p=addr;*p;p++)
		if(*p!='.' && !isdigit(*p))
			break;
	if(!(*p))
657
		return(inet_addr(addr));
658

659
660
	if((host=gethostbyname(inaddr))==NULL) {
		lprintf(LOG_WARNING,"0000 !ERROR resolving hostname: %s",inaddr);
661
		return((u_long)INADDR_NONE);
662
663
664
665
666
667
668
669
670
671
672
673
674
675
	}
	return(*((ulong*)host->h_addr_list[0]));
}


static void pop3_thread(void* arg)
{
	char*		p;
	char		str[128];
	char		buf[512];
	char		host_name[128];
	char		host_ip[64];
	char		username[LEN_ALIAS+1];
	char		password[LEN_PASS+1];
676
677
	char		challenge[256];
	char		digest[MD5_DIGEST_SIZE];
678
	char*		response="";
679
680
681
	char*		msgtxt;
	int			i;
	int			rd;
682
	BOOL		activity=FALSE;
683
	BOOL		apop=FALSE;
684
	long		l;
685
	ulong		lines;
686
	ulong		lines_sent;
deuce's avatar
deuce committed
687
688
	int32_t		msgs;
	long		msgnum;
689
	ulong		bytes;
690
691
692
693
694
695
696
697
698
	SOCKET		socket;
	HOSTENT*	host;
	smb_t		smb;
	smbmsg_t	msg;
	user_t		user;
	client_t	client;
	mail_t*		mail;
	pop3_t		pop3=*(pop3_t*)arg;

699
	SetThreadName("POP3 Thread");
700
	thread_up(TRUE /* setuid */);
701
702
703
704
705
706

	free(arg);

	socket=pop3.socket;

	if(startup->options&MAIL_OPT_DEBUG_POP3)
707
		lprintf(LOG_DEBUG,"%04d POP3 session thread started", socket);
708

709
#ifdef _WIN32
710
711
	if(startup->pop3_sound[0] && !(startup->options&MAIL_OPT_MUTE)) 
		PlaySound(startup->pop3_sound, NULL, SND_ASYNC|SND_FILENAME);
712
#endif
713

714
	SAFECOPY(host_ip,inet_ntoa(pop3.client_addr.sin_addr));
715
716

	if(startup->options&MAIL_OPT_DEBUG_POP3)
717
		lprintf(LOG_INFO,"%04d POP3 connection accepted from: %s port %u"
718
			,socket, host_ip, ntohs(pop3.client_addr.sin_port));
719
720
721
722
723

	if(startup->options&MAIL_OPT_NO_HOST_LOOKUP)
		host=NULL;
	else
		host=gethostbyaddr((char *)&pop3.client_addr.sin_addr
deuce's avatar
deuce committed
724
			,sizeof(pop3.client_addr.sin_addr),AF_INET);
725
726

	if(host!=NULL && host->h_name!=NULL)
rswindell's avatar
rswindell committed
727
		SAFECOPY(host_name,host->h_name);
728
729
730
	else
		strcpy(host_name,"<no name>");

731
	if(startup->options&MAIL_OPT_DEBUG_POP3
732
		&& !(startup->options&MAIL_OPT_NO_HOST_LOOKUP)) {
733
		lprintf(LOG_INFO,"%04d POP3 Hostname: %s", socket, host_name);
734
		for(i=0;host!=NULL && host->h_aliases!=NULL && host->h_aliases[i]!=NULL;i++)
735
			lprintf(LOG_INFO,"%04d POP3 HostAlias: %s", socket, host->h_aliases[i]);
736
	}
737

738
	if(trashcan(&scfg,host_ip,"ip")) {
739
		lprintf(LOG_NOTICE,"%04d !POP3 BLOCKED CLIENT IP ADDRESS: %s"
740
			,socket, host_ip);
741
		sockprintf(socket,"-ERR Access denied.");
742
		mail_close_socket(socket);
743
744
745
746
		thread_down();
		return;
	}

747
	if(trashcan(&scfg,host_name,"host")) {
748
		lprintf(LOG_NOTICE,"%04d !POP3 BLOCKED CLIENT HOSTNAME: %s"
749
			,socket, host_name);
750
		sockprintf(socket,"-ERR Access denied.");
751
		mail_close_socket(socket);
752
753
754
755
		thread_down();
		return;
	}

756
	active_clients++, update_clients();
757
758
759
760

	/* Initialize client display */
	client.size=sizeof(client);
	client.time=time(NULL);
rswindell's avatar
rswindell committed
761
762
	SAFECOPY(client.addr,host_ip);
	SAFECOPY(client.host,host_name);
763
764
765
	client.port=ntohs(pop3.client_addr.sin_port);
	client.protocol="POP3";
	client.user="<unknown>";
766
	client_on(socket,&client,FALSE /* update */);
767

768
	SAFEPRINTF(str,"POP3: %s", host_ip);
769
770
	status(str);

rswindell's avatar
rswindell committed
771
772
	mail=NULL;

773
774
775
	do {
		memset(&smb,0,sizeof(smb));
		memset(&msg,0,sizeof(msg));
776
		memset(&user,0,sizeof(user));
777
		password[0]=0;
778

779
		srand(time(NULL) ^ (DWORD)GetCurrentThreadId());	/* seed random number generator */
780
		rand();	/* throw-away first result */
781
		safe_snprintf(challenge,sizeof(challenge),"<%x%x%lx%lx@%.128s>"
782
			,rand(),socket,(ulong)time(NULL),clock(),startup->host_name);
783
784
785

		sockprintf(socket,"+OK Synchronet POP3 Server %s-%s Ready %s"
			,revision,PLATFORM_DESC,challenge);
786

787
788
		/* Requires USER command first */
		for(i=3;i;i--) {
789
790
791
792
793
794
			if(!sockgetrsp(socket,NULL,buf,sizeof(buf)))
				break;
			if(!strnicmp(buf,"USER ",5))
				break;
			if(!strnicmp(buf,"APOP ",5)) {
				apop=TRUE;
795
				break;
796
797
			}
			sockprintf(socket,"-ERR USER or APOP command expected");
798
		}
799
		if(!i || buf[0]==0)	/* no USER or APOP command received */
800
801
			break;

802
		p=buf+5;
803
		SKIP_WHITESPACE(p);
804
805
806
807
808
809
		if(apop) {
			if((response=strrchr(p,' '))!=NULL)
				*(response++)=0;
			else
				response=p;
		}
rswindell's avatar
rswindell committed
810
		SAFECOPY(username,p);
811
812
813
814
815
816
817
		if(!apop) {
			sockprintf(socket,"+OK");
			if(!sockgetrsp(socket,"PASS ",buf,sizeof(buf))) {
				sockprintf(socket,"-ERR PASS command expected");
				break;
			}
			p=buf+5;
818
			SKIP_WHITESPACE(p);
819
			SAFECOPY(password,p);
820
		}
821
		user.number=matchuser(&scfg,username,FALSE /*sysop_alias*/);
822
		if(!user.number) {
823
			if(scfg.sys_misc&SM_ECHO_PW)
824
				lprintf(LOG_WARNING,"%04d !POP3 UNKNOWN USER: %s (password: %s)"
825
826
					,socket, username, password);
			else
827
				lprintf(LOG_WARNING,"%04d !POP3 UNKNOWN USER: %s"
828
					,socket, username);
829
			sockprintf(socket,pop_err);
830
831
832
			break;
		}
		if((i=getuserdat(&scfg, &user))!=0) {
833
			lprintf(LOG_ERR,"%04d !POP3 ERROR %d getting data on user (%s)"
834
				,socket, i, username);
835
			sockprintf(socket, pop_err);
836
837
838
			break;
		}
		if(user.misc&(DELETED|INACTIVE)) {
839
			lprintf(LOG_WARNING,"%04d !POP3 DELETED or INACTIVE user #%u (%s)"
840
				,socket, user.number, username);
841
			sockprintf(socket, pop_err);
842
843
			break;
		}
844
845
846
847
848
849
		if(apop) {
			strlwr(user.pass);	/* this is case-sensitive, so convert to lowercase */
			strcat(challenge,user.pass);
			MD5_calc(digest,challenge,strlen(challenge));
			MD5_hex(str,digest);
			if(strcmp(str,response)) {
850
				lprintf(LOG_WARNING,"%04d !POP3 %s FAILED APOP authentication"
851
852
					,socket,username);
#if 0
853
854
855
				lprintf(LOG_DEBUG,"%04d !POP3 digest data: %s",socket,challenge);
				lprintf(LOG_DEBUG,"%04d !POP3 calc digest: %s",socket,str);
				lprintf(LOG_DEBUG,"%04d !POP3 resp digest: %s",socket,response);
856
857
858
859
860
#endif
				sockprintf(socket,pop_err);
				break;
			}
		} else if(stricmp(password,user.pass)) {
861
			if(scfg.sys_misc&SM_ECHO_PW)
862
				lprintf(LOG_WARNING,"%04d !POP3 FAILED Password attempt for user %s: '%s' expected '%s'"
863
864
					,socket, username, password, user.pass);
			else
865
				lprintf(LOG_WARNING,"%04d !POP3 FAILED Password attempt for user %s"
866
					,socket, username);
867
			sockprintf(socket, pop_err);
868
869
			break;
		}
870
871
		putuserrec(&scfg,user.number,U_COMP,LEN_COMP,host_name);
		putuserrec(&scfg,user.number,U_NOTE,LEN_NOTE,host_ip);
872
873
874

		/* Update client display */
		client.user=user.alias;
875
		client_on(socket,&client,TRUE /* update */);
876
877

		if(startup->options&MAIL_OPT_DEBUG_POP3)		
878
			lprintf(LOG_INFO,"%04d POP3 %s logged in %s", socket, user.alias, apop ? "via APOP":"");
879
		SAFEPRINTF(str,"POP3: %s",user.alias);
880
881
		status(str);

882
883
		sprintf(smb.file,"%smail",scfg.data_dir);
		if(smb_islocked(&smb)) {
884
			lprintf(LOG_WARNING,"%04d !POP3 MAIL BASE LOCKED: %s",socket,smb.last_error);
885
886
887
888
889
			sockprintf(socket,"-ERR database locked, try again later");
			break;
		}
		smb.retry_time=scfg.smb_retry_time;
		smb.subnum=INVALID_SUB;
890
		if((i=smb_open(&smb))!=SMB_SUCCESS) {
891
			lprintf(LOG_ERR,"%04d !POP3 ERROR %d (%s) opening %s",socket,i,smb.last_error,smb.file);
892
893
894
895
			sockprintf(socket,"-ERR %d opening %s",i,smb.file);
			break;
		}

896
897
898
899
		mail=loadmail(&smb,&msgs,user.number,MAIL_YOUR,0);

		for(l=bytes=0;l<msgs;l++) {
			msg.hdr.number=mail[l].number;
900
			if((i=smb_getmsgidx(&smb,&msg))!=SMB_SUCCESS) {
901
				lprintf(LOG_ERR,"%04d !POP3 ERROR %d (%s) getting message index"
902
					,socket, i, smb.last_error);
903
904
				break;
			}
905
			if((i=smb_lockmsghdr(&smb,&msg))!=SMB_SUCCESS) {
906
				lprintf(LOG_WARNING,"%04d !POP3 ERROR %d (%s) locking message header #%lu"