1/*
2 * Copyright (c) 2002-2014 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    if (status < 0) {
556	perror("setsockopt IPPROTO_IP IP_PORTRANGE");
557	goto failed;
558    }
559
560    /* bind the socket */
561    status = bind(sockfd, (struct sockaddr *)&me, sizeof(me));
562    if (status != 0) {
563	perror("bind");
564	goto failed;
565    }
566    me_len = sizeof(me);
567    if (getsockname(sockfd, (struct sockaddr *)&me,  &me_len) < 0) {
568	perror("getsockname");
569	goto failed;
570    }
571    client_port = ntohs(me.sin_port);
572
573    opt = 1;
574
575#if defined(SO_RECV_ANYIF)
576    /* receive over any interface */
577    if (setsockopt(sockfd, SOL_SOCKET, SO_RECV_ANYIF, (caddr_t)&opt,
578		   sizeof(opt)) < 0) {
579	perror("setsockopt SO_RECV_ANYIF");
580    }
581#endif /* SO_RECV_ANYIF */
582
583    /* broadcast */
584    status = setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &opt,
585			sizeof(opt));
586    if (status < 0) {
587	perror("setsockopt SO_BROADCAST");
588	goto failed;
589    }
590    /* non-blocking I/O */
591    status = ioctl(sockfd, FIONBIO, &opt);
592    if (status < 0) {
593	perror("ioctl FIONBIO");
594	goto failed;
595    }
596
597#if defined(SO_TRAFFIC_CLASS)
598    opt = SO_TC_CTL;
599    /* set control traffic class */
600    status = setsockopt(sockfd, SOL_SOCKET, SO_TRAFFIC_CLASS, &opt,
601			sizeof(opt));
602    if (status < 0) {
603	perror("setsockopt SO_TRAFFIC_CLASS");
604    }
605#endif /* SO_TRAFFIC_CLASS */
606
607    *ret_port = client_port;
608    return sockfd;
609
610 failed:
611    if (sockfd >= 0) {
612	close(sockfd);
613    }
614    return (-1);
615}
616
617/* deprecated: BSDPImageDescriptionIndexIsServerLocal */
618Boolean
619BSDPImageDescriptionIndexIsServerLocal(CFNumberRef index)
620{
621    u_int16_t	index_val = 1;
622
623    (void)CFNumberGetValue(index, kCFNumberShortType, &index_val);
624    return (bsdp_image_index_is_server_local(index_val));
625}
626
627Boolean
628BSDPImageDescriptionIdentifierIsServerLocal(CFNumberRef identifier)
629{
630    u_int32_t	identifier_val = 1;
631
632    (void)CFNumberGetValue(identifier, kCFNumberSInt32Type, &identifier_val);
633    return (bsdp_image_identifier_is_server_local(identifier_val));
634}
635
636Boolean
637BSDPImageDescriptionIdentifierIsInstall(CFNumberRef identifier)
638{
639    u_int32_t	identifier_val = 1;
640
641    (void)CFNumberGetValue(identifier, kCFNumberSInt32Type, &identifier_val);
642    return (bsdp_image_identifier_is_install(identifier_val));
643}
644
645BSDPImageKind
646BSDPImageDescriptionIdentifierImageKind(CFNumberRef identifier)
647{
648    u_int32_t	identifier_val = 1;
649
650    (void)CFNumberGetValue(identifier, kCFNumberSInt32Type, &identifier_val);
651    return (bsdp_image_kind_from_attributes(bsdp_image_attributes(identifier_val)));
652}
653
654/**
655 ** BSDPClient timer functions
656 **/
657
658static void
659BSDPClientProcessTimer(CFRunLoopTimerRef timer, void * info)
660{
661    BSDPClientRef	client;
662
663    client = (BSDPClientRef)info;
664    (*client->timer_callback)(client);
665    return;
666}
667
668static void
669BSDPClientCancelTimer(BSDPClientRef client)
670{
671    if (client->timer) {
672	CFRunLoopTimerInvalidate(client->timer);
673	my_CFRelease(&client->timer);
674    }
675    client->timer_callback = NULL;
676    return;
677}
678
679static void
680BSDPClientSetTimer(BSDPClientRef client, struct timeval rel_time,
681		   BSDPClientTimerCallBack callback)
682{
683    CFRunLoopTimerContext 	context =  { 0, NULL, NULL, NULL, NULL };
684    CFAbsoluteTime 		wakeup_time;
685
686    BSDPClientCancelTimer(client);
687    client->timer_callback = callback;
688    wakeup_time = CFAbsoluteTimeGetCurrent() + rel_time.tv_sec
689	  + ((double)rel_time.tv_usec / USECS_PER_SEC);
690    context.info = client;
691    client->timer
692	= CFRunLoopTimerCreate(NULL, wakeup_time,
693			       0.0, 0, 0,
694			       BSDPClientProcessTimer,
695			       &context);
696    CFRunLoopAddTimer(CFRunLoopGetCurrent(), client->timer,
697		      kCFRunLoopDefaultMode);
698    return;
699}
700
701/*
702 * Function: BSDPClientProcess
703 * Purpose:
704 *   Process a packet received on our open socket.
705 *   Ensure that the packet is a BSDP packet[DHCP ACK] that
706 *   matches our currently outstanding request.
707 *
708 *   Dispatch to the appropriate handler (list or select)
709 *   depending on our current running state.
710 */
711static void
712BSDPClientProcess(CFSocketRef s, CFSocketCallBackType type,
713		  CFDataRef address, const void *data, void *info)
714{
715    dhcpol_t			bsdp_options;
716    bsdp_msgtype_t		bsdp_msg;
717    BSDPClientRef 		client = (BSDPClientRef)info;
718    dhcpo_err_str_t		err;
719    struct sockaddr_in 		from;
720    socklen_t 			fromlen;
721    int 			n;
722    void *			opt;
723    int				opt_len;
724    dhcpol_t			options;
725    uint32_t			receive_buf[RX_BUF_SIZE / sizeof(uint32_t)];
726    struct dhcp *		reply;
727    struct in_addr		server_ip;
728
729    n = recvfrom(client->fd, receive_buf,
730		 sizeof(receive_buf), 0,
731		 (struct sockaddr *)&from, &fromlen);
732    if (n < 0) {
733	if (errno != EAGAIN) {
734	    fprintf(stderr, "BSDPClientProcess: recvfrom %s",
735		    strerror(errno));
736	}
737	return;
738    }
739    if (n < sizeof(struct dhcp)) {
740	/* packet is too short */
741	return;
742    }
743
744    switch (client->state) {
745    case kBSDPClientStateInit:
746    default:
747	/* throw it away */
748	return;
749    case kBSDPClientStateList:
750    case kBSDPClientStateSelect:
751	break;
752    }
753
754    reply = (struct dhcp *)receive_buf;
755    if (dhcp_packet_match((struct bootp *)receive_buf, client->xid,
756			  (u_char) if_link_arptype(client->if_p),
757			  if_link_address(client->if_p),
758			  if_link_length(client->if_p)) == FALSE
759	|| reply->dp_ciaddr.s_addr != client->our_ip.s_addr) {
760	/* wasn't us */
761	return;
762    }
763
764    dhcpol_init(&options);
765    dhcpol_init(&bsdp_options);
766
767    if (dhcpol_parse_packet(&options, reply, n, &err) == FALSE) {
768	fprintf(stderr,
769		"BSDPClientProcess: dhcpol_parse_packet failed, %s\n",
770		err.str);
771	goto done;
772    }
773
774    /* get the DHCP message type */
775    opt = dhcpol_find(&options, dhcptag_dhcp_message_type_e, NULL, NULL);
776    if (opt == NULL || *((unsigned char *)opt) != dhcp_msgtype_ack_e) {
777	goto done; /* response must be a DHCP ack */
778    }
779
780    /* get the vendor class identifier */
781    opt = dhcpol_find(&options, dhcptag_vendor_class_identifier_e,
782		      &opt_len, NULL);
783    if (opt == NULL
784	|| opt_len != strlen(BSDP_VENDOR_CLASS_ID)
785	|| bcmp(opt, BSDP_VENDOR_CLASS_ID, opt_len)) {
786	goto done; /* not BSDP */
787    }
788    /* get the server identifier */
789    opt = dhcpol_find(&options, dhcptag_server_identifier_e,
790		      &opt_len, NULL);
791    if (opt == NULL || opt_len != sizeof(server_ip)) {
792	goto done;
793    }
794    server_ip = *((struct in_addr *)opt);
795
796    /* decode the BSDP options */
797    if (dhcpol_parse_vendor(&bsdp_options, &options, &err) == FALSE) {
798	fprintf(stderr,
799		"BSDPClientProcess: dhcpol_parse_vendor failed, %s", err.str);
800	goto done;
801    }
802    /* get the BSDP message type */
803    opt = dhcpol_find(&bsdp_options, bsdptag_message_type_e,
804		      &opt_len, NULL);
805    if (opt == NULL || opt_len != 1) {
806	goto done; /* no message id */
807    }
808    bsdp_msg = *((unsigned char *)opt);
809    switch (client->state) {
810    case kBSDPClientStateInit:
811    default:
812	break;
813    case kBSDPClientStateList:
814	/* ACK[LIST] */
815	if (bsdp_msg == bsdp_msgtype_list_e) {
816	    BSDPClientProcessList(client, server_ip,
817				  (struct dhcp *)receive_buf, n,
818				  &options,
819				  &bsdp_options);
820	}
821	break;
822    case kBSDPClientStateSelect:
823	/* ACK[SELECT] or ACK[FAILED] */
824	if (bsdp_msg == bsdp_msgtype_select_e
825	    || bsdp_msg == bsdp_msgtype_failed_e) {
826	    BSDPClientProcessSelect(client, bsdp_msg);
827	}
828	break;
829    }
830 done:
831    dhcpol_free(&options);
832    dhcpol_free(&bsdp_options);
833    return;
834}
835
836
837/*
838 * Function: BSDPClientCreateWithInterfaceAndAttributes
839 * Purpose:
840 *   Instantiate a BSDPClientRef, checking to ensure that the machine
841 *   is NetBoot-compatible.
842 */
843BSDPClientRef
844BSDPClientCreateWithInterfaceAndAttributes(BSDPClientStatus * status_p,
845					   const char * ifname,
846					   const u_int16_t * attrs, int n_attrs)
847{
848    BSDPClientRef	client = NULL;
849    u_short		client_port;
850    bsdp_version_t	client_version = htons(BSDP_VERSION_1_1);
851    CFSocketContext	context = { 0, NULL, NULL, NULL, NULL };
852    interface_t *	if_p = NULL;
853    interface_list_t *	ifl = NULL;
854    int			fd = -1;
855    boolean_t		old_firmware = FALSE;
856    CFRunLoopSourceRef	rls = NULL;
857    CFSocketRef		socket = NULL;
858    BSDPClientStatus	status = kBSDPClientStatusAllocationError;
859    char *		system_id = NULL;
860    BSDPClientStatus	this_status;
861    NetBootVersion	version;
862
863    this_status = CopyNetBootVersionAndSystemIdentifier(&version, &system_id);
864    if (this_status != kBSDPClientStatusOK) {
865	status = this_status;
866	goto cleanup;
867    }
868    if (version == kNetBootVersion1) {
869	old_firmware = TRUE;
870    }
871    ifl = ifl_init();
872    if (ifl == NULL) {
873	goto cleanup;
874    }
875
876    if_p = ifl_find_name(ifl, ifname);
877    if (if_p == NULL) {
878	status = kBSDPClientStatusNoSuchInterface;
879	goto cleanup;
880    }
881    if (if_ift_type(if_p) != IFT_ETHER) {
882	status = kBSDPClientStatusInvalidArgument;
883	goto cleanup;
884    }
885    /* make a persistent copy */
886    if_p = if_dup(if_p);
887    if (if_p == NULL) {
888	goto cleanup;
889    }
890    if (if_inet_addr(if_p).s_addr == 0) {
891	status = kBSDPClientStatusInterfaceNotConfigured;
892	goto cleanup;
893    }
894    client = malloc(sizeof(*client));
895    if (client == NULL) {
896	goto cleanup;
897    }
898    bzero(client, sizeof(*client));
899
900    /* use the DHCP-supplied address, if it is available */
901    if (get_dhcp_address(ifname, &client->our_ip) == FALSE) {
902	client->our_ip = if_inet_addr(if_p);
903    }
904    if (n_attrs > 0) {
905	int	i;
906
907	if (n_attrs > MAX_ATTRS) {
908	    n_attrs = MAX_ATTRS;
909	}
910	for (i = 0; i < n_attrs; i++) {
911	    client->attrs[i] = htons(attrs[i]);
912	}
913	client->n_attrs = n_attrs;
914    }
915    fd = S_open_socket(&client_port);
916    if (fd < 0) {
917	if (errno == EPERM || errno == EACCES) {
918	    perror("socket");
919	    status = kBSDPClientStatusPermissionDenied;
920	}
921	goto cleanup;
922    }
923    context.info = client;
924    socket = CFSocketCreateWithNative(NULL, fd, kCFSocketReadCallBack,
925				      BSDPClientProcess, &context);
926    if (socket == NULL) {
927	goto cleanup;
928    }
929    rls = CFSocketCreateRunLoopSource(NULL, socket, 0);
930    if (rls == NULL) {
931	goto cleanup;
932    }
933    CFRunLoopAddSource(CFRunLoopGetCurrent(), rls,
934		       kCFRunLoopDefaultMode);
935    client->system_id = system_id;
936    client->client_version = client_version;
937    client->old_firmware = old_firmware;
938    client->fd = fd;
939    client->rls = rls;
940    client->client_port = client_port;
941    client->socket = socket;
942    client->xid = arc4random();
943    client->if_p = if_p;
944    client->state = kBSDPClientStateInit;
945    ifl_free(&ifl);
946    *status_p = kBSDPClientStatusOK;
947    return (client);
948
949 cleanup:
950    if (rls != NULL) {
951	CFRunLoopRemoveSource(CFRunLoopGetCurrent(), rls,
952			      kCFRunLoopDefaultMode);
953	/* release the run loop source */
954	CFRelease(rls);
955    }
956    if (socket != NULL) {
957	/* remove one socket reference, close the file descriptor */
958	CFSocketInvalidate(socket);
959
960	/* release the socket */
961	CFRelease(socket);
962	fd = -1;
963    }
964    if (fd >= 0) {
965	close(fd);
966    }
967    if (client != NULL) {
968	free(client);
969    }
970    if (ifl != NULL) {
971	ifl_free(&ifl);
972    }
973    if (if_p != NULL) {
974	if_free(&if_p);
975    }
976    if (system_id != NULL) {
977	free(system_id);
978    }
979    *status_p = status;
980    return (NULL);
981}
982
983/*
984 * Function: BSDPClientCreateWithInterface
985 * Purpose:
986 *   Allocate a new session.
987 */
988BSDPClientRef
989BSDPClientCreateWithInterface(BSDPClientStatus * status_p,
990			      const char * ifname)
991{
992    return (BSDPClientCreateWithInterfaceAndAttributes(status_p, ifname,
993						       NULL, 0));
994}
995
996/*
997 * Function: BSDPClientCreate
998 * Purpose:
999 *   Published entry point to instantiate a BSDPClientRef over "en0".
1000 *   XXX we should ask IOKit which interface is the primary.
1001 */
1002BSDPClientRef
1003BSDPClientCreate(BSDPClientStatus * status_p)
1004{
1005    return (BSDPClientCreateWithInterface(status_p, "en0"));
1006}
1007
1008void
1009BSDPClientFree(BSDPClientRef * client_p)
1010{
1011    BSDPClientRef client;
1012
1013    if (client_p == NULL) {
1014	return;
1015    }
1016    client = *client_p;
1017    if (client == NULL) {
1018	return;
1019    }
1020    BSDPClientCancelTimer(client);
1021    if (client->socket != NULL) {
1022	/* remove one socket reference, close the file descriptor */
1023	CFSocketInvalidate(client->socket);
1024
1025	/* release the socket */
1026	CFRelease(client->socket);
1027    }
1028    if (client->rls != NULL) {
1029	CFRunLoopRemoveSource(CFRunLoopGetCurrent(), client->rls,
1030			      kCFRunLoopDefaultMode);
1031	/* release the run loop source */
1032	CFRelease(client->rls);
1033    }
1034    if (client->if_p != NULL) {
1035	if_free(&client->if_p);
1036    }
1037    if (client->system_id != NULL) {
1038	free(client->system_id);
1039    }
1040    free(client);
1041    *client_p = NULL;
1042    return;
1043}
1044
1045/**
1046 ** BSDP List Routines
1047 **/
1048static boolean_t
1049attributes_match(u_int16_t attrs,
1050		 const u_int16_t * attrs_list, int n_attrs_list)
1051{
1052    int		i;
1053
1054    if (attrs_list == NULL || n_attrs_list == 0) {
1055	return (TRUE);
1056    }
1057    for (i = 0; i < n_attrs_list; i++) {
1058	if (attrs_list[i] == attrs) {
1059	    return (TRUE);
1060	}
1061    }
1062    return (FALSE);
1063}
1064
1065static CFArrayRef
1066BSDPClientCreateImageList(BSDPClientRef client,
1067			  bsdp_image_id_t default_image_id,
1068			  bsdp_image_id_t selected_image_id,
1069			  void * image_list, int image_list_len)
1070{
1071    bsdp_image_description_t *	descr;
1072    CFMutableArrayRef		images = NULL;
1073    int				length;
1074
1075    images = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
1076    descr = image_list;
1077    for (length = image_list_len; length > sizeof(*descr); ) {
1078	u_int16_t		attributes;
1079	bsdp_image_id_t		boot_image_id;
1080	CFMutableDictionaryRef	this_dict = NULL;
1081	int			this_len;
1082	CFStringRef		cf_image_name = NULL;
1083	CFNumberRef		cf_image_id = NULL;
1084	CFNumberRef		cf_image_index = NULL;
1085
1086	this_len = sizeof(*descr) + descr->name_length;
1087	if (length < this_len) {
1088	    fprintf(stderr, "short image list at offset %d\n",
1089		    (int)((void *)descr - image_list));
1090	    goto failed;
1091	}
1092	boot_image_id = ntohl(*((bsdp_image_id_t *)descr->boot_image_id));
1093	attributes = bsdp_image_attributes(boot_image_id);
1094	if (boot_image_id != BOOT_IMAGE_ID_NULL
1095	    && attributes_match(htons(attributes),
1096				client->attrs, client->n_attrs)) {
1097	    uint32_t	index;
1098
1099	    this_dict
1100		= CFDictionaryCreateMutable(NULL, 0,
1101					    &kCFTypeDictionaryKeyCallBacks,
1102					    &kCFTypeDictionaryValueCallBacks);
1103	    cf_image_id = CFNumberCreate(NULL, kCFNumberSInt32Type,
1104					 &boot_image_id);
1105	    index = bsdp_image_index(boot_image_id);
1106	    cf_image_index = CFNumberCreate(NULL, kCFNumberSInt32Type,
1107					    &index);
1108	    cf_image_name = CFStringCreateWithBytes(NULL,
1109						    descr->name,
1110						    descr->name_length,
1111						    kCFStringEncodingUTF8,
1112						    TRUE);
1113	    if (this_dict != NULL && cf_image_id != NULL
1114		&& cf_image_index != NULL && cf_image_name != NULL) {
1115		CFDictionarySetValue(this_dict,
1116				     kBSDPImageDescriptionName, cf_image_name);
1117		CFDictionarySetValue(this_dict,
1118				     kBSDPImageDescriptionIdentifier,
1119				     cf_image_id);
1120		CFDictionarySetValue(this_dict, kBSDPImageDescriptionIndex,
1121				     cf_image_index);
1122		if (attributes & BSDP_IMAGE_ATTRIBUTES_INSTALL) {
1123		    CFDictionarySetValue(this_dict,
1124					 kBSDPImageDescriptionIsInstall,
1125					 kCFBooleanTrue);
1126		}
1127		if (boot_image_id == default_image_id) {
1128		    CFDictionarySetValue(this_dict,
1129					 kBSDPImageDescriptionIsDefault,
1130					 kCFBooleanTrue);
1131		}
1132		if (boot_image_id == selected_image_id) {
1133		    CFDictionarySetValue(this_dict,
1134					 kBSDPImageDescriptionIsSelected,
1135					 kCFBooleanTrue);
1136		}
1137		CFArrayAppendValue(images, this_dict);
1138	    }
1139	    my_CFRelease(&cf_image_index);
1140	    my_CFRelease(&cf_image_id);
1141	    my_CFRelease(&cf_image_name);
1142	    my_CFRelease(&this_dict);
1143	}
1144	descr = ((void *)descr) + this_len;
1145	length -= this_len;
1146    }
1147    if (CFArrayGetCount(images) == 0) {
1148	goto failed;
1149    }
1150    return ((CFArrayRef)images);
1151
1152 failed:
1153    my_CFRelease(&images);
1154    return (NULL);
1155}
1156
1157static void
1158BSDPClientProcessList(BSDPClientRef client, struct in_addr server_ip,
1159		      struct dhcp * reply, int reply_len,
1160		      dhcpol_t * options_p, dhcpol_t * bsdp_options_p)
1161{
1162    CFNumberRef			cf_priority = NULL;
1163    CFStringRef			cf_server_ip = NULL;
1164    bsdp_image_id_t		default_image_id = BOOT_IMAGE_ID_NULL;
1165    void *			image_list = NULL;
1166    int				image_list_len = 0;
1167    void *			opt;
1168    int				opt_len;
1169    CFArrayRef			images = NULL;
1170    uint32_t			priority = 0;
1171    bsdp_image_id_t		selected_image_id = BOOT_IMAGE_ID_NULL;
1172
1173    /* get the server priority */
1174    opt = dhcpol_find(bsdp_options_p, bsdptag_server_priority_e, &opt_len,
1175		      NULL);
1176    if (opt != NULL && opt_len == sizeof(bsdp_priority_t)) {
1177	priority = (uint32_t)ntohs(*((bsdp_priority_t *)opt));
1178    }
1179    /* get the default boot image */
1180    opt = dhcpol_find(bsdp_options_p, bsdptag_default_boot_image_e, &opt_len,
1181		      NULL);
1182    if (opt != NULL && opt_len == sizeof(default_image_id)) {
1183	default_image_id = ntohl(*((bsdp_image_id_t *)opt));
1184    }
1185    /* get the selected boot image */
1186    opt = dhcpol_find(bsdp_options_p, bsdptag_selected_boot_image_e, &opt_len,
1187		      NULL);
1188    if (opt && opt_len == sizeof(selected_image_id)) {
1189	selected_image_id = ntohl(*((bsdp_image_id_t *)opt));
1190    }
1191    /* get the list of images */
1192    image_list = dhcpol_option_copy(bsdp_options_p, bsdptag_boot_image_list_e,
1193				    &image_list_len);
1194    if (image_list == NULL) {
1195	goto done;
1196    }
1197    cf_priority = CFNumberCreate(NULL, kCFNumberSInt32Type, &priority);
1198    cf_server_ip = CFStringCreateWithCString(NULL, inet_ntoa(server_ip),
1199					     kCFStringEncodingASCII);
1200    images = BSDPClientCreateImageList(client, default_image_id,
1201				       selected_image_id,
1202				       image_list, image_list_len);
1203    if (images != NULL && cf_priority != NULL && cf_server_ip != NULL) {
1204	client->got_responses = TRUE;
1205	(*client->callback.func.list)(client,
1206				      kBSDPClientStatusOK,
1207				      cf_server_ip,
1208				      cf_priority,
1209				      images,
1210				      client->callback.arg);
1211    }
1212
1213 done:
1214    my_CFRelease(&images);
1215    my_CFRelease(&cf_priority);
1216    my_CFRelease(&cf_server_ip);
1217    if (image_list != NULL) {
1218	free(image_list);
1219    }
1220    my_CFRelease(&images);
1221    return;
1222}
1223
1224static BSDPClientStatus
1225BSDPClientSendListRequest(BSDPClientRef client)
1226{
1227    char		bsdp_buf[DHCP_OPTION_SIZE_MAX];
1228    dhcpoa_t		bsdp_options;
1229    char		buf[DHCP_PACKET_MIN];
1230    struct in_addr	ip_broadcast;
1231    dhcpoa_t		options;
1232    uint16_t		max_message_size = htons(RX_BUF_SIZE); /* max receive size */
1233    unsigned char	msgtype;
1234    u_int16_t		port = htons(client->client_port);
1235    struct dhcp *	request;
1236    int 		request_size = 0;
1237    BSDPClientStatus	status = kBSDPClientStatusAllocationError;
1238
1239    ip_broadcast.s_addr = htonl(INADDR_BROADCAST);
1240    request = make_bsdp_request(client->system_id,
1241				(struct dhcp *)buf, sizeof(buf),
1242				dhcp_msgtype_inform_e,
1243				if_link_address(client->if_p),
1244				if_link_arptype(client->if_p),
1245				if_link_length(client->if_p),
1246				&options);
1247    if (request == NULL) {
1248	goto failed;
1249    }
1250    request->dp_xid = htonl(client->xid);
1251    request->dp_ciaddr = client->our_ip;
1252    dhcpoa_init_no_end(&bsdp_options, bsdp_buf, sizeof(bsdp_buf));
1253    msgtype = bsdp_msgtype_list_e;
1254    if (dhcpoa_add(&bsdp_options, bsdptag_message_type_e,
1255		   sizeof(msgtype), &msgtype)
1256	!= dhcpoa_success_e) {
1257	fprintf(stderr, "BSDPClientSendListRequest add message type failed, %s",
1258		dhcpoa_err(&bsdp_options));
1259	goto failed;
1260    }
1261    if (dhcpoa_add(&bsdp_options, bsdptag_version_e,
1262		   sizeof(client->client_version),
1263		   &client->client_version) != dhcpoa_success_e) {
1264	fprintf(stderr, "BSDPClientSendListRequest add version failed, %s",
1265		dhcpoa_err(&bsdp_options));
1266	goto failed;
1267    }
1268    if (client->old_firmware == TRUE) {
1269	if (dhcpoa_add(&bsdp_options, bsdptag_netboot_1_0_firmware_e,
1270		       0, NULL) != dhcpoa_success_e) {
1271	    fprintf(stderr, "BSDPClientSendListRequest old_firmware failed, %s",
1272		    dhcpoa_err(&bsdp_options));
1273	    goto failed;
1274	}
1275    }
1276    if (dhcpoa_add(&bsdp_options, bsdptag_reply_port_e, sizeof(port),
1277		   &port) != dhcpoa_success_e) {
1278	fprintf(stderr, "BSDPClientSendListRequest add reply port failed, %s",
1279		dhcpoa_err(&bsdp_options));
1280	goto failed;
1281    }
1282    if (dhcpoa_add(&bsdp_options, bsdptag_max_message_size_e,
1283		   sizeof(max_message_size), &max_message_size)
1284	!= dhcpoa_success_e) {
1285	fprintf(stderr,
1286		"BSDPClientSendListRequest add max message size failed, %s",
1287		dhcpoa_err(&bsdp_options));
1288	goto failed;
1289    }
1290    if (client->n_attrs > 0) {
1291	if (dhcpoa_add(&bsdp_options,bsdptag_image_attributes_filter_list_e,
1292		       client->n_attrs * sizeof(client->attrs[0]),
1293		       client->attrs) != dhcpoa_success_e) {
1294	    fprintf(stderr,
1295		    "BSDPClientSendListRequest add image attributes failed, %s",
1296		    dhcpoa_err(&bsdp_options));
1297	    goto failed;
1298	}
1299    }
1300    if (dhcpoa_add(&options, dhcptag_vendor_specific_e,
1301		   dhcpoa_used(&bsdp_options), &bsdp_buf)
1302	!= dhcpoa_success_e) {
1303	fprintf(stderr,
1304		"BSDPClientSendListRequest add vendor specific failed, %s",
1305	       dhcpoa_err(&options));
1306	goto failed;
1307    }
1308    if (dhcpoa_add(&options, dhcptag_end_e, 0, NULL)
1309	!= dhcpoa_success_e) {
1310	fprintf(stderr,
1311		"BSDPClientSendListRequest add dhcp options end failed, %s",
1312		dhcpoa_err(&bsdp_options));
1313	goto failed;
1314    }
1315    request_size = sizeof(*request) + sizeof(rfc_magic)
1316	+ dhcpoa_used(&options);
1317    if (request_size < sizeof(struct bootp)) {
1318	/* pad out to BOOTP-sized packet */
1319	request_size = sizeof(struct bootp);
1320    }
1321    if (bootp_transmit(client->fd, (char *)client->send_buf,
1322		       if_name(client->if_p), ARPHRD_ETHER, NULL, 0,
1323		       ip_broadcast, client->our_ip,
1324		       IPPORT_BOOTPS, client->client_port,
1325		       request, request_size) < 0) {
1326	fprintf(stderr,
1327		"BSDPClientSendListRequest: bootp_transmit failed %s\n",
1328		strerror(errno));
1329	status = kBSDPClientStatusTransmitFailed;
1330	goto failed;
1331    }
1332    status = kBSDPClientStatusOK;
1333
1334 failed:
1335    return (status);
1336}
1337
1338static void
1339BSDPClientListTimeout(BSDPClientRef client)
1340{
1341    BSDPClientStatus	status = kBSDPClientStatusOK;
1342    struct timeval	t;
1343
1344    if (client->try == BSDPCLIENT_LIST_MAX_TRIES) {
1345	if (client->got_responses == FALSE) {
1346	    status = kBSDPClientStatusOperationTimedOut;
1347	    goto report_error;
1348	}
1349	return;
1350    }
1351    client->try++;
1352    client->wait_secs *= 2;
1353    if (client->wait_secs > BSDPCLIENT_MAX_WAIT_SECS) {
1354	client->wait_secs = BSDPCLIENT_MAX_WAIT_SECS;
1355    }
1356    status = BSDPClientSendListRequest(client);
1357    if (status != kBSDPClientStatusOK) {
1358	goto report_error;
1359    }
1360    t.tv_sec = client->wait_secs;
1361    t.tv_usec = random_range(0, USECS_PER_SEC - 1);
1362    BSDPClientSetTimer(client, t, BSDPClientListTimeout);
1363    return;
1364
1365 report_error:
1366    (*client->callback.func.list)(client, status, NULL, NULL,
1367				  NULL, client->callback.arg);
1368    return;
1369}
1370
1371BSDPClientStatus
1372BSDPClientList(BSDPClientRef client, BSDPClientListCallBack callback,
1373	       void * info)
1374{
1375    struct timeval	t;
1376    BSDPClientStatus	status = kBSDPClientStatusAllocationError;
1377
1378    client->state = kBSDPClientStateInit;
1379    BSDPClientCancelTimer(client);
1380    if (callback == NULL) {
1381	status = kBSDPClientStatusInvalidArgument;
1382	goto failed;
1383    }
1384    client->xid++;
1385    status = BSDPClientSendListRequest(client);
1386    if (status != kBSDPClientStatusOK) {
1387	goto failed;
1388    }
1389    client->state = kBSDPClientStateList;
1390    client->try = 1;
1391    client->got_responses = FALSE;
1392    client->callback.func.list = callback;
1393    client->callback.arg = info;
1394    client->wait_secs = BSDPCLIENT_INITIAL_TIMEOUT_SECS;
1395    t.tv_sec = client->wait_secs;
1396    t.tv_usec = random_range(0, USECS_PER_SEC - 1);
1397    BSDPClientSetTimer(client, t, BSDPClientListTimeout);
1398
1399 failed:
1400    return (status);
1401}
1402
1403/**
1404 ** BSDP Select Routines
1405 **/
1406
1407static void
1408BSDPClientProcessSelect(BSDPClientRef client, bsdp_msgtype_t bsdp_msg)
1409{
1410    BSDPClientStatus	status;
1411
1412    BSDPClientCancelTimer(client);
1413    if (bsdp_msg == bsdp_msgtype_select_e) {
1414	status = kBSDPClientStatusOK;
1415    }
1416    else {
1417	status = kBSDPClientStatusServerSentFailure;
1418    }
1419    (*client->callback.func.select)(client, status, client->callback.arg);
1420    return;
1421}
1422
1423static BSDPClientStatus
1424BSDPClientSendSelectRequest(BSDPClientRef client)
1425{
1426    char		bsdp_buf[DHCP_OPTION_SIZE_MAX];
1427    dhcpoa_t		bsdp_options;
1428    char		buf[DHCP_PACKET_MIN];
1429    bsdp_image_id_t	image_id = htonl(client->callback.image_identifier);
1430    struct in_addr	ip_broadcast;
1431    dhcpoa_t		options;
1432    unsigned char	msgtype;
1433    u_int16_t		port = htons(client->client_port);
1434    struct dhcp *	request;
1435    int 		request_size = 0;
1436    BSDPClientStatus	status = kBSDPClientStatusAllocationError;
1437
1438    ip_broadcast.s_addr = htonl(INADDR_BROADCAST);
1439    request = make_bsdp_request(client->system_id,
1440				(struct dhcp *)buf, sizeof(buf),
1441				dhcp_msgtype_inform_e,
1442				if_link_address(client->if_p),
1443				if_link_arptype(client->if_p),
1444				if_link_length(client->if_p),
1445				&options);
1446    if (request == NULL) {
1447	goto failed;
1448    }
1449    request->dp_xid = htonl(client->xid);
1450    request->dp_ciaddr = client->our_ip;
1451    dhcpoa_init_no_end(&bsdp_options, bsdp_buf, sizeof(bsdp_buf));
1452    msgtype = bsdp_msgtype_select_e;
1453    if (dhcpoa_add(&bsdp_options, bsdptag_message_type_e,
1454		   sizeof(msgtype), &msgtype)
1455	!= dhcpoa_success_e) {
1456	fprintf(stderr,
1457		"BSDPClientSendSelectRequest add message type failed, %s",
1458		dhcpoa_err(&bsdp_options));
1459	goto failed;
1460    }
1461    if (dhcpoa_add(&bsdp_options, bsdptag_version_e,
1462		   sizeof(client->client_version),
1463		   &client->client_version) != dhcpoa_success_e) {
1464	fprintf(stderr, "BSDPClientSendSelectRequest add version failed, %s",
1465		dhcpoa_err(&bsdp_options));
1466	goto failed;
1467    }
1468    if (client->old_firmware == TRUE) {
1469	if (dhcpoa_add(&bsdp_options, bsdptag_netboot_1_0_firmware_e,
1470		       0, NULL) != dhcpoa_success_e) {
1471	    fprintf(stderr, "BSDPClientSendListRequest old_firmware failed, %s",
1472		    dhcpoa_err(&bsdp_options));
1473	    goto failed;
1474	}
1475    }
1476    if (dhcpoa_add(&bsdp_options, bsdptag_reply_port_e, sizeof(port),
1477		   &port) != dhcpoa_success_e) {
1478	fprintf(stderr, "BSDPClientSendSelectRequest add reply port failed, %s",
1479		dhcpoa_err(&bsdp_options));
1480	goto failed;
1481    }
1482    if (dhcpoa_add(&bsdp_options, bsdptag_server_identifier_e,
1483		   sizeof(struct in_addr),
1484		   &client->callback.server_ip) != dhcpoa_success_e) {
1485	fprintf(stderr,
1486		"BSDPClientSendSelectRequest: add server identifier failed, %s",
1487		dhcpoa_err(&bsdp_options));
1488	goto failed;
1489    }
1490    if (dhcpoa_add(&bsdp_options, bsdptag_selected_boot_image_e,
1491		   sizeof(image_id), &image_id) != dhcpoa_success_e) {
1492	fprintf(stderr,
1493		"BSDPClientSendSelectRequest: add selected image failed, %s",
1494		dhcpoa_err(&bsdp_options));
1495	goto failed;
1496    }
1497    if (dhcpoa_add(&options, dhcptag_vendor_specific_e,
1498		   dhcpoa_used(&bsdp_options), &bsdp_buf)
1499	!= dhcpoa_success_e) {
1500	fprintf(stderr,
1501		"BSDPClientSendSelectRequest add vendor specific failed, %s",
1502	       dhcpoa_err(&options));
1503	goto failed;
1504    }
1505    if (dhcpoa_add(&options, dhcptag_end_e, 0, NULL)
1506	!= dhcpoa_success_e) {
1507	fprintf(stderr,
1508		"BSDPClientSendSelectRequest add dhcp options end failed, %s",
1509		dhcpoa_err(&bsdp_options));
1510	goto failed;
1511    }
1512    request_size = sizeof(*request) + sizeof(rfc_magic)
1513	+ dhcpoa_used(&options);
1514    if (request_size < sizeof(struct bootp)) {
1515	/* pad out to BOOTP-sized packet */
1516	request_size = sizeof(struct bootp);
1517    }
1518    /* send the packet */
1519    if (bootp_transmit(client->fd, (char *)client->send_buf,
1520		       if_name(client->if_p), ARPHRD_ETHER, NULL, 0,
1521		       ip_broadcast, client->our_ip,
1522		       IPPORT_BOOTPS, client->client_port,
1523		       request, request_size) < 0) {
1524	fprintf(stderr,
1525		"BSDPClientSendSelectRequest: bootp_transmit failed %s\n",
1526		strerror(errno));
1527	status = kBSDPClientStatusTransmitFailed;
1528	goto failed;
1529    }
1530    status = kBSDPClientStatusOK;
1531
1532 failed:
1533    return (status);
1534}
1535
1536static void
1537BSDPClientSelectTimeout(BSDPClientRef client)
1538{
1539    BSDPClientStatus	status = kBSDPClientStatusOK;
1540    struct timeval	t;
1541
1542    if (client->try == BSDPCLIENT_SELECT_MAX_TRIES) {
1543	status = kBSDPClientStatusOperationTimedOut;
1544	goto report_error;
1545    }
1546    client->try++;
1547    client->wait_secs *= 2;
1548    status = BSDPClientSendSelectRequest(client);
1549    if (status != kBSDPClientStatusOK) {
1550	goto report_error;
1551    }
1552    t.tv_sec = client->wait_secs;
1553    t.tv_usec = 0;
1554    BSDPClientSetTimer(client, t, BSDPClientSelectTimeout);
1555    return;
1556
1557 report_error:
1558    (*client->callback.func.select)(client, status, client->callback.arg);
1559    return;
1560}
1561
1562BSDPClientStatus
1563BSDPClientSelect(BSDPClientRef client,
1564		 CFStringRef ServerAddress,
1565		 CFNumberRef Identifier,
1566		 BSDPClientSelectCallBack callback, void * info)
1567{
1568    unsigned long	image_identifier;
1569    struct timeval	t;
1570    BSDPClientStatus	status = kBSDPClientStatusAllocationError;
1571
1572    (void)my_CFStringToIPAddress(ServerAddress, &client->callback.server_ip);
1573    client->state = kBSDPClientStateInit;
1574    BSDPClientCancelTimer(client);
1575    if (callback == NULL
1576	|| CFNumberGetValue(Identifier, kCFNumberLongType,
1577			    &image_identifier) == FALSE) {
1578	status = kBSDPClientStatusInvalidArgument;
1579	goto failed;
1580    }
1581    client->xid++;
1582    client->callback.image_identifier = image_identifier;
1583    status = BSDPClientSendSelectRequest(client);
1584    if (status != kBSDPClientStatusOK) {
1585	goto failed;
1586    }
1587    client->state = kBSDPClientStateSelect;
1588    client->try = 1;
1589    client->callback.func.select = callback;
1590    client->callback.arg = info;
1591    client->wait_secs = BSDPCLIENT_INITIAL_TIMEOUT_SECS;
1592    t.tv_sec = client->wait_secs;
1593    t.tv_usec = 0;
1594    BSDPClientSetTimer(client, t, BSDPClientSelectTimeout);
1595
1596 failed:
1597    return (status);
1598}
1599
1600BSDPClientStatus
1601BSPPClientSelect(BSDPClientRef client,
1602		 CFStringRef ServerAddress,
1603		 CFNumberRef Identifier,
1604		 BSDPClientSelectCallBack callback, void * info)
1605{
1606    return (BSDPClientSelect(client, ServerAddress, Identifier,
1607			     callback, info));
1608}
1609
1610#ifdef TEST_BAD_SYSID
1611static void bad_sysid_callback(BSDPClientRef client,
1612			       BSDPClientStatus status,
1613			       CFStringRef ServerAddress,
1614			       CFNumberRef ServerPriority,
1615			       CFArrayRef list,
1616			       void *info)
1617{
1618    return;
1619}
1620
1621
1622int
1623main(int argc, char * argv[])
1624{
1625    BSDPClientStatus	status;
1626    char *		if_name = "en0";
1627    BSDPClientRef	client;
1628
1629    if (argc > 1) {
1630	if_name = argv[1];
1631    }
1632
1633    client = BSDPClientCreateWithInterface(&status, if_name);
1634    if (client == NULL) {
1635	fprintf(stderr, "BSDPClientCreateWithInterface(%s) failed: %s\n",
1636		if_name, BSDPClientStatusString(status));
1637	exit(2);
1638    }
1639    status = BSDPClientList(client, bad_sysid_callback, NULL);
1640    CFRunLoopRun();
1641    exit (0);
1642    return (0);
1643}
1644#endif /* TEST_BAD_SYSID */
1645