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