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