/* * 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 #include #include #ifdef USE_IPV6 #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #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); }