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