Skip to content
Snippets Groups Projects
mailsrvr.c 211 KiB
Newer Older
			mail_close_socket(&socket, &session);
		if ((cstat = cryptCreateSession(&session, CRYPT_UNUSED, CRYPT_SESSION_SSL_SERVER)) != CRYPT_OK) {
			GCES(cstat, client.protocol, socket, CRYPT_UNUSED, "creating session");
			mail_close_socket(&socket, &session);
		if ((cstat = cryptSetAttribute(session, CRYPT_SESSINFO_SSL_OPTIONS, CRYPT_SSLOPTION_DISABLE_CERTVERIFY)) != CRYPT_OK) {
			GCES(cstat, client.protocol, socket, CRYPT_UNUSED, "disabling certificate verification");
			mail_close_socket(&socket, &session);
		if ((cstat = cryptSetAttribute(session, CRYPT_SESSINFO_PRIVATEKEY, scfg.tls_certificate)) != CRYPT_OK) {
			GCES(cstat, client.protocol, socket, CRYPT_UNUSED, "setting private key");
			mail_close_socket(&socket, &session);
			thread_down();
			return;
		}
		nodelay = TRUE;
		setsockopt(socket,IPPROTO_TCP,TCP_NODELAY,(char*)&nodelay,sizeof(nodelay));
		nb=0;
		ioctlsocket(socket,FIONBIO,&nb);
		if ((cstat = cryptSetAttribute(session, CRYPT_SESSINFO_NETWORKSOCKET, socket)) != CRYPT_OK) {
			GCES(cstat, client.protocol, socket, CRYPT_UNUSED, "setting network socket");
			mail_close_socket(&socket, &session);
		if ((cstat = cryptSetAttribute(session, CRYPT_SESSINFO_ACTIVE, 1)) != CRYPT_OK) {
			GCES(cstat, client.protocol, socket, CRYPT_UNUSED, "setting session active");
			mail_close_socket(&socket, &session);
		if (startup->max_inactivity) {
			if ((cstat = cryptSetAttribute(session, CRYPT_OPTION_NET_READTIMEOUT, startup->max_inactivity)) != CRYPT_OK) {
				GCES(cstat, client.protocol, socket, CRYPT_UNUSED, "setting read timeout");
				mail_close_socket(&socket, &session);
deuce's avatar
deuce committed
	if((i=getsockname(socket, &server_addr.addr, &addr_len))!=0) {
		lprintf(LOG_CRIT,"%04d %s !ERROR %d (%d) getting address/port"
			,socket, client.protocol, i, ERROR_VALUE);
		sockprintf(socket,client.protocol,session,smtp_error, "getsockname failure");
		mail_close_socket(&socket, &session);
deuce's avatar
deuce committed
	}
deuce's avatar
deuce committed
	if((mailproc_to_match=malloc(sizeof(BOOL)*mailproc_count))==NULL) {
		lprintf(LOG_CRIT,"%04d %s !ERROR allocating memory for mailproc_to_match", socket, client.protocol);
		sockprintf(socket,client.protocol,session,smtp_error, "malloc failure");
		mail_close_socket(&socket, &session);
rswindell's avatar
rswindell committed
	memset(mailproc_to_match,FALSE,sizeof(BOOL)*mailproc_count);
	memset(&smb,0,sizeof(smb));
	memset(&spam,0,sizeof(spam));
	memset(&relay_user,0,sizeof(relay_user));
deuce's avatar
deuce committed
	inet_addrtop(&smtp.client_addr,host_ip,sizeof(host_ip));
	inet_addrtop(&server_addr,server_ip,sizeof(server_ip));
	lprintf(LOG_INFO,"%04d %s [%s] Connection accepted on %s port %u from port %u"
		,socket, client.protocol, host_ip, server_ip, inet_addrport(&server_addr), inet_addrport(&smtp.client_addr));
	SAFEPRINTF(client_id, "[%s]", host_ip);
	SAFECOPY(host_name, STR_NO_HOSTNAME);
	if(!(startup->options&MAIL_OPT_NO_HOST_LOOKUP)) {
		getnameinfo(&smtp.client_addr.addr, smtp.client_addr_len, host_name, sizeof(host_name), NULL, 0, NI_NAMEREQD);
		lprintf(LOG_INFO,"%04d %s %s Hostname: %s", socket, client.protocol, client_id, host_name);
	protected_uint32_adjust(&active_clients, 1);
	SAFEPRINTF(spam_bait,"%sspambait.cfg",scfg.ctrl_dir);
	SAFEPRINTF(spam_block,"%sspamblock.cfg",scfg.ctrl_dir);
rswindell's avatar
rswindell committed
	SAFEPRINTF(spam_block_exemptions,"%sspamblock_exempt.cfg",scfg.ctrl_dir);
deuce's avatar
deuce committed
	if(strcmp(server_ip, host_ip)==0) {
		/* local connection */
		dnsbl_result.s_addr=0;
	} else {
		ulong banned = loginBanned(&scfg, startup->login_attempt_list, socket, host_name, startup->login_attempt, &attempted);
rswindell's avatar
rswindell committed
		if(banned) {
			char ban_duration[128];
Rob Swindell's avatar
Rob Swindell committed
			lprintf(LOG_NOTICE, "%04d %s [%s] !TEMPORARY BAN (%lu login attempts, last: %s) - remaining: %s"
				,socket, client.protocol, host_ip, attempted.count-attempted.dupes, attempted.user, seconds_to_str(banned, ban_duration));
			mail_close_socket(&socket, &session);
rswindell's avatar
rswindell committed
			thread_down();
			protected_uint32_adjust(&active_clients, -1);
			update_clients();
			free(mailproc_to_match);
			return;
		}

		spam_block_exempt = findstr(host_ip,spam_block_exemptions) || findstr(host_name,spam_block_exemptions);
		if(trashcan(&scfg,host_ip,"ip") 
			|| ((!spam_block_exempt) && findstr(host_ip,spam_block))) {
			lprintf(LOG_NOTICE,"%04d %s !CLIENT IP ADDRESS BLOCKED: %s (%lu total)"
				,socket, client.protocol, host_ip, ++stats.sessions_refused);
			sockprintf(socket,client.protocol,session,"550 CLIENT IP ADDRESS BLOCKED: %s", host_ip);
			mail_close_socket(&socket, &session);
			protected_uint32_adjust(&active_clients, -1);
deuce's avatar
deuce committed
			free(mailproc_to_match);
		if(trashcan(&scfg,host_name,"host")) {
			lprintf(LOG_NOTICE,"%04d %s [%s] !CLIENT HOSTNAME BLOCKED: %s (%lu total)"
				,socket, client.protocol, host_ip, host_name, ++stats.sessions_refused);
			sockprintf(socket,client.protocol,session,"550 CLIENT HOSTNAME BLOCKED: %s", host_name);
			mail_close_socket(&socket, &session);
			protected_uint32_adjust(&active_clients, -1);
deuce's avatar
deuce committed
			free(mailproc_to_match);
		dnsbl_result.s_addr = dns_blacklisted(socket,client.protocol,&smtp.client_addr,host_name,dnsbl,dnsbl_ip);
Rob Swindell's avatar
Rob Swindell committed
			lprintf(LOG_NOTICE,"%04d %s [%s] BLACKLISTED SERVER on %s: %s = %s"
				,socket, client.protocol, dnsbl_ip, dnsbl, host_name, inet_ntoa(dnsbl_result));
			if(startup->options&MAIL_OPT_DNSBL_REFUSE) {
				SAFEPRINTF2(str,"Listed on %s as %s", dnsbl, inet_ntoa(dnsbl_result));
				spamlog(&scfg, (char*)client.protocol, "SESSION REFUSED", str, host_name, dnsbl_ip, NULL, NULL);
				sockprintf(socket,client.protocol,session
					,"550 Mail from %s refused due to listing at %s"
					,dnsbl_ip, dnsbl);
				mail_close_socket(&socket, &session);
				lprintf(LOG_NOTICE,"%04d %s !REFUSED SESSION from blacklisted server (%lu total)"
					,socket, client.protocol, ++stats.sessions_refused);
				protected_uint32_adjust(&active_clients, -1);
deuce's avatar
deuce committed
				free(mailproc_to_match);
	SAFEPRINTF(smb.file,"%smail",scfg.data_dir);
		lprintf(LOG_WARNING,"%04d %s [%s] !MAIL BASE LOCKED: %s"
			,socket, client.protocol, host_ip, smb.last_error);
		sockprintf(socket,client.protocol,session, smtp_error, "mail base locked");
		mail_close_socket(&socket, &session);
		protected_uint32_adjust(&active_clients, -1);
deuce's avatar
deuce committed
		free(mailproc_to_match);
	SAFEPRINTF(spam.file,"%sspam",scfg.data_dir);
	spam.retry_time=scfg.smb_retry_time;
	spam.subnum=INVALID_SUB;
	srand((unsigned int)(time(NULL) ^ (time_t)GetCurrentThreadId()));	/* seed random number generator */
	rand();	/* throw-away first result */
	SAFEPRINTF4(session_id,"%x%x%x%lx",getpid(),socket,rand(),(long)clock());
	lprintf(LOG_DEBUG,"%04d %s [%s] Session ID=%s", socket, client.protocol, host_ip, session_id);
	SAFEPRINTF3(msgtxt_fname,"%sSBBS_%s.%s.msg", scfg.temp_dir, client.protocol, session_id);
	SAFEPRINTF3(newtxt_fname,"%sSBBS_%s.%s.new", scfg.temp_dir, client.protocol, session_id);
	SAFEPRINTF3(logtxt_fname,"%sSBBS_%s.%s.log", scfg.temp_dir, client.protocol, session_id);
	SAFEPRINTF3(rcptlst_fname,"%sSBBS_%s.%s.lst", scfg.temp_dir, client.protocol, session_id);
	rcptlst=fopen(rcptlst_fname,"w+");
	if(rcptlst==NULL) {
		lprintf(LOG_CRIT,"%04d %s [%s] !ERROR %d creating recipient list: %s"
			,socket, client.protocol, host_ip, errno, rcptlst_fname);
		sockprintf(socket,client.protocol,session,smtp_error, "fopen error");
		mail_close_socket(&socket, &session);
		thread_down();
		protected_uint32_adjust(&active_clients, -1);
deuce's avatar
deuce committed
		free(mailproc_to_match);
	if(trashcan(&scfg,host_name,"smtpspy") 
		|| trashcan(&scfg,host_ip,"smtpspy")) {
		SAFEPRINTF(str,"%ssmtpspy.txt", scfg.logs_dir);
		spy=fopen(str,"a");
	}

	/* Initialize client display */
	client.size=sizeof(client);
	SAFECOPY(client.addr,host_ip);
	SAFECOPY(client.host,host_name);
deuce's avatar
deuce committed
	client.port=inet_addrport(&smtp.client_addr);
	client_on(socket,&client,FALSE /* update */);
	SAFEPRINTF(str,"SMTP: %s",host_ip);
rswindell's avatar
rswindell committed
	if(startup->login_attempt.throttle
		&& (login_attempts=loginAttempts(startup->login_attempt_list, &smtp.client_addr)) > 1) {
		lprintf(LOG_DEBUG,"%04d %s Throttling suspicious connection from: %s (%lu login attempts)"
			,socket, client.protocol, host_ip, login_attempts);
rswindell's avatar
rswindell committed
		mswait(login_attempts*startup->login_attempt.throttle);
	sockprintf(socket,client.protocol,session,"220 %s Synchronet %s Server %s-%s Ready"
		,server_host_name(), client.protocol, revision, PLATFORM_DESC);
		rd = sockreadline(socket, client.protocol, session, buf, sizeof(buf));
		if(spy!=NULL)
			fprintf(spy,"%s\n",buf);
		if(relay_user.number==0 && dnsbl_result.s_addr && startup->options&MAIL_OPT_DNSBL_THROTTLE)
			mswait(DNSBL_THROTTLE_VALUE);
		if(state>=SMTP_STATE_DATA_HEADER) {
			if(!strcmp(buf,".")) {

				state=SMTP_STATE_HELO;	/* RESET state machine here in case of error */
rswindell's avatar
rswindell committed
				cmd=SMTP_CMD_NONE;
					lprintf(LOG_ERR,"%04d %s %s !NO MESSAGE TEXT FILE POINTER?", socket, client.protocol, client_id);
					sockprintf(socket,client.protocol,session,"554 No message text");
					lprintf(LOG_ERR,"%04d %s %s !INVALID MESSAGE LENGTH: %ld (%lu lines)"
						, socket, client.protocol, client_id, ftell(msgtxt), lines);
					sockprintf(socket,client.protocol,session,"554 No message text");
				lprintf(LOG_INFO,"%04d %s %s End of message (body: %lu lines, %lu bytes, header: %lu lines, %lu bytes)"
					, socket, client.protocol, client_id, lines, ftell(msgtxt)-hdr_len, hdr_lines, hdr_len);
				if(!socket_check(socket, NULL, NULL, 0)) {
					lprintf(LOG_WARNING,"%04d %s %s !Sender disconnected (premature evacuation)", socket, client.protocol, client_id);
				/* Twit-listing (sender's name and e-mail addresses) here */
				SAFEPRINTF(path,"%stwitlist.cfg",scfg.ctrl_dir);
				if(fexist(path) && (findstr(sender,path) || findstr(sender_addr,path))) {
					lprintf(LOG_NOTICE,"%04d %s %s !FILTERING TWIT-LISTED SENDER: %s <%s> (%lu total)"
						,socket, client.protocol, client_id, sender, sender_addr, ++stats.msgs_refused);
					SAFEPRINTF2(tmp,"Twit-listed sender: %s <%s>", sender, sender_addr);
					spamlog(&scfg, (char*)client.protocol, "REFUSED", tmp, host_name, host_ip, rcpt_addr, reverse_path);
					sockprintf(socket,client.protocol,session, "554 Sender not allowed.");
				if(telegram==TRUE) {		/* Telegram */
					const char* head="\1n\1h\1cInstant Message\1n from \1h\1y";
					const char* tail="\1n:\r\n\1h";
deuce's avatar
deuce committed
					struct addrinfo ai;
					struct addrinfo *res,*cur;
					BOOL matched=FALSE;

					rewind(msgtxt);
					length=filelength(fileno(msgtxt));
					
					p=strchr(sender_addr,'@');
deuce's avatar
deuce committed
					memset(&ai, 0, sizeof(ai));
					ai.ai_flags = AI_PASSIVE;
					ai.ai_family = smtp.client_addr.addr.sa_family;
					if(getaddrinfo(p+1, NULL, &ai, &res) != 0)
						p=NULL;
					else {
						for(cur=res; cur; cur=cur->ai_next) {
							char cur_ip[INET6_ADDRSTRLEN];

							if(inet_addrtop((void *)cur->ai_addr, cur_ip, sizeof(cur_ip))) {
								if(strcmp(host_ip, cur_ip)==0)
									matched=TRUE;
							}
						}
						freeaddrinfo(res);
						if(!matched)
							p=NULL;
					}
					if(p==NULL)
						/* Append real IP and hostname if different */
						safe_snprintf(str,sizeof(str),"%s%s\r\n\1w[\1n%s\1h] (\1n%s\1h)%s"
							,head,sender_addr,host_ip,host_name,tail);
					else
						safe_snprintf(str,sizeof(str),"%s%s%s",head,sender_addr,tail);
					if((telegram_buf=(char*)malloc(length+strlen(str)+1))==NULL) {
						lprintf(LOG_CRIT,"%04d %s %s !ERROR allocating %lu bytes of memory for telegram from %s"
							,socket, client.protocol, client_id, length+strlen(str)+1,sender_addr);
						sockprintf(socket,client.protocol,session, insuf_stor);
						continue; 
					}
					strcpy(telegram_buf,str);	/* can't use SAFECOPY here */
					if(fread(telegram_buf+strlen(str),1,length,msgtxt)!=length) {
						lprintf(LOG_ERR,"%04d %s %s !ERROR reading %lu bytes from telegram file"
							,socket, client.protocol, client_id, length);
						sockprintf(socket,client.protocol,session, insuf_stor);
						free(telegram_buf);
						continue; 
					}
					telegram_buf[length+strlen(str)]=0;	/* Need ASCIIZ */

					/* Send telegram to users */
					sec_list=iniReadSectionList(rcptlst,NULL);	/* Each section is a recipient */
					for(rcpt_count=0; sec_list!=NULL
						&& sec_list[rcpt_count]!=NULL 
rswindell's avatar
rswindell committed
						&& (startup->max_recipients==0 || rcpt_count<startup->max_recipients); rcpt_count++) {
						SAFECOPY(rcpt_to,iniReadString(rcptlst,section	,smb_hfieldtype(RECIPIENT),"unknown",value));
						usernum=iniReadInteger(rcptlst,section				,smb_hfieldtype(RECIPIENTEXT),0);
						SAFECOPY(rcpt_addr,iniReadString(rcptlst,section	,smb_hfieldtype(RECIPIENTNETADDR),rcpt_to,value));

						if((i=putsmsg(&scfg,usernum,telegram_buf))==0)
							lprintf(LOG_INFO,"%04d %s %s Created telegram (%ld/%lu bytes) from %s to %s <%s>"
								,socket, client.protocol, client_id, length, (ulong)strlen(telegram_buf), sender_addr, rcpt_to, rcpt_addr);
							lprintf(LOG_ERR,"%04d %s %s !ERROR %d creating telegram from %s to %s <%s>"
								,socket, client.protocol, client_id, i, sender_addr, rcpt_to, rcpt_addr);
					sockprintf(socket,client.protocol,session,ok_rsp);
				fclose(msgtxt), msgtxt=NULL;
				fclose(rcptlst), rcptlst=NULL;
					SAFEPRINTF3(proc_err_fname,"%sSBBS_%s.%s.err", scfg.temp_dir, client.protocol, session_id);
					for(i=0;i<mailproc_count && !msg_handled;i++) {
						struct mailproc* mp=&mailproc_list[i];
						if(mp->disabled)
rswindell's avatar
rswindell committed
							continue;
						if(!mp->process_dnsbl && dnsbl_result.s_addr)
						if(!mp->process_spam && spam_bait_result)
						if(!chk_ar(&scfg,mp->ar,&relay_user,&client))
rswindell's avatar
rswindell committed
							continue;

						if(mp->to!=NULL && !mailproc_to_match[i])
						if(mp->from!=NULL 
							&& !findstr_in_list(sender_addr, mp->from))
							,msgtxt_fname, newtxt_fname, logtxt_fname
							,rcptlst_fname, proc_err_fname
							,host_name, host_ip, relay_user.number
							,sender, sender_addr, reverse_path, str);
						lprintf(LOG_INFO,"%04d %s %s Executing external mail processor: %s"
							,socket, client.protocol, client_id, mp->name);
							lprintf(LOG_DEBUG,"%04d %s %s Executing external command: %s"
								,socket, client.protocol, client_id, str);
								lprintf(LOG_NOTICE,"%04d %s %s system(%s) returned %d (errno: %d)"
									,socket, client.protocol, client_id, str, j, errno);
								if(mp->ignore_on_error) {
									lprintf(LOG_WARNING,"%04d %s %s !IGNORED MAIL due to mail processor (%s) error: %d"
										,socket, client.protocol, client_id, mp->name, j);
rswindell's avatar
rswindell committed
							if(!js_mailproc(socket, &client, &relay_user
rswindell's avatar
rswindell committed
								,str /* cmdline */
								,msgtxt_fname, newtxt_fname, logtxt_fname
								,sender, sender_addr, reverse_path, hello_name, &js_result
rswindell's avatar
rswindell committed
								,&js_runtime, &js_cx, &js_glob
#if 0 /* calling exit() in a script causes js_mailproc to return FALSE */
								lprintf(LOG_NOTICE,"%04d !SMTP JavaScript mailproc command (%s) failed (returned: %d)"
									,socket, str, js_result);
									lprintf(LOG_WARNING,"%04d !SMTP IGNORED MAIL due to mail processor (%s) failure"
						/* Log debug output (file) from mailproc: */
						if(flength(logtxt_fname) > 0 && (proc_out=fopen(logtxt_fname,"r"))!=NULL) {
							while(!feof(proc_out)) {
								if(!fgets(str,sizeof(str),proc_out))
									break;
								truncsp(str);
								lprintf(LOG_DEBUG,"%04d %s %s External mail processor (%s) debug: %s"
									,socket, client.protocol, client_id, mp->name, str);
						if(!mp->passthru || flength(proc_err_fname)>0 || !fexist(msgtxt_fname) || !fexist(rcptlst_fname)) {
							mailproc=mp;
							msg_handled=TRUE;
						&& (proc_out=fopen(proc_err_fname,"r"))!=NULL) {
						lprintf(LOG_WARNING,"%04d %s %s !External mail processor (%s) created: %s"
								,socket, client.protocol, client_id, mailproc->name, proc_err_fname);
							if(!fgets(str,sizeof(str),proc_out))
							lprintf(LOG_WARNING,"%04d %s %s !External mail processor (%s) error: %s"
								,socket, client.protocol, client_id, mailproc->name, str);
								sockprintf(socket,client.protocol,session,"%s", str);
								sockprintf(socket,client.protocol,session,"554%c%s"
									,ftell(proc_out)<filelength(fileno(proc_out)) ? '-' : ' '
					else if(!fexist(msgtxt_fname) || !fexist(rcptlst_fname)) {
						lprintf(LOG_NOTICE,"%04d %s %s External mail processor (%s) removed %s file"
							,socket, client.protocol, client_id
							,fexist(msgtxt_fname)==FALSE ? "message text" : "recipient list");
						sockprintf(socket,client.protocol,session,ok_rsp);
						sockprintf(socket,client.protocol,session,ok_rsp);
					remove(proc_err_fname);	/* Remove error file here */
				/* We must do this before continuing for handled msgs */
				/* to prevent freopen(NULL) and orphaned temp files */
				if((rcptlst=fopen(rcptlst_fname,fexist(rcptlst_fname) ? "r":"w+"))==NULL) {
					lprintf(LOG_ERR,"%04d %s %s !ERROR %d re-opening recipient list: %s"
						,socket, client.protocol, client_id, errno, rcptlst_fname);
						sockprintf(socket,client.protocol,session,smtp_error, "fopen error");
				if(!msg_handled && subnum==INVALID_SUB && iniReadSectionCount(rcptlst,NULL) < 1) {
					lprintf(LOG_DEBUG,"%04d %s %s No recipients in recipient list file (message handled by external mail processor?)"
						,socket, client.protocol, client_id);
					sockprintf(socket,client.protocol,session,ok_rsp);
					msg_handled=TRUE;
				}
				if(msg_handled) {
					if(mailproc!=NULL)
						lprintf(LOG_NOTICE,"%04d %s %s Message handled by external mail processor (%s, %lu total)"
							,socket, client.protocol, client_id, mailproc->name, ++mailproc->handled);
				/* If mailproc has written new message text to .new file, use that instead of .msg */
				if(flength(newtxt_fname) > 0) {
					remove(msgtxt_fname);
					SAFECOPY(msgtxt_fname, newtxt_fname);
				} else
					remove(newtxt_fname);
				if((msgtxt=fopen(msgtxt_fname,"rb"))==NULL) {
					lprintf(LOG_ERR,"%04d %s %s !ERROR %d re-opening message file: %s"
						,socket, client.protocol, client_id, errno, msgtxt_fname);
					sockprintf(socket,client.protocol,session,smtp_error, "fopen error");
					continue;
				}

				/* Initialize message header */
				smb_freemsgmem(&msg);
				memset(&msg,0,sizeof(smbmsg_t));		

				/* Parse message header here */
				smb_error=SMB_SUCCESS; /* no SMB error */
					if(!fgets(buf,sizeof(buf),msgtxt))
						break;
					truncsp(buf);
					if(buf[0]==0)	/* blank line marks end of header */
						break;

					if((p=get_header_field(buf, field, sizeof(field)))!=NULL) {
							/* SPAM Filtering/Logging */
							if(relay_user.number==0) {
								if(trashcan(&scfg,p,"subject")) {
									lprintf(LOG_NOTICE,"%04d %s %s !BLOCKED SUBJECT (%s) from: %s (%lu total)"
										,socket, client.protocol, client_id, p, reverse_path, ++stats.msgs_refused);
									SAFEPRINTF2(tmp,"Blocked subject (%s) from: %s"
										,p, reverse_path);
									spamlog(&scfg, (char*)client.protocol, "REFUSED"
										,tmp, host_name, host_ip, rcpt_addr, reverse_path);
									errmsg="554 Subject not allowed.";
									smb_error=SMB_FAILURE;
									break;
								}
								if(dnsbl_result.s_addr && startup->dnsbl_tag[0] && !(startup->options&MAIL_OPT_DNSBL_IGNORE)) {
									safe_snprintf(str,sizeof(str),"%.*s: %.*s"
										,(int)sizeof(str)/2, startup->dnsbl_tag
										,(int)sizeof(str)/2, p);
									p=str;
									lprintf(LOG_NOTICE,"%04d %s %s TAGGED MAIL SUBJECT from blacklisted server with: %s"
										,socket, client.protocol, client_id, startup->dnsbl_tag);
							smb_hfield_str(&msg, hfield_type=RFC822SUBJECT, p);
						if(relay_user.number==0	&& stricmp(field, "FROM")==0
							&& !chk_email_addr(socket, client.protocol,p,host_name,host_ip,rcpt_addr,reverse_path,"FROM")) {
							errmsg="554 Sender not allowed.";
							smb_error=SMB_FAILURE;
							break;
						}
						if(relay_user.number==0 && stricmp(field, "TO")==0 && !spam_bait_result
							&& !chk_email_addr(socket, client.protocol,p,host_name,host_ip,rcpt_addr,reverse_path,"TO")) {
							errmsg="550 Unknown user.";
							smb_error=SMB_FAILURE;
							break;
						if(stricmp(field, "X-Spam-Flag") == 0 && stricmp(p, "Yes") == 0)
							msg.hdr.attr |= MSG_SPAM;	/* e.g. flagged by SpamAssasin */
					if((smb_error=parse_header_field((char*)buf, &msg, &hfield_type))!=SMB_SUCCESS) {
						if(smb_error==SMB_ERR_HDR_LEN)
							lprintf(LOG_WARNING,"%04d %s %s !MESSAGE HEADER EXCEEDS %u BYTES"
								,socket, client.protocol, client_id, SMB_MAX_HDR_LEN);
							lprintf(LOG_ERR,"%04d %s %s !ERROR %d adding header field: %s"
								,socket, client.protocol, client_id, smb_error, buf);
				if(smb_error!=SMB_SUCCESS) {	/* SMB Error */
					sockprintf(socket,client.protocol,session, "%s", errmsg);
				hfield_t* hfield;
				if((p=smb_get_hfield(&msg, RFC822TO, &hfield))!=NULL) {
					char* np = strdup(p);
					if(np != NULL) {
						if(mimehdr_value_decode(np, &msg))
							smb_hfield_str(&msg, RECIPIENTLIST, np);
						else
							hfield->type = RECIPIENTLIST;
						parse_mail_address(np
							,rcpt_name	,sizeof(rcpt_name)-1
							,rcpt_addr	,sizeof(rcpt_addr)-1);
						free(np);
					}
				}
				if((p=smb_get_hfield(&msg, RFC822FROM, NULL))!=NULL) {
					parse_mail_address(p 
						,sender		,sizeof(sender)-1
						,sender_addr,sizeof(sender_addr)-1);
					// We only support MIME-encoded name portion of 'name <user@addr>'
					mimehdr_value_decode(sender, &msg);
				dnsbl_recvhdr=FALSE;
				if(startup->options&MAIL_OPT_DNSBL_CHKRECVHDRS)  {
					for(i=0;!dnsbl_result.s_addr && i<msg.total_hfields;i++)  {
						if(msg.hfield[i].type == SMTPRECEIVED)  {
							if(chk_received_hdr(socket, client.protocol,msg.hfield_dat[i],&dnsbl_result,dnsbl,dnsbl_ip)) {
				if(relay_user.number==0 && dnsbl_result.s_addr && !(startup->options&MAIL_OPT_DNSBL_IGNORE)) {
						safe_snprintf(str,sizeof(str),"%s: %s is listed on %s as %s"
							,startup->dnsbl_hdr, dnsbl_ip
							,dnsbl, inet_ntoa(dnsbl_result));
						smb_hfield_str(&msg, RFC822HEADER, str);
						lprintf(LOG_NOTICE,"%04d %s %s TAGGED MAIL HEADER from blacklisted server with: %s"
							,socket, client.protocol, client_id, startup->dnsbl_hdr);
					if(startup->dnsbl_hdr[0] || startup->dnsbl_tag[0]) {
						SAFEPRINTF2(str,"Listed on %s as %s", dnsbl, inet_ntoa(dnsbl_result));
						spamlog(&scfg, (char*)client.protocol, "TAGGED", str, host_name, dnsbl_ip, rcpt_addr, reverse_path);
				if(dnsbl_recvhdr)			/* DNSBL-listed IP found in Received header? */
					dnsbl_result.s_addr=0;	/* Reset DNSBL look-up result between messages */
				if((scfg.sys_misc&SM_DELREADM)
					|| ((startup->options&MAIL_OPT_KILL_READ_SPAM) && (msg.hdr.attr&MSG_SPAM)))
					msg.hdr.attr |= MSG_KILLREAD;
					lprintf(LOG_WARNING,"%04d %s %s !MISSING mail header 'FROM' field (%lu total)"
						,socket, client.protocol, client_id, ++stats.msgs_refused);
					sockprintf(socket,client.protocol,session, "554 Mail header missing 'FROM' field");
				if(relay_user.number == 0
					&& strchr(sender, '@') != NULL
					&& compare_addrs(sender, sender_addr) != 0) {
					lprintf(LOG_WARNING,"%04d %s %s !FORGED mail header 'FROM' field (%lu total)"
						,socket, client.protocol, client_id, ++stats.msgs_refused);
					sockprintf(socket,client.protocol,session, "554 Mail header contains mismatched 'FROM' field");
					subnum=INVALID_SUB;
					continue;
				}
					SAFEPRINTF(str,"%u",relay_user.number);
					smb_hfield_str(&msg, SENDEREXT, str);
					SAFEPRINTF2(sender_info, "'%s' #%u", sender, relay_user.number);
				} else if(compare_addrs(sender, sender_addr) == 0) {
					angle_bracket(sender_info, sizeof(sender_info), sender_addr);
					safe_snprintf(sender_info, sizeof(sender_info), "'%s' %s", sender, angle_bracket(tmp, sizeof(tmp), sender_addr));
				}
				if(relay_user.number && subnum!=INVALID_SUB) {
					nettype=NET_NONE;
					smb_hfield_str(&msg, SENDER, relay_user.alias);
				} else {
					nettype=NET_INTERNET;
					smb_hfield_str(&msg, SENDER, sender);
					smb_hfield(&msg, SENDERNETTYPE, sizeof(nettype), &nettype);
					smb_hfield_str(&msg, SENDERNETADDR, sender_addr);
				}
				smb_hfield_str(&msg, SMTPREVERSEPATH, reverse_path);
				if((p = smb_get_hfield(&msg, RFC822ORG, &hfield)) != NULL) {
					char* np = strdup(p);
					if(np != NULL) {
						if(mimehdr_value_decode(np, &msg))
							smb_hfield_str(&msg, SENDERORG, np);
						else
							hfield->type = SENDERORG;
						free(np);
					}
				}
				if((p = smb_get_hfield(&msg, RFC822CC, &hfield)) != NULL) {
					char* np = strdup(p);
					if(np != NULL) {
						if(mimehdr_value_decode(np, &msg))
							smb_hfield_str(&msg, SMB_CARBONCOPY, np);
						else
							hfield->type = SMB_CARBONCOPY;
						free(np);
					}
				}
				if((p = smb_get_hfield(&msg, RFC822REPLYTO, &hfield)) != NULL) {
					char* np = strdup(p);
					if(np != NULL) {
						if(mimehdr_value_decode(np, &msg))
							smb_hfield_str(&msg, REPLYTOLIST, np);
						else
							hfield->type = REPLYTOLIST;
						free(np);
					}
				}
				if((p = smb_get_hfield(&msg, RFC822SUBJECT, &hfield)) != NULL) {
					char* np = strdup(p);
					if(np != NULL) {
						if(mimehdr_value_decode(np, &msg))
							smb_hfield_str(&msg, SUBJECT, np);
						else
							hfield->type = SUBJECT;
						free(np);
					}
				}
				else
					smb_hfield(&msg, SUBJECT, 0, NULL);
				length=filelength(fileno(msgtxt))-ftell(msgtxt);

				if(startup->max_msg_size && length>startup->max_msg_size) {
Rob Swindell's avatar
Rob Swindell committed
					lprintf(LOG_WARNING,"%04d %s %s !Message size (%lu) from %s to <%s> exceeds maximum: %u bytes"
						,socket, client.protocol, client_id, length, sender_info, rcpt_addr, startup->max_msg_size);
					sockprintf(socket,client.protocol,session, "552 Message size (%lu) exceeds maximum: %u bytes"
						,length,startup->max_msg_size);
					subnum=INVALID_SUB;
				if((msgbuf=(char*)malloc(length+1))==NULL) {
					lprintf(LOG_CRIT,"%04d %s %s !ERROR allocating %lu bytes of memory"
						,socket, client.protocol, client_id, length+1);
					sockprintf(socket,client.protocol,session, insuf_stor);
					subnum=INVALID_SUB;
					continue;
				}
				fread(msgbuf,length,1,msgtxt);
				msgbuf[length]=0;	/* ASCIIZ */

				/* Do external JavaScript processing here? */

				if(subnum!=INVALID_SUB) {	/* Message Base */
					if(relay_user.number==0)
						memset(&relay_user,0,sizeof(relay_user));

rswindell's avatar
rswindell committed
					if(!can_user_post(&scfg,subnum,&relay_user,&client,&reason)) {
						lprintf(LOG_WARNING,"%04d %s %s !%s (user #%u) cannot post on %s (reason: %u)"
							,socket, client.protocol, client_id, sender_addr, relay_user.number
							,scfg.sub[subnum]->sname, reason + 1);
						sockprintf(socket,client.protocol,session,"550 Insufficient access");
					if(rcpt_name[0]==0)
						strcpy(rcpt_name,"All");
					smb_hfield_str(&msg, RECIPIENT, rcpt_name);
					if((i=savemsg(&scfg, &smb, &msg, &client, server_host_name(), msgbuf, /* remsg: */NULL))!=SMB_SUCCESS) {
						lprintf(LOG_WARNING,"%04d %s %s !ERROR %d (%s) %s posting message to %s (%s)"
							,socket, client.protocol, client_id, i, smb.last_error, sender_info, scfg.sub[subnum]->sname, smb.file);
						sockprintf(socket,client.protocol,session, "452 ERROR %d (%s) posting message"
						lprintf(LOG_INFO,"%04d %s %s %s posted a message on %s (%s)"
							,socket, client.protocol, client_id, sender_info, scfg.sub[subnum]->sname, smb.file);
						sockprintf(socket,client.protocol,session,ok_rsp);
rswindell's avatar
rswindell committed
						if(relay_user.number != 0)
							user_posted_msg(&scfg, &relay_user, 1);
					}
					free(msgbuf);
					smb_close(&smb);
					subnum=INVALID_SUB;
					continue;
				}

				/* Create/check hashes of known SPAM */
				{
					hash_t**	hashes;
					BOOL		is_spam=spam_bait_result;

					if((dnsbl_recvhdr || dnsbl_result.s_addr) && startup->options&MAIL_OPT_DNSBL_SPAMHASH)
						is_spam=TRUE;

					lprintf(LOG_DEBUG,"%04d %s %s Calculating message hashes (sources=%lx, msglen=%lu)"
						,socket, client.protocol, client_id, sources, (ulong)strlen(msgbuf));
					if((hashes=smb_msghashes(&msg, (uchar*)msgbuf, sources)) != NULL) {
							lprintf(LOG_DEBUG,"%04d %s %s Message %s crc32=%x flags=%x length=%u"
								,socket, client.protocol, client_id, smb_hashsourcetype(hashes[i]->source)
								,hashes[i]->crc32, hashes[i]->flags, hashes[i]->length);

						lprintf(LOG_DEBUG, "%04d %s %s Searching SPAM database for a match", socket, client.protocol, client_id);
						if((i=smb_findhash(&spam, hashes, &found, sources, /* Mark: */TRUE))==SMB_SUCCESS) {
							SAFEPRINTF3(str,"%s (%s) found in SPAM database (added on %s)"
								,smb_hashsourcetype(found.source)
								,smb_hashsource(&msg,found.source)
							lprintf(LOG_NOTICE,"%04d %s %s Message from %s %s", socket, client.protocol, client_id, sender_info, str);
								spamlog(&scfg, (char*)client.protocol, "IGNORED"
									,str, host_name, host_ip, rcpt_addr, reverse_path);
								is_spam=TRUE;
							}
							lprintf(LOG_DEBUG, "%04d %s %s Done searching SPAM database", socket, client.protocol, client_id);
								lprintf(LOG_ERR,"%04d %s %s !ERROR %d (%s) opening SPAM database"
									,socket, client.protocol, client_id, i, spam.last_error);
						if(is_spam) {
							size_t	n,total=0;
							for(n=0;hashes[n]!=NULL;n++)
								if(!(hashes[n]->flags&SMB_HASH_MARKED)) {
									lprintf(LOG_INFO,"%04d %s %s Adding message %s (%s) from %s to SPAM database"
										,socket, client.protocol, client_id
										,smb_hashsourcetype(hashes[n]->source)
										,smb_hashsource(&msg,hashes[n]->source)
								lprintf(LOG_DEBUG,"%04d %s %s Adding %lu message hashes to SPAM database", socket, client.protocol, client_id, (ulong)total);
								smb_addhashes(&spam, hashes, /* skip_marked: */TRUE);
							}
							if(i!=SMB_SUCCESS && !spam_bait_result && (dnsbl_recvhdr || dnsbl_result.s_addr))
								is_spam=FALSE;
						}
						smb_close_hash(&spam);

						smb_freehashes(hashes);
Rob Swindell's avatar
Rob Swindell committed
						lprintf(LOG_ERR,"%04d %s %s !smb_msghashes returned NULL", socket, client.protocol, client_id);
rswindell's avatar
rswindell committed
					if(is_spam || ((startup->options&MAIL_OPT_DNSBL_IGNORE) && (dnsbl_recvhdr || dnsbl_result.s_addr))) {
						free(msgbuf);
rswindell's avatar
rswindell committed
						if(is_spam)
Rob Swindell's avatar
Rob Swindell committed
							lprintf(LOG_NOTICE,"%04d %s %s !IGNORED SPAM MESSAGE from %s to <%s> (%lu total)"
								,socket, client.protocol, client_id, sender_info, rcpt_addr, ++stats.msgs_ignored);
rswindell's avatar
rswindell committed
						else {
							SAFEPRINTF2(str,"Listed on %s as %s", dnsbl, inet_ntoa(dnsbl_result));
Rob Swindell's avatar
Rob Swindell committed
							lprintf(LOG_NOTICE,"%04d %s %s !IGNORED MAIL from %s to <%s> from server: %s (%lu total)"
								,socket, client.protocol, client_id, sender_info, rcpt_addr, str, ++stats.msgs_ignored);
							spamlog(&scfg, (char*)client.protocol, "IGNORED"
								,str, host_name, dnsbl_ip, rcpt_addr, reverse_path);
rswindell's avatar
rswindell committed
						}
						/* pretend we received it */
						sockprintf(socket,client.protocol,session,ok_rsp);
						subnum=INVALID_SUB;
						continue;
					}
				}

				char rcpt_info[512];
				if(compare_addrs(rcpt_name, rcpt_addr) == 0)
					angle_bracket(rcpt_info, sizeof(rcpt_info), rcpt_addr);
					safe_snprintf(rcpt_info, sizeof(rcpt_info), "'%s' %s", rcpt_name, angle_bracket(tmp, sizeof(tmp), rcpt_addr));
				lprintf(LOG_DEBUG,"%04d %s %s Saving message data from %s to %s"
					,socket, client.protocol, client_id, sender_info, rcpt_info);
				pthread_mutex_lock(&savemsg_mutex);
				/* creates message data, but no header or index records (since msg.to==NULL) */
				i=savemsg(&scfg, &smb, &msg, &client, server_host_name(), msgbuf, /* remsg: */NULL);
				if(smb_countattachments(&smb, &msg, msgbuf) > 0)
					msg.hdr.auxattr |= MSG_MIMEATTACH;
					pthread_mutex_unlock(&savemsg_mutex);
					lprintf(LOG_CRIT,"%04d %s %s !ERROR %d (%s) saving message from %s to %s"
						,socket, client.protocol, client_id, i, smb.last_error, sender_info, rcpt_info);
					sockprintf(socket,client.protocol,session, "452 ERROR %d (%s) saving message"
				lprintf(LOG_DEBUG,"%04d %s %s Saved message data from %s to %s"
					,socket, client.protocol, client_id, sender_info, rcpt_info);
				sec_list=iniReadSectionList(rcptlst,NULL);	/* Each section is a recipient */
				for(rcpt_count=0; sec_list!=NULL
					&& sec_list[rcpt_count]!=NULL 
rswindell's avatar
rswindell committed
					&& (startup->max_recipients==0 || rcpt_count<startup->max_recipients); rcpt_count++) {
					SAFECOPY(rcpt_to,iniReadString(rcptlst,section	,smb_hfieldtype(RECIPIENT),"unknown",value));
					usernum=iniReadInteger(rcptlst,section				,smb_hfieldtype(RECIPIENTEXT),0);
					agent=iniReadShortInt(rcptlst,section				,smb_hfieldtype(RECIPIENTAGENT),AGENT_PERSON);
					nettype=iniReadShortInt(rcptlst,section				,smb_hfieldtype(RECIPIENTNETTYPE),NET_NONE);
					SAFEPRINTF(str,"#%u",usernum);
					SAFECOPY(rcpt_addr,iniReadString(rcptlst,section	,smb_hfieldtype(RECIPIENTNETADDR),str,value));
					SAFECOPY(forward_path, iniReadString(rcptlst, section, smb_hfieldtype(SMTPFORWARDPATH), "", value));
					if(compare_addrs(rcpt_to, rcpt_addr) == 0)
						angle_bracket(rcpt_info, sizeof(rcpt_info), rcpt_addr);
						safe_snprintf(rcpt_info, sizeof(rcpt_info), "'%s' %s", rcpt_to, angle_bracket(tmp, sizeof(tmp), rcpt_addr));
					if(nettype==NET_NONE /* Local destination */ && usernum==0) {
						lprintf(LOG_ERR,"%04d %s %s !Can't deliver mail from %s to user #0"
							,socket, client.protocol, client_id, sender_info);
						break;
					}

					if((i=smb_copymsgmem(&smb,&newmsg,&msg))!=SMB_SUCCESS) {
						lprintf(LOG_ERR,"%04d %s %s !ERROR %d (%s) copying message from %s"
							,socket, client.protocol, client_id, i, smb.last_error, sender_info);
					with_val = 0;
					if (esmtp)
						with_val |= WITH_ESMTP;
					if (auth_login)
						with_val |= WITH_AUTH;
					if (session != -1)
						with_val |= WITH_TLS;

deuce's avatar
deuce committed
						"from %s (%s [%s%s])\r\n"
						"          by %s [%s%s] (%s %s-%s) with %s\r\n"
deuce's avatar
deuce committed
						,host_name,hello_name
						,smtp.client_addr.addr.sa_family==AF_INET6?"IPv6: ":""
						,host_ip
deuce's avatar
deuce committed
						,server_addr.addr.sa_family==AF_INET6?"IPv6: ":""
						,server_ip
						,revision,PLATFORM_DESC
						,rcpt_to,msgdate(msg.hdr.when_imported,date)
					smb_hfield_add_str(&newmsg, SMTPRECEIVED, hdrfield, /* insert: */TRUE);
						newmsg.hdr.netattr |= MSG_LOCAL | MSG_KILLSENT;
						char* tp = strchr(rcpt_name, '@');
						if(tp != NULL)
							*tp = 0;
						// Remove "(ftn_addr)" portion of to name
						SAFEPRINTF(str,"(%s)", rcpt_addr);
						if((tp = strstr(rcpt_name, str)) != NULL && tp != rcpt_name) {
							*tp = 0;
							truncsp(rcpt_name);
						}
					smb_hfield_str(&newmsg, RECIPIENT, rcpt_name);
					if(forward_path[0] != 0)
						smb_hfield_str(&newmsg, SMTPFORWARDPATH, forward_path);
					if(usernum && nettype!=NET_INTERNET) {	/* Local destination or QWKnet routed */
						/* This is required for fixsmb to be able to rebuild the index */
						SAFEPRINTF(str,"%u",usernum);
						smb_hfield_str(&newmsg, RECIPIENTEXT, str);
						smb_hfield(&newmsg, RECIPIENTNETTYPE, sizeof(nettype), &nettype);
rswindell's avatar
rswindell committed
						smb_hfield_netaddr(&newmsg, RECIPIENTNETADDR, rcpt_addr, &nettype);
					if(agent!=newmsg.to_agent)
						smb_hfield(&newmsg, RECIPIENTAGENT, sizeof(agent), &agent);
					add_msg_ids(&scfg, &smb, &newmsg, /* remsg: */NULL);
					lprintf(LOG_DEBUG,"%04d %s %s Adding message header from %s to %s"
						,socket, client.protocol, client_id, sender_info, rcpt_info);
					i=smb_addmsghdr(&smb,&newmsg,smb_storage_mode(&scfg, &smb));
						lprintf(LOG_ERR,"%04d %s %s !ERROR %d (%s) adding message header from %s to %s"