diff --git a/.clang-format b/.clang-format index 1384c682..d1aba71b 100644 --- a/.clang-format +++ b/.clang-format @@ -10,9 +10,9 @@ AllowAllParametersOfDeclarationOnNextLine: false AllowShortBlocksOnASingleLine: Always AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: All -AllowShortIfStatementsOnASingleLine: Always +AllowShortIfStatementsOnASingleLine: Never AllowShortLambdasOnASingleLine: All -AllowShortLoopsOnASingleLine: true +AllowShortLoopsOnASingleLine: false AlwaysBreakAfterReturnType: None AlwaysBreakTemplateDeclarations: Yes BreakBeforeBraces: Custom @@ -65,3 +65,4 @@ SpacesInParentheses: false SpacesInSquareBrackets: false TabWidth: 4 UseTab: ForContinuationAndIndentation +AlignConsecutiveMacros: true \ No newline at end of file diff --git a/common/struct_def.h b/common/struct_def.h index 41d7c41e..769f96ed 100644 --- a/common/struct_def.h +++ b/common/struct_def.h @@ -274,6 +274,7 @@ typedef enum Status { #define IsCAPNegotiation(x) (MyConnect(x) && (x)->cap_negotation) #define HasCap(x, y) (MyConnect(x) && (x)->caps & y) #define IsSASLAuthed(x) ((x)->flags & FLAGS_SASL) +#define IsPP2(x) (!IN6_IS_ADDR_UNSPECIFIED(&x->pp2_dip) && x->pp2_dport != 0) /* * defined debugging levels */ @@ -405,10 +406,13 @@ struct ListItem { #define PFLAG_DELAYED 0x00001 #define PFLAG_SERVERONLY 0x00002 #define PFLAG_TLS 0x00004 +#define PFLAG_PP2 0x00008 #define IsConfDelayed(x) ((x)->flags & PFLAG_DELAYED) #define IsConfServeronly(x) ((x)->flags & PFLAG_SERVERONLY) #define IsConfTLS(x) ((x)->flags & PFLAG_TLS) +#define IsConfPP2(x) ((x)->flags & PFLAG_PP2) +#define DoingPP2(x) ((x)->pp2_state && (x)->pp2_state->phase != PROXY_DONE) #define IsIllegal(x) ((x)->status & CONF_ILLEGAL) @@ -444,6 +448,35 @@ struct LineItem struct LineItem *next; }; +/* PROXY protocol v2 structures */ +typedef enum +{ + /* waiting for 16-byte header */ + PROXY_NEED_HDR = 1, + /* waiting for payload bytes */ + PROXY_NEED_PAYLOAD = 2, + /* parsing complete */ + PROXY_DONE = 3 +} PP2Phase; + +typedef struct { + /* current parsing phase (see PP2Phase enum) */ + PP2Phase phase; + /* buffer for header and payload data */ + unsigned char buf[512]; + /* number of bytes currently stored in 'buf' */ + size_t buflen; + /* number of bytes still required to finish the current phase: + * - starts at 16 (header size) + * - after header is parsed: payload length + * - becomes 0 once that phase is satisfied */ + size_t need; + /* payload length field as advertised in header */ + uint16_t fam_len; + /* family/protocol byte, identifies AF_INET / AF_INET6 / etc. */ + uint8_t fam; +} PP2State; + /* * Client structures */ @@ -588,6 +621,9 @@ struct Client { int cap_negotation; /* CAP negotiation is in progress. Registration must wait for "CAP END" */ aClient *sasl_service; /* The SASL service that is responsible for this user. */ char *cloak_tmp; /* Contains the cloaked hostname until it was applied to the user */ + PP2State *pp2_state; /* PROXY protocol v2: parser state */ + struct in6_addr pp2_dip; /* PROXY protocol v2: destination IP address */ + uint16_t pp2_dport; /* PROXY protocol v2: destination port */ }; #define CLIENT_LOCAL_SIZE sizeof(aClient) diff --git a/ircd/ircd.c b/ircd/ircd.c index b1153684..85460ae7 100644 --- a/ircd/ircd.c +++ b/ircd/ircd.c @@ -1010,6 +1010,7 @@ int main(int argc, char *argv[]) open_debugfile(); timeofday = time(NULL); (void)init_sys(); + init_trusted_proxy_ips(); #ifdef USE_SYSLOG openlog(mybasename(myargv[0]), LOG_PID|LOG_NDELAY, LOG_FACILITY); diff --git a/ircd/list.c b/ircd/list.c index 4ea63e2b..cc456960 100644 --- a/ircd/list.c +++ b/ircd/list.c @@ -148,6 +148,9 @@ aClient *make_client(aClient *from) cptr->sasl_service = NULL; cptr->cloak_tmp = NULL; memset(&cptr->cloak_ip, 0, sizeof(cptr->cloak_ip)); + cptr->pp2_state = NULL; + memset(&cptr->pp2_dip, 0, sizeof(cptr->pp2_dip)); + cptr->pp2_dport = 0; } return (cptr); } @@ -182,10 +185,14 @@ void free_client(aClient *cptr) if (cptr->user3) MyFree(cptr->user3); #endif - if(cptr->sasl_user) + if (cptr->sasl_user) { MyFree(cptr->sasl_user); } + if (cptr->pp2_state) + { + pp2_free(cptr); + } } MyFree(cptr); } diff --git a/ircd/s_auth.c b/ircd/s_auth.c index c433c080..784ca4ef 100644 --- a/ircd/s_auth.c +++ b/ircd/s_auth.c @@ -560,16 +560,33 @@ void start_auth(aClient *cptr) set_non_blocking(cptr->authfd, cptr); - /* get remote host peer - so that we get right interface -- jrg */ tlen = ulen = sizeof(us); - if (getpeername(cptr->fd, (struct sockaddr *)&them, &tlen) < 0) - { - /* we probably don't need this error message -kalt */ - report_error("getpeername for auth request %s:%s", cptr); - close(cptr->authfd); - cptr->authfd = -1; - return; - } + + if (IsPP2(cptr)) + { + char src[INET6_ADDRSTRLEN], dst[INET6_ADDRSTRLEN]; + memset(&them, 0, sizeof(them)); + them.SIN_FAMILY = AFINET; + memcpy(&them.sin6_addr, &cptr->ip, sizeof(cptr->ip)); + them.SIN_PORT = htons(cptr->port); + + Debug((DEBUG_INFO, "start_auth(%x) pp2 applied: src %s:%u dst %s:%u", + cptr, inetntop(AF_INET6, (char *) &cptr->ip, src, sizeof(src)), + (unsigned) cptr->port, + inetntop(AF_INET6, (char *) &cptr->pp2_dip, dst, sizeof(dst)), + (unsigned) cptr->pp2_dport)); + } + else + { + if (getpeername(cptr->fd, (struct sockaddr *) &them, &tlen) < 0) + { + /* we probably don't need this error message -kalt */ + report_error("getpeername for auth request %s:%s", cptr); + close(cptr->authfd); + cptr->authfd = -1; + return; + } + } them.SIN_FAMILY = AFINET; /* We must bind the local end to the interface that they connected @@ -579,31 +596,60 @@ void start_auth(aClient *cptr) (void)getsockname(cptr->fd, (struct sockaddr *)&us, &ulen); us.SIN_FAMILY = AFINET; - // Check if a source IP has been set in P-Line (usually if an SSL proxy is used) - if (cptr->acpt->confs && IsConfTLS(cptr->acpt->confs->value.aconf) - && !BadPtr(cptr->acpt->confs->value.aconf->source_ip)) + if (IsPP2(cptr)) { - inetpton(AF_INET6, cptr->acpt->confs->value.aconf->source_ip, us.sin6_addr.s6_addr); + memset(&us, 0, sizeof(us)); + us.SIN_FAMILY = AFINET; + memcpy(&us.sin6_addr, &cptr->pp2_dip, sizeof(cptr->pp2_dip)); + } + else + { + if (getsockname(cptr->fd, (struct sockaddr *) &us, &ulen) < 0) + { + report_error("getsockname for auth request %s:%s", cptr); + close(cptr->authfd); + cptr->authfd = -1; + return; + } + us.SIN_FAMILY = AFINET; + + /* + * Check if a source IP has been set in P-Line (usually if an + * SSL proxy is used) + */ + if (cptr->acpt->confs && IsConfTLS(cptr->acpt->confs->value.aconf) && + !BadPtr(cptr->acpt->confs->value.aconf->source_ip)) + { + inetpton(AF_INET6, cptr->acpt->confs->value.aconf->source_ip, + us.sin6_addr.s6_addr); + } } # if defined(USE_IAUTH) if (adfd >= 0) - { + { char abuf[BUFSIZ]; + unsigned int iauth_lport = IsPP2(cptr) ? (unsigned) cptr->pp2_dport + : (unsigned) ntohs(us.SIN_PORT); + sprintf(abuf, "%d C %s %u ", cptr->fd, - inetntop(AF_INET6, (char *)&them.sin6_addr, ipv6string, - sizeof(ipv6string)), ntohs(them.SIN_PORT)); - sprintf(abuf+strlen(abuf), "%s %u", - inetntop(AF_INET6, (char *)&us.sin6_addr, ipv6string, - sizeof(ipv6string)), ntohs(us.SIN_PORT)); + inetntop(AF_INET6, (char *) &them.sin6_addr, ipv6string, + sizeof(ipv6string)), + (unsigned) ntohs(them.SIN_PORT)); + + sprintf(abuf + strlen(abuf), "%s %u", + inetntop(AF_INET6, (char *) &us.sin6_addr, ipv6string, + sizeof(ipv6string)), + iauth_lport); + if (sendto_iauth(abuf) == 0) - { + { close(cptr->authfd); cptr->authfd = -1; cptr->flags |= FLAGS_XAUTH; return; - } - } + } + } # endif Debug((DEBUG_NOTICE,"auth(%x) from %s %x %x", cptr, inet_ntop(AF_INET6, (char *)&us.sin6_addr, ipv6string, @@ -664,43 +710,63 @@ void start_auth(aClient *cptr) */ void send_authports(aClient *cptr) { - struct SOCKADDR_IN us, them; + struct SOCKADDR_IN us, them; - char authbuf[32]; + char authbuf[32]; SOCK_LEN_TYPE ulen, tlen; - Debug((DEBUG_NOTICE,"write_authports(%x) fd %d authfd %d stat %d", - cptr, cptr->fd, cptr->authfd, cptr->status)); + Debug((DEBUG_NOTICE, "write_authports(%x) fd %d authfd %d stat %d", cptr, + cptr->fd, cptr->authfd, cptr->status)); tlen = ulen = sizeof(us); - if (getsockname(cptr->fd, (struct SOCKADDR *)&us, &ulen) || - getpeername(cptr->fd, (struct SOCKADDR *)&them, &tlen)) - { -#ifdef USE_SYSLOG - syslog(LOG_ERR, "auth get{sock,peer}name error for %s:%m", - get_client_name(cptr, TRUE)); + + if (getsockname(cptr->fd, (struct SOCKADDR *) &us, &ulen)) + { +#ifdef USE_SYSLOG + syslog(LOG_ERR, "auth getsockname error for %s:%m", + get_client_name(cptr, TRUE)); #endif goto authsenderr; - } + } - sprintf(authbuf, "%u , %u\r\n", - (unsigned int)ntohs(them.SIN_PORT), - (unsigned int)ntohs(us.SIN_PORT)); + if (IsPP2(cptr)) + { + memset(&them, 0, sizeof(them)); + them.SIN_FAMILY = AFINET; + memcpy(&them.sin6_addr, &cptr->ip, sizeof(cptr->ip)); + them.SIN_PORT = htons(cptr->port); + sprintf(authbuf, "%u , %u\r\n", (unsigned int) cptr->port, + (unsigned int) cptr->pp2_dport); + } + else + { + if (getpeername(cptr->fd, (struct SOCKADDR *) &them, &tlen)) + { +#ifdef USE_SYSLOG + syslog(LOG_ERR, "auth getpeername error for %s:%m", + get_client_name(cptr, TRUE)); +#endif + goto authsenderr; + } - Debug((DEBUG_SEND, "sending [%s] to auth port %s.113", - authbuf, inet_ntop(AF_INET6, (char *)&them.sin6_addr, - ipv6string, sizeof(ipv6string)))); + sprintf(authbuf, "%u , %u\r\n", (unsigned int) ntohs(them.SIN_PORT), + (unsigned int) ntohs(us.SIN_PORT)); + } + + Debug((DEBUG_SEND, "sending [%s] to auth port %s.113", authbuf, + inet_ntop(AF_INET6, (char *) &them.sin6_addr, ipv6string, + sizeof(ipv6string)))); if (write(cptr->authfd, authbuf, strlen(authbuf)) != strlen(authbuf)) - { -authsenderr: + { + authsenderr: ircstp->is_abad++; - (void)close(cptr->authfd); + (void) close(cptr->authfd); if (cptr->authfd == highest_fd) while (!local[highest_fd]) highest_fd--; cptr->authfd = -1; - cptr->flags &= ~(FLAGS_AUTH|FLAGS_WRAUTH); + cptr->flags &= ~(FLAGS_AUTH | FLAGS_WRAUTH); return; - } + } cptr->flags &= ~FLAGS_WRAUTH; return; } diff --git a/ircd/s_bsd.c b/ircd/s_bsd.c index d1f40544..c5f3cbdf 100644 --- a/ircd/s_bsd.c +++ b/ircd/s_bsd.c @@ -859,20 +859,27 @@ static int check_init(aClient *cptr, char *sockn) /* If descriptor is a tty, special checking... */ if (isatty(cptr->fd)) - { + { strncpyzt(sockn, me.sockhost, HOSTLEN); bzero((char *)&sk, sizeof(struct SOCKADDR_IN)); - } + } + else if (IsPP2(cptr)) + { + inetntop(AF_INET6, (char *) &cptr->ip, sockn, INET6_ADDRSTRLEN); + } else if (getpeername(cptr->fd, (SAP)&sk, &len) == -1) - { + { report_error("connect failure: %s %s", cptr); return -1; - } - inetntop(AF_INET6, (char *)&sk.sin6_addr, sockn, INET6_ADDRSTRLEN); - Debug((DEBUG_DNS,"sockn %x",sockn)); - Debug((DEBUG_DNS,"sockn %s",sockn)); - bcopy((char *)&sk.SIN_ADDR, (char *)&cptr->ip, sizeof(struct IN_ADDR)); - cptr->port = ntohs(sk.SIN_PORT); + } + else + { + inetntop(AF_INET6, (char *) &sk.sin6_addr, sockn, INET6_ADDRSTRLEN); + Debug((DEBUG_DNS, "sockn %x", sockn)); + Debug((DEBUG_DNS, "sockn %s", sockn)); + bcopy((char *) &sk.SIN_ADDR, (char *) &cptr->ip, sizeof(struct IN_ADDR)); + cptr->port = ntohs(sk.SIN_PORT); + } return 0; } @@ -1533,7 +1540,7 @@ void set_non_blocking(int fd, aClient *cptr) * check_clones * adapted by jecete 4 IRC Ptnet */ -static int check_clones(aClient *cptr) +int check_clones(aClient *cptr) { struct abacklog { struct IN_ADDR ip; @@ -1599,27 +1606,36 @@ void add_connection_refuse(int fd, aClient *acptr, int delay) */ aClient *add_connection(aClient *cptr, int fd) { - Link lin; aClient *acptr; aConfItem *aconf = NULL; acptr = make_client(NULL); aconf = cptr->confs->value.aconf; acptr->acpt = cptr; + acptr->fd = fd; /* Removed preliminary access check. Full check is performed in * m_server and m_user instead. Also connection time out help to * get rid of unwanted connections. */ - if (isatty(fd)) /* If descriptor is a tty, special checking... */ + if (isatty(fd)) + { + /* If descriptor is a tty, special checking... */ get_sockhost(acptr, cptr->sockhost); + if (IsConfPP2(aconf)) + { + /* Reject tty connections if PROXY protocol is required */ + add_connection_refuse(fd, acptr, 0); + return NULL; + } + } else - { + { struct SOCKADDR_IN addr; SOCK_LEN_TYPE len = sizeof(struct SOCKADDR_IN); if (getpeername(fd, (SAP)&addr, &len) == -1) - { + { #if defined(linux) if (errno != ENOTCONN) #endif @@ -1627,7 +1643,7 @@ aClient *add_connection(aClient *cptr, int fd) cptr); add_connection_refuse(fd, acptr, 0); return NULL; - } + } /* don't want to add "Failed in connecting to" here.. */ if (aconf && IsIllegal(aconf)) { @@ -1639,43 +1655,35 @@ aClient *add_connection(aClient *cptr, int fd) */ inetntop(AF_INET6, (char *)&addr.sin6_addr, ipv6string, sizeof(ipv6string)); - get_sockhost(acptr, (char *)ipv6string); + get_sockhost(acptr, ipv6string); bcopy ((char *)&addr.SIN_ADDR, (char *)&acptr->ip, sizeof(struct IN_ADDR)); acptr->port = ntohs(addr.SIN_PORT); -#ifdef CLONE_CHECK - if (check_clones(acptr) > CLONE_MAX) + if (IsConfPP2(aconf)) { - sendto_flag(SCH_LOCAL, "Rejecting connection from %s.", - get_client_host(acptr)); - acptr->exitc = EXITC_CLONE; - sendto_flog(acptr, EXITC_CLONE, "", acptr->sockhost); -#ifdef DELAY_CLOSE - nextdelayclose = delay_close(fd); -#else - (void)send(fd, "ERROR :Too rapid connections from your " - "host\r\n", 46, 0); -#endif - /* If DELAY_CLOSE is not defined, delay will - ** be changed to 0 inside. --B. */ - add_connection_refuse(fd, acptr, 1); - return NULL; + if (!is_trusted_proxy((const struct in6_addr *) &addr.sin6_addr)) + { + Debug((DEBUG_INFO, + "pp2(%d): rejecting untrusted connection from %s", + acptr->fd, ipv6string)); + sendto_flag(SCH_LOCAL, + "Rejecting PROXY protocol connection from %s", + ipv6string); + send(acptr->fd, "ERROR :Proxy connection not allowed\r\n", 37, + 0); + add_connection_refuse(acptr->fd, acptr, 0); + return NULL; + } + pp2_init(acptr); } -#endif - lin.flags = ASYNC_CLIENT; - lin.value.cptr = acptr; - lin.next = NULL; - Debug((DEBUG_DNS, "lookup %s", - inet_ntop(AF_INET6, (char *)&addr.sin6_addr, - ipv6string, sizeof(ipv6string)))); - acptr->hostp = gethost_byaddr((char *)&acptr->ip, &lin); - if (!acptr->hostp) - SetDNS(acptr); - nextdnscheck = 1; - } + else + { + if (finalize_connection(acptr, ipv6string) < 0) + return NULL; + } + } - acptr->fd = fd; set_non_blocking(acptr->fd, acptr); if (set_sock_opts(acptr->fd, acptr) == -1) { @@ -1689,22 +1697,57 @@ aClient *add_connection(aClient *cptr, int fd) local[fd] = acptr; add_fd(fd, &fdall); add_client_to_list(acptr); - start_auth(acptr); + return acptr; +} + +int finalize_connection(aClient *cptr, const char *ipstr) +{ + Link lin; + +#ifdef CLONE_CHECK + if (check_clones(cptr) > CLONE_MAX) + { + sendto_flag(SCH_LOCAL, "Rejecting connection from %s.", + get_client_host(cptr)); + cptr->exitc = EXITC_CLONE; + sendto_flog(cptr, EXITC_CLONE, "", cptr->sockhost); +#ifdef DELAY_CLOSE + nextdelayclose = delay_close(cptr->fd); +#else + (void) send(cptr->fd, "ERROR :Too rapid connections from your host\r\n", + 46, 0); +#endif + /* If DELAY_CLOSE is not defined, delay will be changed to 0 inside. + * --B. */ + add_connection_refuse(cptr->fd, cptr, 1); + return -1; + } +#endif + + lin.flags = ASYNC_CLIENT; + lin.value.cptr = cptr; + lin.next = NULL; + Debug((DEBUG_DNS, "lookup %s", ipstr)); + cptr->hostp = gethost_byaddr((char *) &cptr->ip, &lin); + if (!cptr->hostp) + SetDNS(cptr); + nextdnscheck = 1; + + start_auth(cptr); #if defined(USE_IAUTH) - if (!isatty(fd) && !DoingDNS(acptr)) - { + if (!isatty(cptr->fd) && !DoingDNS(cptr)) + { int i = 0; - - while (acptr->hostp->h_aliases[i]) - sendto_iauth("%d A %s", acptr->fd, - acptr->hostp->h_aliases[i++]); - if (acptr->hostp->h_name) - sendto_iauth("%d N %s",acptr->fd,acptr->hostp->h_name); - else if (acptr->hostp->h_aliases[0]) - sendto_iauth("%d n", acptr->fd); - } + + while (cptr->hostp->h_aliases[i]) + sendto_iauth("%d A %s", cptr->fd, cptr->hostp->h_aliases[i++]); + if (cptr->hostp->h_name) + sendto_iauth("%d N %s", cptr->fd, cptr->hostp->h_name); + else if (cptr->hostp->h_aliases[0]) + sendto_iauth("%d n", cptr->fd); + } #endif - return acptr; + return 0; } #ifdef UNIXPORT @@ -1920,7 +1963,7 @@ static int read_packet(aClient *cptr, int msg_ready) if (msg_ready && !(IsPerson(cptr) && DBufLength(&cptr->recvQ) > 6090)) - { + { errno = 0; length = recvfrom(cptr->fd, readbuf, sizeof(readbuf), 0, 0, 0); #if defined(DEBUGMODE) && defined(DEBUG_READ) @@ -1946,7 +1989,57 @@ static int read_packet(aClient *cptr, int msg_ready) return 1; if (length <= 0) return length; - } + + if (cptr->pp2_state) + { + size_t consumed = 0; + int pr = pp2_consume(cptr, (const unsigned char *) readbuf, + (size_t) length, &consumed); + if (pr < 0) + { + /* + * Reject if the PROXY protocol header is invalid. + * This typically indicates one of: + * - misconfiguration (e.g. proxy not sending v2 header) + * - mismatch between proxy and ircd implementation + * - a direct client connection to a port reserved for proxy use + */ + char *client_ip = get_client_ip(cptr); + Debug((DEBUG_INFO, + "pp2(%d): invalid PROXY protocol header from %s", + cptr->fd, client_ip)); + sendto_flag(SCH_ERROR, "Invalid PROXY protocol header from %s", + client_ip); + return exit_client(cptr, cptr, &me, "Bad PROXY header"); + } + if (pr == 0) + { + /* waiting for more header bytes */ + return 1; + } + if (consumed > 0) + { + Debug((DEBUG_READ, "pp2(%d): memmove %zu, old length=%d", + cptr->fd, consumed, length)); + if (consumed < (size_t) length) + { + memmove(readbuf, readbuf + consumed, + (size_t) length - consumed); + } + length -= (int) consumed; + if (length == 0) + { + /* header-only read; next read will have IRC bytes */ + return 1; + } + } + if (pr == 1) + { + /* PROXY protocol parsing completed successfully. */ + pp2_free(cptr); + } + } + } else if (msg_ready) return exit_client(cptr, cptr, &me, "EOF From Client"); @@ -2101,12 +2194,12 @@ int read_message(time_t delay, FdAry *fdp, int ro) ** so no need to check for anything! */ #if defined(USE_IAUTH) - if (DoingDNS(cptr) || DoingAuth(cptr) || - WaitingXAuth(cptr) || - (DoingXAuth(cptr) && - !(iauth_options & XOPT_EARLYPARSE))) + if ((DoingDNS(cptr) || DoingAuth(cptr) || WaitingXAuth(cptr) || + (DoingXAuth(cptr) && + !(iauth_options & XOPT_EARLYPARSE))) && + !DoingPP2(cptr)) #else - if (DoingDNS(cptr) || DoingAuth(cptr)) + if ((DoingDNS(cptr) || DoingAuth(cptr)) && !DoingPP2(cptr)) #endif continue; #if !defined(USE_POLL) @@ -2362,10 +2455,10 @@ int read_message(time_t delay, FdAry *fdp, int ro) } length = 1; /* for fall through case */ if (!NoNewLine(cptr) || TST_READ_EVENT(fd)) - { - if (!DoingAuth(cptr)) + { + if (DoingPP2(cptr) || !DoingAuth(cptr)) length = read_packet(cptr, TST_READ_EVENT(fd)); - } + } readcalls++; if (length == FLUSH_BUFFER) continue; diff --git a/ircd/s_bsd_ext.h b/ircd/s_bsd_ext.h index 78329407..c2739985 100644 --- a/ircd/s_bsd_ext.h +++ b/ircd/s_bsd_ext.h @@ -65,6 +65,7 @@ EXTERN int connect_server (aConfItem *aconf, aClient *by, EXTERN void get_my_name (aClient *cptr, char *name, int len); EXTERN int setup_ping (aConfItem *aconf); EXTERN void send_ping (aConfItem *aconf); +EXTERN int finalize_connection(aClient *cptr, const char *ipstr); #if defined(ENABLE_SUMMON) || defined(ENABLE_USERS) EXTERN int utmp_open(void); EXTERN int utmp_read (int fd, char *name, char *line, char *host, diff --git a/ircd/s_conf.c b/ircd/s_conf.c index a6cc33c4..a6f12ddd 100644 --- a/ircd/s_conf.c +++ b/ircd/s_conf.c @@ -191,6 +191,7 @@ char *iline_flags_to_string(long flags) * D - delayed port * S - server only port * T - secure (SSL/TLS) port + * P - PROXY protocol v2 port */ long pline_flags_parse(char *string) { @@ -199,6 +200,10 @@ long pline_flags_parse(char *string) { tmp |= PFLAG_DELAYED; } + if (index(string, 'P')) + { + tmp |= PFLAG_PP2; + } if (index(string, 'S')) { tmp |= PFLAG_SERVERONLY; @@ -223,7 +228,12 @@ char *pline_flags_to_string(long flags) { *s++ = 'D'; } - + + if (flags & PFLAG_PP2) + { + *s++ = 'P'; + } + if (flags & PFLAG_SERVERONLY) { *s++ = 'S'; diff --git a/ircd/s_externs.h b/ircd/s_externs.h index f6d0bee4..7b5cfd15 100644 --- a/ircd/s_externs.h +++ b/ircd/s_externs.h @@ -31,6 +31,7 @@ #include "match_ext.h" #include "packet_ext.h" #include "parse_ext.h" +#include "patricia_ext.h" #include "res_comp_ext.h" #include "res_ext.h" #include "res_init_ext.h" @@ -40,17 +41,17 @@ #include "s_conf_ext.h" #include "s_debug_ext.h" #include "s_err_ext.h" +#include "s_id_ext.h" #include "s_misc_ext.h" #include "s_numeric_ext.h" +#include "s_pp2_ext.h" +#include "s_sasl_ext.h" +#include "s_send_ext.h" #include "s_serv_ext.h" #include "s_service_ext.h" -#include "s_send_ext.h" #include "s_user_ext.h" #include "s_zip_ext.h" -#include "s_id_ext.h" #include "send_ext.h" #include "support_ext.h" #include "version_ext.h" #include "whowas_ext.h" -#include "s_sasl_ext.h" -#include "patricia_ext.h" diff --git a/ircd/s_pp2.c b/ircd/s_pp2.c new file mode 100644 index 00000000..88d948d7 --- /dev/null +++ b/ircd/s_pp2.c @@ -0,0 +1,312 @@ +/* +* IRC - Internet Relay Chat, ircd/s_pp2.c +* +* Copyright (C) 2025 Patrick Okraku +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 1, or (at your option) +* any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "os.h" +#include "s_defines.h" +#include "s_externs.h" + +static const char *trusted_proxy_ip_strings[] = { TRUSTED_PROXY_ADDRESSES }; +#define TRUSTED_PROXY_IP_COUNT elementsof(trusted_proxy_ip_strings) +static struct in6_addr trusted_proxy_ips[TRUSTED_PROXY_IP_COUNT]; + +void init_trusted_proxy_ips() +{ + size_t i; + for (i = 0; i < TRUSTED_PROXY_IP_COUNT; i++) + { + const char *ip_str = trusted_proxy_ip_strings[i]; + struct in6_addr addr6; + + // Try to parse the string as a native IPv6 address + if (inet_pton(AF_INET6, ip_str, &addr6) == 1) + { + trusted_proxy_ips[i] = addr6; + } + else + { + struct in_addr addr4; + + // Try to parse the string as an IPv4 address + if (inet_pton(AF_INET, ip_str, &addr4) == 1) + { + // Build an IPv4-mapped IPv6 address (::ffff:w.x.y.z) + memset(&addr6, 0, sizeof(addr6)); + addr6.s6_addr[10] = 0xff; + addr6.s6_addr[11] = 0xff; + memcpy(&addr6.s6_addr[12], &addr4, 4); + trusted_proxy_ips[i] = addr6; + } + else + { + // Neither IPv6 nor IPv4 + fprintf(stderr, "Invalid IP in TRUSTED_PROXY_ADDRESSES: %s\n", ip_str); + } + } + } +} + +int is_trusted_proxy(const struct in6_addr *addr) +{ + size_t i; + for (i = 0; i < TRUSTED_PROXY_IP_COUNT; i++) + { + if (memcmp(addr, &trusted_proxy_ips[i], sizeof(struct in6_addr)) == 0) + { + return 1; + } + } + return 0; +} + +void pp2_init(aClient *cptr) +{ + cptr->pp2_state = (PP2State *) MyMalloc(sizeof(*cptr->pp2_state)); + memset(cptr->pp2_state, 0, sizeof(*cptr->pp2_state)); + cptr->pp2_state->phase = PROXY_NEED_HDR; + cptr->pp2_state->buflen = 0; + cptr->pp2_state->need = 16; + Debug((DEBUG_INFO, "pp2(%d): init", cptr->fd)); +} + +int pp2_consume(aClient *cptr, const unsigned char *data, size_t len, size_t *consumed_out) +{ + static const unsigned char pp2_sig[12] = { 0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, + 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A }; + char sig[3 * 12 + 1], src[INET6_ADDRSTRLEN], dst[INET6_ADDRSTRLEN]; + size_t off = 0; + int i; + *consumed_out = 0; + + while (off < len && cptr->pp2_state->phase != PROXY_DONE) + { + size_t take = cptr->pp2_state->need; + if (take > (len - off)) + { + take = len - off; + } + if (take) + { + if (cptr->pp2_state->buflen + take > sizeof(cptr->pp2_state->buf)) + { + Debug((DEBUG_ERROR, "pp2(%d): buffer overflow, buflen=%lu, take=%lu, bufsize=%lu", + cptr->fd, (unsigned long) cptr->pp2_state->buflen, (unsigned long) take, + (unsigned long) sizeof(cptr->pp2_state->buf))); + *consumed_out = off; + return -1; + } + + memcpy(cptr->pp2_state->buf + cptr->pp2_state->buflen, data + off, take); + cptr->pp2_state->buflen += take; + cptr->pp2_state->need -= take; + off += take; + } + + if (cptr->pp2_state->phase == PROXY_NEED_HDR && cptr->pp2_state->need == 0) + { + unsigned char ver_cmd, fam; + unsigned short paylen; + + if (cptr->pp2_state->buflen < 16) + { + Debug((DEBUG_ERROR, "pp2(%d): header too short: %lu", cptr->fd, + (unsigned long) cptr->pp2_state->buflen)); + *consumed_out = off; + return -1; + } + if (memcmp(cptr->pp2_state->buf, pp2_sig, 12) != 0) + { + for (i = 0; i < 12; i++) + { + sprintf(sig + i * 3, "%02x ", cptr->pp2_state->buf[i]); + } + sig[3 * 12] = '\0'; + Debug((DEBUG_ERROR, "pp2(%d): bad signature: %s", cptr->fd, sig)); + *consumed_out = off; + return -1; + } + + ver_cmd = cptr->pp2_state->buf[12]; + fam = cptr->pp2_state->buf[13]; + paylen = (unsigned short) ((((unsigned int) cptr->pp2_state->buf[14]) << 8) | + ((unsigned int) cptr->pp2_state->buf[15])); + Debug((DEBUG_INFO, "pp2(%d): header ver=%u cmd=%u fam=0x%02x len=%u", cptr->fd, + (ver_cmd >> 4) & 0x0F, ver_cmd & 0x0F, fam, (unsigned) paylen)); + + if ((ver_cmd & 0xF0) != 0x20) + { + Debug((DEBUG_ERROR, "pp2(%d): unsupported version, ver_cmd=0x%02x", cptr->fd, + ver_cmd)); + *consumed_out = off; + return -1; + } + if ((ver_cmd & 0x0F) != 0x01) + { + Debug((DEBUG_ERROR, + "pp2(%d): unsupported command, ver_cmd=0x%02x (only PROXY=0x01 allowed)", + cptr->fd, ver_cmd)); + *consumed_out = off; + return -1; + } + /* transport protocol: 0x01=STREAM(TCP) */ + if ((fam & 0x0F) != 0x01) + { + Debug((DEBUG_ERROR, "pp2(%d): unsupported transport protocol 0x%02x", cptr->fd, + (unsigned) (fam & 0x0F))); + *consumed_out = off; + return -1; + } + if ((size_t) paylen > sizeof(cptr->pp2_state->buf)) + { + Debug((DEBUG_ERROR, "pp2(%d): payload length too large: %u > %lu", cptr->fd, + (unsigned) paylen, (unsigned long) sizeof(cptr->pp2_state->buf))); + *consumed_out = off; + return -1; + } + + cptr->pp2_state->fam = fam; + cptr->pp2_state->fam_len = paylen; + cptr->pp2_state->phase = PROXY_NEED_PAYLOAD; + cptr->pp2_state->need = paylen; + /* reset buffer before reading payload */ + cptr->pp2_state->buflen = 0; + } + else if (cptr->pp2_state->phase == PROXY_NEED_PAYLOAD && cptr->pp2_state->need == 0) + { + const unsigned char *p = cptr->pp2_state->buf; + + if ((cptr->pp2_state->fam & 0xF0) == 0x10) + { + /* AF_INET */ + unsigned sport, dport; + struct in_addr s, d; + struct in6_addr v4m_src, v4m_dst; + + if (cptr->pp2_state->fam_len < 12) + { + Debug((DEBUG_ERROR, + "pp2(%d): payload too short for AF_INET, need at least 12 bytes, got " + "%lu", + cptr->fd, (unsigned long) cptr->pp2_state->fam_len)); + *consumed_out = off; + return -1; + } + + memcpy(&s.s_addr, p, 4); + memcpy(&d.s_addr, p + 4, 4); + + sport = ((unsigned) p[8] << 8) | p[9]; + dport = ((unsigned) p[10] << 8) | p[11]; + + /* IPv4 -> v4-mapped IPv6 (source + dest) */ + memset(&v4m_src, 0, sizeof(v4m_src)); + v4m_src.s6_addr[10] = 0xff; + v4m_src.s6_addr[11] = 0xff; + memcpy(&v4m_src.s6_addr[12], &s.s_addr, 4); + + memset(&v4m_dst, 0, sizeof(v4m_dst)); + v4m_dst.s6_addr[10] = 0xff; + v4m_dst.s6_addr[11] = 0xff; + memcpy(&v4m_dst.s6_addr[12], &d.s_addr, 4); + + inetntop(AF_INET6, &v4m_src, src, sizeof(src)); + inetntop(AF_INET6, &v4m_dst, dst, sizeof(dst)); + Debug((DEBUG_INFO, "pp2(%d): IPv4 src=%s:%u dst=%s:%u", cptr->fd, src, sport, dst, + dport)); + + /* Source (client) */ + memcpy(&cptr->ip, &v4m_src, sizeof(v4m_src)); + cptr->port = sport; + get_sockhost(cptr, src); + + /* Destination (server) */ + memcpy(&cptr->pp2_dip, &v4m_dst, sizeof(v4m_dst)); + cptr->pp2_dport = dport; + } + else if ((cptr->pp2_state->fam & 0xF0) == 0x20) + { + struct in6_addr s6, d6; + unsigned sport, dport; + + if (cptr->pp2_state->fam_len < 36) + { + Debug((DEBUG_ERROR, + "pp2(%d): payload too short for AF_INET6, need at least 36 bytes, got " + "%lu", + cptr->fd, (unsigned long) cptr->pp2_state->fam_len)); + *consumed_out = off; + return -1; + } + + memcpy(&s6, p, 16); + memcpy(&d6, p + 16, 16); + + sport = ((unsigned) p[32] << 8) | p[33]; + dport = ((unsigned) p[34] << 8) | p[35]; + + inetntop(AF_INET6, &s6, src, sizeof(src)); + inetntop(AF_INET6, &d6, dst, sizeof(dst)); + Debug((DEBUG_INFO, "pp2(%d): IPv6 src=[%s]:%u dst=[%s]:%u", cptr->fd, src, sport, + dst, dport)); + + /* Source (client) */ + memcpy(&cptr->ip, &s6, sizeof(s6)); + cptr->port = sport; + get_sockhost(cptr, src); + + /* Destination (server) */ + memcpy(&cptr->pp2_dip, &d6, sizeof(d6)); + cptr->pp2_dport = dport; + } + else + { + Debug((DEBUG_ERROR, "pp2(%d): unknown address family 0x%02x", cptr->fd, + cptr->pp2_state->fam)); + *consumed_out = off; + return -1; + } + + cptr->pp2_state->phase = PROXY_DONE; + cptr->pp2_state->buflen = 0; + cptr->pp2_state->need = 0; + + Debug((DEBUG_INFO, "pp2(%d): complete", cptr->fd)); + + if (finalize_connection(cptr, src) < 0) + { + *consumed_out = off; + return -1; + } + } + } + *consumed_out = off; + Debug((DEBUG_READ, "pp2(%d): consumed=%lu state=%d len=%lu", cptr->fd, (unsigned long) off, + (int) cptr->pp2_state->phase, (unsigned long) len)); + + return (cptr->pp2_state->phase == PROXY_DONE) ? 1 : 0; +} + +void pp2_free(aClient *cptr) +{ + if (cptr->pp2_state) + { + MyFree(cptr->pp2_state); + cptr->pp2_state = NULL; + } +} \ No newline at end of file diff --git a/ircd/s_pp2_ext.h b/ircd/s_pp2_ext.h new file mode 100644 index 00000000..7deccd67 --- /dev/null +++ b/ircd/s_pp2_ext.h @@ -0,0 +1,54 @@ +/* + * IRC - Internet Relay Chat, ircd/s_pp2_ext.h + * + * Copyright (C) 2025 Patrick Okraku + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 1, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#define EXTERN +/* + * Initializes the list of trusted proxy IP addresses. + */ +void init_trusted_proxy_ips(); + +/* + * Verifies that the given address is listed as a trusted proxy. + * + * Only connections from trusted proxy addresses are allowed to send + * PROXY protocol v2 headers that override the client IP/port. This + * check prevents untrusted clients from spoofing their source address. + */ +int is_trusted_proxy(const struct in6_addr *addr); + +/* + * Initializes PROXY protocol v2 parser for a newly accepted client. + */ +EXTERN void pp2_init(aClient *cptr); + +/* + * Processes incoming bytes of PROXY protocol v2. + * + * Returns: + * -1 → fatal error, drop connection + * 0 → parsing still incomplete, need more data + * 1 → parsing complete, client address applied, continue as normal + */ +EXTERN int pp2_consume(aClient *cptr, const unsigned char *data, size_t len, size_t *consumed_out); + +/* + * Frees the PROXY protocol v2 state of a client. + */ +void pp2_free(aClient *cptr); +#undef EXTERN \ No newline at end of file diff --git a/support/Makefile.in b/support/Makefile.in index af976d12..8e2c7f86 100644 --- a/support/Makefile.in +++ b/support/Makefile.in @@ -147,7 +147,8 @@ IRCD_COMMON_OBJS = bsd.o dbuf.o packet.o send.o match.o parse.o \ IRCD_OBJS = channel.o class.o hash.o ircd.o list.o res.o s_auth.o \ s_bsd.o s_conf.o s_debug.o s_err.o s_id.o s_misc.o s_numeric.o \ s_send.o s_serv.o s_service.o s_user.o s_zip.o whowas.o \ - res_init.o res_comp.o res_mkquery.o patricia.o s_cap.o s_sasl.o + res_init.o res_comp.o res_mkquery.o patricia.o s_cap.o s_sasl.o \ + s_pp2.o IAUTH_COMMON_OBJS = clsupport.o clmatch.o # This is a little evil IAUTH_OBJS = iauth.o a_conf.o a_io.o a_log.o \ @@ -330,6 +331,9 @@ s_auth.o: ../ircd/s_auth.c setup.h config.h ../common/struct_def.h s_bsd.o: ../ircd/s_bsd.c setup.h config.h ../common/struct_def.h $(CC) $(S_CFLAGS) -DIRCDPID_PATH="\"$(IRCDPID_PATH)\"" -DIAUTH_PATH="\"$(IAUTH_PATH)\"" -DIAUTH="\"$(IAUTH)\"" -c -o $@ ../ircd/s_bsd.c +s_cap.o: ../ircd/s_cap.c setup.h config.h ../common/struct_def.h + $(CC) $(S_CFLAGS) -c -o $@ ../ircd/s_cap.c + s_conf.o: ../ircd/s_conf.c setup.h config.h ../common/struct_def.h ../ircd/config_read.c $(CC) $(S_CFLAGS) -DIRCDMOTD_PATH="\"$(IRCDMOTD_PATH)\"" \ -DIRCDM4_PATH="\"$(IRCDM4_PATH)\"" -DIRCDCONF_PATH="\"$(IRCDCONF_PATH)\"" \ @@ -370,6 +374,9 @@ s_zip.o: ../ircd/s_zip.c setup.h config.h ../common/struct_def.h s_cap.o: ../ircd/s_cap.c setup.h config.h ../common/struct_def.h $(CC) $(S_CFLAGS) -c -o $@ ../ircd/s_cap.c +s_pp2.o: ../ircd/s_pp2.c setup.h config.h ../common/struct_def.h + $(CC) $(S_CFLAGS) -c -o $@ ../ircd/s_pp2.c + s_sasl.o: ../ircd/s_sasl.c setup.h config.h ../common/struct_def.h $(CC) $(S_CFLAGS) -c -o $@ ../ircd/s_sasl.c diff --git a/support/config.h.dist b/support/config.h.dist index 5835c1e5..3973db76 100644 --- a/support/config.h.dist +++ b/support/config.h.dist @@ -820,6 +820,16 @@ #define WHOISTLS "is a Secure Connection (SSL/TLS)" #define WHOISTLS_NOTICE "Your connection is secure (SSL/TLS)." +/* + * Only connections originating from these addresses are allowed to provide + * Proxy protocol v2 headers to pass the real client IP. + * + * Notes: + * - Only add internal proxy IP addresses + * - Incorrect or overly broad entries may allow IP spoofing attacks + */ +#define TRUSTED_PROXY_ADDRESSES "127.0.0.1", "::1" + /* STOP STOP STOP STOP STOP STOP STOP STOP STOP STOP STOP STOP STOP STOP */ /* STOP STOP STOP STOP STOP STOP STOP STOP STOP STOP STOP STOP STOP STOP */ /* STOP STOP STOP STOP STOP STOP STOP STOP STOP STOP STOP STOP STOP STOP */