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