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