1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2007 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #include <stdio.h> 27 #include <ndbm.h> 28 #include <netdb.h> 29 #include <sys/types.h> 30 #include <sys/socket.h> 31 #include <netinet/in.h> 32 #include <arpa/inet.h> 33 #include <string.h> 34 #include <ctype.h> 35 #include <errno.h> 36 37 /* 38 * Filter to convert both IPv4 and IPv6 addresses from /etc/hosts file. 39 */ 40 41 /* 42 * Size of buffer for input lines. Add two bytes on input for newline 43 * and terminating NULL. Note that the practical limit for data 44 * storage in ndbm is (PBLKSIZ - 3 * sizeof (short)). Though this 45 * differs from spec 1170 the common industry implementation does 46 * conform to this slightly lower limit. 47 */ 48 49 #define OUTPUTSIZ (PBLKSIZ - 3 * sizeof (short)) 50 #define INPUTSIZ (OUTPUTSIZ + 2) 51 52 static int ipv4 = -1; 53 static char *cmd; 54 int warning = 0; 55 56 static void verify_and_output(const char *key, char *value, int lineno); 57 58 void 59 usage() 60 { 61 fprintf(stderr, "stdhosts [-w] [-n] [in-file]\n"); 62 fprintf(stderr, "\t-w\tprint malformed warning messages.\n"); 63 exit(1); 64 } 65 66 int 67 main(argc, argv) 68 char **argv; 69 { 70 char line[INPUTSIZ]; 71 char adr[INPUTSIZ]; 72 char nadr[INET6_ADDRSTRLEN]; /* Contains normalised address */ 73 const char *nadrp; /* Pointer to the normalised address */ 74 char *trailer; 75 char *commentp; /* Pointer to comment character '#' */ 76 int c; 77 FILE *fp; 78 int lineno = 0; /* Input line counter */ 79 struct in_addr in; /* Used for normalising the IPv4 address */ 80 struct in6_addr in6; /* Used for normalising the IPv6 address */ 81 char *fgetsp; /* Holds return value for fgets() calls */ 82 int endoffile = 0; /* Set when end of file reached */ 83 84 if (cmd = strrchr(argv[0], '/')) 85 ++cmd; 86 else 87 cmd = argv[0]; 88 89 while ((c = getopt(argc, argv, "v:wn")) != -1) { 90 switch (c) { 91 case 'w': /* Send warning messages to stderr */ 92 warning = 1; 93 break; 94 case 'n': 95 ipv4 = 0; 96 break; 97 default: 98 usage(); 99 exit(1); 100 } 101 } 102 103 if (optind < argc) { 104 fp = fopen(argv[optind], "r"); 105 if (fp == NULL) { 106 fprintf(stderr, "%s: can't open %s\n", 107 cmd, argv[optind]); 108 exit(1); 109 } 110 } else 111 fp = stdin; 112 113 while (!endoffile && 114 (fgetsp = fgets(line, sizeof (line), fp)) != NULL) { 115 lineno++; 116 117 /* Check for comments */ 118 if ((commentp = strchr(line, '#')) != NULL) { 119 if ((line[strlen(line) - 1] != '\n') && 120 (strlen(line) >= (sizeof (line) - 1))) { 121 /* 122 * Discard the remainder of the line 123 * until the newline or EOF, then 124 * continue to parse the line. Use 125 * adr[] rather then line[] to 126 * preserve the contents of line[]. 127 */ 128 while ((fgetsp = fgets(adr, sizeof (adr), 129 fp)) != NULL) { 130 if (adr[strlen(adr) - 1] == '\n') 131 break; 132 } 133 if (fgetsp == NULL) 134 endoffile = 1; 135 } 136 /* Terminate line[] at the comment character */ 137 *commentp = '\0'; 138 } else if ((line[strlen(line) - 1] != '\n') && 139 (strlen(line) >= (sizeof (line) - 1))) { 140 /* 141 * Catch long lines but not if this is a short 142 * line with no '\n' at the end of the input. 143 */ 144 if (warning) 145 fprintf(stderr, 146 "%s: Warning: more than %d " 147 "bytes on line %d, ignored\n", 148 cmd, sizeof (line) - 2, lineno); 149 /* 150 * Discard the remaining lines until the 151 * newline or EOF. 152 */ 153 while ((fgetsp = fgets(line, sizeof (line), 154 fp)) != NULL) 155 if (line[strlen(line) - 1] == '\n') 156 break; 157 if (fgetsp == NULL) 158 endoffile = 1; 159 continue; 160 } 161 162 if (sscanf(line, "%s", adr) != 1) { /* Blank line, ignore */ 163 continue; 164 } 165 166 if ((trailer = strpbrk(line, " \t")) == NULL) { 167 if (warning) 168 fprintf(stderr, 169 "%s: Warning: no host names on line %d, " 170 "ignored\n", cmd, lineno); 171 continue; 172 } 173 174 /* 175 * check for valid addresses 176 * 177 * Attempt an ipv4 conversion, this accepts all valid 178 * ipv4 addresses including: 179 * d 180 * d.d 181 * d.d.d 182 * Unfortunately inet_pton() doesn't recognise these. 183 */ 184 185 in.s_addr = inet_addr(adr); 186 if (-1 != (int)in.s_addr) { 187 /* 188 * It's safe not to check return of NULL as 189 * nadrp is checked for validity later. 190 */ 191 nadrp = inet_ntop(AF_INET, &in, nadr, sizeof (nadr)); 192 } else { 193 nadrp = NULL; /* Not a valid IPv4 address */ 194 } 195 196 if (nadrp == NULL) { 197 if (inet_pton(AF_INET6, adr, &in6) == 1) { 198 nadrp = inet_ntop(AF_INET6, &in6, 199 nadr, sizeof (nadr)); 200 } 201 if (nadrp == NULL) { /* Invalid IPv6 too */ 202 if (warning) 203 fprintf(stderr, 204 "%s: Warning: malformed" 205 " address on" 206 " line %d, ignored\n", 207 cmd, lineno); 208 continue; 209 } else if (ipv4) { 210 continue; /* Ignore valid IPv6 */ 211 } 212 } 213 214 verify_and_output(nadrp, trailer, lineno); 215 216 } /* while */ 217 return (0); 218 /* NOTREACHED */ 219 } 220 221 /* 222 * verify_and_output 223 * 224 * Builds and verifies the output key and value string 225 * 226 * It makes sure these rules are followed: 227 * key + separator + value <= OUTPUTSIZ (for ndbm) 228 * names <= MAXALIASES + 1, ie one canonical name + MAXALIASES aliases 229 * It will also ignore everything after a '#' comment character 230 */ 231 static void 232 verify_and_output(const char *key, char *value, int lineno) 233 { 234 char *p; /* General char pointer */ 235 char *endp; /* Points to the NULL at the end */ 236 char *namep; /* First character of a name */ 237 char tmpbuf[OUTPUTSIZ+1]; /* Buffer before writing out */ 238 char *tmpbufp = tmpbuf; /* Current point in output string */ 239 int n = 0; /* Length of output */ 240 int names = 0; /* Number of names found */ 241 int namelen; /* Length of the name */ 242 243 if (key) { /* Just in case key is NULL */ 244 n = strlen(key); 245 if (n > OUTPUTSIZ) { 246 if (warning) 247 fprintf(stderr, 248 "%s: address too long on " 249 "line %d, line discarded\n", 250 cmd, lineno); 251 return; 252 } 253 memcpy(tmpbufp, key, n+1); /* Plus the '\0' */ 254 tmpbufp += n; 255 } 256 257 if (value) { /* Just in case value is NULL */ 258 p = value; 259 if ((endp = strchr(value, '#')) == 0) /* Ignore # comments */ 260 endp = p + strlen(p); /* Or endp = EOL */ 261 do { 262 /* 263 * Skip white space. Type conversion is 264 * necessary to avoid unfortunate effects of 265 * 8-bit characters appearing negative. 266 */ 267 while ((p < endp) && isspace((unsigned char)*p)) 268 p++; 269 270 if (p == endp) /* End of the string */ 271 break; 272 273 names++; 274 if (names > (MAXALIASES+1)) { /* cname + MAXALIASES */ 275 if (warning) 276 fprintf(stderr, 277 "%s: Warning: too many " 278 "host names on line %d, " 279 "truncating\n", 280 cmd, lineno); 281 break; 282 } 283 284 namep = p; 285 while ((p < endp) && !isspace((unsigned char)*p)) 286 p++; 287 288 namelen = p - namep; 289 n += namelen + 1; /* single white space + name */ 290 *p = '\0'; /* Terminate the name string */ 291 if (n > OUTPUTSIZ) { 292 if (warning) 293 fprintf(stderr, 294 "%s: Warning: %d byte ndbm limit " 295 "reached on line %d, truncating\n", 296 cmd, OUTPUTSIZ, lineno); 297 break; 298 } 299 300 if (names == 1) /* First space is a '\t' */ 301 *tmpbufp++ = '\t'; 302 else 303 *tmpbufp++ = ' '; 304 305 memcpy(tmpbufp, namep, namelen+1); /* Plus the '\0' */ 306 tmpbufp += namelen; 307 308 if (p < endp) 309 p++; /* Skip the added NULL */ 310 311 } while (p < endp); 312 } 313 314 if (names > 0) { 315 fputs(tmpbuf, stdout); 316 fputc('\n', stdout); 317 } else { 318 if (warning) 319 fprintf(stderr, 320 "%s: Warning: no host names on line %d, " 321 "ignored\n", cmd, lineno); 322 } 323 } 324