OpenBSD-4.6/usr.sbin/popa3d/standalone.c

Compare this file to the similar file:
Show the results in this format:

/* $OpenBSD: standalone.c,v 1.12 2008/05/24 02:39:23 brad Exp $ */

/*
 * Standalone POP server: accepts connections, checks the anti-flood limits,
 * logs and starts the actual POP sessions.
 */

#include "params.h"

#if POP_STANDALONE

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <syslog.h>
#include <time.h>
#include <errno.h>
#include <netdb.h>
#include <poll.h>
#include <sys/times.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#if DAEMON_LIBWRAP
#include <tcpd.h>
int allow_severity = SYSLOG_PRI_LO;
int deny_severity = SYSLOG_PRI_HI;
#endif

/*
 * These are defined in pop_root.c.
 */
extern int log_error(char *s);
extern int do_pop_startup(void);
extern int do_pop_session(void);
extern int af;

typedef volatile sig_atomic_t va_int;

/*
 * Active POP sessions. Those that were started within the last MIN_DELAY
 * seconds are also considered active (regardless of their actual state),
 * to allow for limiting the logging rate without throwing away critical
 * information about sessions that we could have allowed to proceed.
 */
static struct {
	char addr[NI_MAXHOST];		/* Source IP address */
	va_int pid;			/* PID of the server, or 0 for none */
	clock_t start;			/* When the server was started */
	clock_t log;			/* When we've last logged a failure */
} sessions[MAX_SESSIONS];

static va_int child_blocked;		/* We use blocking to avoid races */
static va_int child_pending;		/* Are any dead children waiting? */

int handle(int);

/*
 * SIGCHLD handler.
 */
static void handle_child(int signum)
{
	int saved_errno;
	pid_t pid;
	int i;

	saved_errno = errno;

	if (child_blocked)
		child_pending = 1;
	else {
		child_pending = 0;

		while ((pid = waitpid(0, NULL, WNOHANG)) > 0)
			for (i = 0; i < MAX_SESSIONS; i++)
				if (sessions[i].pid == pid) {
					sessions[i].pid = 0;
					break;
				}
	}

	signal(SIGCHLD, handle_child);

	errno = saved_errno;
}

#if DAEMON_LIBWRAP
static void check_access(int sock)
{
	struct request_info request;

	request_init(&request,
		RQ_DAEMON, DAEMON_LIBWRAP_IDENT,
		RQ_FILE, sock,
		0);
	fromhost(&request);

	if (!hosts_access(&request)) {
/* refuse() shouldn't return... */
		refuse(&request);
/* ...but just in case */
		exit(1);
	}
}
#endif

#if POP_OPTIONS
int do_standalone(void)
#else
int main(void)
#endif
{
	int error, i, n, true = 1;
	struct pollfd *pfds;
	struct addrinfo hints, *res, *res0;
	char sbuf[NI_MAXSERV];

	if (do_pop_startup()) return 1;

	snprintf(sbuf, sizeof(sbuf), "%u", DAEMON_PORT);
	memset(&hints, 0, sizeof(hints));
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_family = af;
	hints.ai_flags = AI_PASSIVE;
	error = getaddrinfo(NULL, sbuf, &hints, &res0);
	if (error)
		return log_error("getaddrinfo");

	i = 0;
	for (res = res0; res; res = res->ai_next)
		i++;

	pfds = calloc(i, sizeof(pfds[0]));
	if (!pfds) {
		freeaddrinfo(res0);
		return log_error("malloc");
	}

	i = 0;
	for (res = res0; res; res = res->ai_next) {
		if ((pfds[i].fd = socket(res->ai_family, res->ai_socktype,
		    res->ai_protocol)) < 0)
			continue;

		if (setsockopt(pfds[i].fd, SOL_SOCKET, SO_REUSEADDR,
		    (void *)&true, sizeof(true))) {
			close(pfds[i].fd);
			continue;
		}

#ifdef IPV6_V6ONLY
		if (res->ai_family == AF_INET6)
			(void)setsockopt(pfds[i].fd, IPPROTO_IPV6, IPV6_V6ONLY,
			    (void *)&true, sizeof(true));
#endif

		if (bind(pfds[i].fd, res->ai_addr, res->ai_addrlen)) {
			close(pfds[i].fd);
			continue;
		}

		if (listen(pfds[i].fd, MAX_BACKLOG)) {
			close(pfds[i].fd);
			continue;
		}

		pfds[i].events = POLLIN;
		i++;
	}
	freeaddrinfo(res0);

	if (i == 0)
		return log_error("socket");

	n = i;

	chdir("/");
	setsid();

	switch (fork()) {
	case -1:
		return log_error("fork");

	case 0:
		break;

	default:
		return 0;
	}

	setsid();

	child_blocked = 1;
	child_pending = 0;
	signal(SIGCHLD, handle_child);

	memset((void *)sessions, 0, sizeof(sessions));

	while (1) {
		child_blocked = 0;
		if (child_pending) raise(SIGCHLD);

		i = poll(pfds, n, INFTIM);
		if (i < 0) {
			if (errno == EINTR || errno == EAGAIN)
				continue;
			return log_error("poll");
		}

		for (i = 0; i < n; i++)
			if (pfds[i].revents & POLLIN)
				handle(pfds[i].fd);
	}
}

int
handle(int sock)
{
	clock_t now, log;
	int new;
	char hbuf[NI_MAXHOST];
	struct sockaddr_storage addr;
	socklen_t addrlen;
	pid_t pid;
	struct tms buf;
	int error;
	int j, n, i;

	log = 0;
	new = 0;

	addrlen = sizeof(addr);
	new = accept(sock, (struct sockaddr *)&addr, &addrlen);
/*
 * I wish there was a portable way to classify errno's... In this case,
 * it appears to be better to risk eating up the CPU on a fatal error
 * rather than risk terminating the entire service because of a minor
 * temporary error having to do with one particular connection attempt.
 */
	if (new < 0)
		return -1;

	error = getnameinfo((struct sockaddr *)&addr, addrlen,
	    hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST);
	if (error) {
		syslog(SYSLOG_PRI_HI,
		    "could not get host address");
		close(new);
		return -1;
	}

	now = times(&buf);
	if (!now)
		now = 1;

	child_blocked = 1;

	j = -1;
	n = 0;
	for (i = 0; i < MAX_SESSIONS; i++) {
		if (sessions[i].start > now)
			sessions[i].start = 0;
		if (sessions[i].pid ||
		    (sessions[i].start &&
		    now - sessions[i].start < MIN_DELAY * CLK_TCK)) {
			if (strcmp(sessions[i].addr, hbuf) == 0)
				if (++n >= MAX_SESSIONS_PER_SOURCE)
					break;
		} else if (j < 0)
			j = i;
	}

	if (n >= MAX_SESSIONS_PER_SOURCE) {
		if (!sessions[i].log ||
		    now < sessions[i].log ||
		    now - sessions[i].log >= MIN_DELAY * CLK_TCK) {
			syslog(SYSLOG_PRI_HI,
				"%s: per source limit reached",
				hbuf);
			sessions[i].log = now;
		}
		close(new);
		return -1;
	}

	if (j < 0) {
		if (!log ||
		    now < log || now - log >= MIN_DELAY * CLK_TCK) {
			syslog(SYSLOG_PRI_HI,
			    "%s: sessions limit reached", hbuf);
			log = now;
		}
		close(new);
		return -1;
	}

	switch ((pid = fork())) {
	case -1:
		syslog(SYSLOG_PRI_ERROR, "%s: fork: %m", hbuf);
		close(new);
		return -1;

	case 0:
#if DAEMON_LIBWRAP
		check_access(new);
#endif
		syslog(SYSLOG_PRI_LO, "Session from %s",
			hbuf);
		if (dup2(new, 0) < 0 || dup2(new, 1) < 0 || dup2(new, 2) < 0) {
			log_error("dup2");
			_exit(1);
		}
		closefrom(3);
		_exit(do_pop_session());

	default:
		close(new);
		strlcpy(sessions[j].addr, hbuf,
			sizeof(sessions[j].addr));
		sessions[j].pid = (va_int)pid;
		sessions[j].start = now;
		sessions[j].log = 0;
		return 0;
	}
}

#endif