1/*
2 * Copyright (c) 1999-2008 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23/*
24 * bootpdfile.c
25 * - read bootptab to get the default boot file and path
26 * - parse the list of hardware address to ip bindings
27 * - lookup host entries in the file-based host list
28 */
29
30#include <unistd.h>
31#include <stdlib.h>
32#include <sys/stat.h>
33#include <sys/socket.h>
34#include <sys/ioctl.h>
35#include <sys/file.h>
36#include <sys/time.h>
37#include <net/if.h>
38#include <netinet/in.h>
39#include <netinet/in_systm.h>
40#include <netinet/ip.h>
41#include <netinet/udp.h>
42#include <netinet/bootp.h>
43#include <netinet/if_ether.h>
44#include <mach/boolean.h>
45#include <signal.h>
46#include <stdio.h>
47#include <string.h>
48#include <errno.h>
49#include <ctype.h>
50#include <netdb.h>
51#include <syslog.h>
52#include <arpa/inet.h>
53#include <sys/uio.h>
54
55#include "bootpdfile.h"
56#include "hostlist.h"
57
58#define HTYPE_ETHER		1
59#define NUM_EN_ADDR_BYTES	6
60
61#define HOSTNAME_MAX		64
62#define BOOTFILE_MAX		128
63
64#define ETC_BOOTPTAB	"/etc/bootptab"
65static long	modtime = 0;	/* last modification time of bootptab */
66static struct hosts * 	S_file_hosts = NULL; /* list of host entries from the file */
67
68/*
69 * Get next field from 'line' buffer into 'str'.  'linep' is the
70 * pointer to current position.
71 */
72static void
73S_getfield(char * * line_p, int linenum, char *str, int len)
74{
75	register char *cp = str;
76	char * linep = *line_p;
77
78	for ( ; *linep && (*linep == ' ' || *linep == '\t') ; linep++)
79		;	/* skip spaces/tabs */
80	if (*linep == 0) {
81		*cp = 0;
82		goto done;
83	}
84	len--;	/* save a spot for a null */
85	for ( ; *linep && *linep != ' ' && *linep != '\t' ; linep++) {
86		*cp++ = *linep;
87		if (--len <= 0) {
88			*cp = 0;
89			syslog(LOG_NOTICE, "string truncated: %s,"
90			       " on line %d of bootptab", str, linenum);
91			goto done;
92		}
93	}
94	*cp = 0;
95 done:
96	*line_p = linep;
97}
98
99/*
100 * Read bootptab database file.  Avoid rereading the file if the
101 * write date hasnt changed since the last time we read it.
102 */
103void
104bootp_readtab(const char * filename)
105{
106    struct stat st;
107    register char *cp;
108    int	host_count = 0;
109    int host_count_all = 0;
110    register int i;
111    char line[256];	/* line buffer for reading bootptab */
112    char temp[64], tempcpy[64];
113    register struct hosts *hp, *thp;
114    char * linep;
115    int linenum;
116    int skiptopercent;
117    FILE * fp = NULL;
118
119    if (filename == NULL) {
120	filename = ETC_BOOTPTAB;
121    }
122    if ((fp = fopen(filename, "r")) == NULL) {
123	syslog(LOG_INFO, "can't open %s", filename);
124	return;
125    }
126    if (fstat(fileno(fp), &st) == 0
127	&& st.st_mtime == modtime) {
128	fclose(fp);
129	return;	/* hasn't been modified */
130    }
131    syslog(LOG_NOTICE, "re-reading %s", filename);
132    modtime = st.st_mtime;
133    linenum = 0;
134    skiptopercent = 1;
135
136    /*
137     * Free old file entries.
138     */
139    hp = S_file_hosts;
140    while (hp) {
141	thp = hp->next;
142	hostfree(&S_file_hosts, hp);
143	hp = thp;
144    }
145
146    /*
147     * read and parse each line in the file.
148     */
149    for (;;) {
150	boolean_t	all_zeroes;
151	boolean_t	good_hwaddr;
152	char hostname[HOSTNAME_MAX];
153	char bootfile[BOOTFILE_MAX];
154	struct in_addr iaddr;
155	int htype;
156	int hlen;
157	char haddr[32];
158
159	if (fgets(line, sizeof line, fp) == NULL)
160	    break;	/* done */
161
162	if ((i = (int)strlen(line)) != 0)
163	    line[i-1] = 0;	/* remove trailing newline */
164
165	linep = line;
166	linenum++;
167	if (line[0] == '#' || line[0] == 0 || line[0] == ' ')
168	    continue;	/* skip comment lines */
169
170	if (skiptopercent) {	/* allow for future leading fields */
171	    if (line[0] != '%')
172		continue;
173	    skiptopercent = 0;
174	    continue;
175	}
176	host_count_all++;
177	/* fill in host table */
178	S_getfield(&linep, linenum, hostname, sizeof(hostname) - 1);
179	S_getfield(&linep, linenum, temp, sizeof temp);
180	sscanf(temp, "%d", &htype);
181	S_getfield(&linep, linenum, temp, sizeof temp);
182	strlcpy(tempcpy, temp, sizeof(tempcpy));
183	cp = tempcpy;
184	/* parse hardware address */
185	good_hwaddr = TRUE;
186	all_zeroes = TRUE;
187	for (hlen = 0; hlen < sizeof(haddr);) {
188	    char *cpold;
189	    char c;
190	    int v;
191
192	    cpold = cp;
193	    while (*cp != '.' && *cp != ':' && *cp != 0)
194		cp++;
195	    c = *cp;	/* save original terminator */
196	    *cp = 0;
197	    cp++;
198	    if (sscanf(cpold, "%x", &v) != 1) {
199		good_hwaddr = FALSE;
200		syslog(LOG_NOTICE, "bad hex address: %s,"
201		       " at line %d of bootptab", temp, linenum);
202		break;
203	    }
204	    haddr[hlen++] = v;
205	    if (v != 0) {
206		all_zeroes = FALSE;
207	    }
208	    if (c == 0)
209		break;
210	}
211	if (good_hwaddr == FALSE) {
212	    continue;
213	}
214	if (all_zeroes) {
215	    syslog(LOG_NOTICE, "zero hex address: %s,"
216		   " at line %d of bootptab", temp, linenum);
217	    continue;
218	}
219	if (htype == HTYPE_ETHER && hlen != NUM_EN_ADDR_BYTES) {
220	    syslog(LOG_NOTICE, "bad hex address: %s,"
221		   " at line %d of bootptab", temp, linenum);
222	    continue;
223	}
224	S_getfield(&linep, linenum, temp, sizeof(temp));
225	iaddr.s_addr = inet_addr(temp);
226	if (iaddr.s_addr == -1 || iaddr.s_addr == 0) {
227	    syslog(LOG_NOTICE, "bad internet address: %s,"
228		   " at line %d of bootptab", temp, linenum);
229	    continue;
230	}
231	S_getfield(&linep, linenum, bootfile, sizeof(bootfile) - 1);
232	(void)hostadd(&S_file_hosts, NULL, htype, haddr, hlen, &iaddr,
233		      hostname, bootfile);
234	host_count++;
235    }
236    fclose(fp);
237    syslog(LOG_NOTICE, "Loaded %d entries from bootptab (%d bad)",
238	   host_count, host_count_all - host_count);
239    return;
240}
241
242boolean_t
243bootp_getbyhw_file(uint8_t hwtype, void * hwaddr, int hwlen,
244		   subnet_match_func_t * func, void * arg,
245		   struct in_addr * iaddr_p,
246		   char * * hostname_p, char * * bootfile_p)
247{
248    struct hosts * hp;
249
250    hp = hostbyaddr(S_file_hosts, hwtype, hwaddr, hwlen,
251		    func, arg);
252    if (hp == NULL)
253	return (FALSE);
254    if (hostname_p)
255	*hostname_p = strdup(hp->hostname);
256    if (bootfile_p)
257	*bootfile_p = strdup(hp->bootfile);
258    *iaddr_p = hp->iaddr;
259    return (TRUE);
260}
261
262boolean_t
263bootp_getbyip_file(struct in_addr ciaddr, char * * hostname_p,
264		   char * * bootfile_p)
265{
266    struct hosts * hp;
267
268    hp = hostbyip(S_file_hosts, ciaddr);
269    if (hp == NULL)
270	return (FALSE);
271
272    if (hostname_p)
273	*hostname_p = strdup(hp->hostname);
274    if (bootfile_p)
275	*bootfile_p = strdup(hp->bootfile);
276    return (TRUE);
277}
278
279#ifdef MAIN
280#define USECS_PER_SEC	1000000
281/*
282 * Function: timeval_subtract
283 *
284 * Purpose:
285 *   Computes result = tv1 - tv2.
286 */
287void
288timeval_subtract(struct timeval tv1, struct timeval tv2,
289		 struct timeval * result)
290{
291    result->tv_sec = tv1.tv_sec - tv2.tv_sec;
292    result->tv_usec = tv1.tv_usec - tv2.tv_usec;
293    if (result->tv_usec < 0) {
294	result->tv_usec += USECS_PER_SEC;
295	result->tv_sec--;
296    }
297    return;
298}
299
300/*
301 * Function: timeval_add
302 *
303 * Purpose:
304 *   Computes result = tv1 + tv2.
305 */
306void
307timeval_add(struct timeval tv1, struct timeval tv2,
308	    struct timeval * result)
309{
310    result->tv_sec = tv1.tv_sec + tv2.tv_sec;
311    result->tv_usec = tv1.tv_usec + tv2.tv_usec;
312    if (result->tv_usec > USECS_PER_SEC) {
313	result->tv_usec -= USECS_PER_SEC;
314	result->tv_sec++;
315    }
316    return;
317}
318
319/*
320 * Function: timeval_compare
321 *
322 * Purpose:
323 *   Compares two timeval values, tv1 and tv2.
324 *
325 * Returns:
326 *   -1		if tv1 is less than tv2
327 *   0 		if tv1 is equal to tv2
328 *   1 		if tv1 is greater than tv2
329 */
330int
331timeval_compare(struct timeval tv1, struct timeval tv2)
332{
333    struct timeval result;
334
335    timeval_subtract(tv1, tv2, &result);
336    if (result.tv_sec < 0 || result.tv_usec < 0)
337	return (-1);
338    if (result.tv_sec == 0 && result.tv_usec == 0)
339	return (0);
340    return (1);
341}
342
343void
344timestamp_printf(char * msg)
345{
346    static struct timeval	tvp = {0,0};
347    struct timeval		tv;
348
349    gettimeofday(&tv, 0);
350    if (tvp.tv_sec) {
351	struct timeval result;
352
353	timeval_subtract(tv, tvp, &result);
354	printf("%d.%06d (%d.%06d): %s\n",
355	       (int)tv.tv_sec,
356	       (int)tv.tv_usec,
357	       (int)result.tv_sec,
358	       (int)result.tv_usec, msg);
359    }
360    else
361	printf("%d.%06d (%d.%06d): %s\n",
362	       (int)tv.tv_sec, (int)tv.tv_usec, 0, 0, msg);
363    tvp = tv;
364}
365
366int
367main(int argc, char * argv[])
368{
369    struct ether_addr *	ea;
370    struct in_addr ip;
371    char *	host = NULL;
372    boolean_t	found;
373    const char *	file = NULL;
374
375    if (argc < 2) {
376	fprintf(stderr, "usage: %s ethernet_address\n", argv[0]);
377	exit(1);
378    }
379    ea = ether_aton(argv[1]);
380    if (ea == NULL) {
381	fprintf(stderr, "invalid ethernet address '%s'\n", argv[1]);
382	exit(1);
383    }
384    if (argc > 2) {
385	file = argv[2];
386    }
387    timestamp_printf("before read");
388    bootp_readtab(file);
389    timestamp_printf("after read");
390
391    timestamp_printf("before lookup");
392    found = bootp_getbyhw_file(HTYPE_ETHER, ea, 6,
393			       NULL, NULL,
394			       &ip, &host, NULL);
395    timestamp_printf("after lookup");
396    if (found) {
397	printf("%s IP is %s\n", host, inet_ntoa(ip));
398    }
399    else {
400	printf("Not found\n");
401    }
402    if (host != NULL) free(host);
403    exit(0);
404
405}
406#endif /* MAIN */
407