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