/*
* 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