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