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