xref: /netbsd-src/external/ibm-public/postfix/dist/src/util/valid_hostname.c (revision 82d56013d7b633d116a93943de88e08335357a7c)
1 /*	$NetBSD: valid_hostname.c,v 1.2 2017/02/14 01:16:49 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 
88 int     valid_hostname(const char *name, int gripe)
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 
97     /*
98      * Trivial cases first.
99      */
100     if (*name == 0) {
101 	if (gripe)
102 	    msg_warn("%s: empty hostname", myname);
103 	return (0);
104     }
105 
106     /*
107      * Find bad characters or label lengths. Find adjacent delimiters.
108      */
109     for (cp = name; (ch = *(unsigned char *) cp) != 0; cp++) {
110 	if (ISALNUM(ch) || ch == '_') {		/* grr.. */
111 	    if (label_length == 0)
112 		label_count++;
113 	    label_length++;
114 	    if (label_length > VALID_LABEL_LEN) {
115 		if (gripe)
116 		    msg_warn("%s: hostname label too long: %.100s", myname, name);
117 		return (0);
118 	    }
119 	    if (!ISDIGIT(ch))
120 		non_numeric = 1;
121 	} else if (ch == '.') {
122 	    if (label_length == 0 || cp[1] == 0) {
123 		if (gripe)
124 		    msg_warn("%s: misplaced delimiter: %.100s", myname, name);
125 		return (0);
126 	    }
127 	    label_length = 0;
128 	} else if (ch == '-') {
129 	    non_numeric = 1;
130 	    label_length++;
131 	    if (label_length == 1 || cp[1] == 0 || cp[1] == '.') {
132 		if (gripe)
133 		    msg_warn("%s: misplaced hyphen: %.100s", myname, name);
134 		return (0);
135 	    }
136 	}
137 #ifdef SLOPPY_VALID_HOSTNAME
138 	else if (ch == ':' && valid_ipv6_hostaddr(name, DONT_GRIPE)) {
139 	    non_numeric = 0;
140 	    break;
141 	}
142 #endif
143 	else {
144 	    if (gripe)
145 		msg_warn("%s: invalid character %d(decimal): %.100s",
146 			 myname, ch, name);
147 	    return (0);
148 	}
149     }
150 
151     if (non_numeric == 0) {
152 	if (gripe)
153 	    msg_warn("%s: numeric hostname: %.100s", myname, name);
154 #ifndef SLOPPY_VALID_HOSTNAME
155 	return (0);
156 #endif
157     }
158     if (cp - name > VALID_HOSTNAME_LEN) {
159 	if (gripe)
160 	    msg_warn("%s: bad length %d for %.100s...",
161 		     myname, (int) (cp - name), name);
162 	return (0);
163     }
164     return (1);
165 }
166 
167 /* valid_hostaddr - verify numerical address syntax */
168 
169 int     valid_hostaddr(const char *addr, int gripe)
170 {
171     const char *myname = "valid_hostaddr";
172 
173     /*
174      * Trivial cases first.
175      */
176     if (*addr == 0) {
177 	if (gripe)
178 	    msg_warn("%s: empty address", myname);
179 	return (0);
180     }
181 
182     /*
183      * Protocol-dependent processing next.
184      */
185     if (strchr(addr, ':') != 0)
186 	return (valid_ipv6_hostaddr(addr, gripe));
187     else
188 	return (valid_ipv4_hostaddr(addr, gripe));
189 }
190 
191 /* valid_ipv4_hostaddr - test dotted quad string for correctness */
192 
193 int     valid_ipv4_hostaddr(const char *addr, int gripe)
194 {
195     const char *cp;
196     const char *myname = "valid_ipv4_hostaddr";
197     int     in_byte = 0;
198     int     byte_count = 0;
199     int     byte_val = 0;
200     int     ch;
201 
202 #define BYTES_NEEDED	4
203 
204     /*
205      * Scary code to avoid sscanf() overflow nasties.
206      *
207      * This routine is called by valid_ipv6_hostaddr(). It must not call that
208      * routine, to avoid deadly recursion.
209      */
210     for (cp = addr; (ch = *(unsigned const char *) cp) != 0; cp++) {
211 	if (ISDIGIT(ch)) {
212 	    if (in_byte == 0) {
213 		in_byte = 1;
214 		byte_val = 0;
215 		byte_count++;
216 	    }
217 	    byte_val *= 10;
218 	    byte_val += ch - '0';
219 	    if (byte_val > 255) {
220 		if (gripe)
221 		    msg_warn("%s: invalid octet value: %.100s", myname, addr);
222 		return (0);
223 	    }
224 	} else if (ch == '.') {
225 	    if (in_byte == 0 || cp[1] == 0) {
226 		if (gripe)
227 		    msg_warn("%s: misplaced dot: %.100s", myname, addr);
228 		return (0);
229 	    }
230 	    /* XXX Allow 0.0.0.0 but not 0.1.2.3 */
231 	    if (byte_count == 1 && byte_val == 0 && addr[strspn(addr, "0.")]) {
232 		if (gripe)
233 		    msg_warn("%s: bad initial octet value: %.100s", myname, addr);
234 		return (0);
235 	    }
236 	    in_byte = 0;
237 	} else {
238 	    if (gripe)
239 		msg_warn("%s: invalid character %d(decimal): %.100s",
240 			 myname, ch, addr);
241 	    return (0);
242 	}
243     }
244 
245     if (byte_count != BYTES_NEEDED) {
246 	if (gripe)
247 	    msg_warn("%s: invalid octet count: %.100s", myname, addr);
248 	return (0);
249     }
250     return (1);
251 }
252 
253 /* valid_ipv6_hostaddr - validate IPv6 address syntax */
254 
255 int     valid_ipv6_hostaddr(const char *addr, int gripe)
256 {
257     const char *myname = "valid_ipv6_hostaddr";
258     int     null_field = 0;
259     int     field = 0;
260     unsigned char *cp = (unsigned char *) addr;
261     int     len = 0;
262 
263     /*
264      * FIX 200501 The IPv6 patch validated syntax with getaddrinfo(), but I
265      * am not confident that everyone's system library routines are robust
266      * enough, like buffer overflow free. Remember, the valid_hostmumble()
267      * routines are meant to protect Postfix against malformed information in
268      * data received from the network.
269      *
270      * We require eight-field hex addresses of the form 0:1:2:3:4:5:6:7,
271      * 0:1:2:3:4:5:6a.6b.7c.7d, or some :: compressed version of the same.
272      *
273      * Note: the character position is advanced inside the loop. I have added
274      * comments to show why we can't get stuck.
275      */
276     for (;;) {
277 	switch (*cp) {
278 	case 0:
279 	    /* Terminate the loop. */
280 	    if (field < 2) {
281 		if (gripe)
282 		    msg_warn("%s: too few `:' in IPv6 address: %.100s",
283 			     myname, addr);
284 		return (0);
285 	    } else if (len == 0 && null_field != field - 1) {
286 		if (gripe)
287 		    msg_warn("%s: bad null last field in IPv6 address: %.100s",
288 			     myname, addr);
289 		return (0);
290 	    } else
291 		return (1);
292 	case '.':
293 	    /* Terminate the loop. */
294 	    if (field < 2 || field > 6) {
295 		if (gripe)
296 		    msg_warn("%s: malformed IPv4-in-IPv6 address: %.100s",
297 			     myname, addr);
298 		return (0);
299 	    } else
300 		/* NOT: valid_hostaddr(). Avoid recursion. */
301 		return (valid_ipv4_hostaddr((char *) cp - len, gripe));
302 	case ':':
303 	    /* Advance by exactly 1 character position or terminate. */
304 	    if (field == 0 && len == 0 && ISALNUM(cp[1])) {
305 		if (gripe)
306 		    msg_warn("%s: bad null first field in IPv6 address: %.100s",
307 			     myname, addr);
308 		return (0);
309 	    }
310 	    field++;
311 	    if (field > 7) {
312 		if (gripe)
313 		    msg_warn("%s: too many `:' in IPv6 address: %.100s",
314 			     myname, addr);
315 		return (0);
316 	    }
317 	    cp++;
318 	    len = 0;
319 	    if (*cp == ':') {
320 		if (null_field > 0) {
321 		    if (gripe)
322 			msg_warn("%s: too many `::' in IPv6 address: %.100s",
323 				 myname, addr);
324 		    return (0);
325 		}
326 		null_field = field;
327 	    }
328 	    break;
329 	default:
330 	    /* Advance by at least 1 character position or terminate. */
331 	    len = strspn((char *) cp, "0123456789abcdefABCDEF");
332 	    if (len /* - strspn((char *) cp, "0") */ > 4) {
333 		if (gripe)
334 		    msg_warn("%s: malformed IPv6 address: %.100s",
335 			     myname, addr);
336 		return (0);
337 	    }
338 	    if (len <= 0) {
339 		if (gripe)
340 		    msg_warn("%s: invalid character %d(decimal) in IPv6 address: %.100s",
341 			     myname, *cp, addr);
342 		return (0);
343 	    }
344 	    cp += len;
345 	    break;
346 	}
347     }
348 }
349 
350 /* valid_hostport - validate numeric port */
351 
352 int     valid_hostport(const char *str, int gripe)
353 {
354     const char *myname = "valid_hostport";
355     int     port;
356 
357     if (str[0] == '0' && str[1] != 0) {
358 	if (gripe)
359 	    msg_warn("%s: leading zero in port number: %.100s", myname, str);
360 	return (0);
361     }
362     if (alldig(str) == 0) {
363 	if (gripe)
364 	    msg_warn("%s: non-numeric port number: %.100s", myname, str);
365 	return (0);
366     }
367     if (strlen(str) > strlen("65535")
368 	|| (port = atoi(str)) > 65535 || port < 0) {
369 	if (gripe)
370 	    msg_warn("%s: out-of-range port number: %.100s", myname, str);
371 	return (0);
372     }
373     return (1);
374 }
375 
376 #ifdef TEST
377 
378  /*
379   * Test program - reads hostnames from stdin, reports invalid hostnames to
380   * stderr.
381   */
382 #include <stdlib.h>
383 
384 #include "vstring.h"
385 #include "vstream.h"
386 #include "vstring_vstream.h"
387 #include "msg_vstream.h"
388 
389 int     main(int unused_argc, char **argv)
390 {
391     VSTRING *buffer = vstring_alloc(1);
392 
393     msg_vstream_init(argv[0], VSTREAM_ERR);
394     msg_verbose = 1;
395 
396     while (vstring_fgets_nonl(buffer, VSTREAM_IN)) {
397 	msg_info("testing: \"%s\"", vstring_str(buffer));
398 	valid_hostname(vstring_str(buffer), DO_GRIPE);
399 	valid_hostaddr(vstring_str(buffer), DO_GRIPE);
400     }
401     exit(0);
402 }
403 
404 #endif
405