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
usage()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
main(int argc,char ** argv)67 main(int argc, char **argv)
68 {
69 char line[INPUTSIZ];
70 char adr[INPUTSIZ];
71 char nadr[INET6_ADDRSTRLEN]; /* Contains normalised address */
72 const char *nadrp; /* Pointer to the normalised address */
73 char *trailer;
74 char *commentp; /* Pointer to comment character '#' */
75 int c;
76 FILE *fp;
77 int lineno = 0; /* Input line counter */
78 struct in_addr in; /* Used for normalising the IPv4 address */
79 struct in6_addr in6; /* Used for normalising the IPv6 address */
80 char *fgetsp; /* Holds return value for fgets() calls */
81 int endoffile = 0; /* Set when end of file reached */
82
83 if (cmd = strrchr(argv[0], '/'))
84 ++cmd;
85 else
86 cmd = argv[0];
87
88 while ((c = getopt(argc, argv, "v:wn")) != -1) {
89 switch (c) {
90 case 'w': /* Send warning messages to stderr */
91 warning = 1;
92 break;
93 case 'n':
94 ipv4 = 0;
95 break;
96 default:
97 usage();
98 exit(1);
99 }
100 }
101
102 if (optind < argc) {
103 fp = fopen(argv[optind], "r");
104 if (fp == NULL) {
105 fprintf(stderr, "%s: can't open %s\n",
106 cmd, argv[optind]);
107 exit(1);
108 }
109 } else
110 fp = stdin;
111
112 while (!endoffile &&
113 (fgetsp = fgets(line, sizeof (line), fp)) != NULL) {
114 lineno++;
115
116 /* Check for comments */
117 if ((commentp = strchr(line, '#')) != NULL) {
118 if ((line[strlen(line) - 1] != '\n') &&
119 (strlen(line) >= (sizeof (line) - 1))) {
120 /*
121 * Discard the remainder of the line
122 * until the newline or EOF, then
123 * continue to parse the line. Use
124 * adr[] rather then line[] to
125 * preserve the contents of line[].
126 */
127 while ((fgetsp = fgets(adr, sizeof (adr),
128 fp)) != NULL) {
129 if (adr[strlen(adr) - 1] == '\n')
130 break;
131 }
132 if (fgetsp == NULL)
133 endoffile = 1;
134 }
135 /* Terminate line[] at the comment character */
136 *commentp = '\0';
137 } else if ((line[strlen(line) - 1] != '\n') &&
138 (strlen(line) >= (sizeof (line) - 1))) {
139 /*
140 * Catch long lines but not if this is a short
141 * line with no '\n' at the end of the input.
142 */
143 if (warning)
144 fprintf(stderr,
145 "%s: Warning: more than %d "
146 "bytes on line %d, ignored\n",
147 cmd, sizeof (line) - 2, lineno);
148 /*
149 * Discard the remaining lines until the
150 * newline or EOF.
151 */
152 while ((fgetsp = fgets(line, sizeof (line),
153 fp)) != NULL)
154 if (line[strlen(line) - 1] == '\n')
155 break;
156 if (fgetsp == NULL)
157 endoffile = 1;
158 continue;
159 }
160
161 if (sscanf(line, "%s", adr) != 1) { /* Blank line, ignore */
162 continue;
163 }
164
165 if ((trailer = strpbrk(line, " \t")) == NULL) {
166 if (warning)
167 fprintf(stderr,
168 "%s: Warning: no host names on line %d, "
169 "ignored\n", cmd, lineno);
170 continue;
171 }
172
173 /*
174 * check for valid addresses
175 *
176 * Attempt an ipv4 conversion, this accepts all valid
177 * ipv4 addresses including:
178 * d
179 * d.d
180 * d.d.d
181 * Unfortunately inet_pton() doesn't recognise these.
182 */
183
184 in.s_addr = inet_addr(adr);
185 if (-1 != (int)in.s_addr) {
186 /*
187 * It's safe not to check return of NULL as
188 * nadrp is checked for validity later.
189 */
190 nadrp = inet_ntop(AF_INET, &in, nadr, sizeof (nadr));
191 } else {
192 nadrp = NULL; /* Not a valid IPv4 address */
193 }
194
195 if (nadrp == NULL) {
196 if (inet_pton(AF_INET6, adr, &in6) == 1) {
197 nadrp = inet_ntop(AF_INET6, &in6,
198 nadr, sizeof (nadr));
199 }
200 if (nadrp == NULL) { /* Invalid IPv6 too */
201 if (warning)
202 fprintf(stderr,
203 "%s: Warning: malformed"
204 " address on"
205 " line %d, ignored\n",
206 cmd, lineno);
207 continue;
208 } else if (ipv4) {
209 continue; /* Ignore valid IPv6 */
210 }
211 }
212
213 verify_and_output(nadrp, trailer, lineno);
214
215 } /* while */
216 return (0);
217 /* NOTREACHED */
218 }
219
220 /*
221 * verify_and_output
222 *
223 * Builds and verifies the output key and value string
224 *
225 * It makes sure these rules are followed:
226 * key + separator + value <= OUTPUTSIZ (for ndbm)
227 * names <= MAXALIASES + 1, ie one canonical name + MAXALIASES aliases
228 * It will also ignore everything after a '#' comment character
229 */
230 static void
verify_and_output(const char * key,char * value,int lineno)231 verify_and_output(const char *key, char *value, int lineno)
232 {
233 char *p; /* General char pointer */
234 char *endp; /* Points to the NULL at the end */
235 char *namep; /* First character of a name */
236 char tmpbuf[OUTPUTSIZ+1]; /* Buffer before writing out */
237 char *tmpbufp = tmpbuf; /* Current point in output string */
238 int n = 0; /* Length of output */
239 int names = 0; /* Number of names found */
240 int namelen; /* Length of the name */
241
242 if (key) { /* Just in case key is NULL */
243 n = strlen(key);
244 if (n > OUTPUTSIZ) {
245 if (warning)
246 fprintf(stderr,
247 "%s: address too long on "
248 "line %d, line discarded\n",
249 cmd, lineno);
250 return;
251 }
252 memcpy(tmpbufp, key, n+1); /* Plus the '\0' */
253 tmpbufp += n;
254 }
255
256 if (value) { /* Just in case value is NULL */
257 p = value;
258 if ((endp = strchr(value, '#')) == 0) /* Ignore # comments */
259 endp = p + strlen(p); /* Or endp = EOL */
260 do {
261 /*
262 * Skip white space. Type conversion is
263 * necessary to avoid unfortunate effects of
264 * 8-bit characters appearing negative.
265 */
266 while ((p < endp) && isspace((unsigned char)*p))
267 p++;
268
269 if (p == endp) /* End of the string */
270 break;
271
272 names++;
273 if (names > (MAXALIASES+1)) { /* cname + MAXALIASES */
274 if (warning)
275 fprintf(stderr,
276 "%s: Warning: too many "
277 "host names on line %d, "
278 "truncating\n",
279 cmd, lineno);
280 break;
281 }
282
283 namep = p;
284 while ((p < endp) && !isspace((unsigned char)*p))
285 p++;
286
287 namelen = p - namep;
288 n += namelen + 1; /* single white space + name */
289 *p = '\0'; /* Terminate the name string */
290 if (n > OUTPUTSIZ) {
291 if (warning)
292 fprintf(stderr,
293 "%s: Warning: %d byte ndbm limit "
294 "reached on line %d, truncating\n",
295 cmd, OUTPUTSIZ, lineno);
296 break;
297 }
298
299 if (names == 1) /* First space is a '\t' */
300 *tmpbufp++ = '\t';
301 else
302 *tmpbufp++ = ' ';
303
304 memcpy(tmpbufp, namep, namelen+1); /* Plus the '\0' */
305 tmpbufp += namelen;
306
307 if (p < endp)
308 p++; /* Skip the added NULL */
309
310 } while (p < endp);
311 }
312
313 if (names > 0) {
314 fputs(tmpbuf, stdout);
315 fputc('\n', stdout);
316 } else {
317 if (warning)
318 fprintf(stderr,
319 "%s: Warning: no host names on line %d, "
320 "ignored\n", cmd, lineno);
321 }
322 }
323