Skip to content
Snippets Groups Projects
mailsrvr.c 209 KiB
Newer Older
					smb_freemsg_dfields(&smb,&msg,SMB_ALL_REFS);
					sockprintf(socket,client.protocol,session, insuf_stor);
						smb_incmsg_dfields(&smb,&msg,(ushort)(rcpt_count-1));
					sockprintf(socket,client.protocol,session,ok_rsp);
#if 0 /* This shouldn't be necessary here */
				continue;
			}
			if(buf[0]==0 && state==SMTP_STATE_DATA_HEADER) {	
				state=SMTP_STATE_DATA_BODY;	/* Null line separates header and body */
				if(msgtxt!=NULL) {
					fprintf(msgtxt, "\r\n");
				continue;
			}
			if(state==SMTP_STATE_DATA_BODY) {
				p=buf;
				if(*p=='.') p++;	/* Transparency (RFC821 4.5.2) */
				if(msgtxt!=NULL) {
					switch(content_encoding) {
						case ENCODING_BASE64:
							{
								char	decode_buf[sizeof(buf)];

								if(b64_decode(decode_buf, sizeof(decode_buf), p, strlen(p))<0)
									fprintf(msgtxt,"\r\n!Base64 decode error: %s\r\n", p);
								else
									fputs(decode_buf, msgtxt);
							}
							break;
						case ENCODING_QUOTED_PRINTABLE:
							fputs(qp_decode(p), msgtxt);
							break;
						default:
							fprintf(msgtxt, "%s\r\n", p);
							break;
					}
				}
				/* release time-slices every x lines */
				if(startup->lines_per_yield &&
					!(lines%startup->lines_per_yield))	
				if((lines%100) == 0 && (msgtxt != NULL))
					lprintf(LOG_DEBUG,"%04d %s %s received %lu lines (%lu bytes) of body text"
						,socket, client.protocol, client_id, lines, ftell(msgtxt)-hdr_len);
				continue;
			}
			/* RFC822 Header parsing */
			strip_char(buf, buf, '\r');	/* There should be no bare carriage returns in header fields */
			if(startup->options&MAIL_OPT_DEBUG_RX_HEADER)
				lprintf(LOG_DEBUG,"%04d %s %s %s",socket, client.protocol, client_id, buf);
			{
				char field[32];

				if((p=get_header_field(buf, field, sizeof(field)))!=NULL) {
					if(stricmp(field, "FROM")==0) {
						parse_mail_address(p
							,sender,		sizeof(sender)-1
							,sender_addr,	sizeof(sender_addr)-1);
					}
					else if(stricmp(field,"CONTENT-TRANSFER-ENCODING")==0) {
						lprintf(LOG_INFO,"%04d %s %s %s = %s", socket, client.protocol, client_id, field, p);
						if(stricmp(p,"base64")==0)
							content_encoding=ENCODING_BASE64;
						else if(stricmp(p,"quoted-printable")==0)
							content_encoding=ENCODING_QUOTED_PRINTABLE;
						else {	/* Other (e.g. 7bit, 8bit, binary) */
							content_encoding=ENCODING_NONE;
							if(msgtxt!=NULL) 
								fprintf(msgtxt, "%s\r\n", buf);
						}
						hdr_lines++;
						continue;
					}
				}
			}
			if(msgtxt!=NULL) 
				fprintf(msgtxt, "%s\r\n", buf);
		lprintf(LOG_DEBUG,"%04d %s %s RX: %s", socket, client.protocol, client_id, buf);
		if(!strnicmp(buf,"HELO",4)) {
			p=buf+4;
			SAFECOPY(hello_name,p);
			sockprintf(socket,client.protocol,session,"250 %s",server_host_name());
			esmtp=FALSE;
			state=SMTP_STATE_HELO;
rswindell's avatar
rswindell committed
			cmd=SMTP_CMD_NONE;
			continue;
		}
		if(!strnicmp(buf,"EHLO",4)) {
			p=buf+4;
			SAFECOPY(hello_name,p);
			sockprintf(socket,client.protocol,session,"250-%s",server_host_name());
			sockprintf(socket,client.protocol,session,"250-AUTH PLAIN LOGIN CRAM-MD5");
			sockprintf(socket,client.protocol,session,"250-SEND");
			sockprintf(socket,client.protocol,session,"250-SOML");
			sockprintf(socket,client.protocol,session,"250-SAML");
			sockprintf(socket,client.protocol,session,"250-8BITMIME");
			if (session == -1)
				sockprintf(socket,client.protocol,session,"250-STARTTLS");
				sockprintf(socket,client.protocol,session,"250-SIZE %u", startup->max_msg_size);
			sockprintf(socket,client.protocol,session,ok_rsp);
			esmtp=TRUE;
			state=SMTP_STATE_HELO;
rswindell's avatar
rswindell committed
			cmd=SMTP_CMD_NONE;
		if((auth_login=(stricmp(buf,"AUTH LOGIN")==0))==TRUE 
			|| strnicmp(buf,"AUTH PLAIN",10)==0) {
			char user_pass[128] = "";
			ZERO_VAR(relay_user);
			listRemoveTaggedNode(&current_logins, socket, /* free_data */TRUE);
				sockprintf(socket,client.protocol,session,"334 VXNlcm5hbWU6");	/* Base64-encoded "Username:" */
				if((rd=sockreadline(socket, client.protocol, session, buf, sizeof(buf)))<1) {
					lprintf(LOG_WARNING,"%04d %s %s !Missing AUTH LOGIN username argument", socket, client.protocol, client_id);
					badlogin(socket, session, client.protocol, badarg_rsp, NULL, NULL, host_name, &smtp.client_addr);
					continue;
				}
				if(startup->options&MAIL_OPT_DEBUG_RX_RSP) 
					lprintf(LOG_DEBUG,"%04d %s %s RX: %s", socket, client.protocol, client_id, buf);
				if(b64_decode(user_name,sizeof(user_name),buf,rd)<1 || str_has_ctrl(user_name)) {
					lprintf(LOG_WARNING,"%04d %s %s !Bad AUTH LOGIN username argument", socket, client.protocol, client_id);
					badlogin(socket, session, client.protocol, badarg_rsp, NULL, NULL, host_name, &smtp.client_addr);
				sockprintf(socket,client.protocol,session,"334 UGFzc3dvcmQ6");	/* Base64-encoded "Password:" */
				if((rd=sockreadline(socket, client.protocol, session, buf, sizeof(buf)))<1) {
					lprintf(LOG_WARNING,"%04d %s %s !Missing AUTH LOGIN password argument", socket, client.protocol, client_id);
					badlogin(socket, session, client.protocol, badarg_rsp, user_name, NULL, host_name, &smtp.client_addr);
					continue;
				}
				if(startup->options&MAIL_OPT_DEBUG_RX_RSP) 
					lprintf(LOG_DEBUG,"%04d %s %s RX: %s", socket, client.protocol, client_id, buf);
				if(b64_decode(user_pass,sizeof(user_pass),buf,rd)<1 || str_has_ctrl(user_pass)) {
					lprintf(LOG_WARNING,"%04d %s %s !Bad AUTH LOGIN password argument", socket, client.protocol, client_id);
					badlogin(socket, session, client.protocol, badarg_rsp, user_name, NULL, host_name, &smtp.client_addr);
			} else {	/* AUTH PLAIN b64(<username>\0<user-id>\0<password>) */
				p=buf+10;
				SKIP_WHITESPACE(p);
				if(*p==0) {
					lprintf(LOG_WARNING,"%04d %s %s !Missing AUTH PLAIN argument", socket, client.protocol, client_id);
					badlogin(socket, session, client.protocol, badarg_rsp, NULL, NULL, host_name, &smtp.client_addr);
				if(b64_decode(tmp,sizeof(tmp),p,strlen(p))<1 || str_has_ctrl(tmp)) {
					lprintf(LOG_WARNING,"%04d %s %s !Bad AUTH PLAIN argument", socket, client.protocol, client_id);
					badlogin(socket, session, client.protocol, badarg_rsp, NULL, NULL, host_name, &smtp.client_addr);
					continue;
				}
				p=tmp;
				while(*p) p++;	/* skip username */
				p++;			/* skip NULL */
				if(*p==0) {
					lprintf(LOG_WARNING,"%04d %s %s !Missing AUTH PLAIN user-id argument", socket, client.protocol, client_id);
					badlogin(socket, session, client.protocol, badarg_rsp, NULL, NULL, host_name, &smtp.client_addr);
					continue;
				}
				SAFECOPY(user_name,p);
				while(*p) p++;	/* skip user-id */
				p++;			/* skip NULL */
				if(*p==0) {
					lprintf(LOG_WARNING,"%04d %s %s !Missing AUTH PLAIN password argument", socket, client.protocol, client_id);
					badlogin(socket, session, client.protocol, badarg_rsp, user_name, NULL, host_name, &smtp.client_addr);
			if((relay_user.number=matchuser(&scfg,user_name,FALSE))==0) {
				if(scfg.sys_misc&SM_ECHO_PW)
					lprintf(LOG_WARNING,"%04d %s %s !UNKNOWN USER: '%s' (password: %s)"
						,socket, client.protocol, client_id, user_name, user_pass);
					lprintf(LOG_WARNING,"%04d %s %s !UNKNOWN USER: '%s'"
						,socket, client.protocol, client_id, user_name);
				badlogin(socket, session, client.protocol, badauth_rsp, user_name, user_pass, host_name, &smtp.client_addr);
			}
			if((i=getuserdat(&scfg, &relay_user))!=0) {
				lprintf(LOG_ERR,"%04d %s %s !ERROR %d getting data on user (%s)"
					,socket, client.protocol, client_id, i, user_name);
				badlogin(socket, session, client.protocol, badauth_rsp, NULL, NULL, NULL, NULL);
			}
			if(relay_user.misc&(DELETED|INACTIVE)) {
				lprintf(LOG_WARNING,"%04d %s %s !DELETED or INACTIVE user #%u (%s)"
					,socket, client.protocol, client_id, relay_user.number, user_name);
				badlogin(socket, session, client.protocol, badauth_rsp, NULL, NULL, NULL, NULL);
				break;
			}
			if(stricmp(user_pass,relay_user.pass)) {
				if(scfg.sys_misc&SM_ECHO_PW)
					lprintf(LOG_WARNING,"%04d %s %s !FAILED Password attempt for user %s: '%s' expected '%s'"
						,socket, client.protocol, client_id, user_name, user_pass, relay_user.pass);
					lprintf(LOG_WARNING,"%04d %s %s !FAILED Password attempt for user %s"
						,socket, client.protocol, client_id, user_name);
				badlogin(socket, session, client.protocol, badauth_rsp, user_name, user_pass, host_name, &smtp.client_addr);
				loginSuccess(startup->login_attempt_list, &smtp.client_addr);
				listAddNodeData(&current_logins, client.addr, strlen(client.addr) + 1, socket, LAST_NODE);
			}
			/* Update client display */
			client.user=relay_user.alias;
			client.usernum = relay_user.number;
			client_on(socket,&client,TRUE /* update */);

			lprintf(LOG_INFO,"%04d %s %s %s authenticated using %s authentication"
				,socket,client.protocol, client_id, relay_user.alias, auth_login ? "LOGIN" : "PLAIN");
			SAFEPRINTF(client_id, "<%s>", relay_user.alias);
			sockprintf(socket,client.protocol,session,auth_ok);
			continue;
		}
		if(!stricmp(buf,"AUTH CRAM-MD5")) {
			ZERO_VAR(relay_user);
			listRemoveTaggedNode(&current_logins, socket, /* free_data */TRUE);

			safe_snprintf(challenge,sizeof(challenge),"<%x%x%lx%lx@%s>"
				,rand(),socket,(ulong)time(NULL),(ulong)clock(),server_host_name());
			lprintf(LOG_DEBUG,"%04d SMTP CRAM-MD5 challenge: %s"
			b64_encode(str,sizeof(str),challenge,0);
			sockprintf(socket,client.protocol,session,"334 %s",str);
			if((rd=sockreadline(socket, client.protocol, session, buf, sizeof(buf)))<1) {
				lprintf(LOG_WARNING,"%04d %s %s !Missing AUTH CRAM-MD5 response", socket, client.protocol, client_id);
				sockprintf(socket,client.protocol,session,badarg_rsp);
			if(startup->options&MAIL_OPT_DEBUG_RX_RSP) 
				lprintf(LOG_DEBUG,"%04d %s %s RX: %s",socket, client.protocol, client_id, buf);
			if(b64_decode(response,sizeof(response),buf,rd)<1 || str_has_ctrl(response)) {
				lprintf(LOG_WARNING,"%04d %s %s !Bad AUTH CRAM-MD5 response", socket, client.protocol, client_id);
				sockprintf(socket,client.protocol,session,badarg_rsp);
			lprintf(LOG_DEBUG,"%04d SMTP CRAM-MD5 response: %s"
			if((p=strrchr(response,' '))!=NULL)
				*(p++)=0;
			else
				p=response;
			SAFECOPY(user_name,response);
			if((relay_user.number=matchuser(&scfg,user_name,FALSE))==0) {
				lprintf(LOG_WARNING,"%04d %s %s !UNKNOWN USER: '%s'"
					,socket, client.protocol, user_name, client_id);
				badlogin(socket, session, client.protocol, badauth_rsp, user_name, NULL, host_name, &smtp.client_addr);
			}
			if((i=getuserdat(&scfg, &relay_user))!=0) {
				lprintf(LOG_ERR,"%04d %s %s !ERROR %d getting data on user (%s)"
					,socket, client.protocol, client_id, i, user_name);
				badlogin(socket, session, client.protocol, badauth_rsp, NULL, NULL, NULL, NULL);
			}
			if(relay_user.misc&(DELETED|INACTIVE)) {
				lprintf(LOG_WARNING,"%04d %s %s !DELETED or INACTIVE user #%u (%s)"
					,socket, client.protocol, client_id, relay_user.number, user_name);
				badlogin(socket, session, client.protocol, badauth_rsp, NULL, NULL, NULL, NULL);
			}
			/* Calculate correct response */
			memset(secret,0,sizeof(secret));
			SAFECOPY(secret,relay_user.pass);
			strlwr(secret);	/* this is case sensitive, so convert to lowercase first */
			for(i=0;i<sizeof(secret);i++)
				md5_data[i]=secret[i]^0x36;	/* ipad */
			strcpy(md5_data+i,challenge);
			MD5_calc(digest,md5_data,sizeof(secret)+strlen(challenge));
			for(i=0;i<sizeof(secret);i++)
				md5_data[i]=secret[i]^0x5c;	/* opad */
			memcpy(md5_data+i,digest,sizeof(digest));
			MD5_calc(digest,md5_data,sizeof(secret)+sizeof(digest));
			MD5_hex((BYTE*)str,digest);
				lprintf(LOG_WARNING,"%04d SMTP %s !%s FAILED CRAM-MD5 authentication"
					,socket, client_id, relay_user.alias);
				lprintf(LOG_DEBUG,"%04d !SMTP calc digest: %s"
				lprintf(LOG_DEBUG,"%04d !SMTP resp digest: %s"
				badlogin(socket, session, client.protocol, badauth_rsp, user_name, p, host_name, &smtp.client_addr);
				loginSuccess(startup->login_attempt_list, &smtp.client_addr);
				listAddNodeData(&current_logins, client.addr, strlen(client.addr) + 1, socket, LAST_NODE);
			}
			/* Update client display */
			client.user=relay_user.alias;
			client.usernum = relay_user.number;
			client_on(socket,&client,TRUE /* update */);

			lprintf(LOG_INFO,"%04d %s %s %s authenticated using CRAM-MD5 authentication"
				,socket, client.protocol, client_id, relay_user.alias);
			SAFEPRINTF(client_id, "<%s>", relay_user.alias);
			sockprintf(socket,client.protocol,session,auth_ok);
			sockprintf(socket,client.protocol,session,"504 Unrecognized authentication type.");
		if(!stricmp(buf,"QUIT")) {
			sockprintf(socket,client.protocol,session,"221 %s Service closing transmission channel",server_host_name());
			break;
		} 
		if(!stricmp(buf,"NOOP")) {
			sockprintf(socket,client.protocol,session, ok_rsp);
			continue;
		}
		if(state<SMTP_STATE_HELO) {
			/* RFC 821 4.1.1 "The first command in a session must be the HELO command." */
			lprintf(LOG_WARNING,"%04d %s %s !MISSING 'HELO' command (Received: '%s')",socket, client.protocol, client_id, buf);
			sockprintf(socket,client.protocol,session, badseq_rsp);
			continue;
		}
		if(!stricmp(buf,"TURN")) {
			sockprintf(socket,client.protocol,session,"502 command not supported");
			continue;
		}
		if(!stricmp(buf,"RSET")) {
			smb_freemsgmem(&msg);
			memset(&msg,0,sizeof(smbmsg_t));		/* Initialize message header */
			reverse_path[0]=0;
			state=SMTP_STATE_HELO;
rswindell's avatar
rswindell committed
			cmd=SMTP_CMD_NONE;
			spam_bait_result=FALSE;
			if((rcptlst=freopen(rcptlst_fname,"w+",rcptlst))==NULL) {
				lprintf(LOG_ERR,"%04d %s %s !ERROR %d re-opening %s"
					,socket, client.protocol, client_id, errno, rcptlst_fname);
				sockprintf(socket,client.protocol,session,smtp_error, "fopen error");
rswindell's avatar
rswindell committed
			memset(mailproc_to_match,FALSE,sizeof(BOOL)*mailproc_count);
			sockprintf(socket,client.protocol,session,ok_rsp);
			lprintf(LOG_INFO,"%04d %s %s Session reset",socket, client.protocol, client_id);
		if(!strnicmp(buf,"MAIL FROM:",10)
			|| !strnicmp(buf,"SEND FROM:",10)	/* Send a Message (Telegram) to a local ONLINE user */
			|| !strnicmp(buf,"SOML FROM:",10)	/* Send OR Mail a Message to a local user */
			|| !strnicmp(buf,"SAML FROM:",10)	/* Send AND Mail a Message to a local user */
			) {
				&& !chk_email_addr(socket, client.protocol,p,host_name,host_ip,NULL,NULL,"REVERSE PATH")) {
				sockprintf(socket,client.protocol,session, "554 Sender not allowed.");
rswindell's avatar
rswindell committed
				stats.msgs_refused++;
			SAFECOPY(reverse_path,p);
			if((p=strchr(reverse_path,' '))!=NULL)	/* Truncate "<user@domain> KEYWORD=VALUE" to just "<user@domain>" per RFC 1869 */
				*p=0;
			/* If MAIL FROM address is in dnsbl_exempt.cfg, clear DNSBL results */
			if(dnsbl_result.s_addr && email_addr_is_exempt(reverse_path)) {
				lprintf(LOG_INFO,"%04d %s %s Ignoring DNSBL results for exempt sender: %s"
					,socket, client.protocol, client_id, reverse_path);
			/* Update client display */
			if(relay_user.number==0) {
				client.user=reverse_path;
				client_on(socket,&client,TRUE /* update */);
			}
rswindell's avatar
rswindell committed
			state=SMTP_STATE_MAIL_FROM;
			if(!strnicmp(buf,"MAIL FROM:",10))
				cmd=SMTP_CMD_MAIL;
			else if(!strnicmp(buf,"SEND FROM:",10))
				cmd=SMTP_CMD_SEND;
			else if(!strnicmp(buf,"SOML FROM:",10))
				cmd=SMTP_CMD_SOML;
			else if(!strnicmp(buf,"SAML FROM:",10))
				cmd=SMTP_CMD_SAML;

			/* reset recipient list */
			if((rcptlst=freopen(rcptlst_fname,"w+",rcptlst))==NULL) {
				lprintf(LOG_ERR,"%04d %s %s !ERROR %d re-opening %s"
					,socket, client.protocol, client_id, errno, rcptlst_fname);
				sockprintf(socket,client.protocol,session,smtp_error, "fopen error");
			content_encoding=ENCODING_NONE;
			memset(mailproc_to_match,FALSE,sizeof(BOOL)*mailproc_count);
			sockprintf(socket,client.protocol,session,ok_rsp);
#if 0	/* No one uses this command */
		if(!strnicmp(buf,"VRFY",4)) {
			p=buf+4;
				sockprintf(socket,client.protocol,session,"550 No user specified.");
		/* Add to Recipient list */
		if(!strnicmp(buf,"RCPT TO:",8)) {

rswindell's avatar
rswindell committed
			if(state<SMTP_STATE_MAIL_FROM) {
				lprintf(LOG_WARNING,"%04d %s %s !MISSING 'MAIL' command",socket, client.protocol, client_id);
				sockprintf(socket,client.protocol,session, badseq_rsp);
rswindell's avatar
rswindell committed
			p=buf+8;
rswindell's avatar
rswindell committed
			SAFECOPY(rcpt_to,p);
			SAFECOPY(str,p);
			truncstr(str,">");	/* was truncating at space too */
			forward=FALSE;
			no_forward=FALSE;
			if(!strnicmp(p,FORWARD,strlen(FORWARD))) {
				forward=TRUE;		/* force forward to user's netmail address */
				p+=strlen(FORWARD);
			}
			if(!strnicmp(p,NO_FORWARD,strlen(NO_FORWARD))) {
				no_forward=TRUE;	/* do not forward to user's netmail address */
				p+=strlen(NO_FORWARD);
			}

				lprintf(LOG_NOTICE,"%04d %s %s !NO RECIPIENT SPECIFIED"
					,socket, client.protocol, client_id);
				sockprintf(socket,client.protocol,session, "500 No recipient specified");
			SAFECOPY(rcpt_addr,p);
			/* Check recipient counter */
rswindell's avatar
rswindell committed
			if(startup->max_recipients) {
				if(rcpt_count>=startup->max_recipients) {
					lprintf(LOG_NOTICE,"%04d %s %s !MAXIMUM RECIPIENTS (%d) REACHED"
						,socket, client.protocol, client_id, startup->max_recipients);
rswindell's avatar
rswindell committed
					SAFEPRINTF(tmp,"Maximum recipient count (%d)",startup->max_recipients);
					spamlog(&scfg, (char*)client.protocol, "REFUSED", tmp
rswindell's avatar
rswindell committed
						,host_name, host_ip, rcpt_addr, reverse_path);
					sockprintf(socket,client.protocol,session, "452 Too many recipients");
rswindell's avatar
rswindell committed
					stats.msgs_refused++;
					continue;
				}
				if(relay_user.number!=0 && !(relay_user.exempt&FLAG('M'))
					&& rcpt_count+(waiting=getmail(&scfg,relay_user.number,/* sent: */TRUE, /* SPAM: */FALSE)) > startup->max_recipients) {
					lprintf(LOG_NOTICE,"%04d %s %s !MAXIMUM PENDING SENT EMAILS (%lu) REACHED for User #%u (%s)"
						,socket, client.protocol, client_id, waiting, relay_user.number, relay_user.alias);
					sockprintf(socket,client.protocol,session, "452 Too many pending emails sent");
rswindell's avatar
rswindell committed
					stats.msgs_refused++;
					continue;
				}
rswindell's avatar
rswindell committed
			if(relay_user.number && (relay_user.etoday+rcpt_count) >= scfg.level_emailperday[relay_user.level]
				&& !(relay_user.exempt&FLAG('M'))) {
				lprintf(LOG_NOTICE,"%04d %s %s !EMAILS PER DAY LIMIT (%u) REACHED FOR USER #%u (%s)"
					,socket, client.protocol, client_id, scfg.level_emailperday[relay_user.level], relay_user.number, relay_user.alias);
rswindell's avatar
rswindell committed
				SAFEPRINTF2(tmp,"Maximum emails per day (%u) for %s"
					,scfg.level_emailperday[relay_user.level], relay_user.alias);
				spamlog(&scfg, (char*)client.protocol, "REFUSED", tmp
rswindell's avatar
rswindell committed
					,host_name, host_ip, rcpt_addr, reverse_path);
				sockprintf(socket,client.protocol,session, "452 Too many emails today");
rswindell's avatar
rswindell committed
				stats.msgs_refused++;
				continue;
			}
			/* Check for SPAM bait recipient */
			if((spam_bait_result=findstr(rcpt_addr,spam_bait))==TRUE) {
				char	reason[256];
				SAFEPRINTF(reason,"SPAM BAIT (%s) taken", rcpt_addr);
				lprintf(LOG_NOTICE,"%04d %s %s %s by: %s"
					,socket, client.protocol, client_id, reason, reverse_path);
				if(relay_user.number==0) {
					if(dnsbl_result.s_addr==0						/* Don't double-filter */
rswindell's avatar
rswindell committed
						&& !spam_block_exempt)	{ 
						lprintf(LOG_NOTICE,"%04d %s !BLOCKING IP ADDRESS: %s in %s", socket, client.protocol, client_id, spam_block);
						filter_ip(&scfg, client.protocol, reason, host_name, host_ip, reverse_path, spam_block);
						strcat(tmp," and BLOCKED");
					}
					spamlog(&scfg, (char*)client.protocol, tmp, "Attempted recipient in SPAM BAIT list"
						,host_name, host_ip, rcpt_addr, reverse_path);
					dnsbl_result.s_addr=0;
				}
				sockprintf(socket,client.protocol,session,ok_rsp);
				state=SMTP_STATE_RCPT_TO;
				continue;
				&& !chk_email_addr(socket, client.protocol,rcpt_addr,host_name,host_ip,rcpt_addr,reverse_path,"RECIPIENT")) {
				sockprintf(socket,client.protocol,session, "550 Unknown User: %s", rcpt_to);
rswindell's avatar
rswindell committed
				stats.msgs_refused++;
			if(relay_user.number==0 && dnsbl_result.s_addr && startup->options&MAIL_OPT_DNSBL_BADUSER) {
				lprintf(LOG_NOTICE,"%04d %s %s !REFUSED MAIL from blacklisted server (%lu total)"
					,socket, client.protocol, client_id, ++stats.sessions_refused);
				SAFEPRINTF2(str,"Listed on %s as %s", dnsbl, inet_ntoa(dnsbl_result));
				spamlog(&scfg, (char*)client.protocol, "REFUSED", str, host_name, host_ip, rcpt_addr, reverse_path);
				sockprintf(socket,client.protocol,session
					,"550 Mail from %s refused due to listing at %s"
					,host_ip, dnsbl);
			if(spy==NULL 
				&& (trashcan(&scfg,reverse_path,"smtpspy")
					|| trashcan(&scfg,rcpt_addr,"smtpspy"))) {
				SAFEPRINTF2(path,"%s%sspy.txt", scfg.logs_dir, client.protocol);
			p=alias(&scfg,p,alias_buf);
			if(p==alias_buf) 
				lprintf(LOG_DEBUG,"%04d %s %s ADDRESS ALIAS: %s (for %s)"
					,socket, client.protocol, client_id, p, rcpt_addr);
rswindell's avatar
rswindell committed

			tp=strrchr(p,'@');
			if(cmd==SMTP_CMD_MAIL && tp!=NULL) {
deuce's avatar
deuce committed
				dest_port=inet_addrport(&server_addr);
				SAFECOPY(dest_host,tp+1);
rswindell's avatar
rswindell committed
				if(relay_user.number && scfg.total_faddrs) {
					char* ftn_tld = strstr(dest_host, FIDO_TLD);
					if(ftn_tld != NULL && ftn_tld[strlen(FIDO_TLD)] == 0) {
						short point, node, net, zone;

rswindell's avatar
rswindell committed
						fidoaddr_t faddr = scfg.faddr[0];
						if((sscanf(dest_host,"p%hu.f%hu.n%hu.z%hu"FIDO_TLD
							,&point
							,&node
							,&net
							,&zone)==4
rswindell's avatar
rswindell committed
							||
							sscanf(dest_host,"f%hu.n%hu.z%hu"FIDO_TLD
							,&node
							,&net
							,&zone)==3
							) && zone) {
							faddr.point = point;
							faddr.node = node;
							faddr.net = net;
							faddr.zone = zone;
							lprintf(LOG_INFO,"%04d %s %s %s relaying to FidoNet address: %s (%s)"
								,socket, client.protocol, client_id, relay_user.alias, tp+1, smb_faddrtoa(&faddr, NULL));
rswindell's avatar
rswindell committed

							fprintf(rcptlst,"[%u]\n",rcpt_count++);
							fprintf(rcptlst,"%s=%s\n",smb_hfieldtype(RECIPIENT),rcpt_addr);
							fprintf(rcptlst,"%s=%u\n",smb_hfieldtype(RECIPIENTNETTYPE),NET_FIDO);
							fprintf(rcptlst,"%s=%s\n",smb_hfieldtype(RECIPIENTNETADDR),smb_faddrtoa(&faddr,NULL));
							fprintf(rcptlst,"%s=%s\n",smb_hfieldtype(SMTPFORWARDPATH),rcpt_to);
							sockprintf(socket,client.protocol,session,ok_rsp);
rswindell's avatar
rswindell committed
							state=SMTP_STATE_RCPT_TO;
							continue;
						}
					}
				}
				cp=strrchr(dest_host,':');
				if(cp!=NULL) {
					*cp=0;
					dest_port=atoi(cp+1);
				}
				SAFEPRINTF(domain_list,"%sdomains.cfg",scfg.ctrl_dir);
				if((stricmp(dest_host,scfg.sys_inetaddr)!=0
						&& stricmp(dest_host,startup->host_name)!=0
						&& findstr(dest_host,domain_list)==FALSE)
deuce's avatar
deuce committed
					|| dest_port!=inet_addrport(&server_addr)) {
					SAFEPRINTF(relay_list,"%srelay.cfg",scfg.ctrl_dir);
					if(relay_user.number==0 /* not authenticated, search for IP */
						&& startup->options&MAIL_OPT_SMTP_AUTH_VIA_IP) { 
						relay_user.number=userdatdupe(&scfg, 0, U_IPADDR, LEN_IPADDR, host_ip, /* del */FALSE, /* next */FALSE, NULL, NULL);
						if(relay_user.number) {
							getuserdat(&scfg,&relay_user);
							if(relay_user.laston < time(NULL)-(60*60))	/* logon in past hour? */
								relay_user.number=0;
						}
					} else
						getuserdat(&scfg,&relay_user);
					if(p!=alias_buf /* forced relay by alias */ &&
						(!(startup->options&MAIL_OPT_ALLOW_RELAY)
							|| relay_user.number==0
							|| relay_user.rest&(FLAG('G')|FLAG('M'))) &&
						!findstr(host_name,relay_list) && 
						!findstr(host_ip,relay_list)) {
						lprintf(LOG_WARNING,"%04d %s %s !ILLEGAL RELAY ATTEMPT from %s [%s] to %s"
							,socket, client.protocol, client_id, reverse_path, host_ip, p);
						SAFEPRINTF(tmp,"Relay attempt to: %s", p);
						spamlog(&scfg, (char*)client.protocol, "REFUSED", tmp, host_name, host_ip, rcpt_addr, reverse_path);
						if(startup->options&MAIL_OPT_ALLOW_RELAY)
							sockprintf(socket,client.protocol,session, "553 Relaying through this server "
							"Please authenticate before sending.");
							sockprintf(socket,client.protocol,session, "550 Relay not allowed.");
					if(relay_user.number==0)
						SAFECOPY(relay_user.alias,"Unknown User");

					lprintf(LOG_INFO,"%04d %s %s %s relaying to external mail service: %s"
						,socket, client.protocol, client_id, relay_user.alias, tp+1);
					fprintf(rcptlst,"[%u]\n",rcpt_count++);
					fprintf(rcptlst,"%s=%s\n",smb_hfieldtype(RECIPIENT),rcpt_addr);
					fprintf(rcptlst,"%s=%u\n",smb_hfieldtype(RECIPIENTNETTYPE),NET_INTERNET);
					fprintf(rcptlst,"%s=%s\n",smb_hfieldtype(RECIPIENTNETADDR),p);
					fprintf(rcptlst,"%s=%s\n",smb_hfieldtype(SMTPFORWARDPATH),rcpt_to);
					sockprintf(socket,client.protocol,session,ok_rsp);
rswindell's avatar
rswindell committed
					state=SMTP_STATE_RCPT_TO;
rswindell's avatar
rswindell committed
			if(tp!=NULL)
				*tp=0;	/* truncate at '@' */
			tp=strchr(p,'!');	/* Routed QWKnet mail in <qwkid!user@host> format */
			if(tp!=NULL) {
				*(tp++)=0;
				truncstr(tp,"\"");				/* Strip '"' */
				SAFECOPY(rcpt_addr,tp);
			FIND_ALPHANUMERIC(p);				/* Skip '<' or '"' */
rswindell's avatar
rswindell committed
			truncstr(p,"\"");
			p=alias(&scfg,p,name_alias_buf);
				lprintf(LOG_DEBUG,"%04d %s %s NAME ALIAS: %s (for %s)"
					,socket, client.protocol, client_id, p, rcpt_addr);
			/* Check if message is to be processed by one or more external mail processors */
			mailproc_match = INT_MAX;	// no match, by default

				if(!mailproc_list[i].process_dnsbl && dnsbl_result.s_addr)
					continue;

				if(!mailproc_list[i].process_spam && spam_bait_result)
					continue;

				if(!chk_ar(&scfg,mailproc_list[i].ar,&relay_user,&client))
					continue;

				if(findstr_in_list(p, mailproc_list[i].to) || findstr_in_list(rcpt_addr, mailproc_list[i].to)) {
					mailproc_to_match[i]=TRUE;
					if(!mailproc_list[i].passthru)
						mailproc_match = i;
rswindell's avatar
rswindell committed
				}

			if(!strnicmp(p,"sub:",4)) {		/* Post on a sub-board */
				p+=4;
				for(i=0;i<scfg.total_subs;i++)
					if(!stricmp(p,scfg.sub[i]->code))
						break;
				if(i>=scfg.total_subs) {
					lprintf(LOG_NOTICE,"%04d %s %s !UNKNOWN SUB-BOARD: %s", socket, client.protocol, client_id, p);
					sockprintf(socket,client.protocol, session, "550 Unknown sub-board: %s", p);
				sockprintf(socket,client.protocol,session,ok_rsp);
			/* destined for a (non-passthru) external mail processor */
				fprintf(rcptlst,"[%u]\n",rcpt_count++);
				fprintf(rcptlst,"%s=%s\n",smb_hfieldtype(RECIPIENT),rcpt_addr);
				fprintf(rcptlst,"%s=%s\n",smb_hfieldtype(SMTPFORWARDPATH),rcpt_to);

#if 0	/* should we fall-through to the sysop account? */
				fprintf(rcptlst,"%s=%u\n",smb_hfieldtype(RECIPIENTEXT),1);
#endif
				lprintf(LOG_INFO,"%04d %s %s Routing mail for %s to External Mail Processor: %s"
					,socket, client.protocol, client_id, rcpt_addr, mailproc_list[mailproc_match].name);
				sockprintf(socket,client.protocol,session,ok_rsp);
			usernum=0;	/* unknown user at this point */
				SAFECOPY(qwkid,p);
				truncstr(qwkid,"/");
				/* Search QWKnet hub-IDs for route destination */
				for(i=0;i<scfg.total_qhubs;i++) {
					if(!stricmp(qwkid,scfg.qhub[i]->id))
				if(i<scfg.total_qhubs) {	/* found matching QWKnet Hub */
					lprintf(LOG_INFO,"%04d %s %s Routing mail for %s <%s> to QWKnet Hub: %s"
						,socket, client.protocol, client_id, rcpt_addr, p, scfg.qhub[i]->id);

					fprintf(rcptlst,"[%u]\n",rcpt_count++);
					fprintf(rcptlst,"%s=%s\n",smb_hfieldtype(RECIPIENT),rcpt_addr);
					fprintf(rcptlst,"%s=%u\n",smb_hfieldtype(RECIPIENTNETTYPE),NET_QWK);
					fprintf(rcptlst,"%s=%s\n",smb_hfieldtype(RECIPIENTNETADDR),p);
					fprintf(rcptlst,"%s=%s\n",smb_hfieldtype(SMTPFORWARDPATH),rcpt_to);
					sockprintf(socket,client.protocol,session,ok_rsp);
			if((p==alias_buf || p==name_alias_buf || startup->options&MAIL_OPT_ALLOW_RX_BY_NUMBER)
				&& isdigit((uchar)*p)) {
				usernum=atoi(p);			/* RX by user number */
				/* verify usernum */
				username(&scfg,usernum,str);
				if(!str[0] || !stricmp(str,"DELETED USER"))
					usernum=0;
				p=str;
			} else {
				/* RX by "user alias", "user.alias" or "user_alias" */
deuce's avatar
deuce committed
				usernum=smtp_matchuser(&scfg,p,startup->options&MAIL_OPT_ALLOW_SYSOP_ALIASES,FALSE);	

				if(!usernum) { /* RX by "real name", "real.name", or "sysop.alias" */
					
					/* convert "user.name" to "user name" */
					SAFECOPY(rcpt_name,p);
					for(tp=rcpt_name;*tp;tp++)	
						if(*tp=='.') *tp=' ';

					if(!stricmp(p,scfg.sys_op) || !stricmp(rcpt_name,scfg.sys_op))
					if(!usernum && scfg.msg_misc&MM_REALNAME)	/* RX by "real name" */
deuce's avatar
deuce committed
						usernum=smtp_matchuser(&scfg, p, FALSE, TRUE);	
					if(!usernum && scfg.msg_misc&MM_REALNAME)	/* RX by "real.name" */
deuce's avatar
deuce committed
						usernum=smtp_matchuser(&scfg, rcpt_name, FALSE, TRUE);	
			if(!usernum && startup->default_user[0]) {
				usernum=matchuser(&scfg,startup->default_user,TRUE /* sysop_alias */);
					lprintf(LOG_INFO,"%04d %s %s Forwarding mail for UNKNOWN USER to default user-recipient: '%s' #%u"
						,socket, client.protocol, client_id,startup->default_user,usernum);
					lprintf(LOG_WARNING,"%04d %s %s !UNKNOWN DEFAULT USER-RECIPIENT: '%s'"
						,socket, client.protocol, client_id, startup->default_user);
deuce's avatar
deuce committed
			if(usernum==UINT_MAX) {
				lprintf(LOG_INFO,"%04d %s %s Blocked tag: %s", socket, client.protocol, client_id, rcpt_to);
				sockprintf(socket,client.protocol,session, "550 Unknown User: %s", rcpt_to);
deuce's avatar
deuce committed
				continue;
			}
			if(!usernum) {
				lprintf(LOG_WARNING,"%04d %s %s !UNKNOWN USER-RECIPIENT: '%s'", socket, client.protocol, client_id, rcpt_to);
				sockprintf(socket,client.protocol,session, "550 Unknown User: %s", rcpt_to);
				continue;
			}
			user.number=usernum;
			if((i=getuserdat(&scfg, &user))!=0) {
				lprintf(LOG_ERR,"%04d %s %s !ERROR %d getting data on user-recipient #%u (%s)"
					,socket, client.protocol, client_id, i, usernum, p);
				sockprintf(socket,client.protocol,session, "550 Unknown User: %s", rcpt_to);
				continue;
			}
			if(user.misc&(DELETED|INACTIVE)) {
				lprintf(LOG_WARNING,"%04d %s %s !DELETED or INACTIVE user-recipient #%u (%s)"
					,socket, client.protocol, client_id, usernum, p);
				sockprintf(socket,client.protocol,session, "550 Unknown User: %s", rcpt_to);
rswindell's avatar
rswindell committed
			if(cmd==SMTP_CMD_MAIL) {
				if((user.rest&FLAG('M')) && relay_user.number==0) {
					lprintf(LOG_NOTICE,"%04d %s %s !M-restricted user-recipient #%u (%s) cannot receive unauthenticated SMTP mail"
						,socket, client.protocol, client_id, user.number, user.alias);
					sockprintf(socket,client.protocol,session, "550 Closed mailbox: %s", rcpt_to);
rswindell's avatar
rswindell committed
					stats.msgs_refused++;
					continue;
				}
				if(startup->max_msgs_waiting && !(user.exempt&FLAG('W')) 
					&& (waiting=getmail(&scfg, user.number, /* sent: */FALSE, /* spam: */FALSE)) > startup->max_msgs_waiting) {
					lprintf(LOG_NOTICE,"%04d %s %s !User-recipient #%u (%s) mailbox (%lu msgs) exceeds the maximum (%u) msgs waiting"
						,socket, client.protocol, client_id, user.number, user.alias, waiting, startup->max_msgs_waiting);
					sockprintf(socket,client.protocol,session, "450 Mailbox full: %s", rcpt_to);
rswindell's avatar
rswindell committed
					stats.msgs_refused++;
					continue;
				}
			}
			else if(cmd==SMTP_CMD_SEND) { /* Check if user online */
					getnodedat(&scfg, i+1, &node, FALSE, NULL);
					if(node.status==NODE_INUSE && node.useron==user.number
						&& !(node.misc&NODE_POFF))
						break;
				}
				if(i>=scfg.sys_nodes) {
					lprintf(LOG_WARNING,"%04d %s %s !Attempt to send telegram to unavailable user-recipient #%u (%s)"
						,socket, client.protocol, client_id, user.number, user.alias);
					sockprintf(socket,client.protocol,session,"450 User unavailable");
rswindell's avatar
rswindell committed
			if(cmd!=SMTP_CMD_MAIL)
			fprintf(rcptlst,"[%u]\n",rcpt_count++);
			fprintf(rcptlst,"%s=%s\n",smb_hfieldtype(RECIPIENT),rcpt_addr);
			fprintf(rcptlst,"%s=%u\n",smb_hfieldtype(RECIPIENTEXT),user.number);

			/* Forward to Internet */
			tp=strrchr(user.netmail,'@');
				&& tp!=NULL && smb_netaddr_type(user.netmail)==NET_INTERNET 
				&& !strstr(tp,scfg.sys_inetaddr)) {
				lprintf(LOG_INFO,"%04d %s %s Forwarding to: %s"
					,socket, client.protocol, client_id, user.netmail);
				fprintf(rcptlst,"%s=%u\n",smb_hfieldtype(RECIPIENTNETTYPE),NET_INTERNET);
				fprintf(rcptlst,"%s=%s\n",smb_hfieldtype(RECIPIENTNETADDR),user.netmail);
				sockprintf(socket,client.protocol,session,ok_rsp);	// used to be a 251 response, changed per RFC2821
			} else { /* Local (no-forward) */
				if(routed) { /* QWKnet */
					fprintf(rcptlst,"%s=%u\n",smb_hfieldtype(RECIPIENTNETTYPE),NET_QWK);
					fprintf(rcptlst,"%s=%s\n",smb_hfieldtype(RECIPIENTNETADDR),user.alias);
				}						
				fprintf(rcptlst,"%s=%s\n",smb_hfieldtype(SMTPFORWARDPATH),rcpt_to);
				sockprintf(socket,client.protocol,session,ok_rsp);
rswindell's avatar
rswindell committed
			state=SMTP_STATE_RCPT_TO;
		/* Message Data (header and body) */
		if(!strnicmp(buf,"DATA",4)) {
rswindell's avatar
rswindell committed
			if(state<SMTP_STATE_RCPT_TO) {
				lprintf(LOG_WARNING,"%04d %s %s !MISSING 'RCPT TO' command", socket, client.protocol, client_id);
				sockprintf(socket,client.protocol,session, badseq_rsp);
				fclose(msgtxt), msgtxt=NULL;
			if((msgtxt=fopen(msgtxt_fname,"w+b"))==NULL) {
				lprintf(LOG_ERR,"%04d %s %s !ERROR %d opening %s"
					,socket, client.protocol, client_id, errno, msgtxt_fname);
				sockprintf(socket,client.protocol,session, insuf_stor);
			/* These vars are potentially over-written by parsing an RFC822 header */
			/* get sender_addr */
			truncstr(sender_addr,">");
			if(truncstr(sender,"@")==NULL)
			sockprintf(socket,client.protocol,session, "354 send the mail data, end with <CRLF>.<CRLF>");
			if(telegram)
				state=SMTP_STATE_DATA_BODY;	/* No RFC headers in Telegrams */
			else
				state=SMTP_STATE_DATA_HEADER;
			lprintf(LOG_INFO,"%04d %s %s Receiving %s message from %s to <%s>"
				,socket, client.protocol, client_id, telegram ? "telegram":"mail", reverse_path, rcpt_addr);
		if(session == -1 && !stricmp(buf,"STARTTLS")) {
			if (get_ssl_cert(&scfg, &estr, &level) == -1) {
				if (estr) {
					lprintf(level, "%04d %s %s !%s", socket, client.protocol, client_id, estr);
					free_crypt_attrstr(estr);
				sockprintf(socket, client.protocol, session, "454 TLS not available");
			if ((cstat=cryptCreateSession(&session, CRYPT_UNUSED, CRYPT_SESSION_SSL_SERVER)) != CRYPT_OK) {
				GCES(cstat, "SMTPS", socket, CRYPT_UNUSED, "creating TLS session");
				sockprintf(socket, client.protocol, session, "454 TLS not available");
			if ((cstat=cryptSetAttribute(session, CRYPT_SESSINFO_SSL_OPTIONS, CRYPT_SSLOPTION_DISABLE_CERTVERIFY)) != CRYPT_OK) {
				GCES(cstat, "SMTPS", socket, session, "disabling certificate verification");
				cryptDestroySession(session);
				session = -1;
				sockprintf(socket, client.protocol, session, "454 TLS not available");
			if ((cstat=cryptSetAttribute(session, CRYPT_SESSINFO_PRIVATEKEY, scfg.tls_certificate)) != CRYPT_OK) {
				GCES(cstat, "SMTPS", socket, session, "setting private key");
				lprintf(LOG_ERR, "%04d SMTPS %s !Unable to set private key", socket, client_id);
				cryptDestroySession(session);
				session = -1;
				sockprintf(socket, client.protocol, session, "454 TLS not available");
				continue;
			}
			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, "SMTPS", socket, session, "setting network socket");
				cryptDestroySession(session);