File: [local] / src / usr.sbin / popa3d / Attic / standalone.c (download)
Revision 1.5, Mon May 12 19:28:22 2003 UTC (21 years ago) by camield
Branch: MAIN
CVS Tags: OPENBSD_3_5_BASE, OPENBSD_3_5, OPENBSD_3_4_BASE, OPENBSD_3_4 Changes since 1.4: +17 -10 lines
Sync to 0.6.2
- UIDL digest calculation has been improved
- minor bug fixes
- -V switch to show version
|
/* $OpenBSD: standalone.c,v 1.5 2003/05/12 19:28:22 camield 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 <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);
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 {
struct in_addr addr; /* Source IP address */
volatile 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? */
/*
* 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 true = 1;
int sock, new;
struct sockaddr_in addr;
socklen_t addrlen;
pid_t pid;
struct tms buf;
clock_t now, log;
int i, j, n;
if (do_pop_startup()) return 1;
if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
return log_error("socket");
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
(void *)&true, sizeof(true)))
return log_error("setsockopt");
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(DAEMON_ADDR);
addr.sin_port = htons(DAEMON_PORT);
if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)))
return log_error("bind");
if (listen(sock, MAX_BACKLOG))
return log_error("listen");
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));
log = 0;
new = 0;
while (1) {
child_blocked = 0;
if (child_pending) raise(SIGCHLD);
if (new > 0)
if (close(new)) return log_error("close");
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) continue;
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 (sessions[i].addr.s_addr ==
addr.sin_addr.s_addr)
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",
inet_ntoa(addr.sin_addr));
sessions[i].log = now;
}
continue;
}
if (j < 0) {
if (!log ||
now < log || now - log >= MIN_DELAY * CLK_TCK) {
syslog(SYSLOG_PRI_HI,
"%s: sessions limit reached",
inet_ntoa(addr.sin_addr));
log = now;
}
continue;
}
switch ((pid = fork())) {
case -1:
syslog(SYSLOG_PRI_ERROR, "%s: fork: %m",
inet_ntoa(addr.sin_addr));
break;
case 0:
if (close(sock)) return log_error("close");
#if DAEMON_LIBWRAP
check_access(new);
#endif
syslog(SYSLOG_PRI_LO, "Session from %s",
inet_ntoa(addr.sin_addr));
if (dup2(new, 0) < 0) return log_error("dup2");
if (dup2(new, 1) < 0) return log_error("dup2");
if (dup2(new, 2) < 0) return log_error("dup2");
if (close(new)) return log_error("close");
return do_pop_session();
default:
sessions[j].addr = addr.sin_addr;
sessions[j].pid = pid;
sessions[j].start = now;
sessions[j].log = 0;
}
}
}
#endif