/*
 * 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: popd.c,v 1.46 2003/01/24 12:01:25 ianf Exp $
 */

#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#ifdef USE_IPV6
#include <sys/wait.h>
#endif
#include <netinet/in.h>
#include <arpa/inet.h>

#include <errno.h>
#include <netdb.h>
#include <poll.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sysexits.h>
#include <syslog.h>
#include <unistd.h>

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

#ifndef NI_WITHSCOPEID
#define NI_WITHSCOPEID	0
#endif

/* global variables */
extern FILE		*yyin;
extern int		 yyparse(void);
struct config		 config;
int			 sigbuf[2];

static void	getconfig(int, char **, struct connection *);
static void	read_config(struct connection *cxn);

void
usage(void)
{
	printf("Usage: \n");
	printf("\t-c config_file\tFull path to configuration file\n");
}

static void
read_config(struct connection *cxn)
{
#ifdef USE_IPV6
	int		inet_flag = 0, inet6_flag = 0;
#else
	struct servent	*servent;
#endif

	config.socket_in =	stdin;
	config.socket_out =	stdout;
#ifdef USE_SSL
	config.ssl_cert =	_SSL_CERT_FILE;
	config.ssl_key =	_SSL_KEY_FILE;
	config.ssl_server =	FALSE;
	config.ssl_proxy =	FALSE;
#endif
	config.etcdir =		_ETC_DIR;
	config.user =		_POP_USER;
	config.secrets =	NULL;
	config.dbfile = 	_DB_FILE;
#ifdef USE_IPV6
	config.family =		PF_UNSPEC;
	config.bind_address =	NULL;
#else
	config.bind_address.s_addr =	htonl(INADDR_ANY);
#endif
	config.bulletindir =	NULL;
	config.defaultrealm =	NULL;
	config.daemonise =	TRUE;
	config.debug =		FALSE;
	config.hashdepth =	0;
	config.localuser =	TRUE;
	config.maildir =	_MAIL_SPOOL;
	config.port = 		NULL;
	config.proxy = 		FALSE;
	config.proxy_port =	htons(110);
	config.proxy_name =	NULL;
	config.proxy_passwd = 	NULL;
	config.pwcheck =	TRUE;
	config.radius =		NULL;
	config.timeout =	TIMEOUT;
	config.virtual =	FALSE;
	config.flags =		MAILBOX_F_DEFAULT;
	memset(&config.proxy_addr, '\0', sizeof(struct in_addr));

	if ((yyin = fopen(config.config_file, "r")) != NULL) {
		yyparse();
		fclose(yyin);
	}
	else if (!config.force) {
		perror("Unable to open config file");
		exit(EX_CONFIG);
	}
		
	config.lastreload = time(NULL);

	cxn->flags = config.flags;
	cxn->expire = config.expire;
	cxn->remove = config.remove;

#ifdef USE_IPV6
	if (inet_flag && inet6_flag)
		config.family = PF_UNSPEC;

	if (!config.port) {
		if (config.ssl_server)
			config.port = "pop3s";
		else
			config.port = "pop3";
	}
#else
	if (!config.port) {
		if (config.ssl_server)
			servent = getservbyname("pop3s", "tcp");
		else
			servent = getservbyname("pop3", "tcp");
		if (servent)
			config.port = ntohs(servent->s_port);
		else {
			syslog(facility, "Couldn't get pop3 port from services",
			    strerror(errno));
			exit(EX_PROTOCOL);
		}
	}
#endif
}

static void
getconfig(int argc, char **argv, struct connection *cxn)
{
	extern char	*optarg;

	int		ch;

	config.config_file = _CONFIG_FILE;
	config.force = FALSE;
	while ((ch = getopt(argc, argv, "c:f")) != -1) {
		switch (ch) {
			case 'c':
				config.config_file = optarg;
				break;
			case 'f':
				config.force = TRUE;
				break;
			case 'h':
			case '?':
			default:
				usage();
		}
	}
	read_config(cxn);
}

/* Determine if we've been handed a socket by a program such as
 * inetd. Do the required stuff to listen on stdin/stdout or listen
 * on a port.
 */
int
main(int argc, char **argv)
{
	struct connection	cxn;
#ifdef USE_IPV6
	struct sockaddr_storage	addr;
	struct pollfd		*pollfd;
	struct addrinfo		hints, *res, *r;
	char			ip[NI_MAXHOST];
	unsigned int		npollfd;
#else
	struct sockaddr_in	addr;
	struct protoent		*prot;
	struct pollfd		pollfd;
#endif
	int			serverfd, curfd, result,
#ifdef USE_IPV6
				facility, error, i,
				on = 1;
#else
				facility;
#endif
	socklen_t		addrlen;

	addrlen = sizeof(addr);
	facility = LOG_NOTICE;

	memset(ip, '\0', NI_MAXHOST);
	memset(&cxn, NULL, sizeof(struct connection));
	memset(&addr, NULL, sizeof(struct sockaddr_in));
	closelog();
	openlog(IDENT, LOG_PID, LOG_DAEMON);
	getconfig(argc, &*argv, &cxn);
	closelog();
	openlog(config.ssl_server ? IDENTSSL : IDENT, LOG_PID, LOG_DAEMON);
#ifdef USE_SSL
	ssl_init(config.daemonise ? config.ssl_server : FALSE, config.etcdir,
	    config.ssl_cert, config.ssl_key);
#endif
	opensigpipe();
	if (config.debug)
		syslog(LOG_DEBUG, "Opened signal pipe");
	setsignals();

	/* Were we passed a connection on a socket? */
	if ((getpeername(fileno(config.socket_in),
	    (struct sockaddr*)&addr, &addrlen) != NULL) &&
	    (config.daemonise || config.debug)) {
		/* We're in debug mode or daemonise mode
		 * but we weren't given a connected socket,
		 * so open up a socket.
		 */
#ifdef USE_IPV6
		memset(&hints, 0, sizeof(hints));
		hints.ai_flags = AI_PASSIVE;
		hints.ai_family = config.family;
		hints.ai_socktype = SOCK_STREAM;
		error = getaddrinfo(config.bind_address, config.port,
				    &hints, &res);
		if (error) {
			syslog(facility, "%s", gai_strerror(error));
			exit(EX_OSFILE);
		}

		/* Count max number of sockets we may open */
		for (npollfd = 0, r = res; r; r = r->ai_next, npollfd++)
			;

		pollfd = xmalloc(npollfd * sizeof(struct pollfd));

		npollfd = 0;   /* num of sockets counter at start of array */
		for (r = res; r; r = r->ai_next) {
			serverfd = socket(r->ai_family, r->ai_socktype,
					  r->ai_protocol);
			if (serverfd < 0)
				continue;
			if (setsockopt(serverfd, SOL_SOCKET,
				       SO_REUSEADDR, &on, sizeof(on)) < 0) {
				close(serverfd);
				continue;
			}
#if defined(IPV6_BINDV6ONLY) && !(defined(__FreeBSD__) && __FreeBSD__ < 3)
			if (r->ai_family == AF_INET6) {
				if (setsockopt(serverfd, IPPROTO_IPV6,
					       IPV6_BINDV6ONLY,
					       &on, sizeof(on)) < 0) {
					close(serverfd);
					continue;
				}
			}
#endif
			if (bind(serverfd, r->ai_addr, r->ai_addrlen) < 0) {
				getnameinfo(r->ai_addr, r->ai_addrlen,
					    ip, sizeof(ip), NULL, 0,
					    NI_NUMERICHOST | NI_WITHSCOPEID);
				syslog(facility, "Can't bind port %s on %s",
				       config.port, ip);
				close(serverfd);
				continue;
			}
			pollfd[npollfd++].fd = serverfd;
		}

		if (res)
			freeaddrinfo(res);

		if (npollfd == 0) {
			syslog(facility, "Couldn't bind to any socket");
			free(pollfd);
#else
		prot = getprotobyname("TCP");
		serverfd = socket(AF_INET, SOCK_STREAM, prot->p_proto);
		memset(&addr, '\0', addrlen);
		addr.sin_family = AF_INET;
		addr.sin_port = htons(config.port);
		addr.sin_addr = config.bind_address;
		setsockopt(serverfd, SOL_SOCKET, SO_REUSEADDR, &serverfd,
		    sizeof(serverfd));
		if (bind(serverfd, (struct sockaddr *)&addr, addrlen)) {
			syslog(facility, "Can't bind port %d on %s",
			    config.port, inet_ntoa(config.bind_address));
#endif
			exit(EX_OSFILE);
		}

		if (config.debug) {
#ifdef USE_IPV6
			syslog(LOG_DEBUG, "Got socket, listening on port %s",
#else
			syslog(LOG_DEBUG, "Got socket, listening on port %d",
#endif
				config.port);
		}
		if (config.daemonise && !config.debug) {
	 		/* We're not in debug mode so we can fork. */
			setsid();
			syslog(LOG_DEBUG, "Started in daemon mode by PID: %d",
			    getppid());
			result = fork();
			switch (result) {
				case -1:
					syslog(facility,
					    "Unable to fork daemon: %s",
					    strerror(errno));
					exit(EX_OSERR);
					break;
				case 0:
					syslog(facility, "Closing stdio");
					fclose(config.socket_in);
					fclose(config.socket_out);
					fclose(stderr);
					break;
				default:
					syslog(facility, "Spawned daemon: %d",
					    result);
					exit(EXIT_SUCCESS);
			}
		}

#ifdef USE_IPV6
		for (i = 0; i < npollfd; i++) {
			listen(pollfd[i].fd, LISTEN_BACKLOG);
			pollfd[i].events = POLLRDNORM;
			pollfd[i].revents = 0;
		}
#else
		listen(serverfd, LISTEN_BACKLOG);
		pollfd.fd = serverfd;
		pollfd.events = POLLRDNORM;
#endif
		curfd = -1;
#ifdef USE_IPV6
		result = -1;
		while (result != 0 && poll(pollfd, npollfd, INFTIM)) {
#else
		while (poll(&pollfd, 1, INFTIM)) {
#endif
			if (errno == EINTR) {
				removesignal();
				continue;
			}
#ifdef USE_IPV6
			for (i = 0; i < npollfd; ++i) {
				if (!pollfd[i].revents)
					continue;
				pollfd[i].revents = 0;
				addrlen = sizeof(addr); 
				curfd = accept(pollfd[i].fd,
				    (struct sockaddr*)&addr, &addrlen);
				if (curfd < 0) {
					syslog(facility,
					    "Error accepting connection %s",
					    strerror(errno));
					continue;
				}
#else
			curfd = accept(serverfd, (struct sockaddr*)&addr,
			    &addrlen);
			if (curfd < 0) {
				syslog(facility,
				    "Error accepting connection: ",
				    strerror(errno));
				continue;
			}
#endif

				result = fork();
				if (result < 0) {
					syslog(facility,
					       "Unable to fork, sleeping...");
					sleep(10);
					continue;
				}
				if (result == 0)
					break;
				close(curfd);
				if (config.debug)
					syslog(LOG_DEBUG, "Giving child %d fd "
					    "%d", result, curfd);
#ifdef USE_IPV6
			}
#endif
		}
#ifdef USE_IPV6
		for (i = 0; i < npollfd; ++i)
			close(pollfd[i].fd);
#else
		close(serverfd);
#endif
		if (!(config.socket_in = fdopen(curfd, "r")) ||
		    !(config.socket_out = fdopen(curfd, "w")))
			exit(EX_PROTOCOL);
	}
	/* If we get here, handling a connection */
	blocksignals();
	closesigpipe();
	opensigpipe();
	setsignals();
#ifdef USE_SSL
	ssl_accept(fileno(config.socket_in));
#endif
	poputil_init(config.socket_in, config.socket_out, config.timeout,
	    config.flags);
	facility = LOG_INFO;
	if (config.daemonise || config.debug)
#ifdef USE_IPV6
		getnameinfo((struct sockaddr *)&addr, addr.ss_len,
		    ip, sizeof(ip), NULL, 0, NI_NUMERICHOST | NI_WITHSCOPEID);
	if (config.daemonise || config.debug)
		syslog(facility, "Connection from %s", ip);
#else
		syslog(facility, "Connection from %s",
		    inet_ntoa(addr.sin_addr));
#endif
	if (config.debug)
		syslog(LOG_DEBUG, "Handling connection from %s "
		    "on file descriptor %d",
#ifdef USE_IPV6
		    ip, fileno(config.socket_in));
#else
		    inet_ntoa(addr.sin_addr), fileno(config.socket_in));
#endif

	if (config.secrets)
		config.timestamp = make_timestamp();
	else
		config.timestamp = "";
	sendline(SEND_FLUSH, "+OK %s-%s ready %s",
	    config.ssl_server ? IDENTSSL : IDENT,
	    VERSION, config.timestamp);
	switch (authenticate(&cxn)) {
		case TRUE:
			syslog(facility, "Login user=%s host=[%s]",
#ifdef USE_IPV6
			    config.virtual ? cxn.auth_string : cxn.username,
			    ip);
#else
			    config.virtual ? cxn.auth_string : cxn.username,
			    inet_ntoa(addr.sin_addr));
#endif
			if (!config.proxy)
				transaction(&cxn);
			else
				proxy(&cxn);
			break;
		case FALSE:
			sendline(SEND_FLUSH, "-ERR too many authentication "
			    "failures");
			break;
		case QUIT:
			sendline(SEND_FLUSH, "+OK bye");
	}
	return(EXIT_SUCCESS);
}


syntax highlighted by Code2HTML, v. 0.9.1