/*
 * Copyright (c) 1999 Ian Freislich
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *	$Id: proxy.c,v 1.7 2002/11/21 13:32:41 ianf Exp $
 */

#include <sys/param.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#ifdef SOLARIS
#include <crypt.h>
#endif
#include <db.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <netdb.h>
#include <poll.h>
#include <pwd.h>
#include <radlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <syslog.h>
#include <time.h>
#include <unistd.h>

#ifdef USE_SSL
#include <openssl/crypto.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#endif

#include <poputil.h>
#include "config.h"
#include "popd.h"
#include "paths.h"
#include "authenticate.h"
#include "proxy.h"
#include "signals.h"

/* global variables */
#ifdef USE_SSL
static SSL_CTX		*ctx = NULL;
static SSL		*ssl = NULL;
static X509		*server_cert = NULL;
static SSL_METHOD	*meth = NULL;
#endif

extern struct config config;

static char	*remote_auth(struct connection *);
static size_t	proxy_read(int fd, void *ptr, size_t nbytes);
static size_t	proxy_write(int fd, void *ptr, size_t nbytes);

void
proxy(struct connection *cxn)
{
	int			 i, len, result;
	char			*response, buffer[MAXBUFLEN];
	struct protoent		*prot;
	struct sockaddr_in	 pop3_server;
	struct pollfd		*pollfd;

#ifdef USE_SSL
	if (config.ssl_proxy) {
		SSLeay_add_ssl_algorithms();
		meth = SSLv2_client_method();
		SSL_load_error_strings();
		if ((ctx = SSL_CTX_new(meth)) == NULL)
			exit_error(EX_SOFTWARE, "Unable to initialise SSL");
		if ((ssl = SSL_new (ctx)) == NULL)
			exit_error(EX_SOFTWARE, "Unable to initialise SSL");
		SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY);
	}
#endif
	if ((prot = getprotobyname("TCP")) == NULL)
		exit(EX_PROTOCOL);
	config.proxy_socket = socket(PF_INET, SOCK_STREAM, prot->p_proto);
	pop3_server.sin_family = AF_INET;
	pop3_server.sin_addr = config.proxy_addr;
	pop3_server.sin_port = config.proxy_port;
	if (!connect(config.proxy_socket, (struct sockaddr *)&pop3_server,
	    sizeof(pop3_server))) {
#ifdef USE_SSL
		if (config.ssl_proxy) {
			SSL_set_fd(ssl, config.proxy_socket);
			if (SSL_connect(ssl) < 0)
				exit_error(EX_PROTOCOL, "Unable to establish "
				    "SSL connection to remote server");
			if ((server_cert = SSL_get_peer_certificate(ssl))
			    != NULL) {
				/* Perhaps do some certificate verification in
				 * the future
				 */
				 X509_free(server_cert);
			}
		}
#endif
		response = remote_auth(cxn);
		sendline(SEND_FLUSH, "%s", response);

		pollfd = xmalloc(2 * sizeof(struct pollfd));
		pollfd[0].fd = fileno(config.socket_in);
		pollfd[0].events = POLLRDNORM;
		pollfd[0].revents = 0;
		pollfd[1].fd = config.proxy_socket;
		pollfd[1].events = POLLRDNORM;
		pollfd[1].revents = 0;
		while ((result = poll(pollfd, 2, INFTIM))) {
			if (errno == EINTR) {
				removesignal();
				continue;
			}
			for (i = 0; i < 2; ++i) {
				if (!pollfd[i].revents)
					continue;
				if (i == 0) {
					len = xread(buffer, MAXBUFLEN);
					if (len == 0)
						exit(EX_OK);
					proxy_write(pollfd[1].fd, buffer, len);
				}
				if (i == 1) {
					len = proxy_read(pollfd[1].fd, buffer,
					    MAXBUFLEN);
					if (len == 0)
						exit(EX_OK);
					xwrite(buffer, len);
				}
				pollfd[i].revents = 0;
			}
		}
	}
	else {
		sendline(SEND_FLUSH, "-ERR unable to connect to  %s:%d",
		    inet_ntoa(config.proxy_addr), ntohs(config.proxy_port));
	}
}

static size_t
proxy_read(int fd, void *ptr, size_t nbytes)
{
	size_t	len;

#ifdef USE_SSL
	if (config.ssl_proxy)
		len = SSL_read(ssl, ptr, nbytes);
	else
#endif
		len = read(fd, ptr, nbytes);

	return(len);
}

static size_t
proxy_write(int fd, void *ptr, size_t nbytes)
{
	int	len;

#ifdef USE_SSL
	if (config.ssl_proxy)
		len = SSL_write(ssl, ptr, nbytes);
	else
#endif
		len = write(fd, ptr, nbytes);
	return(len);
}

static char *
remote_auth(struct connection *cxn)
{
	int		 len;
	static char	 buffer[MAXBUFLEN + 1];

	if ((len = proxy_read(config.proxy_socket, buffer, MAXBUFLEN)) < 2) {
		sendline(SEND_FLUSH, "-ERR remote error: mangled response "
		    "from backend");
		exit(EX_PROTOCOL);
	}
	buffer[len - 2] = '\0';
	if (strncmp(buffer, "+OK ", 4)) {
		sendline(SEND_FLUSH, "-ERR remote error: '%s'", buffer);
		exit(EX_PROTOCOL);
	}
	len = sprintf(buffer, "user %s\r\n", config.proxy_name ?
	    config.proxy_name : cxn->auth_string);
	proxy_write(config.proxy_socket, buffer, len);
	if ((len = proxy_read(config.proxy_socket, buffer, MAXBUFLEN)) < 2) {
		sendline(SEND_FLUSH, "-ERR remote error: mangled response "
		    "from backend");
		exit(EX_PROTOCOL);
	}
	buffer[len - 2] = '\0';
	if (strncmp(buffer, "+OK ", 4)) {
		sendline(SEND_FLUSH, "-ERR remote error: '%s'", buffer);
		exit(EX_PROTOCOL);
	}
	len = sprintf(buffer, "pass %s\r\n", config.proxy_passwd ?
	    config.proxy_passwd : cxn->password);
	proxy_write(config.proxy_socket, buffer, len);
	if ((len = proxy_read(config.proxy_socket, buffer, MAXBUFLEN)) < 2) {
		sendline(SEND_FLUSH, "-ERR remote error: mangled response "
		    "from backend");
		exit(EX_PROTOCOL);
	}
	buffer[len - 2] = '\0';
	if (strncmp(buffer, "+OK ", 4)) {
		sendline(SEND_FLUSH, "-ERR remote error: '%s'", buffer);
		exit(EX_PROTOCOL);
	}
	/* No errors, so return the response string from the remote server */
	return(buffer);
}


syntax highlighted by Code2HTML, v. 0.9.1