diff options
Diffstat (limited to 'src/util/valid_hostname.c')
-rw-r--r-- | src/util/valid_hostname.c | 412 |
1 files changed, 412 insertions, 0 deletions
diff --git a/src/util/valid_hostname.c b/src/util/valid_hostname.c new file mode 100644 index 0000000..8b234c4 --- /dev/null +++ b/src/util/valid_hostname.c @@ -0,0 +1,412 @@ +/*++ +/* NAME +/* valid_hostname 3 +/* SUMMARY +/* network name validation +/* SYNOPSIS +/* #include <valid_hostname.h> +/* +/* int valid_hostname(name, gripe) +/* const char *name; +/* int gripe; +/* +/* 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. +/* +/* 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 <sys_defs.h> +#include <string.h> +#include <ctype.h> +#include <stdlib.h> + +/* 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 <stdlib.h> + +#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); + valid_hostaddr(vstring_str(buffer), DO_GRIPE); + } + exit(0); +} + +#endif |