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