1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
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;
}
|