/*
client.c - MessageWall SMTP client 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 <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <firedns.h>
#include "messagewall.h"
#include "smtp.h"
#include "tls.h"
#include "client.h"

static const char tagstring[] = "$Id: client.c,v 1.37.2.3 2002/09/21 21:17:24 ian Exp $";

int client_connect(int n) {
	/*
	 * clear up variables
	 */
	if (backends[n].ssl != NULL)
		SSL_free(backends[n].ssl);
	backends[n].state = BACKEND_STATE_NOTCONNECTED;
	backends[n].client = -1;
	backends[n].eightbit = 0;
	backends[n].pipelining = 0;
	backends[n].tls = 0;
	backends[n].auth_plain = 0;
	backends[n].buffer.l = 0;
	backends[n].ssl = NULL;

	fprintf(stderr,"{%d} [%d] BACKEND/STATUS: connect to %s started\n",process,n,firedns_ntoa4(&backendaddr.sin_addr));

	/* 
	 * start a client connection
	 */
	/*
         * this socket is created after the security content starts up;
	 * root socket attacks are a non-event
         */
	backends[n].fd = socket(PF_INET, SOCK_STREAM, 0); /* ITS4: ignore socket */
	if (backends[n].fd == -1)
		return 1;

	if (socket_nonblock(backends[n].fd) != 0) {
		close(backends[n].fd);
		return 1;
	}

	if (connect(backends[n].fd,(struct sockaddr *)&backendaddr,sizeof(backendaddr)) != 0 && errno != EINPROGRESS) {
		close(backends[n].fd);
		return 1;
	}

	backends[n].state = BACKEND_STATE_CONNECTING;

	return 0;
}

int client_read_event(int n) {
	struct firestring_estr_t *line;
	static struct firestring_estr_t outline = {0};
	int i;

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

	switch (backends[n].state) {
		case BACKEND_STATE_CONNECTING:
			backends[n].state = BACKEND_STATE_GREETING;
			break;
		case BACKEND_STATE_GREETING:
			if (tls_backend_eread(n,&backends[n].buffer) != 0) {
				close(backends[n].fd);
				backends[n].state = BACKEND_STATE_NOTCONNECTED;
				return 1;
			}
			while (1) {
				line = client_get_line(n);
				if (line == NULL)
					return 0;
				if (line->l < 3 || line->s[0] != '2') {
					close(backends[n].fd);
					backends[n].state = BACKEND_STATE_NOTCONNECTED;
					return 1;
				}
				if (line->l >= 4 && line->s[3] == '-') {
					/* 
				 	* continued line
				 	*/
					client_fold_back(n,line->l);
					continue;
				}
	
				/*
			 	* got a pleasant greeting 
			 	*/
				client_fold_back(n,line->l);
				firestring_estr_sprintf(&outline,"EHLO %s\r\n",domain);
				tls_backend_write(n,outline.s,outline.l);
				backends[n].state = BACKEND_STATE_EHLO_RESPONSE;
				return 0;
			}
		case BACKEND_STATE_EHLO_RESPONSE:
			if (tls_backend_eread(n,&backends[n].buffer) != 0) {
				close(backends[n].fd);
				backends[n].state = BACKEND_STATE_NOTCONNECTED;
				return 1;
			}
			while (1) {
				line = client_get_line(n);
				if (line == NULL)
					return 0;
				if (line->l >= 12 && firestring_strncasecmp(&line->s[4],"8BITMIME",8) == 0) {
					/*
				 	* we've got 8bit mime support
				 	*/
					backends[n].eightbit = 1;
				}
				if (line->l >= 14 && firestring_strncasecmp(&line->s[4],"PIPELINING",10) == 0) {
					/*
				 	* we've got pipelining support
				 	*/
					backends[n].pipelining = 1;
				}
				if (line->l >= 12 && firestring_strncasecmp(&line->s[4],"STARTTLS",8) == 0) {
					/*
					 * we've got TLS support
					 */
					backends[n].tls = 1;
				}
				if (line->l >= 14 && firestring_strncasecmp(&line->s[4],"AUTH ",5) == 0 && firestring_estr_stristr(line,"PLAIN",9) != -1) {
					/*
					 * we've got AUTH PLAIN support
					 */
					backends[n].auth_plain = 1;
				}
				if (line->l >= 4 && line->s[3] == '-') {
					/* 
				 	* continued line
				 	*/
					client_fold_back(n,line->l);
					continue;
				}
				if (line->l < 3 || line->s[0] != '2') {
					fprintf(stderr,"{%d} [%d] BACKEND/WARNING: missing ESMTP support; please upgrade your MTA\n",process,n);
					client_fold_back(n,line->l);
					firestring_estr_sprintf(&outline,"HELO %s\r\n",domain);
					tls_backend_write(n,outline.s,outline.l);
					backends[n].state = BACKEND_STATE_HELO_RESPONSE;
					return 0;
				}
				if (backends[n].pipelining == 0)
					fprintf(stderr,"{%d} [%d] BACKEND/WARNING: missing PIPELINING support; backend conversations will be *much* slower\n",process,n);
				if (sevenbit == 0 && backends[n].eightbit == 0) {
					fprintf(stderr,"{%d} [%d] BACKEND/FATAL: missing 8BITMIME suport; get a modern MTA or set 7bit in the config file to override\n",process,n);
					exit(100);
				}
				client_fold_back(n,line->l);
				if (backend_cert != NULL && backends[n].tls == 1 && backends[n].ssl == NULL) {
					backends[n].state = BACKEND_STATE_STARTTLS_RESPONSE;
					tls_backend_write(n,"STARTTLS\r\n",10);
				} else {
					backends[n].state = BACKEND_STATE_IDLE;
					fprintf(stderr,"{%d} [%d] BACKEND/STATUS: connection established\n",process,n);
				}
				return 0;
			}
		case BACKEND_STATE_HELO_RESPONSE:
			if (tls_backend_eread(n,&backends[n].buffer) != 0) {
				close(backends[n].fd);
				backends[n].state = BACKEND_STATE_NOTCONNECTED;
				return 1;
			}
			while (1) {
				line = client_get_line(n);
				if (line == NULL)
					return 0;
				if (line->l < 3 || line->s[0] != '2') {
					firestring_estr_0(line);
					fprintf(stderr,"{%d} [%d] BACKEND/FATAL: error in HELO response: %s\n",process,n,line->s);
					close(backends[n].fd);
					backends[n].state = BACKEND_STATE_NOTCONNECTED;
					return 1;
				}
				if (line->l >= 4 && line->s[3] == '-') {
					/* 
				 	* continued line
				 	*/
					client_fold_back(n,line->l);
					continue;
				}
				client_fold_back(n,line->l);
				backends[n].state = BACKEND_STATE_IDLE;
				fprintf(stderr,"{%d} [%d] BACKEND/WARNING: missing PIPELINING support; backend conversations will be *much* slower\n",process,n);
				if (sevenbit == 0) {
					fprintf(stderr,"{%d} [%d] BACKEND/FATAL: missing 8BITMIME suport; get a modern MTA or set 7bit in the config file to override\n",process,n);
					exit(100);
				}
				fprintf(stderr,"{%d} [%d] BACKEND/STATUS: connection established\n",process,n);
				return 0;
			}
		case BACKEND_STATE_STARTTLS_RESPONSE:
			if (tls_backend_eread(n,&backends[n].buffer) != 0) {
				close(backends[n].fd);
				backends[n].state = BACKEND_STATE_NOTCONNECTED;
				return 1;
			}
			while (1) {
				line = client_get_line(n);
				if (line == NULL)
					return 0;
				if (line->l >= 4 && line->s[3] == '-') {
					/* 
				 	* continued line
				 	*/
					client_fold_back(n,line->l);
					continue;
				}
				client_fold_back(n,line->l);
				if (line->l < 3 || line->s[0] != '2') {
					/*
					 * TLS support failed
					 */
					backends[n].state = BACKEND_STATE_IDLE;
					return 0;
				}
				backends[n].state = BACKEND_STATE_SSL_HANDSHAKE;
				i = tls_client_start(n);
				if (i == 1) {
					fprintf(stderr,"{%d} [%d] TLS/ERROR: negotiation failed, falling back\n",process,n);
					backends[n].state = BACKEND_STATE_IDLE;
					return 0;
				}
				if (i == 2)
					goto ssl_handshake;
				return 0;
			}
		case BACKEND_STATE_SSL_HANDSHAKE:
			if (tls_client_handshake(n) == 0) {
ssl_handshake:
				firestring_estr_sprintf(&outline,"EHLO %s\r\n",domain);
				tls_backend_write(n,outline.s,outline.l);
				backends[n].state = BACKEND_STATE_EHLO_RESPONSE;
			}
			return 0;
		case BACKEND_STATE_IDLE:
			/*
			 * we're idle, just ignore the server
			 * if it disconnects, break out and parent will reconnect
			 */
			if (tls_backend_eread(n,&backends[n].buffer) != 0) {
				close(backends[n].fd);
				backends[n].state = BACKEND_STATE_NOTCONNECTED;
				return 1;
			}
			while (1) {
				line = client_get_line(n);
				if (line == NULL)
					return 0;
				firestring_estr_0(line);
				fprintf(stderr,"{%d} [%d] BACKEND/STATUS: non-reply line from server: '%s'\n",process,n,line->s);
				client_fold_back(n,line->l);
			}
			break;
		case BACKEND_STATE_CLEARPIPE:
			/*
			 * we're getting pipe responses
			 */
			if (tls_backend_eread(n,&backends[n].buffer) != 0) {
				close(backends[n].fd);
				backends[n].state = BACKEND_STATE_NOTCONNECTED;
				return 1;
			}
			while (1) {
				line = client_get_line(n);
				if (line == NULL)
					return 0;
				if (line->l < 3 || (line->s[0] != '2' && line->s[0] != '3')) {
					clients[backends[n].client].num_to = 0;
					if (clients[backends[n].client].error == 0) {
						firestring_estr_0(line);
						fprintf(stderr,"{%d} [%d] BACKEND/REJECT: refused message: '%s'\n",process,n,line->s);
						if (line->l < 3 || line->s[0] != '4')
							clients[backends[n].client].error = 5;
						else
							clients[backends[n].client].error = 4;
					}
				}
				if (line->l >= 4 && line->s[3] != '-') {
					/* 
				 	* not a continued line
				 	*/
					if (backends[n].pipelining == 1) {
						backends[n].piped--;
						if (backends[n].piped == 0) {
							client_fold_back(n,line->l);
							if (clients[backends[n].client].error != 0) {
								/*
							 	* pass error along to client, take everything to idle
							 	*/
								if (clients[backends[n].client].error == 4)
									tls_client_write(backends[n].client,SMTP_REFUSED_TEMP,sizeof(SMTP_REFUSED_TEMP) - 1);
								else
									tls_client_write(backends[n].client,SMTP_REFUSED,sizeof(SMTP_REFUSED) - 1);
								clients[backends[n].client].send_progress = 0;
								client_reset(n);
								return 0;
							} else {
								/*
							 	* take it into write state and fill the outgoing tcp buffer
							 	*/
								backends[n].state = BACKEND_STATE_SENDING;
								backends[n].sent = 0;
								client_send(n);
								return 0;
							}
						}
					} else {
						if (clients[backends[n].client].error != 0) {
							if (clients[backends[n].client].error == 4)
								tls_client_write(backends[n].client,SMTP_REFUSED_TEMP,sizeof(SMTP_REFUSED_TEMP) - 1);
							else
								tls_client_write(backends[n].client,SMTP_REFUSED,sizeof(SMTP_REFUSED) - 1);
							clients[backends[n].client].send_progress = 0;
							client_reset(n);
							return 0;
						}
						backends[n].piped++;
						if (backends[n].piped - 1 <= clients[backends[n].client].num_to) {
							/* 
							 * 2 - client.num_to + 1
							 * send another destination
							 */
							firestring_estr_sprintf(&outline,"RCPT TO:<%e>\r\n",&clients[backends[n].client].to[backends[n].piped - 2]);
							tls_backend_write(n,outline.s,outline.l);
						} else if (backends[n].piped - 2 == clients[backends[n].client].num_to) {
							/* 
							 * client.num_to + 2
							 * send DATA
							 */
							tls_backend_write(n,"DATA\r\n",6);
						} else {
							/*
							 * we're done
							 */
							backends[n].state = BACKEND_STATE_SENDING;
							backends[n].sent = 0;
							clients[backends[n].client].num_to = 0;
							backends[n].piped = 0;
							client_fold_back(n,line->l);
							client_send(n);
							return 0;
						}
					}
				}
				client_fold_back(n,line->l);
			}
			break;
		case BACKEND_STATE_WAITDELIVER:
			if (tls_backend_eread(n,&backends[n].buffer) != 0) {
				close(backends[n].fd);
				backends[n].state = BACKEND_STATE_NOTCONNECTED;
				return 1;
			}
			while (1) {
				line = client_get_line(n);
				if (line == NULL)
					return 0;
				/*
				 * Patch from Russell Vincent <rv2@sanger.ac.uk>
				 * Applied by Douglas K. Rand <rand@meridian-enviro.com>
				 * Works around problem with MS Exchange servers not liking
				 * the \r\n showing up in a seperate packet.
				 * See http://messagewall.org/cgi-bin/ezmlm-browse.cgi?command=showthread&list=messagewall-discuss&month=200302&threadid=nnoiaogojliciagmgpbk
				 */
				firestring_estr_sprintf(&outline,"%e\r\n", line);
				tls_client_write(backends[n].client,outline.s,outline.l);
				firestring_estr_0(line);
				fprintf(stderr,"{%d} [%d] BACKEND/ACCEPT: accepted message responsibility: '%s'\n",process,n,line->s);
				if (line->l >= 4 && line->s[3] != '-') {
					/* 
				 	* not a continued line
				 	*/
					client_fold_back(n,line->l);
					clients[backends[n].client].send_progress = 0;
					if (line->s[0] == '2')
						backends[n].state = BACKEND_STATE_IDLE;
					else
						client_reset(n);
					return 0;
				}
				client_fold_back(n,line->l);
			}
			break;
		case BACKEND_STATE_RSET:
			if (tls_backend_eread(n,&backends[n].buffer) != 0) {
				close(backends[n].fd);
				backends[n].state = BACKEND_STATE_NOTCONNECTED;
				return 1;
			}
			while (1) {
				line = client_get_line(n);
				if (line == NULL)
					return 0;
				if (line->l >= 4 && line->s[3] != '-') {
					/* 
				 	* not a continued line
				 	*/
					client_fold_back(n,line->l);
					backends[n].state = BACKEND_STATE_IDLE;
					return 0;
				}
				client_fold_back(n,line->l);
			}
			break;

	}

	return 0;
}

struct firestring_estr_t *client_get_line(int n) {
	static struct firestring_estr_t ret;
	int i;

	i = firestring_estr_strstr(&backends[n].buffer,"\r\n",0);
	if (i == -1)
		return NULL;

	ret.a = ret.l = i;
	ret.s = backends[n].buffer.s;
	
	return &ret;
}

void client_fold_back(int n, int i) {
	backends[n].buffer.l -= i + 2;
	memmove(backends[n].buffer.s,&backends[n].buffer.s[i + 2],backends[n].buffer.l);
}

int client_start(int backend, int client) {
	int i;
	static struct firestring_estr_t outline = {0};

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

	/*
	 * set progress indicators
	 */
	clients[client].send_progress = PROGRESS_WORKING;
	backends[backend].state = BACKEND_STATE_CLEARPIPE;
	clients[client].error = 0;
	backends[backend].client = client;
	clients[client].backend = backend;
	fprintf(stderr,"{%d} (%d) BACKEND/STATUS: message to [%d]\n",process,client,backend);

	firestring_estr_sprintf(&outline,"MAIL FROM:<%e>%s\r\n",&clients[client].from,sevenbit == 1 ? "" : " BODY=8BITMIME");
	tls_backend_write(backend,outline.s,outline.l);
	if (backends[backend].pipelining == 1) {
		/*
		 * pipeline everything in
		 */
		for (i = 0; i < clients[client].num_to; i++) {
			firestring_estr_sprintf(&outline,"RCPT TO:<%e>\r\n",&clients[client].to[i]);
			tls_backend_write(backend,outline.s,outline.l);
		}
		tls_backend_write(backend,"DATA\r\n",6);
		backends[backend].piped = i + 2;
		clients[client].num_to = 0;
	} else
		/*
		 * send 'em one by one -- SLOW
		 */
		backends[backend].piped = 1;

	return 0;
}

int client_send(int backend) {
	int i, s;

	/*
	 * send as much as we can
	 */
	s = backends[backend].sent;
	i = tls_backend_write(backend,
			&backends[backend].message.s[s],
			backends[backend].message.l - s);

	if (i == -1)
		return 0;

	if (i == 0) {
		/* 
		 * server went away on us
		 */
		fprintf(stderr,"{%d} [%d] BACKEND/FATAL: unable to write to backend\n",process,backend);
		tls_client_write(backends[backend].client,SMTP_BACKEND_BROKEN,sizeof(SMTP_BACKEND_BROKEN) - 1);
		clients[backends[backend].client].send_progress = 0;
		client_connect(backend);
		return 0;
	}

	backends[backend].sent += i;

	if (backends[backend].sent == backends[backend].message.l) {
		/*
		 * message done
		 * put socket back into blocking for a sec to send .
		 * because we can't tolerate failure here
		 */
		backends[backend].state = BACKEND_STATE_WAITDELIVER;
	}
	time(&clients[backends[backend].client].lasttalk);
	return 0;
}

int client_reset(int backend) {
	backends[backend].state = BACKEND_STATE_RSET;
	tls_backend_write(backend,"RSET\r\n",6);
	return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1