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