/*++ /* NAME /* valid_hostname 3 /* SUMMARY /* network name validation /* SYNOPSIS /* #include /* /* int valid_hostname(name, flags) /* const char *name; /* int flags; /* /* int valid_hostaddr(addr, gripe) /* const char *addr; /* int gripe; /* /* int valid_ipv4_hostaddr(addr, gripe) /* const char *addr; /* int gripe; /* /* int valid_ipv6_hostaddr(addr, gripe) /* const char *addr; /* int gripe; /* /* int valid_hostport(port, gripe) /* const char *port; /* int gripe; /* DESCRIPTION /* valid_hostname() scrutinizes a hostname: the name should /* be no longer than VALID_HOSTNAME_LEN characters, should /* contain only letters, digits, dots and hyphens, no adjacent /* dots, no leading or trailing dots or hyphens, no labels /* longer than VALID_LABEL_LEN characters, and it should not /* be all numeric. /* The flags argument is the bit-wise or of zero or more of /* DO_GRIPE or DO_WILDCARD (the latter allows the "*." name /* prefix, which is rare but valid in some DNS responses and /* queries). /* /* valid_hostaddr() requires that the input is a valid string /* representation of an IPv4 or IPv6 network address as /* described next. /* /* valid_ipv4_hostaddr() and valid_ipv6_hostaddr() implement /* protocol-specific address syntax checks. A valid IPv4 /* address is in dotted-quad decimal form. A valid IPv6 address /* has 16-bit hexadecimal fields separated by ":", and does not /* include the RFC 2821 style "IPv6:" prefix. /* /* These routines operate silently unless the gripe parameter /* specifies a non-zero value. The macros DO_GRIPE and DONT_GRIPE /* provide suitable constants. /* /* valid_hostport() requires that the input is a valid string /* representation of a TCP or UDP port number. /* BUGS /* valid_hostmumble() does not guarantee that string lengths /* fit the buffer sizes defined in myaddrinfo(3h). /* DIAGNOSTICS /* All functions return zero if they disagree with the input. /* SEE ALSO /* RFC 952, RFC 1123, RFC 1035, RFC 2373. /* LICENSE /* .ad /* .fi /* The Secure Mailer license must be distributed with this software. /* AUTHOR(S) /* Wietse Venema /* IBM T.J. Watson Research /* P.O. Box 704 /* Yorktown Heights, NY 10598, USA /*--*/ /* System library. */ #include #include #include #include /* Utility library. */ #include "msg.h" #include "mymalloc.h" #include "stringops.h" #include "valid_hostname.h" /* valid_hostname - screen out bad hostnames */ int valid_hostname(const char *name, int flags) { const char *myname = "valid_hostname"; const char *cp; int label_length = 0; int label_count = 0; int non_numeric = 0; int ch; int gripe = flags & DO_GRIPE; /* * Trivial cases first. */ if (*name == 0) { if (gripe) msg_warn("%s: empty hostname", myname); return (0); } /* * Find bad characters or label lengths. Find adjacent delimiters. */ for (cp = name; (ch = *(unsigned char *) cp) != 0; cp++) { if (ISALNUM(ch) || ch == '_') { /* grr.. */ if (label_length == 0) label_count++; label_length++; if (label_length > VALID_LABEL_LEN) { if (gripe) msg_warn("%s: hostname label too long: %.100s", myname, name); return (0); } if (!ISDIGIT(ch)) non_numeric = 1; } else if ((flags & DO_WILDCARD) && ch == '*') { if (label_length || label_count || (cp[1] && cp[1] != '.')) { if (gripe) msg_warn("%s: '*' can be the first label only: %.100s", myname, name); return (0); } label_count++; label_length++; non_numeric = 1; } else if (ch == '.') { if (label_length == 0 || cp[1] == 0) { if (gripe) msg_warn("%s: misplaced delimiter: %.100s", myname, name); return (0); } label_length = 0; } else if (ch == '-') { non_numeric = 1; label_length++; if (label_length == 1 || cp[1] == 0 || cp[1] == '.') { if (gripe) msg_warn("%s: misplaced hyphen: %.100s", myname, name); return (0); } } #ifdef SLOPPY_VALID_HOSTNAME else if (ch == ':' && valid_ipv6_hostaddr(name, DONT_GRIPE)) { non_numeric = 0; break; } #endif else { if (gripe) msg_warn("%s: invalid character %d(decimal): %.100s", myname, ch, name); return (0); } } if (non_numeric == 0) { if (gripe) msg_warn("%s: numeric hostname: %.100s", myname, name); #ifndef SLOPPY_VALID_HOSTNAME return (0); #endif } if (cp - name > VALID_HOSTNAME_LEN) { if (gripe) msg_warn("%s: bad length %d for %.100s...", myname, (int) (cp - name), name); return (0); } return (1); } /* valid_hostaddr - verify numerical address syntax */ int valid_hostaddr(const char *addr, int gripe) { const char *myname = "valid_hostaddr"; /* * Trivial cases first. */ if (*addr == 0) { if (gripe) msg_warn("%s: empty address", myname); return (0); } /* * Protocol-dependent processing next. */ if (strchr(addr, ':') != 0) return (valid_ipv6_hostaddr(addr, gripe)); else return (valid_ipv4_hostaddr(addr, gripe)); } /* valid_ipv4_hostaddr - test dotted quad string for correctness */ int valid_ipv4_hostaddr(const char *addr, int gripe) { const char *cp; const char *myname = "valid_ipv4_hostaddr"; int in_byte = 0; int byte_count = 0; int byte_val = 0; int ch; #define BYTES_NEEDED 4 /* * Scary code to avoid sscanf() overflow nasties. * * This routine is called by valid_ipv6_hostaddr(). It must not call that * routine, to avoid deadly recursion. */ for (cp = addr; (ch = *(unsigned const char *) cp) != 0; cp++) { if (ISDIGIT(ch)) { if (in_byte == 0) { in_byte = 1; byte_val = 0; byte_count++; } byte_val *= 10; byte_val += ch - '0'; if (byte_val > 255) { if (gripe) msg_warn("%s: invalid octet value: %.100s", myname, addr); return (0); } } else if (ch == '.') { if (in_byte == 0 || cp[1] == 0) { if (gripe) msg_warn("%s: misplaced dot: %.100s", myname, addr); return (0); } /* XXX Allow 0.0.0.0 but not 0.1.2.3 */ if (byte_count == 1 && byte_val == 0 && addr[strspn(addr, "0.")]) { if (gripe) msg_warn("%s: bad initial octet value: %.100s", myname, addr); return (0); } in_byte = 0; } else { if (gripe) msg_warn("%s: invalid character %d(decimal): %.100s", myname, ch, addr); return (0); } } if (byte_count != BYTES_NEEDED) { if (gripe) msg_warn("%s: invalid octet count: %.100s", myname, addr); return (0); } return (1); } /* valid_ipv6_hostaddr - validate IPv6 address syntax */ int valid_ipv6_hostaddr(const char *addr, int gripe) { const char *myname = "valid_ipv6_hostaddr"; int null_field = 0; int field = 0; unsigned char *cp = (unsigned char *) addr; int len = 0; /* * FIX 200501 The IPv6 patch validated syntax with getaddrinfo(), but I * am not confident that everyone's system library routines are robust * enough, like buffer overflow free. Remember, the valid_hostmumble() * routines are meant to protect Postfix against malformed information in * data received from the network. * * We require eight-field hex addresses of the form 0:1:2:3:4:5:6:7, * 0:1:2:3:4:5:6a.6b.7c.7d, or some :: compressed version of the same. * * Note: the character position is advanced inside the loop. I have added * comments to show why we can't get stuck. */ for (;;) { switch (*cp) { case 0: /* Terminate the loop. */ if (field < 2) { if (gripe) msg_warn("%s: too few `:' in IPv6 address: %.100s", myname, addr); return (0); } else if (len == 0 && null_field != field - 1) { if (gripe) msg_warn("%s: bad null last field in IPv6 address: %.100s", myname, addr); return (0); } else return (1); case '.': /* Terminate the loop. */ if (field < 2 || field > 6) { if (gripe) msg_warn("%s: malformed IPv4-in-IPv6 address: %.100s", myname, addr); return (0); } else /* NOT: valid_hostaddr(). Avoid recursion. */ return (valid_ipv4_hostaddr((char *) cp - len, gripe)); case ':': /* Advance by exactly 1 character position or terminate. */ if (field == 0 && len == 0 && ISALNUM(cp[1])) { if (gripe) msg_warn("%s: bad null first field in IPv6 address: %.100s", myname, addr); return (0); } field++; if (field > 7) { if (gripe) msg_warn("%s: too many `:' in IPv6 address: %.100s", myname, addr); return (0); } cp++; len = 0; if (*cp == ':') { if (null_field > 0) { if (gripe) msg_warn("%s: too many `::' in IPv6 address: %.100s", myname, addr); return (0); } null_field = field; } break; default: /* Advance by at least 1 character position or terminate. */ len = strspn((char *) cp, "0123456789abcdefABCDEF"); if (len /* - strspn((char *) cp, "0") */ > 4) { if (gripe) msg_warn("%s: malformed IPv6 address: %.100s", myname, addr); return (0); } if (len <= 0) { if (gripe) msg_warn("%s: invalid character %d(decimal) in IPv6 address: %.100s", myname, *cp, addr); return (0); } cp += len; break; } } } /* valid_hostport - validate numeric port */ int valid_hostport(const char *str, int gripe) { const char *myname = "valid_hostport"; int port; if (str[0] == '0' && str[1] != 0) { if (gripe) msg_warn("%s: leading zero in port number: %.100s", myname, str); return (0); } if (alldig(str) == 0) { if (gripe) msg_warn("%s: non-numeric port number: %.100s", myname, str); return (0); } if (strlen(str) > strlen("65535") || (port = atoi(str)) > 65535 || port < 0) { if (gripe) msg_warn("%s: out-of-range port number: %.100s", myname, str); return (0); } return (1); } #ifdef TEST /* * Test program - reads hostnames from stdin, reports invalid hostnames to * stderr. */ #include #include "vstring.h" #include "vstream.h" #include "vstring_vstream.h" #include "msg_vstream.h" int main(int unused_argc, char **argv) { VSTRING *buffer = vstring_alloc(1); msg_vstream_init(argv[0], VSTREAM_ERR); msg_verbose = 1; while (vstring_fgets_nonl(buffer, VSTREAM_IN)) { msg_info("testing: \"%s\"", vstring_str(buffer)); valid_hostname(vstring_str(buffer), DO_GRIPE | DO_WILDCARD); if (strchr(vstring_str(buffer), '*') == 0) valid_hostaddr(vstring_str(buffer), DO_GRIPE); } exit(0); } #endif