1 /* $NetBSD: valid_hostname.c,v 1.3 2023/12/23 20:30:46 christos Exp $ */
2
3 /*++
4 /* NAME
5 /* valid_hostname 3
6 /* SUMMARY
7 /* network name validation
8 /* SYNOPSIS
9 /* #include <valid_hostname.h>
10 /*
11 /* int valid_hostname(name, gripe)
12 /* const char *name;
13 /* int gripe;
14 /*
15 /* int valid_hostaddr(addr, gripe)
16 /* const char *addr;
17 /* int gripe;
18 /*
19 /* int valid_ipv4_hostaddr(addr, gripe)
20 /* const char *addr;
21 /* int gripe;
22 /*
23 /* int valid_ipv6_hostaddr(addr, gripe)
24 /* const char *addr;
25 /* int gripe;
26 /*
27 /* int valid_hostport(port, gripe)
28 /* const char *port;
29 /* int gripe;
30 /* DESCRIPTION
31 /* valid_hostname() scrutinizes a hostname: the name should
32 /* be no longer than VALID_HOSTNAME_LEN characters, should
33 /* contain only letters, digits, dots and hyphens, no adjacent
34 /* dots, no leading or trailing dots or hyphens, no labels
35 /* longer than VALID_LABEL_LEN characters, and it should not
36 /* be all numeric.
37 /*
38 /* valid_hostaddr() requires that the input is a valid string
39 /* representation of an IPv4 or IPv6 network address as
40 /* described next.
41 /*
42 /* valid_ipv4_hostaddr() and valid_ipv6_hostaddr() implement
43 /* protocol-specific address syntax checks. A valid IPv4
44 /* address is in dotted-quad decimal form. A valid IPv6 address
45 /* has 16-bit hexadecimal fields separated by ":", and does not
46 /* include the RFC 2821 style "IPv6:" prefix.
47 /*
48 /* These routines operate silently unless the gripe parameter
49 /* specifies a non-zero value. The macros DO_GRIPE and DONT_GRIPE
50 /* provide suitable constants.
51 /*
52 /* valid_hostport() requires that the input is a valid string
53 /* representation of a TCP or UDP port number.
54 /* BUGS
55 /* valid_hostmumble() does not guarantee that string lengths
56 /* fit the buffer sizes defined in myaddrinfo(3h).
57 /* DIAGNOSTICS
58 /* All functions return zero if they disagree with the input.
59 /* SEE ALSO
60 /* RFC 952, RFC 1123, RFC 1035, RFC 2373.
61 /* LICENSE
62 /* .ad
63 /* .fi
64 /* The Secure Mailer license must be distributed with this software.
65 /* AUTHOR(S)
66 /* Wietse Venema
67 /* IBM T.J. Watson Research
68 /* P.O. Box 704
69 /* Yorktown Heights, NY 10598, USA
70 /*--*/
71
72 /* System library. */
73
74 #include <sys_defs.h>
75 #include <string.h>
76 #include <ctype.h>
77 #include <stdlib.h>
78
79 /* Utility library. */
80
81 #include "msg.h"
82 #include "mymalloc.h"
83 #include "stringops.h"
84 #include "valid_hostname.h"
85
86 /* valid_hostname - screen out bad hostnames */
87
valid_hostname(const char * name,int flags)88 int valid_hostname(const char *name, int flags)
89 {
90 const char *myname = "valid_hostname";
91 const char *cp;
92 int label_length = 0;
93 int label_count = 0;
94 int non_numeric = 0;
95 int ch;
96 int gripe = flags & DO_GRIPE;
97
98 /*
99 * Trivial cases first.
100 */
101 if (*name == 0) {
102 if (gripe)
103 msg_warn("%s: empty hostname", myname);
104 return (0);
105 }
106
107 /*
108 * Find bad characters or label lengths. Find adjacent delimiters.
109 */
110 for (cp = name; (ch = *(unsigned char *) cp) != 0; cp++) {
111 if (ISALNUM(ch) || ch == '_') { /* grr.. */
112 if (label_length == 0)
113 label_count++;
114 label_length++;
115 if (label_length > VALID_LABEL_LEN) {
116 if (gripe)
117 msg_warn("%s: hostname label too long: %.100s", myname, name);
118 return (0);
119 }
120 if (!ISDIGIT(ch))
121 non_numeric = 1;
122 } else if ((flags & DO_WILDCARD) && ch == '*') {
123 if (label_length || label_count || (cp[1] && cp[1] != '.')) {
124 if (gripe)
125 msg_warn("%s: '*' can be the first label only: %.100s", myname, name);
126 return (0);
127 }
128 label_count++;
129 label_length++;
130 non_numeric = 1;
131 } else if (ch == '.') {
132 if (label_length == 0 || cp[1] == 0) {
133 if (gripe)
134 msg_warn("%s: misplaced delimiter: %.100s", myname, name);
135 return (0);
136 }
137 label_length = 0;
138 } else if (ch == '-') {
139 non_numeric = 1;
140 label_length++;
141 if (label_length == 1 || cp[1] == 0 || cp[1] == '.') {
142 if (gripe)
143 msg_warn("%s: misplaced hyphen: %.100s", myname, name);
144 return (0);
145 }
146 }
147 #ifdef SLOPPY_VALID_HOSTNAME
148 else if (ch == ':' && valid_ipv6_hostaddr(name, DONT_GRIPE)) {
149 non_numeric = 0;
150 break;
151 }
152 #endif
153 else {
154 if (gripe)
155 msg_warn("%s: invalid character %d(decimal): %.100s",
156 myname, ch, name);
157 return (0);
158 }
159 }
160
161 if (non_numeric == 0) {
162 if (gripe)
163 msg_warn("%s: numeric hostname: %.100s", myname, name);
164 #ifndef SLOPPY_VALID_HOSTNAME
165 return (0);
166 #endif
167 }
168 if (cp - name > VALID_HOSTNAME_LEN) {
169 if (gripe)
170 msg_warn("%s: bad length %d for %.100s...",
171 myname, (int) (cp - name), name);
172 return (0);
173 }
174 return (1);
175 }
176
177 /* valid_hostaddr - verify numerical address syntax */
178
valid_hostaddr(const char * addr,int gripe)179 int valid_hostaddr(const char *addr, int gripe)
180 {
181 const char *myname = "valid_hostaddr";
182
183 /*
184 * Trivial cases first.
185 */
186 if (*addr == 0) {
187 if (gripe)
188 msg_warn("%s: empty address", myname);
189 return (0);
190 }
191
192 /*
193 * Protocol-dependent processing next.
194 */
195 if (strchr(addr, ':') != 0)
196 return (valid_ipv6_hostaddr(addr, gripe));
197 else
198 return (valid_ipv4_hostaddr(addr, gripe));
199 }
200
201 /* valid_ipv4_hostaddr - test dotted quad string for correctness */
202
valid_ipv4_hostaddr(const char * addr,int gripe)203 int valid_ipv4_hostaddr(const char *addr, int gripe)
204 {
205 const char *cp;
206 const char *myname = "valid_ipv4_hostaddr";
207 int in_byte = 0;
208 int byte_count = 0;
209 int byte_val = 0;
210 int ch;
211
212 #define BYTES_NEEDED 4
213
214 /*
215 * Scary code to avoid sscanf() overflow nasties.
216 *
217 * This routine is called by valid_ipv6_hostaddr(). It must not call that
218 * routine, to avoid deadly recursion.
219 */
220 for (cp = addr; (ch = *(unsigned const char *) cp) != 0; cp++) {
221 if (ISDIGIT(ch)) {
222 if (in_byte == 0) {
223 in_byte = 1;
224 byte_val = 0;
225 byte_count++;
226 }
227 byte_val *= 10;
228 byte_val += ch - '0';
229 if (byte_val > 255) {
230 if (gripe)
231 msg_warn("%s: invalid octet value: %.100s", myname, addr);
232 return (0);
233 }
234 } else if (ch == '.') {
235 if (in_byte == 0 || cp[1] == 0) {
236 if (gripe)
237 msg_warn("%s: misplaced dot: %.100s", myname, addr);
238 return (0);
239 }
240 /* XXX Allow 0.0.0.0 but not 0.1.2.3 */
241 if (byte_count == 1 && byte_val == 0 && addr[strspn(addr, "0.")]) {
242 if (gripe)
243 msg_warn("%s: bad initial octet value: %.100s", myname, addr);
244 return (0);
245 }
246 in_byte = 0;
247 } else {
248 if (gripe)
249 msg_warn("%s: invalid character %d(decimal): %.100s",
250 myname, ch, addr);
251 return (0);
252 }
253 }
254
255 if (byte_count != BYTES_NEEDED) {
256 if (gripe)
257 msg_warn("%s: invalid octet count: %.100s", myname, addr);
258 return (0);
259 }
260 return (1);
261 }
262
263 /* valid_ipv6_hostaddr - validate IPv6 address syntax */
264
valid_ipv6_hostaddr(const char * addr,int gripe)265 int valid_ipv6_hostaddr(const char *addr, int gripe)
266 {
267 const char *myname = "valid_ipv6_hostaddr";
268 int null_field = 0;
269 int field = 0;
270 unsigned char *cp = (unsigned char *) addr;
271 int len = 0;
272
273 /*
274 * FIX 200501 The IPv6 patch validated syntax with getaddrinfo(), but I
275 * am not confident that everyone's system library routines are robust
276 * enough, like buffer overflow free. Remember, the valid_hostmumble()
277 * routines are meant to protect Postfix against malformed information in
278 * data received from the network.
279 *
280 * We require eight-field hex addresses of the form 0:1:2:3:4:5:6:7,
281 * 0:1:2:3:4:5:6a.6b.7c.7d, or some :: compressed version of the same.
282 *
283 * Note: the character position is advanced inside the loop. I have added
284 * comments to show why we can't get stuck.
285 */
286 for (;;) {
287 switch (*cp) {
288 case 0:
289 /* Terminate the loop. */
290 if (field < 2) {
291 if (gripe)
292 msg_warn("%s: too few `:' in IPv6 address: %.100s",
293 myname, addr);
294 return (0);
295 } else if (len == 0 && null_field != field - 1) {
296 if (gripe)
297 msg_warn("%s: bad null last field in IPv6 address: %.100s",
298 myname, addr);
299 return (0);
300 } else
301 return (1);
302 case '.':
303 /* Terminate the loop. */
304 if (field < 2 || field > 6) {
305 if (gripe)
306 msg_warn("%s: malformed IPv4-in-IPv6 address: %.100s",
307 myname, addr);
308 return (0);
309 } else
310 /* NOT: valid_hostaddr(). Avoid recursion. */
311 return (valid_ipv4_hostaddr((char *) cp - len, gripe));
312 case ':':
313 /* Advance by exactly 1 character position or terminate. */
314 if (field == 0 && len == 0 && ISALNUM(cp[1])) {
315 if (gripe)
316 msg_warn("%s: bad null first field in IPv6 address: %.100s",
317 myname, addr);
318 return (0);
319 }
320 field++;
321 if (field > 7) {
322 if (gripe)
323 msg_warn("%s: too many `:' in IPv6 address: %.100s",
324 myname, addr);
325 return (0);
326 }
327 cp++;
328 len = 0;
329 if (*cp == ':') {
330 if (null_field > 0) {
331 if (gripe)
332 msg_warn("%s: too many `::' in IPv6 address: %.100s",
333 myname, addr);
334 return (0);
335 }
336 null_field = field;
337 }
338 break;
339 default:
340 /* Advance by at least 1 character position or terminate. */
341 len = strspn((char *) cp, "0123456789abcdefABCDEF");
342 if (len /* - strspn((char *) cp, "0") */ > 4) {
343 if (gripe)
344 msg_warn("%s: malformed IPv6 address: %.100s",
345 myname, addr);
346 return (0);
347 }
348 if (len <= 0) {
349 if (gripe)
350 msg_warn("%s: invalid character %d(decimal) in IPv6 address: %.100s",
351 myname, *cp, addr);
352 return (0);
353 }
354 cp += len;
355 break;
356 }
357 }
358 }
359
360 /* valid_hostport - validate numeric port */
361
valid_hostport(const char * str,int gripe)362 int valid_hostport(const char *str, int gripe)
363 {
364 const char *myname = "valid_hostport";
365 int port;
366
367 if (str[0] == '0' && str[1] != 0) {
368 if (gripe)
369 msg_warn("%s: leading zero in port number: %.100s", myname, str);
370 return (0);
371 }
372 if (alldig(str) == 0) {
373 if (gripe)
374 msg_warn("%s: non-numeric port number: %.100s", myname, str);
375 return (0);
376 }
377 if (strlen(str) > strlen("65535")
378 || (port = atoi(str)) > 65535 || port < 0) {
379 if (gripe)
380 msg_warn("%s: out-of-range port number: %.100s", myname, str);
381 return (0);
382 }
383 return (1);
384 }
385
386 #ifdef TEST
387
388 /*
389 * Test program - reads hostnames from stdin, reports invalid hostnames to
390 * stderr.
391 */
392 #include <stdlib.h>
393
394 #include "vstring.h"
395 #include "vstream.h"
396 #include "vstring_vstream.h"
397 #include "msg_vstream.h"
398
main(int unused_argc,char ** argv)399 int main(int unused_argc, char **argv)
400 {
401 VSTRING *buffer = vstring_alloc(1);
402
403 msg_vstream_init(argv[0], VSTREAM_ERR);
404 msg_verbose = 1;
405
406 while (vstring_fgets_nonl(buffer, VSTREAM_IN)) {
407 msg_info("testing: \"%s\"", vstring_str(buffer));
408 valid_hostname(vstring_str(buffer), DO_GRIPE);
409 valid_hostaddr(vstring_str(buffer), DO_GRIPE);
410 }
411 exit(0);
412 }
413
414 #endif
415