1/*
2 * Copyright (c) 2000-2010 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
25/*
26 * NICache.c
27 * - netinfo host cache routines
28 */
29
30#include <unistd.h>
31#include <stdlib.h>
32#include <stdio.h>
33#include <sys/stat.h>
34#include <sys/socket.h>
35#include <sys/ioctl.h>
36#include <sys/file.h>
37#include <sys/time.h>
38#include <sys/types.h>
39#include <net/if.h>
40#include <netinet/in.h>
41#include <netinet/in_systm.h>
42#include <netinet/ip.h>
43#include <netinet/udp.h>
44#include <netinet/bootp.h>
45#include <net/ethernet.h>
46#include <netinet/if_ether.h>
47#include <net/if_arp.h>
48#include <mach/boolean.h>
49#include <errno.h>
50#include <ctype.h>
51#include <arpa/inet.h>
52#include <string.h>
53#include <syslog.h>
54#include "dprintf.h"
55#include "NICache.h"
56#include "NICachePrivate.h"
57#include "util.h"
58#include "netinfo.h"
59
60#ifdef NICACHE_TEST
61#define TIMESTAMPS
62#endif /* NICACHE_TEST */
63#ifdef READ_TEST
64#define TIMESTAMPS
65#endif /* READ_TEST */
66
67#ifdef TIMESTAMPS
68static void
69timestamp_printf(char * msg)
70{
71    static struct timeval	tvp = {0,0};
72    struct timeval		tv;
73
74    gettimeofday(&tv, 0);
75    if (tvp.tv_sec) {
76	struct timeval result;
77
78	timeval_subtract(tv, tvp, &result);
79	printf("%d.%06d (%d.%06d): %s\n",
80	       (int)tv.tv_sec, (int)tv.tv_usec,
81	       (int)result.tv_sec, (int)result.tv_usec, msg);
82    }
83    else
84	printf("%d.%06d (%d.%06d): %s\n",
85	       (int)tv.tv_sec, (int)tv.tv_usec, 0, 0, msg);
86    tvp = tv;
87}
88static __inline__ void
89S_timestamp(char * msg)
90{
91    timestamp_printf(msg);
92}
93#else /* NICACHE_TEST */
94static __inline__ void
95S_timestamp(char * msg)
96{
97}
98#endif /* TIMESTAMPS */
99
100/**
101 ** Module: PLCacheEntry
102 **/
103
104PLCacheEntry_t *
105PLCacheEntry_create(ni_proplist pl)
106{
107    PLCacheEntry_t * entry = malloc(sizeof(*entry));
108
109    if (entry == NULL)
110	return (NULL);
111    entry->pl = ni_proplist_dup(pl);
112    entry->next = entry->prev = NULL;
113    return (entry);
114}
115
116void
117PLCacheEntry_free(PLCacheEntry_t * ent)
118{
119    ni_proplist_free(&ent->pl);
120    bzero(ent, sizeof(*ent));
121    free(ent);
122    return;
123}
124
125/**
126 ** Module: PLCache
127 **/
128
129void
130PLCache_print(PLCache_t * cache)
131{
132    int			i;
133    PLCacheEntry_t *	scan;
134
135    printf("PLCache contains %d elements\n", cache->count);
136    for (i = 0, scan = cache->head; scan; scan = scan->next, i++) {
137	printf("\nEntry %d\n", i);
138	ni_proplist_dump(&scan->pl);
139    }
140}
141
142void
143PLCache_init(PLCache_t * cache)
144{
145    bzero(cache, sizeof(*cache));
146    cache->max_entries = CACHE_MAX;
147    return;
148}
149
150int
151PLCache_count(PLCache_t * c)
152{
153    return (c->count);
154}
155
156void
157PLCache_set_max(PLCache_t * c, int m)
158{
159    if (m < CACHE_MIN)
160	m = CACHE_MIN;
161    c->max_entries = m;
162    if (c->count > c->max_entries) {
163	int 			i;
164	int 			num = c->count - c->max_entries;
165	PLCacheEntry_t * 	prev;
166	PLCacheEntry_t * 	scan;
167
168	dprintf(("Count %d max %d, removing %d\n", c->count, c->max_entries,
169		 num));
170
171	/* drop num items from the cache */
172	prev = NULL;
173	scan = c->tail;
174	for (i = 0; i < num; i++) {
175	    dprintf(("Deleting %d\n", i));
176	    prev = scan->prev;
177	    PLCacheEntry_free(scan);
178	    scan = prev;
179	}
180	c->tail = prev;
181	if (c->tail) {
182	    c->tail->next = NULL;
183	}
184	else {
185	    c->head = NULL;
186	}
187	c->count = c->max_entries;
188    }
189    return;
190}
191
192void
193PLCache_free(PLCache_t * cache)
194{
195    PLCacheEntry_t * scan;
196
197    for (scan = cache->head; scan; ) {
198	PLCacheEntry_t * next;
199
200	next = scan->next;
201	PLCacheEntry_free(scan);
202	scan = next;
203	dprintf(("deleting %d\n", ++i));
204    }
205    bzero(cache, sizeof(*cache));
206    return;
207}
208
209void
210PLCache_add(PLCache_t * cache, PLCacheEntry_t * entry)
211{
212    if (entry == NULL)
213	return;
214
215    entry->next = cache->head;
216    entry->prev = NULL;
217    if (cache->head == NULL) {
218	cache->head = cache->tail = entry;
219    }
220    else {
221	cache->head->prev = entry;
222	cache->head = entry;
223    }
224    cache->count++;
225    return;
226}
227
228void
229PLCache_append(PLCache_t * cache, PLCacheEntry_t * entry)
230{
231    if (entry == NULL)
232	return;
233
234    entry->next = NULL;
235    entry->prev = cache->tail;
236    if (cache->head == NULL) {
237	cache->head = cache->tail = entry;
238    }
239    else {
240	cache->tail->next = entry;
241	cache->tail = entry;
242    }
243    cache->count++;
244    return;
245}
246
247/*
248 * Function: my_fgets
249 * Purpose:
250 *   like fgets() but consumes/discards characters until the next newline
251 *   once the line buffer is full.
252 */
253static char *
254my_fgets(char * buf, int buf_size, FILE * f)
255{
256    boolean_t	done = FALSE;
257    int		left = buf_size - 1;
258    char *	scan;
259
260    scan = buf;
261    while (!done) {
262	int		this_char;
263
264	this_char = fgetc(f);
265	switch (this_char) {
266	case 0:
267	case EOF:
268	    done = TRUE;
269	    break;
270	default:
271	    if (left > 0) {
272		*scan++ = (char)this_char;
273		left--;
274	    }
275	    if (this_char == '\n') {
276		done = TRUE;
277	    }
278	    break;
279	}
280    }
281    if (scan == buf) {
282	/* we didn't read anything */
283	return (NULL);
284    }
285    *scan = '\0';
286    return (buf);
287}
288
289boolean_t
290PLCache_read(PLCache_t * cache, const char * filename)
291{
292    FILE *	file = NULL;
293    int		line_number = 0;
294    char	line[1024];
295    ni_proplist	pl;
296    enum {
297	nowhere_e,
298	start_e,
299	body_e,
300	end_e
301    }		where = nowhere_e;
302
303    NI_INIT(&pl);
304    file = fopen(filename, "r");
305    if (file == NULL) {
306	perror(filename);
307	goto failed;
308    }
309
310    while (1) {
311	if (my_fgets(line, sizeof(line), file) != line) {
312	    if (where == start_e || where == body_e) {
313		fprintf(stderr, "file ends prematurely\n");
314	    }
315	    break;
316	}
317	line_number++;
318	if (strcmp(line, "{\n") == 0) {
319	    if (where != end_e && where != nowhere_e) {
320		fprintf(stderr, "unexpected '{' at line %d\n",
321			line_number);
322		goto failed;
323	    }
324	    where = start_e;
325	}
326	else if (strcmp(line, "}\n") == 0) {
327	    if (where != start_e && where != body_e) {
328		fprintf(stderr, "unexpected '}' at line %d\n",
329			line_number);
330		goto failed;
331	    }
332	    if (pl.nipl_len > 0) {
333		PLCache_append(cache, PLCacheEntry_create(pl));
334		ni_proplist_free(&pl);
335	    }
336	    where = end_e;
337	}
338	else {
339	    char	propname[128];
340	    char	propval[768] = "";
341	    int 	len = strlen(line);
342	    char *	sep = strchr(line, '=');
343	    int 	whitespace_len = strspn(line, " \t\n");
344
345	    if (whitespace_len == len) {
346		continue;
347	    }
348	    if (sep) {
349		int nlen = (sep - line) - whitespace_len;
350		int vlen = len - whitespace_len - nlen - 2;
351
352		if (nlen >= sizeof(propname)) {
353		    fprintf(stderr,
354			    "property name truncated to %d bytes at line %d\n",
355			    (int)sizeof(propname) - 1,
356			    line_number);
357		    nlen = sizeof(propname) - 1;
358		}
359		if (vlen >= sizeof(propval)) {
360		    fprintf(stderr,
361			    "value truncated to %d bytes at line %d\n",
362			    (int)sizeof(propval) - 1,
363			    line_number);
364		    vlen = sizeof(propval) - 1;
365		}
366		strncpy(propname, line + whitespace_len, nlen);
367		propname[nlen] = '\0';
368		strncpy(propval, sep + 1, vlen);
369		propval[vlen] = '\0';
370		ni_proplist_insertprop(&pl, propname, propval, NI_INDEX_NULL);
371	    }
372	    else {
373		int nlen = len - whitespace_len - 1;
374
375		if (nlen >= sizeof(propname)) {
376		    fprintf(stderr,
377			    "property name truncated to %d bytes at line %d\n",
378			    (int)sizeof(propname) - 1,
379			    line_number);
380		    nlen = sizeof(propname) - 1;
381		}
382		strncpy(propname, line + whitespace_len, nlen);
383		propname[nlen] = '\0';
384		ni_proplist_insertprop(&pl, propname, NULL, NI_INDEX_NULL);
385	    }
386	    where = body_e;
387	}
388    }
389
390 failed:
391    if (file)
392	fclose(file);
393    ni_proplist_free(&pl);
394    return (TRUE);
395}
396
397boolean_t
398PLCache_write(PLCache_t * cache, const char * filename)
399{
400    FILE *		file = NULL;
401    PLCacheEntry_t *	scan;
402    char		tmp_filename[256];
403
404    snprintf(tmp_filename, sizeof(tmp_filename), "%s-", filename);
405    file = fopen(tmp_filename, "w");
406    if (file == NULL) {
407	perror(tmp_filename);
408	return (FALSE);
409    }
410
411    for (scan = cache->head; scan; scan = scan->next) {
412	int i;
413
414	fprintf(file, "{\n");
415	for (i = 0; i < scan->pl.nipl_len; i++) {
416	    ni_property * prop = &(scan->pl.nipl_val[i]);
417	    ni_namelist * nl_p = &prop->nip_val;
418	    if (nl_p->ninl_len == 0) {
419		fprintf(file, "\t%s\n", prop->nip_name);
420	    }
421	    else {
422		fprintf(file, "\t%s=%s\n", prop->nip_name,
423			nl_p->ninl_val[0]);
424	    }
425	}
426	fprintf(file, "}\n");
427    }
428    fclose(file);
429    rename(tmp_filename, filename);
430    return (TRUE);
431}
432
433void
434PLCache_remove(PLCache_t * cache, PLCacheEntry_t * entry)
435{
436    if (entry == NULL)
437	return;
438
439    if (entry->prev)
440	entry->prev->next = entry->next;
441    if (entry->next)
442	entry->next->prev = entry->prev;
443    if (entry == cache->head) {
444	cache->head = cache->head->next;
445    }
446    if (entry == cache->tail) {
447	cache->tail = cache->tail->prev;
448    }
449    entry->next = entry->prev = NULL;
450    cache->count--;
451    return;
452}
453
454void
455PLCache_make_head(PLCache_t * cache, PLCacheEntry_t * entry)
456{
457    if (entry == cache->head)
458	return; /* already the head */
459
460    PLCache_remove(cache, entry);
461    PLCache_add(cache, entry);
462}
463
464PLCacheEntry_t *
465PLCache_lookup_prop(PLCache_t * PLCache, char * prop, char * value, boolean_t make_head)
466{
467    PLCacheEntry_t * scan;
468
469    for (scan = PLCache->head; scan; scan = scan->next) {
470	int		name_index;
471
472	name_index = ni_proplist_match(scan->pl, prop, value);
473	if (name_index != NI_INDEX_NULL) {
474	    if (make_head) {
475		PLCache_make_head(PLCache, scan);
476	    }
477	    return (scan);
478	}
479    }
480    return (NULL);
481}
482
483PLCacheEntry_t *
484PLCache_lookup_hw(PLCache_t * PLCache,
485		  uint8_t hwtype, void * hwaddr, int hwlen,
486		  NICacheFunc_t * func, void * arg,
487		  struct in_addr * client_ip,
488		  boolean_t * has_binding)
489{
490    struct ether_addr *	en_search = (struct ether_addr *)hwaddr;
491    PLCacheEntry_t *	scan;
492
493    if (has_binding)
494	*has_binding = FALSE;
495    for (scan = PLCache->head; scan; scan = scan->next) {
496	ni_namelist *	en_nl_p;
497	int 		n;
498	int		en_index;
499	int		ip_index;
500	ni_namelist *	ip_nl_p;
501
502	en_index = ni_proplist_match(scan->pl, NIPROP_ENADDR, NULL);
503	if (en_index == NI_INDEX_NULL)
504	    continue;
505	ip_index = ni_proplist_match(scan->pl, NIPROP_IPADDR, NULL);
506	if (ip_index == NI_INDEX_NULL)
507	    continue;
508
509	en_nl_p = &scan->pl.nipl_val[en_index].nip_val;
510	ip_nl_p = &scan->pl.nipl_val[ip_index].nip_val;
511	if (en_nl_p->ninl_len == 0 || ip_nl_p->ninl_len == 0)
512	    continue;
513	for (n = 0; n < en_nl_p->ninl_len; n++) {
514	    struct ether_addr * en_p = ether_aton(en_nl_p->ninl_val[n]);
515	    if (en_p == NULL)
516		continue;
517	    if (bcmp(en_p, en_search, sizeof(*en_search)) == 0) {
518		int		which_ip;
519
520		which_ip = n % ip_nl_p->ninl_len;
521		if (inet_aton(ip_nl_p->ninl_val[which_ip], client_ip) == 0)
522		    continue;
523		if (has_binding)
524		    *has_binding = TRUE;
525		if (func == NULL || (*func)(arg, *client_ip)) {
526		    PLCache_make_head(PLCache, scan);
527		    return (scan);
528		}
529	    }
530	}
531    }
532    return (NULL);
533}
534
535
536PLCacheEntry_t *
537PLCache_lookup_identifier(PLCache_t * PLCache,
538			 char * idstr, NICacheFunc_t * func, void * arg,
539			 struct in_addr * client_ip,
540			 boolean_t * has_binding)
541{
542    PLCacheEntry_t *	scan;
543
544    if (has_binding)
545	*has_binding = FALSE;
546    for (scan = PLCache->head; scan; scan = scan->next) {
547	ni_namelist *	ident_nl_p;
548	int 		n;
549	int		ident_index;
550	int		ip_index;
551	ni_namelist *	ip_nl_p;
552
553	ident_index = ni_proplist_match(scan->pl, NIPROP_IDENTIFIER,
554					NULL);
555	if (ident_index == NI_INDEX_NULL)
556	    continue;
557	ident_nl_p = &scan->pl.nipl_val[ident_index].nip_val;
558	if (ident_nl_p->ninl_len == 0)
559	    continue;
560
561	if (client_ip == NULL) { /* don't care about IP binding */
562	    if (strcmp(ident_nl_p->ninl_val[0], idstr) == 0) {
563		if (has_binding)
564		    *has_binding = TRUE;
565		PLCache_make_head(PLCache, scan);
566		return (scan);
567	    }
568	}
569
570	ip_index = ni_proplist_match(scan->pl, NIPROP_IPADDR, NULL);
571	if (ip_index == NI_INDEX_NULL)
572	    continue;
573	ip_nl_p = &scan->pl.nipl_val[ip_index].nip_val;
574	if (ip_nl_p->ninl_len == 0)
575	    continue;
576
577	for (n = 0; n < ident_nl_p->ninl_len; n++) {
578	    if (strcmp(ident_nl_p->ninl_val[n], idstr) == 0) {
579		int		which_ip;
580
581		which_ip = n % ip_nl_p->ninl_len;
582		if (inet_aton(ip_nl_p->ninl_val[which_ip], client_ip) == 0)
583		    continue;
584		if (has_binding)
585		    *has_binding = TRUE;
586		if (func == NULL || (client_ip != NULL && (*func)(arg, *client_ip))) {
587		    PLCache_make_head(PLCache, scan);
588		    return (scan);
589		}
590	    }
591	}
592    }
593    return (NULL);
594}
595
596
597PLCacheEntry_t *
598PLCache_lookup_ip(PLCache_t * PLCache, struct in_addr iaddr)
599{
600    PLCacheEntry_t *	scan;
601
602    for (scan = PLCache->head; scan; scan = scan->next) {
603	int 		n;
604	int		ip_index;
605	ni_namelist *	ip_nl_p;
606
607	ip_index = ni_proplist_match(scan->pl, NIPROP_IPADDR, NULL);
608	if (ip_index == NI_INDEX_NULL)
609	    continue;
610	ip_nl_p = &scan->pl.nipl_val[ip_index].nip_val;
611	for (n = 0; n < ip_nl_p->ninl_len; n++) {
612	    struct in_addr entry_ip;
613	    if (inet_aton(ip_nl_p->ninl_val[n], &entry_ip) == 0)
614		continue;
615	    if (iaddr.s_addr == entry_ip.s_addr) {
616		PLCache_make_head(PLCache, scan);
617		return (scan);
618	    }
619	}
620    }
621    return (NULL);
622}
623
624#ifdef READ_TEST
625int
626main(int argc, char * argv[])
627{
628    PLCache_t 	PLCache;
629
630    if (argc < 2)
631	exit(1);
632
633    PLCache_init(&PLCache);
634    PLCache_set_max(&PLCache, 100 * 1024 * 1024);
635    S_timestamp("before read");
636    if (PLCache_read(&PLCache, argv[1]) == TRUE) {
637	S_timestamp("after read");
638	PLCache_print(&PLCache);
639
640	printf("writing /tmp/readtest.out\n");
641	PLCache_write(&PLCache, "/tmp/readtest.out");
642    }
643    else {
644	S_timestamp("after read");
645	printf("PLCache_read failed\n");
646    }
647    exit(0);
648}
649
650#endif /* READ_TEST */
651