1/* * Copyright (c) 2006 Apple Inc. All rights reserved.
2 *
3 * @APPLE_LICENSE_HEADER_START@
4 *
5 * This file contains Original Code and/or Modifications of Original Code
6 * as defined in and that are subject to the Apple Public Source License
7 * Version 2.0 (the 'License'). You may not use this file except in
8 * compliance with the License. Please obtain a copy of the License at
9 * http://www.opensource.apple.com/apsl/ and read it before using this
10 * file.
11 *
12 * The Original Code and all software distributed under the License are
13 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
14 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
15 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
17 * Please see the License for the specific language governing rights and
18 * limitations under the License.
19 *
20 * @APPLE_LICENSE_HEADER_END@
21 */
22
23#include <stdio.h>
24#include <servers/bootstrap.h>
25#include <DirectoryService/DirServicesConst.h>
26#include <opendirectory/DSlibinfoMIG_types.h>
27#include <syslog.h>
28#include <stdlib.h>
29#include <arpa/inet.h>
30#include <mach/mach.h>
31#include <net/ethernet.h>
32#include <kvbuf.h>
33#include "bootplookup.h"
34
35extern kern_return_t
36libinfoDSmig_GetProcedureNumber(mach_port_t server,
37                                proc_name_t name,
38                                int32_t *procno,
39                                security_token_t *usertoken);
40
41extern kern_return_t
42libinfoDSmig_Query(mach_port_t server,
43                   int32_t proc,
44                   inline_data_t request,
45                   mach_msg_type_number_t requestCnt,
46                   inline_data_t reply,
47                   mach_msg_type_number_t *replyCnt,
48                   vm_offset_t *ooreply,
49                   mach_msg_type_number_t *ooreplyCnt,
50                   security_token_t *usertoken);
51
52typedef struct bootpent
53{
54    struct bootpent *bp_next;
55    char            *bp_name;       // will only be populated at head of list
56    char            *bp_bootfile;   // will only be populated at head of list
57    char            *bp_hw;
58    char            *bp_addr;
59    void            *reserved[4];
60} bootpent;
61
62
63enum {
64    kDSLUgetbootpbyhw = 0,
65    kDSLUgetbootpbyaddr,
66    kDSLUlastentry
67};
68
69static char *gProcNames[] =
70{
71    "getbootpbyhw",
72    "getbootpbyaddr",
73    NULL
74};
75
76static bootpent *
77dolookup(int32_t inProc, kvbuf_t *inRequest)
78{
79    static int32_t	    procs[kDSLUlastentry]   = { 0 };
80    static mach_port_t	    serverPort		    = MACH_PORT_NULL;
81    security_token_t	    userToken;
82    kern_return_t	    kr			    = KERN_SUCCESS;
83    int32_t		    retryCount		    = 0;
84    char		    reply[16384]	    = { 0, };
85    mach_msg_type_number_t  replyCnt		    = 0;
86    vm_offset_t		    ooreply		    = 0;
87    mach_msg_type_number_t  ooreplyCnt		    = 0;
88    bootpent		    *returnValue	    = NULL;
89
90    if (inProc > kDSLUlastentry) {
91	return NULL;
92    }
93
94    do {
95	// see if we have a port
96	if (serverPort == MACH_PORT_NULL) {
97	    kr = bootstrap_look_up(bootstrap_port, kDSStdMachDSLookupPortName, &serverPort);
98	    if (kr != KERN_SUCCESS) {
99		syslog(LOG_CRIT, "Cannot find bootstrap port for DirectoryService\n");
100		return NULL;
101	    }
102	}
103
104	if (serverPort != MACH_PORT_NULL && procs[inProc] == 0) {
105	    kr = libinfoDSmig_GetProcedureNumber(serverPort, gProcNames[inProc], &procs[inProc], &userToken);
106	    if (kr != KERN_SUCCESS) {
107		syslog(LOG_CRIT, "Cannot find procedure number for lookup %s\n", gProcNames[inProc]);
108		return NULL;
109	    }
110	}
111
112	if (procs[inProc] != 0) {
113	    kr = libinfoDSmig_Query(serverPort, procs[inProc], inRequest->databuf, inRequest->datalen, reply, &replyCnt, &ooreply, &ooreplyCnt, &userToken);
114	    if (KERN_SUCCESS == kr) {
115		uint32_t    length = (replyCnt ? replyCnt : ooreplyCnt);
116		char	    *buffer = (replyCnt ? reply : (char *)ooreply);
117		kvbuf_t *   kv;
118
119		kv = kvbuf_init(buffer, length);
120		if (ooreplyCnt != 0) {
121		    vm_deallocate(mach_task_self(), ooreply, ooreplyCnt);
122		}
123
124		uint32_t    dictCount	= kvbuf_reset(kv);
125
126		// time to parse this into a list of bootpent...
127		if (dictCount > 0) {
128		    uint32_t	count	    = 0;
129		    char	*key	    = NULL;
130		    char	*keys[]	    =	{
131						    "bp_name",
132						    "bp_bootfile",
133						    "bp_hw",
134						    "bp_addr"
135						};
136		    uint32_t	addrCnt	    = 0;
137		    uint32_t	hwCnt	    = 0;
138
139		    returnValue = (bootpent *) calloc(1, sizeof(bootpent));
140		    returnValue->reserved[0] = kv;
141
142		    kvbuf_next_dict(kv);
143
144		    // expand the results to a list
145		    while ((key = kvbuf_next_key(kv, &count)) != NULL) {
146			// bp_name
147			if (keys[0] != NULL && strcmp(key, keys[0]) == 0) {
148			    returnValue->bp_name = kvbuf_next_val(kv);
149			    keys[0] = NULL;
150			}
151			// bp_bootfile
152			else if (keys[1] != NULL && strcmp(key, keys[1]) == 0) {
153			    returnValue->bp_bootfile = kvbuf_next_val(kv);
154			    keys[1] = NULL;
155			}
156			// bp_hw
157			else if (keys[2] != NULL && strcmp(key, keys[2]) == 0) {
158			    uint32_t	ii	= 0;
159			    bootpent	*entry	= returnValue;
160
161			    do {
162				entry->bp_hw = kvbuf_next_val(kv);
163				if ((++ii) < count) {
164				    if (entry->bp_next == NULL) {
165					entry->bp_next = (bootpent *) calloc(1, sizeof(bootpent));
166				    }
167				    entry = entry->bp_next;
168				    continue;
169				}
170				break;
171			    } while (1);
172
173			    keys[2] = NULL;
174			    hwCnt = count;
175			}
176			// bp_addr
177			else if (keys[3] != NULL && strcmp(key, keys[3]) == 0) {
178			    uint32_t	ii	= 0;
179			    bootpent	*entry	= returnValue;
180
181			    do {
182				entry->bp_addr = kvbuf_next_val(kv);
183				if ((++ii) < count) {
184				    if (entry->bp_next == NULL) {
185					entry->bp_next = (bootpent *) calloc(1, sizeof(bootpent));
186				    }
187				    entry = entry->bp_next;
188				    continue;
189				}
190				break;
191			    } while (1);
192
193			    keys[3] = NULL;
194			    addrCnt = count;
195			}
196		    }
197
198		    // if our counts aren't the same we need to expand them out
199		    if (addrCnt != hwCnt && addrCnt > 0 && hwCnt > 0) {
200			// we also trim the list if there are more addr than hw
201			bootpent    *currEntry	= returnValue;
202			bootpent    *copyEntry	= returnValue;
203			int	    ii		= 0;
204			int	    expAddr	= (addrCnt < hwCnt);
205			int	    expCnt	= (expAddr ? addrCnt : hwCnt);
206
207			while (currEntry != NULL) {
208			    if (expAddr) {
209				currEntry->bp_addr = copyEntry->bp_addr;
210			    }
211			    else {
212				currEntry->bp_hw = copyEntry->bp_hw;
213			    }
214
215			    currEntry = currEntry->bp_next;
216
217			    if ((++ii) < expCnt) {
218				// move to the next one
219				copyEntry = copyEntry->bp_next;
220			    }
221			    else {
222				// reset to top of list to start copy again
223				copyEntry = returnValue;
224				ii = 0;
225			    }
226			}
227		    }
228		}
229		else {
230		    kvbuf_free(kv);
231		}
232	    }
233	    else if (MACH_SEND_INVALID_DEST == kr) {
234		mach_port_mod_refs(mach_task_self(),
235				   serverPort, MACH_PORT_RIGHT_SEND, -1);
236		bzero(procs, sizeof(procs));
237		serverPort = MACH_PORT_NULL;
238		retryCount++;
239	    }
240	    else {
241		return (NULL);
242	    }
243	}
244
245    } while (KERN_SUCCESS != kr && retryCount < 10);
246
247    return returnValue;
248}
249
250static bootpent *
251getbootpbyhw(const char *hw)
252{
253    kvbuf_t *request = kvbuf_new();
254
255    kvbuf_add_dict(request);
256
257    kvbuf_add_key(request, "hw");
258    kvbuf_add_val(request, hw);
259
260    bootpent *result = dolookup(kDSLUgetbootpbyhw, request);
261
262    kvbuf_free(request);
263
264    return result;
265}
266
267static bootpent *
268getbootpbyaddr(const char *addr)
269{
270    kvbuf_t *request = kvbuf_new();
271
272    kvbuf_add_dict(request);
273
274    kvbuf_add_key(request, "addr");
275    kvbuf_add_val(request, addr);
276
277    bootpent *result = dolookup(kDSLUgetbootpbyaddr, request);
278
279    kvbuf_free(request);
280
281    return result;
282}
283
284void
285freebootpent(bootpent *listhead)
286{
287    if (listhead == NULL) {
288	return;
289    }
290
291    if (listhead->reserved[0] == NULL) {
292	syslog(LOG_CRIT, "freebootpent called without the head of the entry list");
293	abort();
294    }
295
296    kvbuf_free(listhead->reserved[0]);
297
298    // free the chain first
299    bootpent *nextEntry = listhead->bp_next;
300    while (nextEntry != NULL) {
301	bootpent *temp = nextEntry->bp_next;
302	free(nextEntry);
303	nextEntry = temp;
304    }
305
306    free(listhead);
307}
308
309#define ETHER_ADDR_BUFLEN	sizeof("xx:xx:xx:xx:xx:xx")
310#define EI(i)   (u_char)(e->ether_addr_octet[(i)])
311
312static char *
313my_ether_ntoa(const struct ether_addr *e,
314	      char *buf, int bufLen)
315{
316    if (bufLen < ETHER_ADDR_BUFLEN) {
317	return NULL;
318    }
319
320    buf[0] = 0;
321    sprintf(buf, "%02x:%02x:%02x:%02x:%02x:%02x",
322            EI(0), EI(1), EI(2), EI(3), EI(4), EI(5));
323    return (buf);
324}
325
326#ifdef	NOT_NEEDED
327static struct ether_addr *
328my_ether_aton(const char *a, struct ether_addr *ea, int eaLen)
329{
330    register int i;
331
332    if (eaLen < ETHER_ADDR_LEN) {
333	return NULL;
334    }
335
336    i = sscanf(a, " %x:%x:%x:%x:%x:%x",
337	       &ea[0], &ea[1], &ea[2], &ea[3], &ea[4], &ea[5]);
338    if (i != 6) {
339	return NULL;
340    }
341
342    return ea;
343}
344#endif	// 0
345
346boolean_t
347bootp_getbyhw_ds(uint8_t hwtype, void * hwaddr, int hwlen,
348		 subnet_match_func_t * func, void * arg,
349		 struct in_addr * iaddr_p,
350		 char * * hostname_p, char * * bootfile_p)
351{
352    bootpent		*be;
353    bootpent		*bp;
354    char		buf[ETHER_ADDR_BUFLEN];
355    struct in_addr	ia;
356
357    (void) my_ether_ntoa(hwaddr, buf, sizeof(buf));
358    be = getbootpbyhw(buf);
359    if (be == NULL)
360	return FALSE;
361
362    for (bp = be; bp != NULL; bp = bp->bp_next) {
363	if ((bp->bp_hw != NULL) && (strcmp(buf, bp->bp_hw) == 0)) {
364	    if ((bp->bp_addr == NULL) || (inet_aton(bp->bp_addr, &ia) == 0)
365		|| ia.s_addr == 0) {
366		/* don't return 0.0.0.0 */
367		continue;
368	    }
369
370	    if ((func == NULL) || (*func)(arg, ia)) {
371		goto done;
372	    }
373	}
374    }
375
376    freebootpent(be);
377    return FALSE;
378
379 done :
380
381    if (hostname_p != NULL)
382	*hostname_p = (be->bp_name != NULL) ? strdup(be->bp_name) : NULL;
383    if (bootfile_p != NULL)
384	*bootfile_p = (be->bp_bootfile != NULL) ? strdup(be->bp_bootfile) : NULL;
385    *iaddr_p = ia;
386
387    freebootpent(be);
388    return TRUE;
389}
390
391#define INET_ADDR_BUFLEN	sizeof("255.255.255.255")
392
393boolean_t
394bootp_getbyip_ds(struct in_addr ciaddr, char * * hostname_p,
395		 char * * bootfile_p)
396{
397    bootpent	*be;
398    bootpent	*bp;
399    char	buf[INET_ADDR_BUFLEN];
400
401    (void)inet_ntop(AF_INET, &ciaddr, buf, sizeof(buf));
402    be = getbootpbyaddr(buf);
403    if (be == NULL)
404	return FALSE;
405
406    for (bp = be; bp != NULL; bp = bp->bp_next) {
407	if ((bp->bp_addr != NULL) && (strcmp(buf, bp->bp_addr) == 0)) {
408	    goto done;
409	}
410    }
411
412    freebootpent(be);
413    return FALSE;
414
415 done :
416
417    if (hostname_p != NULL)
418	*hostname_p = (be->bp_name != NULL) ? strdup(be->bp_name) : NULL;
419    if (bootfile_p != NULL)
420	*bootfile_p = (be->bp_bootfile != NULL) ? strdup(be->bp_bootfile) : NULL;
421
422    freebootpent(be);
423    return TRUE;
424}
425
426#ifdef	TEST_BOOTPLOOKUP
427
428#include <sys/time.h>
429#include "util.h"
430
431void
432dumpEntry(bootpent *entry)
433{
434    bootpent *entryTemp;
435
436    printf("  bp_name     = %s\n", entry->bp_name);
437    printf("  bp_bootfile = %s\n", entry->bp_bootfile);
438
439    for(entryTemp = entry; entryTemp != NULL; entryTemp = entryTemp->bp_next) {
440	printf("\n");
441	printf("  bp_hw   = %s\n", entryTemp->bp_hw);
442	printf("  bp_addr = %s\n", entryTemp->bp_addr);
443    }
444
445    printf("\n");
446}
447
448#define HTYPE_ETHER	1
449
450typedef enum {
451    isIP,
452    isMAC,
453    isUnknown
454} queryType_t;
455
456int
457main(int argc, const char * argv[])
458{
459    char		buf[256];
460    FILE		*f;
461    char		*line;
462    int			n		= 0;
463    int			n_stalls	= 0;
464    boolean_t		showRaw		= FALSE;
465    struct timeval	tv_elapsed	= { 0, 0 };
466    struct timeval	tv_max		= { 0, 0 };
467    struct timeval	tv_min		= { 99999, 0 };
468
469    if ((argc > 1) && (strcmp(argv[1], "RAW") == 0)) {
470	showRaw = TRUE;
471	argv++;
472	argc--;
473    }
474
475    if (argc < 2) {
476	f = stdin;
477    }
478    else {
479	f = fopen(argv[1], "r");
480	if (f == NULL) {
481	    fprintf(stderr, "%s: could not open %s\n", argv[0], argv[1]);
482	    exit(1);
483	}
484    }
485    if (isatty(0)) {
486	fprintf(stdout, "Enter a MAC address or IP Address"
487		" (e.g. 00:0d:93:9d:e7:d7 or 17.203.16.241)\n");
488    }
489    while ((line = fgets(buf, sizeof(buf), f)) != NULL) {
490	bootpent		*entry;
491	boolean_t		found	= FALSE;
492	struct ether_addr	*hw_address;
493	char			*hn	= NULL;
494	struct in_addr		ip_address;
495	size_t			len;
496	queryType_t		qt;
497	char			*query;
498	struct timeval		tv_end;
499	struct timeval		tv_query;
500	struct timeval		tv_start;
501
502	len = strlen(line) - 1;
503	while ((len >= 0) && (line[len] == '\n')) {
504		line[len--] = 0;
505	}
506
507	if (inet_aton(line, &ip_address) == 1) {
508	    // process IP address
509	    qt = isIP;
510	    query = inet_ntoa(ip_address);
511	    if (showRaw) {
512		gettimeofday(&tv_start, 0);
513		entry = getbootpbyaddr(query);
514	    }
515	    else {
516		gettimeofday(&tv_start, 0);
517		found = bootp_getbyip_ds(ip_address,
518					 &hn,
519					 NULL);
520	    }
521	}
522	else if ((hw_address = ether_aton(line)) != NULL) {
523	    char	buf[ETHER_ADDR_BUFLEN];
524
525	    // process MAC address
526	    qt = isMAC;
527	    query = my_ether_ntoa(hw_address, buf, sizeof(buf));
528	    if (showRaw) {
529		gettimeofday(&tv_start, 0);
530		entry = getbootpbyhw(query);
531	    }
532	    else {
533		gettimeofday(&tv_start, 0);
534		found = bootp_getbyhw_ds(HTYPE_ETHER, hw_address, sizeof(*hw_address),
535					 NULL, NULL,
536					 &ip_address,
537					 &hn,
538					 NULL);
539	    }
540	}
541	else {
542	    // 'tis neither an IP or MAC address
543	    fprintf(stdout, "Invalid entry\n");
544	    continue;
545	}
546
547	gettimeofday(&tv_end, 0);
548	timeval_subtract(tv_end, tv_start, &tv_query);
549	if (tv_query.tv_sec == 0) {
550	    timeval_add(tv_elapsed, tv_query, &tv_elapsed);
551	    n++;
552
553	    if (timeval_compare(tv_query, tv_min) < 0) {
554		tv_min = tv_query;
555	    }
556
557	    if (timeval_compare(tv_query, tv_max) > 0) {
558		tv_max = tv_query;
559	    }
560	}
561	else {
562	    n_stalls++;
563	}
564
565	if (qt == isIP) {
566	    if (showRaw && (entry != NULL)) {
567		printf("IP: %-*s                        host: %-20s",
568		       (int)INET_ADDR_BUFLEN - 1, query,
569		       entry->bp_name);
570		dumpEntry(entry);
571		freebootpent(entry);
572	    }
573	    else if (found) {
574		printf("IP: %-*s                        host: %-20s",
575		       (int)INET_ADDR_BUFLEN - 1, query,
576		       hn);
577	    }
578	    else {
579		printf("IP: %-*s                        host: %-20s",
580		       (int)INET_ADDR_BUFLEN - 1, query,
581		       "NOT FOUND");
582	    }
583	}
584	else if (qt == isMAC) {
585	    if (showRaw && (entry != NULL)) {
586		printf("IP: %-*s MAC: %s host: %-20s",
587		       (int)INET_ADDR_BUFLEN - 1, "",
588		       query,
589		       entry->bp_name);
590		dumpEntry(entry);
591		freebootpent(entry);
592	    }
593	    else if (found) {
594		printf("IP: %-*s MAC: %s host: %-20s",
595		       (int)INET_ADDR_BUFLEN - 1, inet_ntoa(ip_address),
596		       query,
597		       hn);
598	    }
599	    else {
600		printf("IP: %-*s MAC: %s host: %-20s",
601		       (int)INET_ADDR_BUFLEN - 1, "",
602		       query,
603		       "NOT FOUND");
604	    }
605	}
606
607	printf(" (%d.%06d%s)\n",
608	       (int)tv_query.tv_sec,
609	       (int)tv_query.tv_usec,
610	       (tv_query.tv_sec > 0) ? ", stalled?" : "");
611
612	if (hn != NULL) free(hn);
613    }
614
615    if (n > 0) {
616	time_t	t;
617
618	t = tv_elapsed.tv_sec * USECS_PER_SEC + tv_elapsed.tv_usec;
619	t = t / n;
620
621	printf("processed %d queries (min %d.%06d, avg %d.%06d, max %d.%06d)\n",
622		       n,
623		       (int)tv_min.tv_sec,
624		       (int)tv_min.tv_usec,
625		       (int)(t / USECS_PER_SEC),
626		       (int)(t % USECS_PER_SEC),
627		       (int)tv_max.tv_sec,
628		       (int)tv_max.tv_usec);
629    }
630
631    if (n_stalls > 0) {
632	printf("%4d %s took longer than 1 second (and %s\n",
633		   n_stalls,
634		   (n_stalls == 1) ? "query" : "queries",
635		   (n_stalls == 1) ? "was" : "were");
636	printf("     not included in the per-query statistics)\n");
637    }
638
639    return 0;
640}
641
642#endif	// TEST_BOOTPLOOKUP
643