/*
smtp.c - MessageWall SMTP definitions
Copyright (C) 2002 Ian Gulliver

This program is free software; you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License as
published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <time.h>
#include <firestring.h>
#include <firedns.h>
#include "messagewall.h"
#include "dbase.h"
#include "client.h"
#include "rfc822.h"
#include "mime.h"
#include "dnsbl.h"
#include "dnsdcc.h"
#include "rdns.h"
#include "rmx.h"
#include "auth.h"
#include "tls.h"
#include "smtp.h"

static const char tagstring[] = "$Id: smtp.c,v 1.92.2.3 2002/10/16 14:00:29 ian Exp $";

#define SMTP_RESET \
	clients[client].have_from = 0; \
	clients[client].num_to = 0;

const char *months[] = {
	        "Jan", "Feb", "Mar", "Apr", "May",  "Jun", "Jul",  "Aug",  "Sep", "Oct", "Nov", "Dec"
};

int smtp_parseline(int client) {
	int i;
	struct firestring_estr_t line;

	/*
	 * find the newline
	 */
	i = firestring_estr_strstr(&clients[client].buffer,"\r\n",0);
	if (i == -1) {
		if (firestring_estr_strchr(&clients[client].buffer,'\n',0) != -1) {
			/*
			 * bare LF
			 */
			fprintf(stderr,"{%d} (%d) SMTP/FATAL: client sent bare LF in command\n",process,client);
			tls_client_write(client,SMTP_LF,sizeof(SMTP_LF) - 1);
			smtp_clear(client);
		}
		return 1;
	}

	/*
	 * create the new line object
	 */
	line.a = line.l = i;
	line.s = clients[client].buffer.s;

	if (smtp_handle_line(client,&line) != 0)
		return 1;

	line.a = line.l = i;
	line.s = clients[client].buffer.s;

	/*
	 * fold the buffer back on itself
	 * or into the message buffer, if we've just entered the DATA phase
	 */

	if (clients[client].in_data == 1) {
		firestring_estr_estrcat(&clients[client].message,&clients[client].buffer,line.l + 2);
		clients[client].buffer.l = 0;
	} else {
		clients[client].buffer.l -= line.l + 2;
		memmove(clients[client].buffer.s,&clients[client].buffer.s[line.l + 2],clients[client].buffer.l);
	}

	return 0;
}

int smtp_parsedata(int client) {
	time(&clients[client].lasttalk);

	for (; clients[client].message_point < clients[client].message.l - 2; clients[client].message_point++) {
		if (clients[client].message.s[clients[client].message_point] == '\n') {
			if (clients[client].message_point == 0 || clients[client].message.s[clients[client].message_point - 1] != '\r') {
				if (clients[client].bare_lf == 0) {
					fprintf(stderr,"{%d} (%d) SMTP/FATAL: client sent bare LF inside DATA\n",process,client);
					tls_client_write(client,SMTP_LF,sizeof(SMTP_LF) - 1);
					clients[client].bare_lf = 1;
				}
			}
		} else if (sevenbit == 1 && (unsigned char) clients[client].message.s[clients[client].message_point] > 127) {
			if (clients[client].bare_lf == 0) {
				fprintf(stderr,"{%d} (%d) SMTP/FATAL: client sent 8bit data to server in 7bit mode\n",process,client);
				tls_client_write(client,SMTP_7BIT,sizeof(SMTP_7BIT) - 1);
				clients[client].bare_lf = 1;
			}
		} else if (clients[client].message.s[clients[client].message_point] == '.') {
			if (clients[client].message_point != 0 && clients[client].message.s[clients[client].message_point - 1] != '\n')
				continue;

			if (clients[client].message.s[clients[client].message_point+1] == '\r' &&
				clients[client].message.s[clients[client].message_point+2] == '\n') {
				/*
			 	* end of data section
			 	*/
	
				/*
			 	* copy remaining message back into buffer, erroring if they've pipelined too much
				* (pipelining isn't actually legal here, but who knows)
			 	*/
				if (clients[client].message.l - clients[client].message_point - 2 > SMTP_LINE_MAXLEN) {
					if (clients[client].bare_lf == 0)
						tls_client_write(client,SMTP_TOOLONG,sizeof(SMTP_TOOLONG) - 1);
					smtp_clear(client);
					return 0;
				}
				firestring_estr_estrcpy(&clients[client].buffer,&clients[client].message,clients[client].message_point + 3);
	
				/*
			 	* truncate message to end
				* smtp messages always end with the newline that starts the delimeter
			 	*/
				clients[client].message.l = clients[client].message_point;

				fprintf(stderr,"{%d} (%d) SMTP/STATUS: received message of %d bytes\n",process,client,clients[client].message.l);

				return smtp_checks_gotmessage(client);
			} else {
				/*
				 * smtp quote
				 */
				memmove(&clients[client].message.s[clients[client].message_point],&clients[client].message.s[clients[client].message_point+1],clients[client].message.l - clients[client].message_point - 1);
				clients[client].message.l--;
			}
		}
	}

	return 1;
}

int smtp_reject(int client, const char *facility, const char *logmessage, const char *clientmessage, int score, int hard, void *var1, void *var2) {
	static char buffer[1024];
	int i,j;
	int code;
	firestring_snprintf(buffer,1024,logmessage,var1,var2);
	if (hard == 1) {
		fprintf(stderr,"{%d} (%d) %s/REJECT: %s\n",process,client,facility,buffer);
		i = firestring_snprintf(buffer,1024,clientmessage,var1,var2);
		tls_client_write(client,buffer,i);
		clients[client].in_data = 0;
		clients[client].have_from = 0;
		clients[client].num_to = 0;
		clients[client].send_progress = 0;
		smtp_message_clear(client);
		return 1;
	}
	fprintf(stderr,"{%d} (%d) %s/WARNING: %s\n",process,client,facility,buffer);
	clients[client].score += score;
	clients[client].points[clients[client].num_warnings] = score;
	firestring_estr_sprintf(&clients[client].warnings[clients[client].num_warnings++],&clientmessage[17],var1,var2);
	if (clients[client].score >= clients[client].profile->reject_score || clients[client].num_warnings == SMTP_MAX_WARNINGS - 1) {
		if (clients[client].score >= clients[client].profile->reject_score) {
			fprintf(stderr,"{%d} (%d) %s/REJECT: Message score (%d) has reached or exceeded maximum (%d)\n",process,client,facility,clients[client].score,clients[client].profile->reject_score);
			code = atoi(clientmessage);
			i = firestring_snprintf(buffer,1024,"%d-MessageWall: Message score (%d) has reached or exceeded maximum (%d):\r\n",code,clients[client].score,clients[client].profile->reject_score);
			tls_client_write(client,buffer,i);
		} else {
			fprintf(stderr,"{%d} (%d) %s/REJECT: Number of warnings (%d) has reached maximum\n",process,client,facility,clients[client].num_warnings);
			i = firestring_snprintf(buffer,1024,"552-MessageWall: Number of warnings (%d) has reached maximum:\r\n",clients[client].num_warnings);
			tls_client_write(client,buffer,i);
			code = 552;
		}
		for (j = 0; j < clients[client].num_warnings; j++) {
			i = firestring_snprintf(buffer,1024,"%d-%5d %e",code,clients[client].points[j],&clients[client].warnings[j]);
			tls_client_write(client,buffer,i);
		}
		i = firestring_snprintf(buffer,1024,"%d MessageWall: This message is being rejected\r\n",code);
		tls_client_write(client,buffer,i);
		clients[client].in_data = 0;
		clients[client].have_from = 0;
		clients[client].num_to = 0;
		clients[client].send_progress = 0;
		smtp_message_clear(client);
		return 1;
	}
	return 0;
}

int smtp_checks_gotmessage(int client) {
	int numparts;

	/*
	 * clear dnsdcc queries for client
	 */
	dnsdcc_clear_queries(client);

	if (clients[client].bare_lf == 1) {
		fprintf(stderr,"{%d} (%d) SMTP/REJECT: delayed reject from bare LF or 8bit error\n",process,client);
		clients[client].in_data = 0;
		clients[client].have_from = 0;
		clients[client].num_to = 0;
		clients[client].send_progress = 0;
		return 0;
	}

	/* 
	 * parse MIME parts
	 */
	numparts = -1;
	if (mime_parse(client, 0,&numparts,-1,&clients[client].message,clients[client].profile) == 1)
		return 0;

	if (clients[client].parts[0].visible == 0) {
		/*
		* useless
		*/
		smtp_reject(client,"MIME","message contains no parts accepted at this address",SMTP_USELESS,1,1,NULL,NULL);
		return 0;
	}

	/*
	 * to_cc check
	 */
	if (clients[client].profile->to_cc_check == 1 && rfc822_to_cc_check(&clients[client].parts[0].message,&clients[client].to[0]) != 0) {
		if (smtp_reject(client,"RFC822","%e: envelope forward path not in To/CC",SMTP_TO_CC,clients[client].profile->to_cc_score,0,&clients[client].to[0],NULL) != 0)
			return 0;
	}

	/*
	 * from check
	 */
	if (clients[client].profile->from_check == 1 && rfc822_from_check(&clients[client].parts[0].message,&clients[client].from) != 0) {
		if (smtp_reject(client,"RFC822","%e: envelope reverse path not in From",SMTP_FROM,clients[client].profile->from_score,0,&clients[client].from,NULL) != 0)
			return 0;
	}

	/*
	 * realname check
	 */
	if (clients[client].profile->realname_check == 1 && rfc822_realname_check(&clients[client].parts[0].message) != 0) {
		struct firestring_estr_t *value;
		value = rfc822_header_value(&clients[client].parts[0].message,"From:");
		if (smtp_reject(client,"RFC822","%e: From: field does not contain a real name",SMTP_REALNAME,clients[client].profile->realname_score,0,value,NULL) != 0)
			return 0;
	}

	/*
	 * header rejection checks
	 */
	if (rfc822_header_reject_check(client) == 1 || rfc822_header_rejecti_check(client) == 1)
		return 0;

	if (rmx_check_profile(client) != -1)
		return smtp_checks_gotRMX(client,0);
	else {
		clients[client].send_progress = PROGRESS_WAITRMX;
		return 0;
	}
}

int smtp_checks_gotRMX(int client, int timedout) {
	if (rmx_check_profile(client) != 0) {
		if (timedout == 1) {
			if (smtp_reject(client,"RMX","%e: reverse path domain has no MX or A records (temporary)",SMTP_RMX_TEMP,clients[client].profile->rmx_score,0,&clients[client].fromdomain,NULL) == 1)
				return 0;
		} else {
			if (smtp_reject(client,"RMX","%e: reverse path domain has no MX or A records",SMTP_RMX,clients[client].profile->rmx_score,0,&clients[client].fromdomain,NULL) == 1)
				return 0;
		}
	}

	if (dnsbl_domain_check_profile(client,0) != -1)
		return smtp_checks_gotDNSBL_DOMAIN(client);
	else {
		clients[client].send_progress = PROGRESS_WAITDNSBL_DOMAIN;
		return 0;
	}
}

int smtp_checks_gotDNSBL_DOMAIN(int client) {
	if (dnsbl_domain_check_profile(client,1) == 1)
		return 0;

	if (rdns_check_profile(client) != -1)
		return smtp_checks_gotRDNS(client,0);
	else {
		clients[client].send_progress = PROGRESS_WAITRDNS;
		return 0;
	}
}

int smtp_checks_gotRDNS(int client, int timedout) {
	if (rdns_check_profile(client) != 0) {
		if (timedout == 1) {
			if (smtp_reject(client,"RDNS","%s: IP has no reverse DNS (temporary)",SMTP_RDNS_TEMP,clients[client].profile->rdns_score,0,firedns_ntoa4(&clients[client].ip),NULL) == 1)
				return 0;
		} else {
			if (smtp_reject(client,"RDNS","%s: IP has no reverse DNS",SMTP_RDNS,clients[client].profile->rdns_score,0,firedns_ntoa4(&clients[client].ip),NULL) == 1)
				return 0;
		}
	}
	
	if (dnsbl_check_profile(client,0) != -1)
		return smtp_checks_gotDNSBL(client);
	else {
		clients[client].send_progress = PROGRESS_WAITDNSBL;
		return 0;
	}
}

int smtp_checks_gotDNSBL(int client) {
	if (dnsbl_check_profile(client,1) == 1)
		return 0;

	if (dnsdcc_check_profile(client,0) != -1) {
		return smtp_checks_gotDNSDCC(client);
	} else {
		clients[client].send_progress = PROGRESS_WAITDNSDCC;
		return 0;
	}
}

int smtp_checks_gotDNSDCC(int client) {
	if (dnsdcc_check_profile(client,1) == 1)
		return 0;

	clients[client].send_progress = 0;
	return smtp_start_send(client);
}

int smtp_start_send(int client) {
	int i,o;
	/*
	 * reserve a backend
	 */
	i = smtp_get_backend();
	if (i == -1) {
		smtp_reject(client,"BACKEND","none available to receive message",SMTP_NO_BACKEND,1,1,NULL,NULL);
		return 0;
	}

	/*
	 * build the message
	 */
	backends[i].message.l = 0;
	o = 0;
	mime_build_message(client,i,&o);

	/*
	 * it passes, start sending it
	 */
	client_start(i,client);
	clients[client].in_data = 0;
	clients[client].have_from = 0;
	return 0;
}

/*
 * checks before allowing message transmission to begin
 * (determine DATA response)
 */
int smtp_checks_gotDATA(int client) {
	if (clients[client].profile->reject == 1) {
		smtp_reject(client,"PROFILE","reject set in profile",SMTP_REJECT,1,1,NULL,NULL);
		return 1;
	}

	return 0;
}

int smtp_handle_line(int client, struct firestring_estr_t *line) {
	static struct firestring_estr_t outline = {0};
	struct firestring_estr_t rcptdomain;
	struct firestring_estr_t username, password;
	int i;
	int atsign;
	int l;
	int j;

	if (outline.a == 0)
		firestring_estr_alloc(&outline,SMTP_LINE_MAXLEN);

	time(&clients[client].lasttalk);

	if (clients[client].auth_status != AUTH_STATUS_NONE) {
		switch (clients[client].auth_status) {
			case AUTH_STATUS_LOGIN_USER:
				if (mime_base64(line,&clients[client].username) == 1) {
					clients[client].username.l = 0;
					clients[client].auth_status = AUTH_STATUS_NONE;
					fprintf(stderr,"{%d} (%d) AUTH/FAIL: Bad credential format\n",process,client);
					tls_client_write(client,SMTP_AUTH_BADFORMAT,sizeof(SMTP_AUTH_BADFORMAT) - 1);
					return 0;
				}
				clients[client].auth_status = AUTH_STATUS_LOGIN_PASS;
				tls_client_write(client,SMTP_AUTH_LOGIN_PASS,sizeof(SMTP_AUTH_LOGIN_PASS) - 1);
				break;
			case AUTH_STATUS_LOGIN_PASS:
				clients[client].auth_status = AUTH_STATUS_NONE;
				if (mime_base64(line,&clients[client].password) == 1) {
					clients[client].username.l = 0;
					fprintf(stderr,"{%d} (%d) AUTH/FAIL: Bad credential format\n",process,client);
					tls_client_write(client,SMTP_AUTH_BADFORMAT,sizeof(SMTP_AUTH_BADFORMAT) - 1);
					return 0;
				}
				firestring_estr_0(&clients[client].username);
				if (auth_check_password(&clients[client].username,&clients[client].password) == 0) {
					fprintf(stderr,"{%d} (%d) AUTH/SUCCEED: Password accepted via LOGIN for '%s'\n",process,client,clients[client].username.s);
					tls_client_write(client,SMTP_AUTH_SUCCEED,sizeof(SMTP_AUTH_SUCCEED) - 1);
					clients[client].can_relay = 1;
				} else {
					clients[client].username.l = 0;
					fprintf(stderr,"{%d} (%d) AUTH/FAIL: Password rejected via LOGIN for '%s'\n",process,client,clients[client].username.s);
					tls_client_write(client,SMTP_AUTH_FAIL,sizeof(SMTP_AUTH_FAIL) - 1);
				}
				memset(clients[client].password.s,0,clients[client].password.l);
				return 0;
				break;
			case AUTH_STATUS_PLAIN:
				clients[client].auth_status = AUTH_STATUS_NONE;
				/*
				 * ignore anything before the first nil
				 */
auth_plain_jumpin:
				if (mime_base64(line,&outline) == 1) {
					fprintf(stderr,"{%d} (%d) AUTH/FAIL: Bad credential format\n",process,client);
					tls_client_write(client,SMTP_AUTH_BADFORMAT,sizeof(SMTP_AUTH_BADFORMAT) - 1);
					return 0;
				}
				i = firestring_estr_strchr(&outline,'\0',0);
				if (i == -1) {
					fprintf(stderr,"{%d} (%d) AUTH/FAIL: Bad credential format\n",process,client);
					tls_client_write(client,SMTP_AUTH_BADFORMAT,sizeof(SMTP_AUTH_BADFORMAT) - 1);
					return 0;
				}
				i++;
				j = firestring_estr_strchr(&outline,'\0',i);
				if (j == -1) {
					fprintf(stderr,"{%d} (%d) AUTH/FAIL: Bad credential format\n",process,client);
					tls_client_write(client,SMTP_AUTH_BADFORMAT,sizeof(SMTP_AUTH_BADFORMAT) - 1);
					return 0;
				}
				username.l = username.a = j - i;
				username.s = &outline.s[i];
				j++;
				password.l = password.a = outline.l - j;
				password.s = &outline.s[j];
				if (auth_check_password(&username,&password) == 0) {
					firestring_estr_estrcpy(&clients[client].username,&username,0);
					fprintf(stderr,"{%d} (%d) AUTH/SUCCEED: Password accepted via PLAIN for '%s'\n",process,client,username.s);
					tls_client_write(client,SMTP_AUTH_SUCCEED,sizeof(SMTP_AUTH_SUCCEED) - 1);
					clients[client].can_relay = 1;
				} else {
					fprintf(stderr,"{%d} (%d) AUTH/FAIL: Password rejected via PLAIN for '%s'\n",process,client,username.s);
					tls_client_write(client,SMTP_AUTH_FAIL,sizeof(SMTP_AUTH_FAIL) - 1);
				}
				memset(password.s,0,password.l);
				return 0;
				break;
		}
		return 0;
	}

	if (firestring_estr_starts(line,"HELO ") == 0) {
		SMTP_RESET
		tls_client_write(client,SMTP_OK,sizeof(SMTP_OK) - 1);
	} else if (firestring_estr_starts(line,"EHLO ") == 0) {
		SMTP_RESET
		firestring_estr_sprintf(&outline,"250-MessageWall: Hello\r\n"
		                                 "250-PIPELINING\r\n"
		                                 "%s"
						 "%s"
						 "%s"
		                                 "250 SIZE %l\r\n",
						 sevenbit == 1 ? "" : "250-8BITMIME\r\n",
						 relay_auth == NULL ? "" : "250-AUTH LOGIN PLAIN\r\n"
						                           "250-AUTH=LOGIN PLAIN\r\n",
						 cert == NULL ? "" : "250-STARTTLS\r\n",
						 max_message_size);
		tls_client_write(client,outline.s,outline.l);
	} else if (firestring_estr_strcasecmp(line,"STARTTLS") == 0) {
		if (cert != NULL && tls_start(client) == 0) {
			fprintf(stderr,"{%d} (%d) TLS/STATUS: TLS negotiation started\n",process,client);
			tls_client_write(client,SMTP_TLS_OK,sizeof(SMTP_TLS_OK) - 1);
		} else {
			fprintf(stderr,"{%d} (%d) TLS/STATUS: TLS startup failed\n",process,client);
			tls_client_write(client,SMTP_TLS_ERROR,sizeof(SMTP_TLS_ERROR) - 1);
		}
	} else if (firestring_estr_strcasecmp(line,"AUTH LOGIN") == 0) {
		tls_client_write(client,SMTP_AUTH_LOGIN_USER,sizeof(SMTP_AUTH_LOGIN_USER) - 1);
		clients[client].auth_status = AUTH_STATUS_LOGIN_USER;
	} else if (firestring_estr_starts(line,"AUTH PLAIN") == 0) {
		if (line->l == 10) {
			tls_client_write(client,SMTP_AUTH_PLAIN,sizeof(SMTP_AUTH_PLAIN) - 1);
			clients[client].auth_status = AUTH_STATUS_PLAIN;
		} else {
			line->s += 10;
			line->l -= 10;
			goto auth_plain_jumpin;
		}
	} else if (firestring_estr_starts(line,"MAIL FROM:") == 0) {
		if (clients[client].have_from != 0) {
			tls_client_write(client,SMTP_OOO,sizeof(SMTP_OOO) - 1);
			return 0;
		}

		smtp_message_clear(client);

		/*
		 * find end of address
		 */
		j = firestring_estr_strchr(line,'<',10);
		if (j == -1) {
			fprintf(stderr,"{%d} (%d) SMTP/REJECT: no < in MAIL FROM line\n",process,client);
			if (smtp_error(client) == 0) {
				SMTP_RESET
				tls_client_write(client,SMTP_INVALIDPATH,sizeof(SMTP_INVALIDPATH) - 1);
				return 0;
			} else
				return 1;
		}
		j++;
		l = firestring_estr_strchr(line,'>',j);
		if (l == -1) {
			fprintf(stderr,"{%d} (%d) SMTP/REJECT: no > in MAIL FROM line\n",process,client);
			if (smtp_error(client) == 0) {
				SMTP_RESET
				tls_client_write(client,SMTP_INVALIDPATH,sizeof(SMTP_INVALIDPATH) - 1);
				return 0;
			} else
				return 1;
		}
			

		/*
		 * validate character set
		 */
		atsign = 0;
		clients[client].fromdomain.l = 0;
		for (i = j; i < l; i++) {
			/*
			 * RFC violation:
			 * restrict to one @ sign
			 */
			if (line->s[i] == '@') {
				firestring_estr_estrcpy(&clients[client].fromdomain,line,i+1);
				clients[client].fromdomain.l -= (line->l - l);
				atsign++;
			}
			if (strchr(path_charset,line->s[i]) == NULL || atsign == 2) {
				/*
				 * invalid character
				 */
				firestring_estr_0(line);
				fprintf(stderr,"{%d} (%d) SMTP/REJECT: %s: invalid MAIL character (%c)\n",process,client,line->s,line->s[i]);
				tls_client_write(client,SMTP_BADCHAR,sizeof(SMTP_BADCHAR) - 1);
				return 0;
			}
		}
		firestring_estr_0(&clients[client].fromdomain);


		if (l - 11 > SMTP_PATH_MAXLEN || l - j > clients[client].from.a) {
			fprintf(stderr,"{%d} (%d) SMTP/REJECT: MAIL path too long\n",process,client);
			tls_client_write(client,SMTP_BADCHAR,sizeof(SMTP_BADCHAR) - 1);
			return 0;
		}

		/*
		 * we just checked this size; this is safe
		 */
		memcpy(clients[client].from.s,&line->s[j],l - j); /* ITS4: ignore memcpy */
		clients[client].from.l = l - j;
		clients[client].have_from = 1;
		tls_client_write(client,SMTP_OK,sizeof(SMTP_OK) - 1);
		rmx_send_queries(client);
		dnsbl_domain_send_queries(client);
	} else if (firestring_estr_starts(line,"RCPT TO:") == 0 && firestring_estr_ends(line,">") == 0) {
		if (clients[client].have_from == 0) {
			tls_client_write(client,SMTP_OOO,sizeof(SMTP_OOO) - 1);
			return 0;
		}

		/*
		 * find end of address
		 */
		j = firestring_estr_strchr(line,'<',8);
		if (j == -1) {
			/*
			 * no < sign
			 */
			fprintf(stderr,"{%d} (%d) SMTP/REJECT: no < in RCPT TO line\n",process,client);
			if (smtp_error(client) == 0) {
				SMTP_RESET
				tls_client_write(client,SMTP_INVALIDPATH,sizeof(SMTP_INVALIDPATH) - 1);
				return 0;
			} else
				return 1;
		}
		j++;
		l = firestring_estr_strchr(line,'>',j);
		if (l == -1) {
			fprintf(stderr,"{%d} (%d) SMTP/REJECT: no > in RCPT TO line\n",process,client);
			if (smtp_error(client) == 0) {
				SMTP_RESET
				tls_client_write(client,SMTP_INVALIDPATH,sizeof(SMTP_INVALIDPATH) - 1);
				return 0;
			} else
				return 1;
		}

		/* 
		 * check that we have recipient space
		 */
		if (clients[client].can_relay == 1) {
			if (clients[client].num_to == max_rcpt) {
				fprintf(stderr,"{%d} (%d) SMTP/REJECT: too many RCPT\n",process,client);
				SMTP_RESET
				tls_client_write(client,SMTP_MAXRCPT,sizeof(SMTP_MAXRCPT) - 1);
				return 0;
			}
		} else {
			if (clients[client].num_to == 1) {
				fprintf(stderr,"{%d} (%d) SMTP/TEMPORARY: external host attempted multiple recipient delivery, asked for one at a time\n",process,client);
				tls_client_write(client,SMTP_ONLYONE,sizeof(SMTP_ONLYONE) - 1);
				return 0;
			}
		}

		/*
		 * validate character set
		 */
		atsign = 0;
		for (i = j; i < l; i++) {
			/*
			 * RFC violation:
			 * restrict to one @ sign
			 */
			if (line->s[i] == '@')
				atsign++;
			if (strchr(path_charset,line->s[i]) == NULL || atsign == 2) {
				/*
				 * invalid character
				 */
				firestring_estr_0(line);
				fprintf(stderr,"{%d} (%d) SMTP/REJECT: %s: invalid RCPT character (%c)\n",process,client,line->s,line->s[i]);
				tls_client_write(client,SMTP_BADCHAR,sizeof(SMTP_BADCHAR) - 1);
				return 0;
			}
		}

		/*
		 * one @ sign is required
		 */
		if (atsign != 1) {
			fprintf(stderr,"{%d} (%d) SMTP/REJECT: too many @ signs in RCPT\n",process,client);
			tls_client_write(client,SMTP_BADCHAR,sizeof(SMTP_BADCHAR) - 1);
			return 0;
		}

		/*
		 * if client isn't permitted to relay, check for local domain
		 */
		if (clients[client].can_relay == 0) {
			i = firestring_estr_strchr(line,'@',j);
			rcptdomain.s = &line->s[i + 1];
			rcptdomain.a = rcptdomain.l = l - i - 1;
			if (dbase_domain_is_local(&rcptdomain) != 0) {
				smtp_reject(client,"SMTP","relaying denied to '%e'",SMTP_NORELAY,1,1,&rcptdomain,NULL);
				return 0;
			}
		}

		if (l - j > SMTP_PATH_MAXLEN || l - j > clients[client].to[clients[client].num_to].a) {
			fprintf(stderr,"{%d} (%d) SMTP/REJECT: RCPT path too long\n",process,client);
			tls_client_write(client,SMTP_BADCHAR,sizeof(SMTP_BADCHAR) - 1);
			return 0;
		}

		/*
		 * we just checked this length; this is safe
		 */
		memcpy(clients[client].to[clients[client].num_to].s,&line->s[j],l - j); /* ITS4: ignore memcpy */
		clients[client].to[clients[client].num_to].l = l - j;
		clients[client].num_to++;
		tls_client_write(client,SMTP_OK,sizeof(SMTP_OK) - 1);
	} else if (firestring_estr_strcasecmp(line,"DATA") == 0) {
		struct firestring_estr_t blank = {0};
		struct tm *timestruct;
		time_t temptime;

		if (clients[client].have_from == 0 || clients[client].num_to == 0) {
			tls_client_write(client,SMTP_OOO,sizeof(SMTP_OOO) - 1);
			return 0;
		}

		/*
		 * discover the profile that will apply to this message
		 */
		if (clients[client].can_relay == 1) {
			clients[client].profile = profile_relay;
		} else {
			/*
			 * delivery to local user from outside
			 */
			unsigned int u;
			int atsign;
			struct messagewall_special_user_t *special_user;

			atsign = firestring_estr_strchr(&clients[client].to[0],'@',0);
			atsign++;
			u = dbase_text_hash(&clients[client].to[0],0);
			special_user = special_user_hash[u];
			while (special_user != NULL) {
				if (firestring_estr_estrcasecmp(&clients[client].to[0],&special_user->address,0) == 0)
					break;
				special_user = special_user->next;
			}
			if (special_user == NULL) {
				/*
				 * no specific address match, check for domain match
				 */
				u = dbase_text_hash(&clients[client].to[0],atsign);
				special_user = special_user_hash[u];
				while (special_user != NULL) {
					if (firestring_estr_estrcasecmp(&clients[client].to[0],&special_user->address,atsign) == 0)
						break;
					special_user = special_user->next;
				}
			}
			if (special_user == NULL)
				/*
				 * not a special user
				 */
				clients[client].profile = profile_default;
			else
				clients[client].profile = special_user->profile;
		}
		fprintf(stderr,"{%d} (%d) SMTP/STATUS: Message start: %s\n",process,client,clients[client].profile->name);
		firestring_estr_0(&clients[client].from);
		fprintf(stderr,"{%d} (%d) SMTP/INFO: From: %s\n",process,client,clients[client].from.s);
		for (i = 0; i < clients[client].num_to; i++) {
			firestring_estr_0(&clients[client].to[i]);
			fprintf(stderr,"{%d} (%d) SMTP/INFO: To: %s\n",process,client,clients[client].to[i].s);
		}
		clients[client].num_warnings = 0;
		clients[client].score = 0;
		clients[client].bare_lf = 0;

		/*
		 * run prechecks
		 */
		if (smtp_checks_gotDATA(client) != 0)
			return 0;

		clients[client].in_data = 1;
		tls_client_write(client,SMTP_START_DATA,sizeof(SMTP_START_DATA) - 1);

		time(&temptime);
		timestruct = gmtime(&temptime);

		clients[client].message_point = firestring_estr_sprintf(&clients[client].message,"Received: from [%s] %s%e%sby %s (MessageWall " MESSAGEWALL_VERSION ") with SMTP%s%s%s; %d %s %d %02d:%02d:%02d -0000\r\n",firedns_ntoa4(&clients[client].ip),
				clients[client].username.l == 0 ? "" : "(authenticated as ",
				clients[client].username.l == 0 ? &blank : &clients[client].username,
				clients[client].username.l == 0 ? "" : ") ",
				domain,
				clients[client].ssl == NULL ? "" : " (encrypted with ",
				clients[client].ssl == NULL ? "" : SSL_get_cipher(clients[client].ssl),
				clients[client].ssl == NULL ? "" : ")",
				timestruct->tm_mday,months[timestruct->tm_mon],1900 + timestruct->tm_year,timestruct->tm_hour,timestruct->tm_min,timestruct->tm_sec);

	} else if (firestring_estr_strcasecmp(line,"RSET") == 0) {
		SMTP_RESET
		tls_client_write(client,SMTP_OK,sizeof(SMTP_OK) - 1);
	} else if (firestring_estr_strcasecmp(line,"NOOP") == 0) {
		tls_client_write(client,SMTP_OK,sizeof(SMTP_OK) - 1);
	} else if (firestring_estr_starts(line,"VRFY ") == 0) {
		fprintf(stderr,"{%d} (%d) SMTP/STATUS: VRFY from client (refused)\n",process,client);
		tls_client_write(client,SMTP_VRFY,sizeof(SMTP_VRFY) - 1);
	} else if (firestring_estr_strcasecmp(line,"QUIT") == 0) {
		fprintf(stderr,"{%d} (%d) SMTP/FATAL: client QUIT\n",process,client);
		firestring_estr_sprintf(&outline,"221 %s MessageWall: Goodbye\r\n",domain);
		tls_client_write(client,outline.s,outline.l);
		smtp_clear(client);
		return 1;
	} else {
		firestring_estr_sprintf(&outline,"{%d} (%d) SMTP/STATUS: Invalid command: '%e'\n",process,client,line);
		write(2,outline.s,outline.l);
		if (smtp_error(client) == 0) {
			firestring_estr_sprintf(&outline,"500 MessageWall: Unrecognized command\r\n");
			tls_client_write(client,outline.s,outline.l);
		} else
			return 1;
	}
	return 0;
}

int smtp_get_backend() {
	int i;

	for (i = 0; i < max_backends; i++)
		if (backends[i].state == BACKEND_STATE_IDLE)
			return i;

	return -1;
}

int smtp_error(int client) {
	clients[client].errors++;
	if (clients[client].errors > max_errors) {
		fprintf(stderr,"{%d} (%d) SMTP/FATAL: too many errors\n",process,client);
		tls_client_write(client,SMTP_ERRORS,sizeof(SMTP_ERRORS) - 1);
		smtp_clear(client);
		return 1;
	}

	return 0;
}

int smtp_clear(int client) {
	int i;

	smtp_message_clear(client);

	if (clients[client].rdns_fd >= 0) {
		firedns_getresult(clients[client].rdns_fd);
		clients[client].rdns_fd = -1;
	}

	for (i = 0; i < dnsbls; i++) {
		if (clients[client].dnsbl_fd[i] >= 0) {
			firedns_getresult(clients[client].dnsbl_fd[i]);
			clients[client].dnsbl_fd[i] = -1;
		}
	}

	if (clients[client].ssl != NULL) {
		SSL_free(clients[client].ssl);
		clients[client].ssl = NULL;
	}

	if (clients[client].fd != -1) {
		close(clients[client].fd);
		clients[client].fd = -1;
	}

	return 0;
}

int smtp_message_clear(int client) {
	int i,j;

	if (clients[client].rmx_fd >= 0) {
		firedns_getresult(clients[client].rmx_fd);
		clients[client].rmx_fd = -1;
	}

	if (clients[client].rmx_a_fd >= 0) {
		firedns_getresult(clients[client].rmx_a_fd);
		clients[client].rmx_a_fd = -1;
	}

	for (i = 0; i < dnsbls_domain; i++) {
		if (clients[client].dnsbl_domain_fd[i] >= 0) {
			firedns_getresult(clients[client].dnsbl_domain_fd[i]);
			clients[client].dnsbl_domain_fd[i] = -1;
		}
	}

	for (i = 0; i < max_parts; i++) {
		for (j = 0; j < dnsdccs; j++) {
			if (clients[client].dnsdcc_fd[i][j] >= 0) {
				firedns_getresult(clients[client].dnsdcc_fd[i][j]);
				clients[client].dnsdcc_fd[i][j] = -1;
			}
		}
	}

	return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1