diff --git a/popa3d.8 b/popa3d.8 index 2fcfca0..6096a0a 100644 --- a/popa3d.8 +++ b/popa3d.8 @@ -5,6 +5,8 @@ popa3d \- Post Office Protocol (POP3) server .B popa3d .RB [ -D ] .RB [ -V ] +.RB [ -4 ] +.RB [ -6 ] .SH DESCRIPTION .B popa3d is a Post Office Protocol version 3 (POP3) server. @@ -44,6 +46,23 @@ In this mode also does quite a few checks to significantly reduce the impact of connection flood attacks. .TP +.B -4 +When used with +.BR -D , +listen on +.BR 0.0.0.0 . +.TP +.B -6 +When used with +.BR -D , +listen on +.BR :: . +If both +.B -4 +and +.B -6 +are combined, listen on a dual-stack socket. +.TP .B -V Print version information and exit. .SH COMMANDS diff --git a/standalone.c b/standalone.c index 216d937..3d89c16 100644 --- a/standalone.c +++ b/standalone.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -34,9 +35,20 @@ int deny_severity = SYSLOG_PRI_HI; extern int log_error(char *s); extern int do_pop_startup(void); extern int do_pop_session(void); +extern int af; +extern int dual_stack; typedef volatile sig_atomic_t va_int; +struct ip_addr { + sa_family_t af; + socklen_t len; + union { + struct in_addr sin_addr; + struct in6_addr sin6_addr; + } a; +}; + /* * Active POP sessions. Those that were started within the last MIN_DELAY * seconds are also considered active (regardless of their actual state), @@ -44,7 +56,7 @@ typedef volatile sig_atomic_t va_int; * information about sessions that we could have allowed to proceed. */ static struct { - struct in_addr addr; /* Source IP address */ + struct ip_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 */ @@ -102,36 +114,84 @@ static void check_access(int sock) } #endif +static void save_ip_addr(struct ip_addr *to, + const struct sockaddr *from) +{ + to->af = from->sa_family; + if (to->af == AF_INET6) { + to->a.sin6_addr = ((struct sockaddr_in6 *) from)->sin6_addr; + to->len = sizeof to->a.sin6_addr; + } else { + to->a.sin_addr = ((struct sockaddr_in *) from)->sin_addr; + to->len = sizeof to->a.sin_addr; + } +} + +static int cmp_ip_addr(const struct ip_addr *a, + const struct ip_addr *b) +{ + return memcmp(a, b, ((size_t)&((struct ip_addr *)(0))->a) + a->len); +} + #if POP_OPTIONS int do_standalone(void) #else int main(void) #endif { - int true = 1; + int off = 0; + int on = 1; int sock, new; - struct sockaddr_in addr; + struct sockaddr_storage addr; socklen_t addrlen; + struct ip_addr peer; int pid; struct tms buf; clock_t min_delay, now, log; int i, j, n; + struct addrinfo hints, *res; + char hbuf[NI_MAXHOST]; + char sbuf[NI_MAXSERV]; + int error; if (do_pop_startup()) return 1; - if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) + 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, &res); + if (error) + return log_error("getaddrinfo"); + + sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + if (sock < 0) { + freeaddrinfo(res); return log_error("socket"); + } if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, - (void *)&true, sizeof(true))) + (void *)&on, sizeof(on))) { + freeaddrinfo(res); return log_error("setsockopt"); + } + +#ifdef IPV6_V6ONLY + if (res->ai_family == AF_INET6 && + (af == AF_INET6 || dual_stack) && + setsockopt(sock, IPPROTO_IPV6, + IPV6_V6ONLY, (void *)(dual_stack ? &off : &on), sizeof(on))) { + freeaddrinfo(res); + return log_error("setsockopt"); + } +#endif - 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))) + if (bind(sock, res->ai_addr, res->ai_addrlen)) { + freeaddrinfo(res); return log_error("bind"); + } + freeaddrinfo(res); if (listen(sock, MAX_BACKLOG)) return log_error("listen"); @@ -185,6 +245,17 @@ int main(void) */ if (new < 0) continue; + error = getnameinfo((struct sockaddr *)&addr, addrlen, + hbuf, sizeof(hbuf), + NULL, 0, NI_NUMERICHOST); + if (error) { + log_error("getnameinfo"); + /* If rendering the numerical address failed, + * no good can come of accepting it! */ + continue; + } + save_ip_addr(&peer, (struct sockaddr *) &addr); + now = times(&buf); if (!now) now = 1; @@ -197,9 +268,9 @@ int main(void) if (sessions[i].pid || (sessions[i].start && now - sessions[i].start < min_delay)) { - if (sessions[i].addr.s_addr == - addr.sin_addr.s_addr) - if (++n >= MAX_SESSIONS_PER_SOURCE) break; + if (cmp_ip_addr(&sessions[i].addr, &peer) == 0) + if (++n >= MAX_SESSIONS_PER_SOURCE) + break; } else if (j < 0) j = i; } @@ -210,7 +281,7 @@ int main(void) now - sessions[i].log >= min_delay) { syslog(SYSLOG_PRI_HI, "%s: per source limit reached", - inet_ntoa(addr.sin_addr)); + hbuf); sessions[i].log = now; } continue; @@ -221,7 +292,7 @@ int main(void) now < log || now - log >= min_delay) { syslog(SYSLOG_PRI_HI, "%s: sessions limit reached", - inet_ntoa(addr.sin_addr)); + hbuf); log = now; } continue; @@ -229,8 +300,7 @@ int main(void) switch ((pid = fork())) { case -1: - syslog(SYSLOG_PRI_ERROR, "%s: fork: %m", - inet_ntoa(addr.sin_addr)); + syslog(SYSLOG_PRI_ERROR, "%s: fork: %m", hbuf); break; case 0: @@ -239,7 +309,7 @@ int main(void) check_access(new); #endif syslog(SYSLOG_PRI_LO, "Session from %s", - inet_ntoa(addr.sin_addr)); + hbuf); 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"); @@ -247,7 +317,7 @@ int main(void) return do_pop_session(); default: - sessions[j].addr = addr.sin_addr; + sessions[j].addr = peer; sessions[j].pid = pid; sessions[j].start = now; sessions[j].log = 0; diff --git a/startup.c b/startup.c index 25b2b9c..7e62bce 100644 --- a/startup.c +++ b/startup.c @@ -6,6 +6,7 @@ #if POP_OPTIONS +#include #include #include #include @@ -28,6 +29,9 @@ extern char *__progname; static char *progname; #endif +int af = AF_UNSPEC; +int dual_stack = 0; + static void usage(void) { fprintf(stderr, "Usage: %s [-D] [-V]\n", progname); @@ -50,11 +54,22 @@ int main(int argc, char **argv) progname = POP_SERVER; #endif - while ((c = getopt(argc, argv, "DV")) != -1) { + while ((c = getopt(argc, argv, "DV46")) != -1) { switch (c) { case 'D': standalone++; break; + case '4': + if (af == AF_INET6) + dual_stack = 1; + else + af = AF_INET; + break; + case '6': + if (af == AF_INET) + dual_stack = 1; + af = AF_INET6; + break; case 'V': version(); diff --git a/virtual.c b/virtual.c index bea0bb2..24a9697 100644 --- a/virtual.c +++ b/virtual.c @@ -6,7 +6,7 @@ #if POP_VIRTUAL -#define _XOPEN_SOURCE 4 +/* Inhibits NI_MAXHOST definition if present: #define _XOPEN_SOURCE 4 */ #define _XOPEN_SOURCE_EXTENDED #define _XOPEN_VERSION 4 #define _XPG4_2 @@ -22,6 +22,7 @@ #include #include #include +#include #include #ifndef NAME_MAX @@ -42,18 +43,29 @@ int virtual_startup(void) static char *lookup(void) { - struct sockaddr_in sin; + struct sockaddr_storage ss; socklen_t length; - - length = sizeof(sin); - if (getsockname(0, (struct sockaddr *)&sin, &length)) { - if (errno == ENOTSOCK) return ""; + int error; + static char hbuf[NI_MAXHOST]; + + length = sizeof(ss); + if (getsockname(0, (struct sockaddr *)&ss, &length)) { + if (errno == ENOTSOCK) { + hbuf[0] = '\0'; + return hbuf; + } log_error("getsockname"); return NULL; } - if (length != sizeof(sin) || sin.sin_family != AF_INET) return NULL; - return inet_ntoa(sin.sin_addr); + error = getnameinfo((struct sockaddr *)&ss, length, hbuf, sizeof(hbuf), + NULL, 0, NI_NUMERICHOST); + if (error) { + log_error("getnameinfo"); + return NULL; + } + + return hbuf; } static int is_valid_user(char *user) @@ -118,7 +130,6 @@ struct passwd *virtual_userpass(char *user, char *pass, int *known) } free(pathname); - if (!(address = strdup(address))) return NULL; virtual_domain = address; pathname = concat(VIRTUAL_HOME_PATH, "/", address, "/",