*: don't use getservbyname, it links in a static buffer
No binary (build failed)
gcc -Wp,-MD,applets/.usage.d -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -Iinclude -Iinclude -o applets/usage applets/usage.c
(log yok)
| @@ -911,9 +911,11 @@ char* xmalloc_sockaddr2hostonly_noport(const struct sockaddr *sa) FAST_FUNC RETU | |
| /* inet_[ap]ton on steroids */ | |
| char* xmalloc_sockaddr2dotted(const struct sockaddr *sa) FAST_FUNC RETURNS_MALLOC; | |
| char* xmalloc_sockaddr2dotted_noport(const struct sockaddr *sa) FAST_FUNC RETURNS_MALLOC; | |
| - | // NB: unlike getservbyport, port parameter is NOT in network order |
| + | // NB: unlike getservbyport, port parameter/retval are in host, NOT network order! |
| #define getservbyport dont_use_getservbyport_uses_global_buffer | |
| char* bb_get_servname_by_port(char **p_etc_services, int port, const char *type) FAST_FUNC RETURNS_MALLOC; | |
| + | #define getservbyname dont_use_getservbyname_uses_global_buffer |
| + | int bb_get_servport_by_name(char **p_etc_services, const char *name, const char *type) FAST_FUNC; |
| // "old" (ipv4 only) API | |
| // users: traceroute.c hostname.c - use _list_ of all IPs | |
| struct hostent *xgethostbyname(const char *name) FAST_FUNC; | |
| @@ -9,11 +9,9 @@ | |
| //kbuild:lib-$(CONFIG_PSCAN) += bb_get_servname_by_port.o | |
| #include "libbb.h" | |
| - | //Rationale for exising: |
| + | //Rationale for existing: |
| //#define getservbyport dont_use_getservbyport_uses_global_buffer | |
| //(for example: -280 bytes on musl, x86-32) | |
| - | //TODO: |
| - | //avoid getservbyname() as well |
| char* FAST_FUNC bb_get_servname_by_port(char **p_etc_services, int port, const char *type) | |
| { | |
| @@ -45,7 +43,7 @@ char* FAST_FUNC bb_get_servname_by_port(char **p_etc_services, int port, const c | |
| end = is_prefixed_with(end, type); | |
| if (!end | |
| || !(isspace(*end) || *end == '\0' | |
| - | || *end == '#') // glibc treats "http 80/tcp#COMMENT" as valid |
| + | || *end == '#') // glibc treats "http 80/tcp#COMMENT" (no space!) as valid |
| ) { | |
| goto next; | |
| } | |
| @@ -0,0 +1,129 @@ | |
| + | /* |
| + | * Utility routines. |
| + | * |
| + | * Copyright (C) 2026 Denys Vlasenko |
| + | * |
| + | * Licensed under GPLv2, see file LICENSE in this source tree. |
| + | */ |
| + | //kbuild:lib-y += bb_get_servport_by_name.o |
| + | //kbuild:lib-y += bb_get_servport_by_name.o |
| + | #include "libbb.h" |
| + | |
| + | //Rationale for existing: |
| + | //#define getservbyname dont_use_getservbyname_uses_global_buffer |
| + | //(for example: -32 BSS bytes on musl, x86-32) |
| + | |
| + | int FAST_FUNC bb_get_servport_by_name(char **p_etc_services, const char *name, const char *proto) |
| + | { |
| + | unsigned namelen; |
| + | char *sp; |
| + | |
| + | if (!name[0]) // This can hang the search (strstr advances by 0 bytes) |
| + | return -1; // bad service name form: "" |
| + | // Any other bogosity to reject? |
| + | // Or just enforce isalnum_or_dash_or_slash(name[i])? |
| + | // Yes, service name like "cl/1" is allowed and _exists_. |
| + | // |
| + | // Protect against finding service "www#c" in "http 10/tcp www#c" line |
| + | //if (strchr(name, '#')) |
| + | // return -1; // bad service name form |
| + | //^^^^ the main code already catches this possibility. |
| + | // |
| + | // Current code allows e.g. service names like "http 80/tcp" |
| + | // to map to port 80. Probably harmless? |
| + | //if (skip_non_whitespace(name)[0]) |
| + | // return -1; // bad service name form (has whitespace) |
| + | |
| + | sp = *p_etc_services; |
| + | if (!sp) { |
| + | //TODO: we need mmap_entire_file() for this use case! |
| + | sp = xmalloc_open_read_close("/etc/services", NULL); |
| + | if (!sp) |
| + | return -1; |
| + | *p_etc_services = sp; |
| + | } |
| + | namelen = strlen(name); |
| + | while (*sp) { |
| + | const char *pnstr; |
| + | char *start, *end; |
| + | unsigned n; |
| + | |
| + | // First, find a possible service name without regard for line separators (!) |
| + | start = strstr(sp, name); |
| + | if (!start) |
| + | return -1; |
| + | sp = start + namelen; |
| + | if (start != *p_etc_services && !isspace(start[-1])) { |
| + | // There is a char before it, and it's not whitespace |
| + | continue; |
| + | } |
| + | if (!(isspace(*sp) || *sp == '\0' || *sp == '#')) |
| + | // After it: not whitespace/EOF/comment |
| + | continue; |
| + | // The found substring _is_ correctly delimited before and after |
| + | |
| + | // Find beginning of the line we are on |
| + | while (start != *p_etc_services && start[-1] != '\n') |
| + | start--; |
| + | start = skip_whitespace(start); |
| + | // Is there a comment char between start of line and "service name"? |
| + | if (memchr(start, '#', sp - start)) { |
| + | // The "service name" we found is actually in comment / has comment in it. |
| + | // Also rejects names with #: |
| + | // service "www#c" won't be found even on "http 80/tcp www#c" line |
| + | continue; |
| + | } |
| + | // Is the [start...sp) of the form "SERVNAME NUM/PROTO[ ALIAS[ ALIAS...][ ]]"? |
| + | pnstr = skip_whitespace(skip_non_whitespace(start)); // go to NUM... |
| + | |
| + | // I've seen this line in /etc/services of a real-world machine: |
| + | //914c/g 211/tcp 914c-g |
| + | // Now consider just a bit more pathological example: |
| + | //914c/tcp 914/tcp 914/tcp |
| + | // If we search for service name "914/tcp", |
| + | // we'll find it at second word first, yet we must not reject this line |
| + | // because the THIRD word also matches, and it's a valid match (tested on glibc!). |
| + | // But in this case we must not match: |
| + | //914c/tcp 914/tcp something-else |
| + | if (sp - namelen == pnstr) |
| + | continue; // the match is at NUM..., try matching further |
| + | |
| + | n = bb_strtou(pnstr, &end, 10); |
| + | if (n > 0xffff || *end != '/') |
| + | continue; // NUM... part is not a valid port#, or has no slash |
| + | |
| + | if (proto) { |
| + | end++; // points to PROTO |
| + | end = is_prefixed_with(end, proto); |
| + | if (!end |
| + | || !(isspace(*end) || *end == '\0' |
| + | || *end == '#') // glibc treats "http 80/tcp#COMMENT" (no space!) as valid |
| + | ) { |
| + | continue; // PROTO does not match |
| + | } |
| + | } |
| + | // By now, either WORD or one of the ALIASes has to be the part |
| + | // which was found by strstr()! |
| + | return n; |
| + | } |
| + | return -1; |
| + | } |
| + | |
| + | // Return port number for a service. |
| + | // If "port" is a number use it as the port. |
| + | // If "port" is a name it is looked up in /etc/services. |
| + | // if NULL, return default_port |
| + | unsigned FAST_FUNC bb_lookup_port(const char *port, const char *protocol, unsigned port_nr) |
| + | { |
| + | if (port) { |
| + | port_nr = bb_strtou(port, NULL, 10); |
| + | if (errno || port_nr > 65535) { |
| + | char *p_etc_services = NULL; |
| + | port_nr = bb_get_servport_by_name(&p_etc_services, port, protocol); |
| + | if (port_nr > 0xffff) // -1? |
| + | bb_error_msg_and_die("bad port '%s'", port); |
| + | free(p_etc_services); |
| + | } |
| + | } |
| + | return (uint16_t)port_nr; |
| + | } |
| @@ -113,29 +113,8 @@ void FAST_FUNC xconnect(int s, const struct sockaddr *s_addr, socklen_t addrlen) | |
| } | |
| } | |
| - | /* Return port number for a service. |
| - | * If "port" is a number use it as the port. |
| - | * If "port" is a name it is looked up in /etc/services. |
| - | * if NULL, return default_port |
| - | */ |
| - | unsigned FAST_FUNC bb_lookup_port(const char *port, const char *protocol, unsigned port_nr) |
| - | { |
| - | if (port) { |
| - | port_nr = bb_strtou(port, NULL, 10); |
| - | if (errno || port_nr > 65535) { |
| - | struct servent *tserv = getservbyname(port, protocol); |
| - | if (!tserv) |
| - | bb_error_msg_and_die("bad port '%s'", port); |
| - | port_nr = ntohs(tserv->s_port); |
| - | } |
| - | } |
| - | return (uint16_t)port_nr; |
| - | } |
| - | |
| - | |
| /* "New" networking API */ | |
| - | |
| int FAST_FUNC get_nport(const struct sockaddr *sa) | |
| { | |
| #if ENABLE_FEATURE_IPV6 | |
| @@ -969,9 +969,8 @@ static void reread_config_file(int sig UNUSED_PARAM) | |
| servtab_t *sep, *cp, **sepp; | |
| len_and_sockaddr *lsa; | |
| sigset_t omask; | |
| - | unsigned n; |
| - | uint16_t port; |
| int save_errno = errno; | |
| + | char *p_etc_services = NULL; |
| if (!reopen_config_file()) | |
| goto ret; | |
| @@ -1039,7 +1038,9 @@ static void reread_config_file(int sig UNUSED_PARAM) | |
| break; | |
| default: /* case AF_INET, case AF_INET6 */ | |
| - | n = bb_strtou(sep->se_service, NULL, 10); |
| + | { |
| + | unsigned portno; |
| + | portno = bb_strtou(sep->se_service, NULL, 10); |
| #if ENABLE_FEATURE_INETD_RPC | |
| if (is_rpc_service(sep)) { | |
| sep->se_rpcprog = n; | |
| @@ -1059,26 +1060,23 @@ static void reread_config_file(int sig UNUSED_PARAM) | |
| } | |
| #endif | |
| /* what port to listen on? */ | |
| - | port = htons(n); |
| - | if (errno || n > 0xffff) { /* se_service is not numeric */ |
| + | if (errno || portno > 0xffff) { /* se_service is not numeric */ |
| char protoname[4]; | |
| - | struct servent *sp; |
| /* can result only in "tcp" or "udp": */ | |
| safe_strncpy(protoname, sep->se_proto, 4); | |
| - | sp = getservbyname(sep->se_service, protoname); |
| - | if (sp == NULL) { |
| + | portno = bb_get_servport_by_name(&p_etc_services, sep->se_service, protoname); |
| + | if (portno > 0xffff) { |
| bb_error_msg("%s/%s: unknown service", | |
| sep->se_service, sep->se_proto); | |
| goto next_cp; | |
| } | |
| - | port = sp->s_port; |
| } | |
| if (LONE_CHAR(sep->se_local_hostname, '*')) { | |
| lsa = xzalloc_lsa(sep->se_family); | |
| - | set_nport(&lsa->u.sa, port); |
| + | set_nport(&lsa->u.sa, htons(portno)); |
| } else { | |
| lsa = host_and_af2sockaddr(sep->se_local_hostname, | |
| - | ntohs(port), sep->se_family); |
| + | portno, sep->se_family); |
| if (!lsa) { | |
| bb_error_msg("%s/%s: unknown host '%s'", | |
| sep->se_service, sep->se_proto, | |
| @@ -1087,6 +1085,7 @@ static void reread_config_file(int sig UNUSED_PARAM) | |
| } | |
| } | |
| break; | |
| + | }//default: |
| } /* end of "switch (sep->se_family)" */ | |
| /* did lsa change? Then close/open */ | |
| @@ -1134,6 +1133,7 @@ static void reread_config_file(int sig UNUSED_PARAM) | |
| } | |
| restore_sigmask(&omask); | |
| ret: | |
| + | free(p_etc_services); |
| errno = save_errno; | |
| } | |