1/*
2 * Copyright (c) 2002-2012 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 * BSDPClient.c
26 * - BSDP client library functions
27 */
28
29/*
30 * Modification History
31 *
32 * February 25, 2002	Dieter Siegmund (dieter@apple.com)
33 * - initial revision
34 */
35
36#include <stdlib.h>
37#include <unistd.h>
38#include <string.h>
39#include <stdio.h>
40#include <stdarg.h>
41#include <sys/types.h>
42#include <sys/errno.h>
43#include <sys/socket.h>
44#include <sys/ioctl.h>
45#include <sys/fcntl.h>
46#include <ctype.h>
47#include <net/if.h>
48#include <net/ethernet.h>
49#include <netinet/in.h>
50#include <netinet/udp.h>
51#include <netinet/in_systm.h>
52#include <netinet/ip.h>
53#include <netinet/bootp.h>
54#include <arpa/inet.h>
55
56#include <CoreFoundation/CFDictionary.h>
57#include <CoreFoundation/CFString.h>
58#include <CoreFoundation/CFNumber.h>
59#include <CoreFoundation/CFRunLoop.h>
60#include <CoreFoundation/CFSocket.h>
61#include <SystemConfiguration/SystemConfiguration.h>
62#include <SystemConfiguration/SCValidation.h>
63#include <SystemConfiguration/SCPrivate.h>
64
65#include "ioregpath.h"
66#include "BSDPClient.h"
67#include "BSDPClientPrivate.h"
68#include "rfc_options.h"
69#include "dhcp_options.h"
70#include "bsdp.h"
71#include "util.h"
72#include "cfutil.h"
73#include "dhcplib.h"
74#include "interfaces.h"
75#include "bootp_transmit.h"
76
77#define BSDPCLIENT_MAX_WAIT_SECS		16
78#define BSDPCLIENT_LIST_MAX_TRIES		7
79#define BSDPCLIENT_SELECT_MAX_TRIES		2
80#define BSDPCLIENT_INITIAL_TIMEOUT_SECS		2
81
82#ifdef TEST_BAD_SYSID
83const uint8_t	bad_sysid1[] = {
84    'A', 'A', 'P', 'L', 'B', 'S', 'D', 'P', 'C',
85    '/', 'p', 'p', 'c',
86    '/', '\n', 'b', 'a', 'd', '1'
87};
88const uint8_t	bad_sysid2[] = {
89    'A', 'A', 'P', 'L', 'B', 'S', 'D', 'P', 'C',
90    '/', 'p', 'p', 'c', 'n',
91    '/', '\0', 'b', 'a', 'd', '2'
92};
93const uint8_t	bad_sysid3[] = {
94    'A', 'A', 'P', 'L', 'B', 'S', 'D', 'P', 'C',
95    '/', 'p', '\0', 'c', 'a', 'b',
96    '/', 'b', 'a', 'd', '3'
97};
98const uint8_t	bad_sysid4[] = {
99    'A', 'A', 'P', 'L', 'B', 'S', 'D', 'P', 'C',
100    '/', 'p', '\n', 'c',
101    '/', 'b', 'a', 'd', 's', 'y', 's', '4'
102};
103
104#define BAD_SYSID_COUNT		4
105struct {
106    const uint8_t *	sysid;
107    int			size;
108} bad_sysids[BAD_SYSID_COUNT] = {
109    { bad_sysid1, sizeof(bad_sysid1) },
110    { bad_sysid2, sizeof(bad_sysid2) },
111    { bad_sysid3, sizeof(bad_sysid3) },
112    { bad_sysid4, sizeof(bad_sysid4) },
113};
114#endif /* TEST_BAD_SYSID */
115static const unsigned char	rfc_magic[4] = RFC_OPTIONS_MAGIC;
116
117static const u_char dhcp_params[] = {
118    dhcptag_vendor_class_identifier_e,
119    dhcptag_vendor_specific_e,
120};
121#define N_DHCP_PARAMS	(sizeof(dhcp_params) / sizeof(dhcp_params[0]))
122
123#define NetBoot2InfoVersion	0x33000
124typedef enum {
125    kNetBootVersionNone = 0,
126    kNetBootVersion1 = 1,
127    kNetBootVersion2 = 2
128} NetBootVersion;
129
130typedef enum {
131    kBSDPClientStateInit = 0,
132    kBSDPClientStateList = 1,
133    kBSDPClientStateSelect = 2,
134} BSDPClientState;
135
136typedef void (*BSDPClientTimerCallBack)(BSDPClientRef client);
137
138typedef union {
139    BSDPClientListCallBack	list;
140    BSDPClientSelectCallBack	select;
141} BSDPClientCallBackUnion;
142
143#define MAX_ATTRS	10
144
145struct BSDPClient_s {
146    char *				system_id;
147    bsdp_version_t			client_version; /* network order */
148    boolean_t				old_firmware;
149    int					fd;
150    u_short				client_port;
151    CFSocketRef				socket;
152    CFRunLoopSourceRef			rls;
153    interface_t *			if_p;
154    u_int32_t				xid;
155    /*
156     * send_buf is cast to some struct types containing short fields;
157     * force it to be aligned as much as an int
158     */
159    int					send_buf[512];
160    BSDPClientState			state;
161    CFRunLoopTimerRef			timer;
162    BSDPClientTimerCallBack		timer_callback;
163    int					try;
164    int					wait_secs;
165    u_int16_t				attrs[MAX_ATTRS];
166    int					n_attrs;
167    struct in_addr			our_ip;
168    boolean_t				got_responses;
169
170    /* values provided by caller */
171    struct {
172	BSDPClientCallBackUnion		func;
173	void *				arg;
174	struct in_addr			server_ip;
175	bsdp_image_id_t			image_identifier;
176    } callback;
177
178};
179
180/*
181 * Function: mySCNetworkServicePathCopyServiceID
182 * Purpose:
183 *    Take a path of the form:
184 *	"<domain>:/Network/Service/<serviceID>[/<entity>]";
185 *    and return the <serviceID> portion of the string.
186 */
187static CFStringRef
188mySCNetworkServicePathCopyServiceID(CFStringRef path)
189{
190    CFArrayRef		arr;
191    CFStringRef		serviceID = NULL;
192
193    arr = CFStringCreateArrayBySeparatingStrings(NULL, path, CFSTR("/"));
194    if (arr == NULL) {
195	goto done;
196    }
197    /*
198     * arr = {"<domain:>","Network","Service","<serviceID>"[,"<entity>"]}
199     * and we want the 4th component (arr[3]).
200     */
201    if (CFArrayGetCount(arr) < 4) {
202	goto done;
203    }
204    serviceID = CFRetain(CFArrayGetValueAtIndex(arr, 3));
205 done:
206    if (arr != NULL) {
207	CFRelease(arr);
208    }
209    return (serviceID);
210
211}
212
213static Boolean
214get_dhcp_address(const char * ifname, struct in_addr * ret_ip)
215{
216    CFStringRef		ifname_cf = NULL;
217    int			count;
218    CFDictionaryRef	info_dict = NULL;
219    int			i;
220    const void * *	keys = NULL;
221    CFStringRef		pattern;
222    CFMutableArrayRef	patterns;
223    Boolean		ret = FALSE;
224    SCDynamicStoreRef	store;
225    const void * *	values = NULL;
226
227    store = SCDynamicStoreCreate(NULL, CFSTR("get_dhcp_address"),
228				 NULL, NULL);
229    if (store == NULL) {
230	goto done;
231    }
232    ifname_cf = CFStringCreateWithCString(NULL, ifname, kCFStringEncodingASCII);
233
234    /* pattern State:/Network/Service/[^/]+/DHCP */
235    pattern
236	= SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
237						      kSCDynamicStoreDomainState,
238						      kSCCompAnyRegex,
239						      kSCEntNetDHCP);
240    patterns = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
241    CFArrayAppendValue(patterns, pattern);
242    CFRelease(pattern);
243    pattern
244	= SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
245						      kSCDynamicStoreDomainState,
246						      kSCCompAnyRegex,
247						      kSCEntNetIPv4);
248    CFArrayAppendValue(patterns, pattern);
249    CFRelease(pattern);
250
251    info_dict = SCDynamicStoreCopyMultiple(store, NULL, patterns);
252    CFRelease(patterns);
253    if (isA_CFDictionary(info_dict) == NULL) {
254	goto done;
255    }
256    count = CFDictionaryGetCount(info_dict);
257    values = malloc(sizeof(void *) * count);
258    keys = malloc(sizeof(void *) * count);
259    CFDictionaryGetKeysAndValues(info_dict, keys, values);
260    for (i = 0; i < count; i++) {
261	CFArrayRef	addrs;
262	CFDictionaryRef	ipv4_dict;
263	Boolean		got_match;
264	CFStringRef	key;
265	CFStringRef	name;
266	CFStringRef	serviceID;
267
268	if (CFStringHasSuffix(keys[i], kSCEntNetIPv4) == FALSE) {
269	    continue;
270	}
271	ipv4_dict = isA_CFDictionary(values[i]);
272	if (ipv4_dict == NULL) {
273	    continue;
274	}
275	name = CFDictionaryGetValue(ipv4_dict, kSCPropInterfaceName);
276	if (name == NULL) {
277	    continue;
278	}
279	if (CFEqual(name, ifname_cf) == FALSE) {
280	    continue;
281	}
282	/* look for the DHCP entity for this service ID */
283	serviceID = mySCNetworkServicePathCopyServiceID(keys[i]);
284	if (serviceID == NULL) {
285	    continue;
286	}
287	key
288	    = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
289							  kSCDynamicStoreDomainState,
290							  serviceID,
291							  kSCEntNetDHCP);
292	CFRelease(serviceID);
293	got_match = CFDictionaryContainsKey(info_dict, key);
294	CFRelease(key);
295	if (got_match == FALSE) {
296	    continue;
297	}
298	/* grab the IP address for this IPv4 dict */
299	addrs = CFDictionaryGetValue(ipv4_dict, kSCPropNetIPv4Addresses);
300	if (isA_CFArray(addrs) != NULL && CFArrayGetCount(addrs) > 0
301	    && my_CFStringToIPAddress(CFArrayGetValueAtIndex(addrs, 0),
302				      ret_ip)) {
303	    ret = TRUE;
304	}
305	break;
306    }
307
308 done:
309    if (ifname_cf != NULL) {
310	CFRelease(ifname_cf);
311    }
312    if (values != NULL) {
313	free(values);
314    }
315    if (keys != NULL) {
316	free(keys);
317    }
318    if (info_dict != NULL) {
319	CFRelease(info_dict);
320    }
321    if (store != NULL) {
322	CFRelease(store);
323    }
324    return (ret);
325}
326
327static void
328BSDPClientProcessList(BSDPClientRef client, struct in_addr server_ip,
329		      struct dhcp * reply, int reply_len,
330		      dhcpol_t * options_p, dhcpol_t * bsdp_options_p);
331static void
332BSDPClientProcessSelect(BSDPClientRef client, bsdp_msgtype_t bsdp_msg);
333
334void
335my_log(int priority, const char *message, ...)
336{
337    va_list 		ap;
338
339    va_start(ap, message);
340    vfprintf(stderr, message, ap);
341    return;
342}
343
344static char * SystemIdentifierCopy(void);
345
346#if defined(__ppc__) || defined(__ppc64__)
347#define BSDP_ARCHITECTURE	"ppc"
348static NetBootVersion
349NetBootVersionGet()
350{
351    CFDictionaryRef		properties = NULL;
352    CFDataRef			info = NULL;
353    u_int32_t			version;
354    NetBootVersion		support = kNetBootVersionNone;
355
356    properties = myIORegistryEntryCopyValue("IODeviceTree:/rom/boot-rom");
357    if (properties != NULL) {
358	info = CFDictionaryGetValue(properties, CFSTR("info"));
359    }
360    if (info == NULL) {
361	goto done;
362    }
363    CFDataGetBytes(info, CFRangeMake(8, sizeof(version)), (void *)&version);
364    if (ntohl(version) < NetBoot2InfoVersion) {
365	support = kNetBootVersion1;
366    }
367    else {
368	support = kNetBootVersion2;
369    }
370 done:
371    my_CFRelease(&properties);
372    return (support);
373}
374
375static BSDPClientStatus
376CopyNetBootVersionAndSystemIdentifier(NetBootVersion * version_p,
377				      char * * system_id_p)
378{
379    *system_id_p = NULL;
380    *version_p = NetBootVersionGet();
381    if (*version_p == kNetBootVersionNone) {
382	return (kBSDPClientStatusUnsupportedFirmware);
383    }
384    *system_id_p = SystemIdentifierCopy();
385    if (*system_id_p == NULL) {
386	return (kBSDPClientStatusAllocationError);
387    }
388    return (kBSDPClientStatusOK);
389}
390
391#elif defined(__i386__) || defined(__x86_64__)
392#define BSDP_ARCHITECTURE	"i386"
393
394static BSDPClientStatus
395CopyNetBootVersionAndSystemIdentifier(NetBootVersion * version_p,
396				      char * * system_id_p)
397{
398    *system_id_p = SystemIdentifierCopy();
399    if (*system_id_p == NULL) {
400	return (kBSDPClientStatusAllocationError);
401    }
402    *version_p = kNetBootVersion2;
403    return (kBSDPClientStatusOK);
404}
405
406#else
407
408static BSDPClientStatus
409CopyNetBootVersionAndSystemIdentifier(NetBootVersion * version_p,
410				      char * * system_id_p)
411{
412    return (kBSDPClientStatusUnsupportedFirmware);
413}
414
415#endif
416
417static char *
418SystemIdentifierCopy()
419{
420    CFDictionaryRef		properties = NULL;
421    char *			system_id = NULL;
422    CFDataRef			system_id_data = NULL;
423    int				system_id_len = 0;
424
425    properties = myIORegistryEntryCopyValue("IODeviceTree:/");
426    if (properties != NULL) {
427	system_id_data = CFDictionaryGetValue(properties, CFSTR("model"));
428	if (system_id_data) {
429	    system_id_len = CFDataGetLength(system_id_data);
430	}
431    }
432    if (system_id_len == 0) {
433	goto done;
434    }
435    system_id = (char *)malloc(system_id_len + 1);
436    if (system_id == NULL) {
437	goto done;
438    }
439    CFDataGetBytes(system_id_data, CFRangeMake(0, system_id_len),
440		   (UInt8 *)system_id);
441    system_id[system_id_len] = '\0';
442    my_CFRelease(&properties);
443    return (system_id);
444
445 done:
446    my_CFRelease(&properties);
447    return (NULL);
448}
449
450#define RX_BUF_SIZE		(8 * 1024)
451
452static struct dhcp *
453make_bsdp_request(char * system_id, struct dhcp * request, int pkt_size,
454		  dhcp_msgtype_t msg, u_char * hwaddr, u_char hwtype,
455		  u_char hwlen, dhcpoa_t * options_p)
456{
457    char	vendor_class_id[DHCP_OPTION_SIZE_MAX];
458    uint16_t	max_dhcp_message_size = htons(ETHERMTU);
459
460    bzero(request, pkt_size);
461    request->dp_op = BOOTREQUEST;
462    request->dp_htype = hwtype;
463    request->dp_hlen = hwlen;
464    bcopy(hwaddr, request->dp_chaddr, hwlen);
465    bcopy(rfc_magic, request->dp_options, sizeof(rfc_magic));
466    dhcpoa_init(options_p, request->dp_options + sizeof(rfc_magic),
467		pkt_size - sizeof(struct dhcp) - sizeof(rfc_magic));
468
469    /* make the request a dhcp message */
470    if (dhcpoa_add_dhcpmsg(options_p, msg) != dhcpoa_success_e) {
471	fprintf(stderr,
472	       "make_bsdp_request: couldn't add dhcp message tag %d, %s", msg,
473	       dhcpoa_err(options_p));
474	goto err;
475    }
476
477    /* add the list of required parameters */
478    if (dhcpoa_add(options_p, dhcptag_parameter_request_list_e,
479		   N_DHCP_PARAMS, dhcp_params)
480	!= dhcpoa_success_e) {
481	fprintf(stderr, "make_bsdp_request: "
482	       "couldn't add parameter request list, %s",
483	       dhcpoa_err(options_p));
484	goto err;
485    }
486    /* add the max message size */
487    if (dhcpoa_add(options_p, dhcptag_max_dhcp_message_size_e,
488		   sizeof(max_dhcp_message_size), &max_dhcp_message_size)
489	!= dhcpoa_success_e) {
490	fprintf(stderr, "make_bsdp_request: "
491	       "couldn't add max message size, %s",
492	       dhcpoa_err(options_p));
493	goto err;
494    }
495#ifndef TEST_BAD_SYSID
496    /* add our vendor class identifier */
497    snprintf(vendor_class_id, sizeof(vendor_class_id),
498	     BSDP_VENDOR_CLASS_ID "/" BSDP_ARCHITECTURE "/%s", system_id);
499    if (dhcpoa_add(options_p,
500		   dhcptag_vendor_class_identifier_e,
501		   strlen(vendor_class_id), vendor_class_id)
502	!= dhcpoa_success_e) {
503	fprintf(stderr, "make_bsdp_request: add class id failed, %s",
504		dhcpoa_err(options_p));
505	goto err;
506    }
507#else /* TEST_BAD_SYSID */
508    {
509	static int	bad_sysid_index;
510
511	if (dhcpoa_add(options_p,
512		       dhcptag_vendor_class_identifier_e,
513		       bad_sysids[bad_sysid_index].size,
514		       bad_sysids[bad_sysid_index].sysid)
515	    != dhcpoa_success_e) {
516	    fprintf(stderr, "make_bsdp_request: add class id failed, %s",
517		    dhcpoa_err(options_p));
518	    goto err;
519	}
520	bad_sysid_index++;
521	if (bad_sysid_index == BAD_SYSID_COUNT) {
522	    bad_sysid_index = 0;
523	}
524    }
525#endif /* TEST_BAD_SYSID */
526    return (request);
527
528  err:
529    return (NULL);
530}
531
532static int
533S_open_socket(u_short * ret_port)
534{
535    u_short			client_port;
536    struct sockaddr_in 		me;
537    socklen_t		   	me_len;
538    int 			opt;
539    int				sockfd;
540    int 			status;
541
542    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
543    if (sockfd < 0) {
544	perror("socket");
545	goto failed;
546    }
547
548    bzero((char *)&me, sizeof(me));
549    me.sin_family = AF_INET;
550
551    /* get a privileged port */
552    opt = IP_PORTRANGE_LOW;
553    status = setsockopt(sockfd, IPPROTO_IP, IP_PORTRANGE, &opt,
554			sizeof(opt));
555
556    if (status < 0) {
557	perror("setsockopt IPPROTO_IP IP_PORTRANGE");
558	goto failed;
559    }
560
561#ifdef SO_TC_CTL
562    opt = SO_TC_CTL;
563    /* set traffic class, we don't care if it failed. */
564    (void)setsockopt(sockfd, SOL_SOCKET, SO_TRAFFIC_CLASS, &opt,
565		     sizeof(opt));
566#endif /* SO_TC_CTL */
567
568    status = bind(sockfd, (struct sockaddr *)&me, sizeof(me));
569    if (status != 0) {
570	perror("bind");
571	goto failed;
572    }
573    me_len = sizeof(me);
574    if (getsockname(sockfd, (struct sockaddr *)&me,  &me_len) < 0) {
575	perror("getsockname");
576	goto failed;
577    }
578    client_port = ntohs(me.sin_port);
579    opt = 1;
580    status = setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &opt,
581			sizeof(opt));
582    if (status < 0) {
583	perror("setsockopt SO_BROADCAST");
584	goto failed;
585    }
586    opt = 1;
587    status = ioctl(sockfd, FIONBIO, &opt);
588    if (status < 0) {
589	perror("FIONBIO");
590	goto failed;
591    }
592    *ret_port = client_port;
593    return sockfd;
594
595 failed:
596    if (sockfd >= 0) {
597	close(sockfd);
598    }
599    return (-1);
600}
601
602/* deprecated: BSDPImageDescriptionIndexIsServerLocal */
603Boolean
604BSDPImageDescriptionIndexIsServerLocal(CFNumberRef index)
605{
606    u_int16_t	index_val = 1;
607
608    (void)CFNumberGetValue(index, kCFNumberShortType, &index_val);
609    return (bsdp_image_index_is_server_local(index_val));
610}
611
612Boolean
613BSDPImageDescriptionIdentifierIsServerLocal(CFNumberRef identifier)
614{
615    u_int32_t	identifier_val = 1;
616
617    (void)CFNumberGetValue(identifier, kCFNumberSInt32Type, &identifier_val);
618    return (bsdp_image_identifier_is_server_local(identifier_val));
619}
620
621Boolean
622BSDPImageDescriptionIdentifierIsInstall(CFNumberRef identifier)
623{
624    u_int32_t	identifier_val = 1;
625
626    (void)CFNumberGetValue(identifier, kCFNumberSInt32Type, &identifier_val);
627    return (bsdp_image_identifier_is_install(identifier_val));
628}
629
630BSDPImageKind
631BSDPImageDescriptionIdentifierImageKind(CFNumberRef identifier)
632{
633    u_int32_t	identifier_val = 1;
634
635    (void)CFNumberGetValue(identifier, kCFNumberSInt32Type, &identifier_val);
636    return (bsdp_image_kind_from_attributes(bsdp_image_attributes(identifier_val)));
637}
638
639/**
640 ** BSDPClient timer functions
641 **/
642
643static void
644BSDPClientProcessTimer(CFRunLoopTimerRef timer, void * info)
645{
646    BSDPClientRef	client;
647
648    client = (BSDPClientRef)info;
649    (*client->timer_callback)(client);
650    return;
651}
652
653static void
654BSDPClientCancelTimer(BSDPClientRef client)
655{
656    if (client->timer) {
657	CFRunLoopTimerInvalidate(client->timer);
658	my_CFRelease(&client->timer);
659    }
660    client->timer_callback = NULL;
661    return;
662}
663
664static void
665BSDPClientSetTimer(BSDPClientRef client, struct timeval rel_time,
666		   BSDPClientTimerCallBack callback)
667{
668    CFRunLoopTimerContext 	context =  { 0, NULL, NULL, NULL, NULL };
669    CFAbsoluteTime 		wakeup_time;
670
671    BSDPClientCancelTimer(client);
672    client->timer_callback = callback;
673    wakeup_time = CFAbsoluteTimeGetCurrent() + rel_time.tv_sec
674	  + ((double)rel_time.tv_usec / USECS_PER_SEC);
675    context.info = client;
676    client->timer
677	= CFRunLoopTimerCreate(NULL, wakeup_time,
678			       0.0, 0, 0,
679			       BSDPClientProcessTimer,
680			       &context);
681    CFRunLoopAddTimer(CFRunLoopGetCurrent(), client->timer,
682		      kCFRunLoopDefaultMode);
683    return;
684}
685
686/*
687 * Function: BSDPClientProcess
688 * Purpose:
689 *   Process a packet received on our open socket.
690 *   Ensure that the packet is a BSDP packet[DHCP ACK] that
691 *   matches our currently outstanding request.
692 *
693 *   Dispatch to the appropriate handler (list or select)
694 *   depending on our current running state.
695 */
696static void
697BSDPClientProcess(CFSocketRef s, CFSocketCallBackType type,
698		  CFDataRef address, const void *data, void *info)
699{
700    dhcpol_t			bsdp_options;
701    bsdp_msgtype_t		bsdp_msg;
702    BSDPClientRef 		client = (BSDPClientRef)info;
703    dhcpo_err_str_t		err;
704    struct sockaddr_in 		from;
705    socklen_t 			fromlen;
706    int 			n;
707    void *			opt;
708    int				opt_len;
709    dhcpol_t			options;
710    uint32_t			receive_buf[RX_BUF_SIZE / sizeof(uint32_t)];
711    struct dhcp *		reply;
712    struct in_addr		server_ip;
713
714    n = recvfrom(client->fd, receive_buf,
715		 sizeof(receive_buf), 0,
716		 (struct sockaddr *)&from, &fromlen);
717    if (n < 0) {
718	if (errno != EAGAIN) {
719	    fprintf(stderr, "BSDPClientProcess: recvfrom %s",
720		    strerror(errno));
721	}
722	return;
723    }
724    if (n < sizeof(struct dhcp)) {
725	/* packet is too short */
726	return;
727    }
728
729    switch (client->state) {
730    case kBSDPClientStateInit:
731    default:
732	/* throw it away */
733	return;
734    case kBSDPClientStateList:
735    case kBSDPClientStateSelect:
736	break;
737    }
738
739    reply = (struct dhcp *)receive_buf;
740    if (dhcp_packet_match((struct bootp *)receive_buf, client->xid,
741			  (u_char) if_link_arptype(client->if_p),
742			  if_link_address(client->if_p),
743			  if_link_length(client->if_p)) == FALSE
744	|| reply->dp_ciaddr.s_addr != client->our_ip.s_addr) {
745	/* wasn't us */
746	return;
747    }
748
749    dhcpol_init(&options);
750    dhcpol_init(&bsdp_options);
751
752    if (dhcpol_parse_packet(&options, reply, n, &err) == FALSE) {
753	fprintf(stderr,
754		"BSDPClientProcess: dhcpol_parse_packet failed, %s\n",
755		err.str);
756	goto done;
757    }
758
759    /* get the DHCP message type */
760    opt = dhcpol_find(&options, dhcptag_dhcp_message_type_e, NULL, NULL);
761    if (opt == NULL || *((unsigned char *)opt) != dhcp_msgtype_ack_e) {
762	goto done; /* response must be a DHCP ack */
763    }
764
765    /* get the vendor class identifier */
766    opt = dhcpol_find(&options, dhcptag_vendor_class_identifier_e,
767		      &opt_len, NULL);
768    if (opt == NULL
769	|| opt_len != strlen(BSDP_VENDOR_CLASS_ID)
770	|| bcmp(opt, BSDP_VENDOR_CLASS_ID, opt_len)) {
771	goto done; /* not BSDP */
772    }
773    /* get the server identifier */
774    opt = dhcpol_find(&options, dhcptag_server_identifier_e,
775		      &opt_len, NULL);
776    if (opt == NULL || opt_len != sizeof(server_ip)) {
777	goto done;
778    }
779    server_ip = *((struct in_addr *)opt);
780
781    /* decode the BSDP options */
782    if (dhcpol_parse_vendor(&bsdp_options, &options, &err) == FALSE) {
783	fprintf(stderr,
784		"BSDPClientProcess: dhcpol_parse_vendor failed, %s", err.str);
785	goto done;
786    }
787    /* get the BSDP message type */
788    opt = dhcpol_find(&bsdp_options, bsdptag_message_type_e,
789		      &opt_len, NULL);
790    if (opt == NULL || opt_len != 1) {
791	goto done; /* no message id */
792    }
793    bsdp_msg = *((unsigned char *)opt);
794    switch (client->state) {
795    case kBSDPClientStateInit:
796    default:
797	break;
798    case kBSDPClientStateList:
799	/* ACK[LIST] */
800	if (bsdp_msg == bsdp_msgtype_list_e) {
801	    BSDPClientProcessList(client, server_ip,
802				  (struct dhcp *)receive_buf, n,
803				  &options,
804				  &bsdp_options);
805	}
806	break;
807    case kBSDPClientStateSelect:
808	/* ACK[SELECT] or ACK[FAILED] */
809	if (bsdp_msg == bsdp_msgtype_select_e
810	    || bsdp_msg == bsdp_msgtype_failed_e) {
811	    BSDPClientProcessSelect(client, bsdp_msg);
812	}
813	break;
814    }
815 done:
816    dhcpol_free(&options);
817    dhcpol_free(&bsdp_options);
818    return;
819}
820
821
822/*
823 * Function: BSDPClientCreateWithInterfaceAndAttributes
824 * Purpose:
825 *   Instantiate a BSDPClientRef, checking to ensure that the machine
826 *   is NetBoot-compatible.
827 */
828BSDPClientRef
829BSDPClientCreateWithInterfaceAndAttributes(BSDPClientStatus * status_p,
830					   const char * ifname,
831					   const u_int16_t * attrs, int n_attrs)
832{
833    BSDPClientRef	client = NULL;
834    u_short		client_port;
835    bsdp_version_t	client_version = htons(BSDP_VERSION_1_1);
836    CFSocketContext	context = { 0, NULL, NULL, NULL, NULL };
837    interface_t *	if_p = NULL;
838    interface_list_t *	ifl = NULL;
839    int			fd = -1;
840    boolean_t		old_firmware = FALSE;
841    CFRunLoopSourceRef	rls = NULL;
842    CFSocketRef		socket = NULL;
843    BSDPClientStatus	status = kBSDPClientStatusAllocationError;
844    char *		system_id = NULL;
845    BSDPClientStatus	this_status;
846    NetBootVersion	version;
847
848    this_status = CopyNetBootVersionAndSystemIdentifier(&version, &system_id);
849    if (this_status != kBSDPClientStatusOK) {
850	status = this_status;
851	goto cleanup;
852    }
853    if (version == kNetBootVersion1) {
854	old_firmware = TRUE;
855    }
856    ifl = ifl_init();
857    if (ifl == NULL) {
858	goto cleanup;
859    }
860
861    if_p = ifl_find_name(ifl, ifname);
862    if (if_p == NULL) {
863	status = kBSDPClientStatusNoSuchInterface;
864	goto cleanup;
865    }
866    if (if_ift_type(if_p) != IFT_ETHER) {
867	status = kBSDPClientStatusInvalidArgument;
868	goto cleanup;
869    }
870    /* make a persistent copy */
871    if_p = if_dup(if_p);
872    if (if_p == NULL) {
873	goto cleanup;
874    }
875    if (if_inet_addr(if_p).s_addr == 0) {
876	status = kBSDPClientStatusInterfaceNotConfigured;
877	goto cleanup;
878    }
879    client = malloc(sizeof(*client));
880    if (client == NULL) {
881	goto cleanup;
882    }
883    bzero(client, sizeof(*client));
884
885    /* use the DHCP-supplied address, if it is available */
886    if (get_dhcp_address(ifname, &client->our_ip) == FALSE) {
887	client->our_ip = if_inet_addr(if_p);
888    }
889    if (n_attrs > 0) {
890	int	i;
891
892	if (n_attrs > MAX_ATTRS) {
893	    n_attrs = MAX_ATTRS;
894	}
895	for (i = 0; i < n_attrs; i++) {
896	    client->attrs[i] = htons(attrs[i]);
897	}
898	client->n_attrs = n_attrs;
899    }
900    fd = S_open_socket(&client_port);
901    if (fd < 0) {
902	if (errno == EPERM || errno == EACCES) {
903	    perror("socket");
904	    status = kBSDPClientStatusPermissionDenied;
905	}
906	goto cleanup;
907    }
908    context.info = client;
909    socket = CFSocketCreateWithNative(NULL, fd, kCFSocketReadCallBack,
910				      BSDPClientProcess, &context);
911    if (socket == NULL) {
912	goto cleanup;
913    }
914    rls = CFSocketCreateRunLoopSource(NULL, socket, 0);
915    if (rls == NULL) {
916	goto cleanup;
917    }
918    CFRunLoopAddSource(CFRunLoopGetCurrent(), rls,
919		       kCFRunLoopDefaultMode);
920    client->system_id = system_id;
921    client->client_version = client_version;
922    client->old_firmware = old_firmware;
923    client->fd = fd;
924    client->rls = rls;
925    client->client_port = client_port;
926    client->socket = socket;
927    client->xid = arc4random();
928    client->if_p = if_p;
929    client->state = kBSDPClientStateInit;
930    ifl_free(&ifl);
931    *status_p = kBSDPClientStatusOK;
932    return (client);
933
934 cleanup:
935    if (rls != NULL) {
936	CFRunLoopRemoveSource(CFRunLoopGetCurrent(), rls,
937			      kCFRunLoopDefaultMode);
938	/* release the run loop source */
939	CFRelease(rls);
940    }
941    if (socket != NULL) {
942	/* remove one socket reference, close the file descriptor */
943	CFSocketInvalidate(socket);
944
945	/* release the socket */
946	CFRelease(socket);
947	fd = -1;
948    }
949    if (fd >= 0) {
950	close(fd);
951    }
952    if (client != NULL) {
953	free(client);
954    }
955    if (ifl != NULL) {
956	ifl_free(&ifl);
957    }
958    if (if_p != NULL) {
959	if_free(&if_p);
960    }
961    if (system_id != NULL) {
962	free(system_id);
963    }
964    *status_p = status;
965    return (NULL);
966}
967
968/*
969 * Function: BSDPClientCreateWithInterface
970 * Purpose:
971 *   Allocate a new session.
972 */
973BSDPClientRef
974BSDPClientCreateWithInterface(BSDPClientStatus * status_p,
975			      const char * ifname)
976{
977    return (BSDPClientCreateWithInterfaceAndAttributes(status_p, ifname,
978						       NULL, 0));
979}
980
981/*
982 * Function: BSDPClientCreate
983 * Purpose:
984 *   Published entry point to instantiate a BSDPClientRef over "en0".
985 *   XXX we should ask IOKit which interface is the primary.
986 */
987BSDPClientRef
988BSDPClientCreate(BSDPClientStatus * status_p)
989{
990    return (BSDPClientCreateWithInterface(status_p, "en0"));
991}
992
993void
994BSDPClientFree(BSDPClientRef * client_p)
995{
996    BSDPClientRef client;
997
998    if (client_p == NULL) {
999	return;
1000    }
1001    client = *client_p;
1002    if (client == NULL) {
1003	return;
1004    }
1005    BSDPClientCancelTimer(client);
1006    if (client->socket != NULL) {
1007	/* remove one socket reference, close the file descriptor */
1008	CFSocketInvalidate(client->socket);
1009
1010	/* release the socket */
1011	CFRelease(client->socket);
1012    }
1013    if (client->rls != NULL) {
1014	CFRunLoopRemoveSource(CFRunLoopGetCurrent(), client->rls,
1015			      kCFRunLoopDefaultMode);
1016	/* release the run loop source */
1017	CFRelease(client->rls);
1018    }
1019    if (client->if_p != NULL) {
1020	if_free(&client->if_p);
1021    }
1022    if (client->system_id != NULL) {
1023	free(client->system_id);
1024    }
1025    free(client);
1026    *client_p = NULL;
1027    return;
1028}
1029
1030/**
1031 ** BSDP List Routines
1032 **/
1033static boolean_t
1034attributes_match(u_int16_t attrs,
1035		 const u_int16_t * attrs_list, int n_attrs_list)
1036{
1037    int		i;
1038
1039    if (attrs_list == NULL || n_attrs_list == 0) {
1040	return (TRUE);
1041    }
1042    for (i = 0; i < n_attrs_list; i++) {
1043	if (attrs_list[i] == attrs) {
1044	    return (TRUE);
1045	}
1046    }
1047    return (FALSE);
1048}
1049
1050static CFArrayRef
1051BSDPClientCreateImageList(BSDPClientRef client,
1052			  bsdp_image_id_t default_image_id,
1053			  bsdp_image_id_t selected_image_id,
1054			  void * image_list, int image_list_len)
1055{
1056    bsdp_image_description_t *	descr;
1057    CFMutableArrayRef		images = NULL;
1058    int				length;
1059
1060    images = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
1061    descr = image_list;
1062    for (length = image_list_len; length > sizeof(*descr); ) {
1063	u_int16_t		attributes;
1064	bsdp_image_id_t		boot_image_id;
1065	CFMutableDictionaryRef	this_dict = NULL;
1066	int			this_len;
1067	CFStringRef		cf_image_name = NULL;
1068	CFNumberRef		cf_image_id = NULL;
1069	CFNumberRef		cf_image_index = NULL;
1070
1071	this_len = sizeof(*descr) + descr->name_length;
1072	if (length < this_len) {
1073	    fprintf(stderr, "short image list at offset %d\n",
1074		    (int)((void *)descr - image_list));
1075	    goto failed;
1076	}
1077	boot_image_id = ntohl(*((bsdp_image_id_t *)descr->boot_image_id));
1078	attributes = bsdp_image_attributes(boot_image_id);
1079	if (boot_image_id != BOOT_IMAGE_ID_NULL
1080	    && attributes_match(htons(attributes),
1081				client->attrs, client->n_attrs)) {
1082	    uint32_t	index;
1083
1084	    this_dict
1085		= CFDictionaryCreateMutable(NULL, 0,
1086					    &kCFTypeDictionaryKeyCallBacks,
1087					    &kCFTypeDictionaryValueCallBacks);
1088	    cf_image_id = CFNumberCreate(NULL, kCFNumberSInt32Type,
1089					 &boot_image_id);
1090	    index = bsdp_image_index(boot_image_id);
1091	    cf_image_index = CFNumberCreate(NULL, kCFNumberSInt32Type,
1092					    &index);
1093	    cf_image_name = CFStringCreateWithBytes(NULL,
1094						    descr->name,
1095						    descr->name_length,
1096						    kCFStringEncodingUTF8,
1097						    TRUE);
1098	    if (this_dict != NULL && cf_image_id != NULL
1099		&& cf_image_index != NULL && cf_image_name != NULL) {
1100		CFDictionarySetValue(this_dict,
1101				     kBSDPImageDescriptionName, cf_image_name);
1102		CFDictionarySetValue(this_dict,
1103				     kBSDPImageDescriptionIdentifier,
1104				     cf_image_id);
1105		CFDictionarySetValue(this_dict, kBSDPImageDescriptionIndex,
1106				     cf_image_index);
1107		if (attributes & BSDP_IMAGE_ATTRIBUTES_INSTALL) {
1108		    CFDictionarySetValue(this_dict,
1109					 kBSDPImageDescriptionIsInstall,
1110					 kCFBooleanTrue);
1111		}
1112		if (boot_image_id == default_image_id) {
1113		    CFDictionarySetValue(this_dict,
1114					 kBSDPImageDescriptionIsDefault,
1115					 kCFBooleanTrue);
1116		}
1117		if (boot_image_id == selected_image_id) {
1118		    CFDictionarySetValue(this_dict,
1119					 kBSDPImageDescriptionIsSelected,
1120					 kCFBooleanTrue);
1121		}
1122		CFArrayAppendValue(images, this_dict);
1123	    }
1124	    my_CFRelease(&cf_image_index);
1125	    my_CFRelease(&cf_image_id);
1126	    my_CFRelease(&cf_image_name);
1127	    my_CFRelease(&this_dict);
1128	}
1129	descr = ((void *)descr) + this_len;
1130	length -= this_len;
1131    }
1132    if (CFArrayGetCount(images) == 0) {
1133	goto failed;
1134    }
1135    return ((CFArrayRef)images);
1136
1137 failed:
1138    my_CFRelease(&images);
1139    return (NULL);
1140}
1141
1142static void
1143BSDPClientProcessList(BSDPClientRef client, struct in_addr server_ip,
1144		      struct dhcp * reply, int reply_len,
1145		      dhcpol_t * options_p, dhcpol_t * bsdp_options_p)
1146{
1147    CFNumberRef			cf_priority = NULL;
1148    CFStringRef			cf_server_ip = NULL;
1149    bsdp_image_id_t		default_image_id = BOOT_IMAGE_ID_NULL;
1150    void *			image_list = NULL;
1151    int				image_list_len = 0;
1152    void *			opt;
1153    int				opt_len;
1154    CFArrayRef			images = NULL;
1155    uint32_t			priority = 0;
1156    bsdp_image_id_t		selected_image_id = BOOT_IMAGE_ID_NULL;
1157
1158    /* get the server priority */
1159    opt = dhcpol_find(bsdp_options_p, bsdptag_server_priority_e, &opt_len,
1160		      NULL);
1161    if (opt != NULL && opt_len == sizeof(bsdp_priority_t)) {
1162	priority = (uint32_t)ntohs(*((bsdp_priority_t *)opt));
1163    }
1164    /* get the default boot image */
1165    opt = dhcpol_find(bsdp_options_p, bsdptag_default_boot_image_e, &opt_len,
1166		      NULL);
1167    if (opt != NULL && opt_len == sizeof(default_image_id)) {
1168	default_image_id = ntohl(*((bsdp_image_id_t *)opt));
1169    }
1170    /* get the selected boot image */
1171    opt = dhcpol_find(bsdp_options_p, bsdptag_selected_boot_image_e, &opt_len,
1172		      NULL);
1173    if (opt && opt_len == sizeof(selected_image_id)) {
1174	selected_image_id = ntohl(*((bsdp_image_id_t *)opt));
1175    }
1176    /* get the list of images */
1177    image_list = dhcpol_option_copy(bsdp_options_p, bsdptag_boot_image_list_e,
1178				    &image_list_len);
1179    if (image_list == NULL) {
1180	goto done;
1181    }
1182    cf_priority = CFNumberCreate(NULL, kCFNumberSInt32Type, &priority);
1183    cf_server_ip = CFStringCreateWithCString(NULL, inet_ntoa(server_ip),
1184					     kCFStringEncodingASCII);
1185    images = BSDPClientCreateImageList(client, default_image_id,
1186				       selected_image_id,
1187				       image_list, image_list_len);
1188    if (images != NULL && cf_priority != NULL && cf_server_ip != NULL) {
1189	client->got_responses = TRUE;
1190	(*client->callback.func.list)(client,
1191				      kBSDPClientStatusOK,
1192				      cf_server_ip,
1193				      cf_priority,
1194				      images,
1195				      client->callback.arg);
1196    }
1197
1198 done:
1199    my_CFRelease(&images);
1200    my_CFRelease(&cf_priority);
1201    my_CFRelease(&cf_server_ip);
1202    if (image_list != NULL) {
1203	free(image_list);
1204    }
1205    my_CFRelease(&images);
1206    return;
1207}
1208
1209static BSDPClientStatus
1210BSDPClientSendListRequest(BSDPClientRef client)
1211{
1212    char		bsdp_buf[DHCP_OPTION_SIZE_MAX];
1213    dhcpoa_t		bsdp_options;
1214    char		buf[DHCP_PACKET_MIN];
1215    struct in_addr	ip_broadcast;
1216    dhcpoa_t		options;
1217    uint16_t		max_message_size = htons(RX_BUF_SIZE); /* max receive size */
1218    unsigned char	msgtype;
1219    u_int16_t		port = htons(client->client_port);
1220    struct dhcp *	request;
1221    int 		request_size = 0;
1222    BSDPClientStatus	status = kBSDPClientStatusAllocationError;
1223
1224    ip_broadcast.s_addr = htonl(INADDR_BROADCAST);
1225    request = make_bsdp_request(client->system_id,
1226				(struct dhcp *)buf, sizeof(buf),
1227				dhcp_msgtype_inform_e,
1228				if_link_address(client->if_p),
1229				if_link_arptype(client->if_p),
1230				if_link_length(client->if_p),
1231				&options);
1232    if (request == NULL) {
1233	goto failed;
1234    }
1235    request->dp_xid = htonl(client->xid);
1236    request->dp_ciaddr = client->our_ip;
1237    dhcpoa_init_no_end(&bsdp_options, bsdp_buf, sizeof(bsdp_buf));
1238    msgtype = bsdp_msgtype_list_e;
1239    if (dhcpoa_add(&bsdp_options, bsdptag_message_type_e,
1240		   sizeof(msgtype), &msgtype)
1241	!= dhcpoa_success_e) {
1242	fprintf(stderr, "BSDPClientSendListRequest add message type failed, %s",
1243		dhcpoa_err(&bsdp_options));
1244	goto failed;
1245    }
1246    if (dhcpoa_add(&bsdp_options, bsdptag_version_e,
1247		   sizeof(client->client_version),
1248		   &client->client_version) != dhcpoa_success_e) {
1249	fprintf(stderr, "BSDPClientSendListRequest add version failed, %s",
1250		dhcpoa_err(&bsdp_options));
1251	goto failed;
1252    }
1253    if (client->old_firmware == TRUE) {
1254	if (dhcpoa_add(&bsdp_options, bsdptag_netboot_1_0_firmware_e,
1255		       0, NULL) != dhcpoa_success_e) {
1256	    fprintf(stderr, "BSDPClientSendListRequest old_firmware failed, %s",
1257		    dhcpoa_err(&bsdp_options));
1258	    goto failed;
1259	}
1260    }
1261    if (dhcpoa_add(&bsdp_options, bsdptag_reply_port_e, sizeof(port),
1262		   &port) != dhcpoa_success_e) {
1263	fprintf(stderr, "BSDPClientSendListRequest add reply port failed, %s",
1264		dhcpoa_err(&bsdp_options));
1265	goto failed;
1266    }
1267    if (dhcpoa_add(&bsdp_options, bsdptag_max_message_size_e,
1268		   sizeof(max_message_size), &max_message_size)
1269	!= dhcpoa_success_e) {
1270	fprintf(stderr,
1271		"BSDPClientSendListRequest add max message size failed, %s",
1272		dhcpoa_err(&bsdp_options));
1273	goto failed;
1274    }
1275    if (client->n_attrs > 0) {
1276	if (dhcpoa_add(&bsdp_options,bsdptag_image_attributes_filter_list_e,
1277		       client->n_attrs * sizeof(client->attrs[0]),
1278		       client->attrs) != dhcpoa_success_e) {
1279	    fprintf(stderr,
1280		    "BSDPClientSendListRequest add image attributes failed, %s",
1281		    dhcpoa_err(&bsdp_options));
1282	    goto failed;
1283	}
1284    }
1285    if (dhcpoa_add(&options, dhcptag_vendor_specific_e,
1286		   dhcpoa_used(&bsdp_options), &bsdp_buf)
1287	!= dhcpoa_success_e) {
1288	fprintf(stderr,
1289		"BSDPClientSendListRequest add vendor specific failed, %s",
1290	       dhcpoa_err(&options));
1291	goto failed;
1292    }
1293    if (dhcpoa_add(&options, dhcptag_end_e, 0, NULL)
1294	!= dhcpoa_success_e) {
1295	fprintf(stderr,
1296		"BSDPClientSendListRequest add dhcp options end failed, %s",
1297		dhcpoa_err(&bsdp_options));
1298	goto failed;
1299    }
1300    request_size = sizeof(*request) + sizeof(rfc_magic)
1301	+ dhcpoa_used(&options);
1302    if (request_size < sizeof(struct bootp)) {
1303	/* pad out to BOOTP-sized packet */
1304	request_size = sizeof(struct bootp);
1305    }
1306    if (bootp_transmit(client->fd, (char *)client->send_buf,
1307		       if_name(client->if_p), ARPHRD_ETHER, NULL, 0,
1308		       ip_broadcast, client->our_ip,
1309		       IPPORT_BOOTPS, client->client_port,
1310		       request, request_size) < 0) {
1311	fprintf(stderr,
1312		"BSDPClientSendListRequest: bootp_transmit failed %s\n",
1313		strerror(errno));
1314	status = kBSDPClientStatusTransmitFailed;
1315	goto failed;
1316    }
1317    status = kBSDPClientStatusOK;
1318
1319 failed:
1320    return (status);
1321}
1322
1323static void
1324BSDPClientListTimeout(BSDPClientRef client)
1325{
1326    BSDPClientStatus	status = kBSDPClientStatusOK;
1327    struct timeval	t;
1328
1329    if (client->try == BSDPCLIENT_LIST_MAX_TRIES) {
1330	if (client->got_responses == FALSE) {
1331	    status = kBSDPClientStatusOperationTimedOut;
1332	    goto report_error;
1333	}
1334	return;
1335    }
1336    client->try++;
1337    client->wait_secs *= 2;
1338    if (client->wait_secs > BSDPCLIENT_MAX_WAIT_SECS) {
1339	client->wait_secs = BSDPCLIENT_MAX_WAIT_SECS;
1340    }
1341    status = BSDPClientSendListRequest(client);
1342    if (status != kBSDPClientStatusOK) {
1343	goto report_error;
1344    }
1345    t.tv_sec = client->wait_secs;
1346    t.tv_usec = random_range(0, USECS_PER_SEC - 1);
1347    BSDPClientSetTimer(client, t, BSDPClientListTimeout);
1348    return;
1349
1350 report_error:
1351    (*client->callback.func.list)(client, status, NULL, NULL,
1352				  NULL, client->callback.arg);
1353    return;
1354}
1355
1356BSDPClientStatus
1357BSDPClientList(BSDPClientRef client, BSDPClientListCallBack callback,
1358	       void * info)
1359{
1360    struct timeval	t;
1361    BSDPClientStatus	status = kBSDPClientStatusAllocationError;
1362
1363    client->state = kBSDPClientStateInit;
1364    BSDPClientCancelTimer(client);
1365    if (callback == NULL) {
1366	status = kBSDPClientStatusInvalidArgument;
1367	goto failed;
1368    }
1369    client->xid++;
1370    status = BSDPClientSendListRequest(client);
1371    if (status != kBSDPClientStatusOK) {
1372	goto failed;
1373    }
1374    client->state = kBSDPClientStateList;
1375    client->try = 1;
1376    client->got_responses = FALSE;
1377    client->callback.func.list = callback;
1378    client->callback.arg = info;
1379    client->wait_secs = BSDPCLIENT_INITIAL_TIMEOUT_SECS;
1380    t.tv_sec = client->wait_secs;
1381    t.tv_usec = random_range(0, USECS_PER_SEC - 1);
1382    BSDPClientSetTimer(client, t, BSDPClientListTimeout);
1383
1384 failed:
1385    return (status);
1386}
1387
1388/**
1389 ** BSDP Select Routines
1390 **/
1391
1392static void
1393BSDPClientProcessSelect(BSDPClientRef client, bsdp_msgtype_t bsdp_msg)
1394{
1395    BSDPClientStatus	status;
1396
1397    BSDPClientCancelTimer(client);
1398    if (bsdp_msg == bsdp_msgtype_select_e) {
1399	status = kBSDPClientStatusOK;
1400    }
1401    else {
1402	status = kBSDPClientStatusServerSentFailure;
1403    }
1404    (*client->callback.func.select)(client, status, client->callback.arg);
1405    return;
1406}
1407
1408static BSDPClientStatus
1409BSDPClientSendSelectRequest(BSDPClientRef client)
1410{
1411    char		bsdp_buf[DHCP_OPTION_SIZE_MAX];
1412    dhcpoa_t		bsdp_options;
1413    char		buf[DHCP_PACKET_MIN];
1414    bsdp_image_id_t	image_id = htonl(client->callback.image_identifier);
1415    struct in_addr	ip_broadcast;
1416    dhcpoa_t		options;
1417    unsigned char	msgtype;
1418    u_int16_t		port = htons(client->client_port);
1419    struct dhcp *	request;
1420    int 		request_size = 0;
1421    BSDPClientStatus	status = kBSDPClientStatusAllocationError;
1422
1423    ip_broadcast.s_addr = htonl(INADDR_BROADCAST);
1424    request = make_bsdp_request(client->system_id,
1425				(struct dhcp *)buf, sizeof(buf),
1426				dhcp_msgtype_inform_e,
1427				if_link_address(client->if_p),
1428				if_link_arptype(client->if_p),
1429				if_link_length(client->if_p),
1430				&options);
1431    if (request == NULL) {
1432	goto failed;
1433    }
1434    request->dp_xid = htonl(client->xid);
1435    request->dp_ciaddr = client->our_ip;
1436    dhcpoa_init_no_end(&bsdp_options, bsdp_buf, sizeof(bsdp_buf));
1437    msgtype = bsdp_msgtype_select_e;
1438    if (dhcpoa_add(&bsdp_options, bsdptag_message_type_e,
1439		   sizeof(msgtype), &msgtype)
1440	!= dhcpoa_success_e) {
1441	fprintf(stderr,
1442		"BSDPClientSendSelectRequest add message type failed, %s",
1443		dhcpoa_err(&bsdp_options));
1444	goto failed;
1445    }
1446    if (dhcpoa_add(&bsdp_options, bsdptag_version_e,
1447		   sizeof(client->client_version),
1448		   &client->client_version) != dhcpoa_success_e) {
1449	fprintf(stderr, "BSDPClientSendSelectRequest add version failed, %s",
1450		dhcpoa_err(&bsdp_options));
1451	goto failed;
1452    }
1453    if (client->old_firmware == TRUE) {
1454	if (dhcpoa_add(&bsdp_options, bsdptag_netboot_1_0_firmware_e,
1455		       0, NULL) != dhcpoa_success_e) {
1456	    fprintf(stderr, "BSDPClientSendListRequest old_firmware failed, %s",
1457		    dhcpoa_err(&bsdp_options));
1458	    goto failed;
1459	}
1460    }
1461    if (dhcpoa_add(&bsdp_options, bsdptag_reply_port_e, sizeof(port),
1462		   &port) != dhcpoa_success_e) {
1463	fprintf(stderr, "BSDPClientSendSelectRequest add reply port failed, %s",
1464		dhcpoa_err(&bsdp_options));
1465	goto failed;
1466    }
1467    if (dhcpoa_add(&bsdp_options, bsdptag_server_identifier_e,
1468		   sizeof(struct in_addr),
1469		   &client->callback.server_ip) != dhcpoa_success_e) {
1470	fprintf(stderr,
1471		"BSDPClientSendSelectRequest: add server identifier failed, %s",
1472		dhcpoa_err(&bsdp_options));
1473	goto failed;
1474    }
1475    if (dhcpoa_add(&bsdp_options, bsdptag_selected_boot_image_e,
1476		   sizeof(image_id), &image_id) != dhcpoa_success_e) {
1477	fprintf(stderr,
1478		"BSDPClientSendSelectRequest: add selected image failed, %s",
1479		dhcpoa_err(&bsdp_options));
1480	goto failed;
1481    }
1482    if (dhcpoa_add(&options, dhcptag_vendor_specific_e,
1483		   dhcpoa_used(&bsdp_options), &bsdp_buf)
1484	!= dhcpoa_success_e) {
1485	fprintf(stderr,
1486		"BSDPClientSendSelectRequest add vendor specific failed, %s",
1487	       dhcpoa_err(&options));
1488	goto failed;
1489    }
1490    if (dhcpoa_add(&options, dhcptag_end_e, 0, NULL)
1491	!= dhcpoa_success_e) {
1492	fprintf(stderr,
1493		"BSDPClientSendSelectRequest add dhcp options end failed, %s",
1494		dhcpoa_err(&bsdp_options));
1495	goto failed;
1496    }
1497    request_size = sizeof(*request) + sizeof(rfc_magic)
1498	+ dhcpoa_used(&options);
1499    if (request_size < sizeof(struct bootp)) {
1500	/* pad out to BOOTP-sized packet */
1501	request_size = sizeof(struct bootp);
1502    }
1503    /* send the packet */
1504    if (bootp_transmit(client->fd, (char *)client->send_buf,
1505		       if_name(client->if_p), ARPHRD_ETHER, NULL, 0,
1506		       ip_broadcast, client->our_ip,
1507		       IPPORT_BOOTPS, client->client_port,
1508		       request, request_size) < 0) {
1509	fprintf(stderr,
1510		"BSDPClientSendSelectRequest: bootp_transmit failed %s\n",
1511		strerror(errno));
1512	status = kBSDPClientStatusTransmitFailed;
1513	goto failed;
1514    }
1515    status = kBSDPClientStatusOK;
1516
1517 failed:
1518    return (status);
1519}
1520
1521static void
1522BSDPClientSelectTimeout(BSDPClientRef client)
1523{
1524    BSDPClientStatus	status = kBSDPClientStatusOK;
1525    struct timeval	t;
1526
1527    if (client->try == BSDPCLIENT_SELECT_MAX_TRIES) {
1528	status = kBSDPClientStatusOperationTimedOut;
1529	goto report_error;
1530    }
1531    client->try++;
1532    client->wait_secs *= 2;
1533    status = BSDPClientSendSelectRequest(client);
1534    if (status != kBSDPClientStatusOK) {
1535	goto report_error;
1536    }
1537    t.tv_sec = client->wait_secs;
1538    t.tv_usec = 0;
1539    BSDPClientSetTimer(client, t, BSDPClientSelectTimeout);
1540    return;
1541
1542 report_error:
1543    (*client->callback.func.select)(client, status, client->callback.arg);
1544    return;
1545}
1546
1547BSDPClientStatus
1548BSDPClientSelect(BSDPClientRef client,
1549		 CFStringRef ServerAddress,
1550		 CFNumberRef Identifier,
1551		 BSDPClientSelectCallBack callback, void * info)
1552{
1553    unsigned long	image_identifier;
1554    struct timeval	t;
1555    BSDPClientStatus	status = kBSDPClientStatusAllocationError;
1556
1557    (void)my_CFStringToIPAddress(ServerAddress, &client->callback.server_ip);
1558    client->state = kBSDPClientStateInit;
1559    BSDPClientCancelTimer(client);
1560    if (callback == NULL
1561	|| CFNumberGetValue(Identifier, kCFNumberLongType,
1562			    &image_identifier) == FALSE) {
1563	status = kBSDPClientStatusInvalidArgument;
1564	goto failed;
1565    }
1566    client->xid++;
1567    client->callback.image_identifier = image_identifier;
1568    status = BSDPClientSendSelectRequest(client);
1569    if (status != kBSDPClientStatusOK) {
1570	goto failed;
1571    }
1572    client->state = kBSDPClientStateSelect;
1573    client->try = 1;
1574    client->callback.func.select = callback;
1575    client->callback.arg = info;
1576    client->wait_secs = BSDPCLIENT_INITIAL_TIMEOUT_SECS;
1577    t.tv_sec = client->wait_secs;
1578    t.tv_usec = 0;
1579    BSDPClientSetTimer(client, t, BSDPClientSelectTimeout);
1580
1581 failed:
1582    return (status);
1583}
1584
1585BSDPClientStatus
1586BSPPClientSelect(BSDPClientRef client,
1587		 CFStringRef ServerAddress,
1588		 CFNumberRef Identifier,
1589		 BSDPClientSelectCallBack callback, void * info)
1590{
1591    return (BSDPClientSelect(client, ServerAddress, Identifier,
1592			     callback, info));
1593}
1594
1595#ifdef TEST_BAD_SYSID
1596static void bad_sysid_callback(BSDPClientRef client,
1597			       BSDPClientStatus status,
1598			       CFStringRef ServerAddress,
1599			       CFNumberRef ServerPriority,
1600			       CFArrayRef list,
1601			       void *info)
1602{
1603    return;
1604}
1605
1606
1607int
1608main(int argc, char * argv[])
1609{
1610    BSDPClientStatus	status;
1611    char *		if_name = "en0";
1612    BSDPClientRef	client;
1613
1614    if (argc > 1) {
1615	if_name = argv[1];
1616    }
1617
1618    client = BSDPClientCreateWithInterface(&status, if_name);
1619    if (client == NULL) {
1620	fprintf(stderr, "BSDPClientCreateWithInterface(%s) failed: %s\n",
1621		if_name, BSDPClientStatusString(status));
1622	exit(2);
1623    }
1624    status = BSDPClientList(client, bad_sysid_callback, NULL);
1625    CFRunLoopRun();
1626    exit (0);
1627    return (0);
1628}
1629#endif /* TEST_BAD_SYSID */
1630