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