From d7785478034e121b42dfae9c6199ab7fc32d2c25 Mon Sep 17 00:00:00 2001 From: Andrew Bower Date: Thu, 21 Aug 2025 23:44:16 +0100 Subject: [PATCH 1/7] Import IPv6 patch by Jun-ichiro itojun Hagino written against v0.5.1, applied against v0.5.0 then rebased to 1.0.3+. Sourced from: https://www.openwall.com/popa3d/contrib/popa3d-0.5.1-ipv6-patch-1.diff.gz --- standalone.c | 70 +++++++++++++++++++++++++++++++++++++++------------- startup.c | 11 ++++++++- virtual.c | 20 ++++++++++----- 3 files changed, 77 insertions(+), 24 deletions(-) diff --git a/standalone.c b/standalone.c index 216d937..1076d7b 100644 --- a/standalone.c +++ b/standalone.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -34,6 +35,7 @@ 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; typedef volatile sig_atomic_t va_int; @@ -44,7 +46,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 */ + char addr[NI_MAXHOST]; /* 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 */ @@ -110,28 +112,56 @@ int main(void) { int true = 1; int sock, new; - struct sockaddr_in addr; + struct sockaddr_storage addr; socklen_t addrlen; 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) return log_error("socket"); + 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 *)&true, sizeof(true))) { + freeaddrinfo(res); 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))) +#ifdef IPV6_V6ONLY + if (res->ai_family == AF_INET6 && setsockopt(sock, IPPROTO_IPV6, + IPV6_V6ONLY, (void *)&true, sizeof(true))) { + freeaddrinfo(res); + return log_error("setsockopt"); + } +#endif + + 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"); @@ -177,6 +207,12 @@ int main(void) addrlen = sizeof(addr); new = accept(sock, (struct sockaddr *)&addr, &addrlen); + error = getnameinfo((struct sockaddr *)&addr, addrlen, + hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST); + if (error) + ; /* XXX */ + + /* * I wish there were 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 @@ -197,9 +233,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 (strcmp(sessions[i].addr, hbuf) == 0) + if (++n >= MAX_SESSIONS_PER_SOURCE) + break; } else if (j < 0) j = i; } @@ -210,7 +246,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 +257,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 +265,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 +274,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 +282,8 @@ int main(void) return do_pop_session(); default: - sessions[j].addr = addr.sin_addr; + strlcpy(sessions[j].addr, hbuf, + sizeof(sessions[j].addr)); sessions[j].pid = pid; sessions[j].start = now; sessions[j].log = 0; diff --git a/startup.c b/startup.c index 25b2b9c..6e180d4 100644 --- a/startup.c +++ b/startup.c @@ -6,6 +6,7 @@ #if POP_OPTIONS +#include #include #include #include @@ -28,6 +29,8 @@ extern char *__progname; static char *progname; #endif +int af = AF_INET; + static void usage(void) { fprintf(stderr, "Usage: %s [-D] [-V]\n", progname); @@ -50,11 +53,17 @@ 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': + af = AF_INET; + break; + case '6': + af = AF_INET6; + break; case 'V': version(); diff --git a/virtual.c b/virtual.c index bea0bb2..278b25d 100644 --- a/virtual.c +++ b/virtual.c @@ -40,20 +40,28 @@ int virtual_startup(void) return 0; } -static char *lookup(void) +static const char *lookup(void) { - struct sockaddr_in sin; + struct sockaddr_storage ss; socklen_t length; + int error; + static char hbuf[NI_MAXHOST]; - length = sizeof(sin); - if (getsockname(0, (struct sockaddr *)&sin, &length)) { + length = sizeof(ss); + if (getsockname(0, (struct sockaddr *)&ss, &length)) { if (errno == ENOTSOCK) return ""; 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) { + /* logging? */ + return NULL; + } + + return hbuf; } static int is_valid_user(char *user) From dafe522605d07c01ad34123b8f77eeaf8096d6f9 Mon Sep 17 00:00:00 2001 From: Andrew Bower Date: Thu, 21 Aug 2025 23:44:21 +0100 Subject: [PATCH 2/7] No need to create socket twice --- standalone.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/standalone.c b/standalone.c index 1076d7b..f8fba26 100644 --- a/standalone.c +++ b/standalone.c @@ -125,9 +125,6 @@ int main(void) if (do_pop_startup()) return 1; - if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) - return log_error("socket"); - snprintf(sbuf, sizeof(sbuf), "%u", DAEMON_PORT); memset(&hints, 0, sizeof(hints)); hints.ai_socktype = SOCK_STREAM; From 99e80a67196adf8be69be1bb2ce65216f4bb8134 Mon Sep 17 00:00:00 2001 From: Andrew Bower Date: Thu, 21 Aug 2025 23:44:22 +0100 Subject: [PATCH 3/7] If neither -4 nor -6 specified, let OS choose AF --- standalone.c | 4 +++- startup.c | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/standalone.c b/standalone.c index f8fba26..fc34966 100644 --- a/standalone.c +++ b/standalone.c @@ -147,7 +147,9 @@ int main(void) } #ifdef IPV6_V6ONLY - if (res->ai_family == AF_INET6 && setsockopt(sock, IPPROTO_IPV6, + if (res->ai_family == AF_INET6 && + af == AF_INET6 && + setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&true, sizeof(true))) { freeaddrinfo(res); return log_error("setsockopt"); diff --git a/startup.c b/startup.c index 6e180d4..c905d7d 100644 --- a/startup.c +++ b/startup.c @@ -29,7 +29,7 @@ extern char *__progname; static char *progname; #endif -int af = AF_INET; +int af = AF_UNSPEC; static void usage(void) { From 3abb9bb9d6fc5f3b68977bf6010fac07806f266e Mon Sep 17 00:00:00 2001 From: Andrew Bower Date: Thu, 21 Aug 2025 23:44:24 +0100 Subject: [PATCH 4/7] Add dual-stack option --- standalone.c | 10 ++++++---- startup.c | 8 +++++++- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/standalone.c b/standalone.c index fc34966..9028337 100644 --- a/standalone.c +++ b/standalone.c @@ -36,6 +36,7 @@ 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; @@ -110,7 +111,8 @@ int do_standalone(void) int main(void) #endif { - int true = 1; + int off = 0; + int on = 1; int sock, new; struct sockaddr_storage addr; socklen_t addrlen; @@ -141,16 +143,16 @@ int main(void) } 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 && + (af == AF_INET6 || dual_stack) && setsockopt(sock, IPPROTO_IPV6, - IPV6_V6ONLY, (void *)&true, sizeof(true))) { + IPV6_V6ONLY, (void *)(dual_stack ? &off : &on), sizeof(on))) { freeaddrinfo(res); return log_error("setsockopt"); } diff --git a/startup.c b/startup.c index c905d7d..7e62bce 100644 --- a/startup.c +++ b/startup.c @@ -30,6 +30,7 @@ static char *progname; #endif int af = AF_UNSPEC; +int dual_stack = 0; static void usage(void) { @@ -59,9 +60,14 @@ int main(int argc, char **argv) standalone++; break; case '4': - af = AF_INET; + 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; From 10b30d9f6f6b39caf18f98a804972dddb5a76046 Mon Sep 17 00:00:00 2001 From: Andrew Bower Date: Thu, 21 Aug 2025 23:44:25 +0100 Subject: [PATCH 5/7] Document IPv6 feature --- popa3d.8 | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) 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 From f484463dda01165c315395edf0fcf18968160229 Mon Sep 17 00:00:00 2001 From: Andrew Bower Date: Sat, 23 Aug 2025 16:39:47 +0100 Subject: [PATCH 6/7] Use binary IP address for identifying peers Also fixes IPv6 build with POP_VIRTUAL --- standalone.c | 53 ++++++++++++++++++++++++++++++++++++++++++---------- virtual.c | 5 +++-- 2 files changed, 46 insertions(+), 12 deletions(-) diff --git a/standalone.c b/standalone.c index 9028337..3d89c16 100644 --- a/standalone.c +++ b/standalone.c @@ -40,6 +40,15 @@ 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), @@ -47,7 +56,7 @@ typedef volatile sig_atomic_t va_int; * information about sessions that we could have allowed to proceed. */ static struct { - char addr[NI_MAXHOST]; /* 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 */ @@ -105,6 +114,25 @@ 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 @@ -116,6 +144,7 @@ int main(void) int sock, new; struct sockaddr_storage addr; socklen_t addrlen; + struct ip_addr peer; int pid; struct tms buf; clock_t min_delay, now, log; @@ -208,12 +237,6 @@ int main(void) addrlen = sizeof(addr); new = accept(sock, (struct sockaddr *)&addr, &addrlen); - error = getnameinfo((struct sockaddr *)&addr, addrlen, - hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST); - if (error) - ; /* XXX */ - - /* * I wish there were 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 @@ -222,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; @@ -234,7 +268,7 @@ int main(void) if (sessions[i].pid || (sessions[i].start && now - sessions[i].start < min_delay)) { - if (strcmp(sessions[i].addr, hbuf) == 0) + if (cmp_ip_addr(&sessions[i].addr, &peer) == 0) if (++n >= MAX_SESSIONS_PER_SOURCE) break; } else @@ -283,8 +317,7 @@ int main(void) return do_pop_session(); default: - strlcpy(sessions[j].addr, hbuf, - sizeof(sessions[j].addr)); + sessions[j].addr = peer; sessions[j].pid = pid; sessions[j].start = now; sessions[j].log = 0; diff --git a/virtual.c b/virtual.c index 278b25d..45e4060 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 @@ -57,7 +58,7 @@ static const char *lookup(void) error = getnameinfo((struct sockaddr *)&ss, length, hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST); if (error) { - /* logging? */ + log_error("getnameinfo"); return NULL; } From c4e4ffc063960b264cbc3757e278370ade44edd4 Mon Sep 17 00:00:00 2001 From: Andrew Bower Date: Sat, 23 Aug 2025 16:40:46 +0100 Subject: [PATCH 7/7] Take advantage of static buffer and fix -Wdiscarded-qualifiers --- virtual.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/virtual.c b/virtual.c index 45e4060..24a9697 100644 --- a/virtual.c +++ b/virtual.c @@ -41,7 +41,7 @@ int virtual_startup(void) return 0; } -static const char *lookup(void) +static char *lookup(void) { struct sockaddr_storage ss; socklen_t length; @@ -50,7 +50,10 @@ static const char *lookup(void) length = sizeof(ss); if (getsockname(0, (struct sockaddr *)&ss, &length)) { - if (errno == ENOTSOCK) return ""; + if (errno == ENOTSOCK) { + hbuf[0] = '\0'; + return hbuf; + } log_error("getsockname"); return NULL; } @@ -127,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, "/",