1/*
2 * Copyright (c) 2001-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 * nbimages.c
26 * - NetBoot image list routines
27 */
28
29#include <stdio.h>
30#include <unistd.h>
31#include <stdlib.h>
32#include <sys/errno.h>
33#include <sys/types.h>
34#include <sys/param.h>
35#include <sys/stat.h>
36#include <sys/fcntl.h>
37#include <string.h>
38#include <sys/syslimits.h>
39#include <dirent.h>
40#include <syslog.h>
41
42#include "dynarray.h"
43#include "nbimages.h"
44#include "cfutil.h"
45#include "util.h"
46#include "NetBootServer.h"
47#include "NetBootImageInfo.h"
48
49#include <arpa/inet.h>
50#include <netdb.h>
51
52#include <CoreFoundation/CFURL.h>
53#include <SystemConfiguration/SCValidation.h>
54
55#ifdef TEST_NBIMAGES
56#define CHECK_TOTAL_SPACE 1
57#endif /* TEST_NBIMAGES */
58
59struct NBImageList_s {
60    dynarray_t		list;
61};
62
63extern void
64my_log(int priority, const char *message, ...);
65
66static int
67cfstring_to_cstring(CFStringRef cfstr, char * str, int len)
68{
69    if (CFStringGetCString(cfstr, str, len, kCFStringEncodingUTF8)) {
70	return (TRUE);
71    }
72    *str = '\0';
73    return (FALSE);
74}
75
76/*
77 * Function: find_colon
78 * Purpose:
79 *   Find the next unescaped instance of the colon character.
80 */
81static __inline__ char *
82find_colon(char * str)
83{
84    char * start = str;
85    char * colon;
86
87    while ((colon = strchr(start, ':')) != NULL) {
88	if (colon == start) {
89	    break;
90	}
91	if (colon[-1] != '\\')
92	    break;
93	start = colon;
94    }
95    return (colon);
96}
97
98/*
99 * Function: parse_nfs_path
100 * Purpose:
101 *   Parse a string of the form:
102 *        "<IP | hostname>:<mount>[:<image_path>]"
103 *   into the given ip address, mount point, and optionally, image_path.
104 * Notes:
105 * - the passed in string is modified i.e. ':' is replaced by '\0'
106 * - literal colons must be escaped with a backslash
107 *
108 * Examples:
109 * 17.202.42.112:/Library/NetBoot/NetBootSP0:Jaguar/Jaguar.dmg
110 * siegdi6:/Volumes/Foo\:/Library/NetBoot/NetBootSP0:Jaguar/Jaguar.dmg
111 */
112static __inline__ boolean_t
113parse_nfs_path(char * path, struct in_addr * iaddr_p,
114	       char * * mount_dir, char * * image_path)
115{
116    char *	start;
117    char *	colon;
118
119    /* IP address */
120    start = path;
121    colon = strchr(start, ':');
122    if (colon == NULL) {
123	return (FALSE);
124    }
125    *colon = '\0';
126    if (inet_aton(start, iaddr_p) != 1) {
127	struct in_addr * * 	addr;
128	struct hostent * 	ent;
129
130	ent = gethostbyname(start);
131	if (ent == NULL) {
132	    return (FALSE);
133	}
134	addr = (struct in_addr * *)ent->h_addr_list;
135	if (*addr == NULL)
136	    return (FALSE);
137	*iaddr_p = **addr;
138    }
139
140    /* mount point */
141    start = colon + 1;
142    colon = find_colon(start);
143    *mount_dir = start;
144    if (colon == NULL) {
145	*image_path = NULL;
146    }
147    else {
148	/* image path */
149	*colon = '\0';
150	start = colon + 1;
151	(void)find_colon(start);
152	*image_path = start;
153    }
154    return (TRUE);
155}
156
157/*
158 * Function: parse_http_path
159 * Purpose:
160 *   Parse a string of the form:
161 *        "http://[user:password@]<IP | hostname>[:port]/<image_path>"
162 *   into the given ip address, image_path, and option user/password and port
163 * Notes:
164 * - the passed in string is modified i.e. ':' is replaced by '\0'
165 *
166 * Examples:
167 * http://17.203.12.194:8080/NetBootSP0/Jaguar/Jaguar.dmg
168 * http://foo:bar@17.203.12.194/NetBootSP0/Jaguar/Jaguar.dmg
169 */
170
171static __inline__ boolean_t
172parse_http_path(char * path, struct in_addr * iaddr_p,
173		char * * username, char * * password, int * port,
174		char * * image_path)
175{
176    char *	atchar;
177    char *      colon;
178    char *      slash;
179    char *	start;
180
181    *username = NULL;
182    *password = NULL;
183    *port = 0;
184
185#define HTTP_URL_PREFIX		"http://"
186#define HTTP_URL_PREFIX_LEN	7
187
188    /* scheme */
189    start = path;
190    if (strncmp(HTTP_URL_PREFIX, start, HTTP_URL_PREFIX_LEN) != 0) {
191	return (FALSE);
192    }
193    start += HTTP_URL_PREFIX_LEN;
194
195    /* look for start of image path */
196    slash = strchr(start, '/');
197    if (slash == NULL) {
198	return (FALSE);
199    }
200    *slash = '\0';
201    *image_path = slash + 1;
202
203    /* check for optional username:password@... */
204    atchar = strchr(start, '@');
205    if (atchar != NULL && atchar < slash) {
206	*atchar = '\0';
207	*username = start;
208	*password = strsep(username, ":");
209	if (*password == NULL) {
210	    /* both username and password need to specified */
211	    return (FALSE);
212	}
213	start = atchar + 1;
214    }
215
216    /* check for optional port in server_name_or_ip[:port] */
217    colon = strchr(start, ':');
218    if (colon) {
219	*colon = '\0';
220	*port = atoi(colon + 1);
221    }
222
223    /* if the server specification isn't an IP address, look it up by name */
224    if (inet_aton(start, iaddr_p) != 1) {
225	struct in_addr * * 	addr;
226	struct hostent * 	ent;
227
228	ent = gethostbyname(start);
229	if (ent == NULL) {
230	    return (FALSE);
231	}
232	addr = (struct in_addr * *)ent->h_addr_list;
233	if (*addr == NULL)
234	    return (FALSE);
235	*iaddr_p = **addr;
236    }
237    return (TRUE);
238}
239
240int
241NBImageList_count(NBImageListRef image_list)
242{
243    dynarray_t *	dlist = &image_list->list;
244
245    return (dynarray_count(dlist));
246}
247
248NBImageEntryRef
249NBImageList_element(NBImageListRef image_list, int i)
250{
251    dynarray_t *	dlist = &image_list->list;
252
253    return (dynarray_element(dlist, i));
254}
255
256
257NBImageEntryRef
258NBImageList_elementWithID(NBImageListRef image_list, bsdp_image_id_t image_id)
259{
260    dynarray_t *	dlist = &image_list->list;
261    int 		i;
262    int			count;
263
264    count = dynarray_count(dlist);
265    for (i = 0; i < count; i++) {
266	NBImageEntryRef	entry = dynarray_element(dlist, i);
267
268	if (image_id == entry->image_id) {
269	    return (entry);
270	}
271    }
272    return (NULL);
273}
274
275void
276NBImageList_free(NBImageListRef * l)
277{
278    NBImageListRef	image_list;
279
280    if (l == NULL) {
281	return;
282    }
283    image_list = *l;
284    if (image_list == NULL) {
285	return;
286    }
287    dynarray_free(&image_list->list);
288    free(image_list);
289    *l = NULL;
290    return;
291}
292
293static boolean_t
294stat_file(const char * dir, const char * file)
295{
296    char		path[PATH_MAX];
297    struct stat		sb;
298
299    snprintf(path, sizeof(path), "%s/%s", dir, file);
300    if (stat(path, &sb) < 0) {
301#if 0
302	fprintf(stderr, "stat %s failed, %s\n",
303		path, strerror(errno));
304#endif /* 0 */
305	return (FALSE);
306    }
307    if ((sb.st_mode & S_IFREG) == 0) {
308#if 0
309	fprintf(stderr, "%s is not a file\n", path);
310#endif /* 0 */
311	return (FALSE);
312    }
313    return (TRUE);
314}
315
316static const char *
317NBImageTypeStr(NBImageType type)
318{
319    switch (type) {
320    case kNBImageTypeClassic:
321	return "Classic";
322    case kNBImageTypeNFS:
323	return "NFS";
324    case kNBImageTypeHTTP:
325	return "HTTP";
326    case kNBImageTypeBootFileOnly:
327	return "BootFile";
328    default:
329	return "<unknown>";
330    }
331}
332
333static void
334dump_strlist(const char * * strlist, int count)
335{
336    int i;
337
338    for (i = 0; i < count; i++) {
339	printf("%s%s", (i != 0) ? ", " : "", strlist[i]);
340    }
341    return;
342}
343
344static void
345dump_ether_list(const struct ether_addr * list, int count)
346{
347    int i;
348
349    for (i = 0; i < count; i++) {
350	printf("%s%s", (i != 0) ? ", " : "", ether_ntoa(list + i));
351    }
352    return;
353}
354
355static void
356NBImageEntry_print(NBImageEntryRef entry)
357{
358    int		i;
359
360    printf("%-12s %-35s %-35s 0x%08x %-9s %-12s",
361	   entry->sharepoint->name,
362	   entry->dir_name_esc,
363	   entry->name,
364	   entry->image_id,
365	   NBImageTypeStr(entry->type),
366	   entry->bootfile);
367    switch (entry->type) {
368    case kNBImageTypeClassic:
369	printf(" %-12s", entry->type_info.classic.shared);
370	if (entry->type_info.classic.private != NULL) {
371	    printf(" %-12s", entry->type_info.classic.private);
372	}
373	break;
374    case kNBImageTypeNFS:
375	printf(" %-12s%s", entry->type_info.nfs.root_path,
376	       (entry->type_info.nfs.indirect == TRUE)? " [indirect]" : "");
377	break;
378    case kNBImageTypeHTTP:
379	printf(" %-12s%s",
380	       entry->type_info.http.root_path_esc,
381	       (entry->type_info.http.indirect == TRUE)? " [indirect]" : "");
382	break;
383    default:
384	break;
385    }
386    printf(" [");
387    for (i = 0; i < entry->archlist_count; i++) {
388	printf("%s%s", (i > 0) ? ", " : "",
389	       entry->archlist[i]);
390    }
391    printf("]");
392    if (entry->sysids != NULL) {
393	printf(" [ ");
394	dump_strlist(entry->sysids, entry->sysids_count);
395	printf(" ]");
396    }
397    if (entry->disabled_mac_addresses != NULL) {
398	printf(" -[ ");
399	dump_ether_list(entry->disabled_mac_addresses,
400			entry->disabled_mac_addresses_count);
401	printf(" ]-");
402    }
403    if (entry->enabled_mac_addresses != NULL) {
404	printf(" +[ ");
405	dump_ether_list(entry->enabled_mac_addresses,
406			entry->enabled_mac_addresses_count);
407	printf(" ]+");
408    }
409    if (entry->filter_only) {
410	printf(" <filter>");
411    }
412    if (entry->diskless) {
413	printf(" <diskless>");
414    }
415    printf("\n");
416    return;
417}
418
419static int
420escape_path(const char * path, int path_len,
421	    char * escaped_path, int escaped_path_len)
422{
423    CFDataRef	data = NULL;
424    int		data_len;
425    int		len = 0;
426    CFURLRef	url = NULL;
427
428    url = CFURLCreateFromFileSystemRepresentation(NULL, (const UInt8 *)path,
429						  path_len, FALSE);
430    if (url == NULL) {
431	goto done;
432    }
433    data = CFURLCreateData(NULL, url, kCFStringEncodingUTF8, TRUE);
434    if (data == NULL) {
435	goto done;
436    }
437    data_len = CFDataGetLength(data);
438    if (data_len >= escaped_path_len) {
439	/* would truncate, so don't bother */
440	goto done;
441    }
442    if (data_len == path_len
443	&& bcmp(CFDataGetBytePtr(data), path, path_len) == 0) {
444	/* exactly the same string, no need to escape */
445	goto done;
446    }
447    bcopy(CFDataGetBytePtr(data), escaped_path, data_len);
448    escaped_path[data_len] = '\0';
449    len = data_len;
450
451 done:
452    if (data != NULL) {
453	CFRelease(data);
454    }
455    if (url != NULL) {
456	CFRelease(url);
457    }
458    return (len);
459}
460
461static boolean_t
462S_get_plist_boolean(CFDictionaryRef plist, CFStringRef key, boolean_t d)
463{
464    CFBooleanRef	b;
465    boolean_t 		ret = d;
466
467    b = CFDictionaryGetValue(plist, key);
468    if (isA_CFBoolean(b) != NULL) {
469	ret = CFBooleanGetValue(b);
470    }
471    return (ret);
472}
473
474static int
475my_ptrstrcmp(const void * v1, const void * v2)
476{
477    const char * * s1 = (const char * *)v1;
478    const char * * s2 = (const char * *)v2;
479
480    return (strcmp(*s1, *s2));
481}
482
483static struct in_addr
484cfhost_to_ip(CFStringRef host)
485{
486    struct in_addr	iaddr = { 0 };
487    char		tmp[PATH_MAX];
488
489    if (isA_CFString(host) == NULL) {
490	goto done;
491    }
492    if (cfstring_to_cstring(host, tmp, sizeof(tmp)) == FALSE) {
493	goto done;
494    }
495
496    /* if the server specification isn't an IP address, look it up by name */
497    if (inet_aton(tmp, &iaddr) != 1) {
498	struct in_addr * * 	addr;
499	struct hostent * 	ent;
500
501	ent = gethostbyname(tmp);
502	if (ent == NULL) {
503	    goto done;
504	}
505	addr = (struct in_addr * *)ent->h_addr_list;
506	if (*addr == NULL) {
507	    goto done;
508	}
509	iaddr = **addr;
510    }
511
512 done:
513    return (iaddr);
514}
515
516typedef int (*qsort_compare_func_t)(const void *, const void *);
517
518static NBImageEntryRef
519NBImageEntry_create(NBSPEntryRef sharepoint, char * dir_name,
520		    char * dir_path, char * info_plist_path,
521		    boolean_t allow_diskless)
522{
523    CFArrayRef		archlist_prop;
524    int			archlist_space = 0;
525    u_int16_t		attr = 0;
526    CFStringRef		bootfile_prop;
527    int			bootfile_space = 0;
528    char		dir_name_esc[PATH_MAX];
529    int			dir_name_esc_len;
530    int			dir_name_len;
531    CFArrayRef		disabled_mac_prop = NULL;
532    int			disabled_mac_space = 0;
533    boolean_t		diskless;
534    NBImageEntryRef	entry = NULL;
535    CFArrayRef		enabled_mac_prop = NULL;
536    int			enabled_mac_space = 0;
537    boolean_t		filter_only;
538    int 		i;
539    int32_t		idx_val = -1;
540    CFNumberRef		idx;
541    char *		image_file = NULL;
542    boolean_t		image_is_default;
543    boolean_t		indirect = FALSE;
544    CFNumberRef		kind;
545    int32_t		kind_val = -1;
546    char *		mount_point = NULL;
547    CFStringRef		name_prop;
548    int			name_space;
549    char *		offset;
550    CFPropertyListRef	plist;
551    CFStringRef		private_prop = NULL;
552    int			private_space = 0;
553    char		root_path[PATH_MAX];
554    int			root_path_len = 0;
555    char		root_path_esc[PATH_MAX];
556    int			root_path_esc_len = 0;
557    CFStringRef		root_path_prop = NULL;
558    struct in_addr	server_ip;
559    char *              server_password;
560    int                 server_port;
561    char *              server_username;
562    CFStringRef		shared_prop = NULL;
563    int			shared_space = 0;
564    int			tail_space = 0;
565    int			sysids_space = 0;
566    CFArrayRef		sysids_prop;
567    char		tmp[PATH_MAX];
568    CFStringRef		type;
569    NBImageType		type_val = kNBImageTypeNone;
570
571    /* space for directory name */
572    dir_name_len = strlen(dir_name);
573    tail_space += dir_name_len + 1;
574
575    plist = my_CFPropertyListCreateFromFile(info_plist_path);
576    if (isA_CFDictionary(plist) == NULL) {
577	goto failed;
578    }
579    if (S_get_plist_boolean(plist, kNetBootImageInfoIsEnabled, TRUE) == FALSE) {
580	/* image is disabled */
581	goto failed;
582    }
583    if (S_get_plist_boolean(plist, kNetBootImageInfoIsInstall, FALSE) == TRUE) {
584	attr |= BSDP_IMAGE_ATTRIBUTES_INSTALL;
585    }
586    image_is_default = S_get_plist_boolean(plist, kNetBootImageInfoIsDefault,
587					   FALSE);
588    diskless = S_get_plist_boolean(plist, kNetBootImageInfoSupportsDiskless,
589				   FALSE);
590    if (allow_diskless == FALSE) {
591	/* ignore diskless setting if not allowed */
592	diskless = FALSE;
593    }
594    filter_only = S_get_plist_boolean(plist, kNetBootImageInfoFilterOnly,
595				      FALSE);
596    name_prop = CFDictionaryGetValue(plist, kNetBootImageInfoName);
597    if (isA_CFString(name_prop) == NULL) {
598	fprintf(stderr, "missing/invalid Name property\n");
599	goto failed;
600    }
601    name_space = my_CFStringToCStringAndLengthExt(name_prop, NULL, 0, TRUE);
602    if (name_space <= 1) {
603	printf("empty Name property\n");
604	goto failed;
605    }
606    tail_space += name_space;
607
608    idx = CFDictionaryGetValue(plist, kNetBootImageInfoIndex);
609    if (isA_CFNumber(idx) == NULL
610	|| CFNumberGetValue(idx, kCFNumberSInt32Type, &idx_val) == FALSE
611	|| idx_val <= 0 || idx_val > BSDP_IMAGE_INDEX_MAX) {
612	fprintf(stderr, "missing/invalid Index property\n");
613	goto failed;
614    }
615    kind = CFDictionaryGetValue(plist, kNetBootImageInfoKind);
616    if (isA_CFNumber(kind) != NULL) {
617	if (CFNumberGetValue(kind, kCFNumberSInt32Type, &kind_val) == FALSE
618	    || kind_val < 0 || kind_val > BSDP_IMAGE_ATTRIBUTES_KIND_MAX) {
619	    kind_val = -1;
620	}
621    }
622    type = CFDictionaryGetValue(plist, kNetBootImageInfoType);
623    if (isA_CFString(type) == NULL) {
624	fprintf(stderr, "missing/invalid Type property\n");
625	goto failed;
626    }
627
628    if (CFEqual(type, kNetBootImageInfoTypeClassic)) {
629	if (allow_diskless == FALSE) {
630	    fprintf(stderr,
631		    "ignoring Classic image: diskless resources unavailable\n");
632	    goto failed;
633	}
634	type_val = kNBImageTypeClassic;
635	if (kind_val == -1) {
636	    kind_val = bsdp_image_kind_MacOS9;
637	}
638	diskless = TRUE;	/* Mac OS 9 requires diskless */
639    }
640    else if (CFEqual(type, kNetBootImageInfoTypeNFS)) {
641	type_val = kNBImageTypeNFS;
642	if (kind_val == -1) {
643	    kind_val = bsdp_image_kind_MacOSX;
644	}
645    }
646    else if (CFEqual(type, kNetBootImageInfoTypeHTTP)) {
647	type_val = kNBImageTypeHTTP;
648	if (kind_val == -1) {
649	    kind_val = bsdp_image_kind_MacOSX;
650	}
651    }
652    else if (CFEqual(type, kNetBootImageInfoTypeBootFileOnly)) {
653	type_val = kNBImageTypeBootFileOnly;
654	diskless = FALSE;
655    }
656    if (type_val == kNBImageTypeNone) {
657	fprintf(stderr, "unrecognized Type property\n");
658	goto failed;
659    }
660    if (kind_val == -1) {
661	fprintf(stderr, "missing/unrecognized Kind value\n");
662	goto failed;
663    }
664    if (kind_val == bsdp_image_kind_Diagnostics) {
665	/* if FilterOnly was not set, set it to TRUE */
666	if (CFDictionaryContainsKey(plist,
667				    kNetBootImageInfoFilterOnly) == FALSE) {
668	    filter_only = TRUE;
669	}
670    }
671    attr |= bsdp_image_attributes_from_kind(kind_val);
672
673    /* space for escaped directory name */
674    if (type_val == kNBImageTypeHTTP) {
675	dir_name_esc_len = escape_path(dir_name, dir_name_len,
676				       dir_name_esc, sizeof(dir_name_esc));
677	if (dir_name_esc_len != 0) {
678	    tail_space += dir_name_esc_len + 1;
679	}
680    }
681    else {
682	dir_name_esc_len = 0;
683    }
684
685    /* architectures */
686    archlist_prop = CFDictionaryGetValue(plist, kNetBootImageInfoArchitectures);
687    if (archlist_prop != NULL) {
688	int archlist_count;
689
690	if (my_CFStringArrayToCStringArray(archlist_prop,
691					   NULL, &archlist_space,
692					   &archlist_count) == FALSE) {
693	    fprintf(stderr,
694		    "Couldn't calculate Archlist length\n");
695	    goto failed;
696	}
697	if (archlist_count == 0) {
698	    fprintf(stderr,
699		    "Empty ArchList array");
700	    goto failed;
701	}
702	tail_space += archlist_space;
703    }
704
705    /* bootfile */
706    bootfile_prop = CFDictionaryGetValue(plist, kNetBootImageInfoBootFile);
707    if (bootfile_prop != NULL && isA_CFString(bootfile_prop) == NULL) {
708	fprintf(stderr, "invalid BootFile property\n");
709	goto failed;
710    }
711    if (bootfile_prop == NULL) {
712	fprintf(stderr, "no BootFile property specified\n");
713	goto failed;
714    }
715    bootfile_space = my_CFStringToCStringAndLength(bootfile_prop, NULL, 0);
716    tail_space += bootfile_space;
717
718    /* supported system ids */
719    sysids_prop
720	= CFDictionaryGetValue(plist,
721			       kNetBootImageInfoEnabledSystemIdentifiers);
722    if (sysids_prop != NULL) {
723	int sysids_count;
724
725	if (isA_CFArray(sysids_prop) == NULL) {
726	    fprintf(stderr, "EnabledSystemIdentifiers isn't an array\n");
727	    goto failed;
728	}
729	if (my_CFStringArrayToCStringArray(sysids_prop, NULL, &sysids_space,
730					   &sysids_count) == FALSE) {
731	    fprintf(stderr,
732		    "Couldn't calculate EnabledSystemIdentifiers length\n");
733	    goto failed;
734	}
735	if (sysids_count == 0) {
736	    /* if the list is empty, treat it as if it were not there at all */
737	    sysids_prop = NULL;
738	}
739	else {
740	    tail_space += sysids_space;
741	}
742    }
743
744    /* enabled MAC addresses */
745    enabled_mac_prop = CFDictionaryGetValue(plist,
746					    kNetBootImageInfoEnabledMACAddresses);
747    if (enabled_mac_prop != NULL) {
748	int	enabled_mac_count;
749
750	if (isA_CFArray(enabled_mac_prop) == NULL) {
751	    fprintf(stderr, "EnabledMACAddresses isn't an array\n");
752	    goto failed;
753	}
754	if (my_CFStringArrayToEtherArray(enabled_mac_prop, NULL,
755					 &enabled_mac_space,
756					 &enabled_mac_count) == FALSE) {
757	    fprintf(stderr,
758		    "Couldn't calculate EnabledMACAddresses length\n");
759	    goto failed;
760	}
761	if (enabled_mac_count == 0) {
762	    enabled_mac_prop = NULL;
763	}
764	else {
765	    tail_space += enabled_mac_space;
766	}
767    }
768
769    /* disabled MAC addresses */
770    disabled_mac_prop = CFDictionaryGetValue(plist,
771					    kNetBootImageInfoDisabledMACAddresses);
772    if (disabled_mac_prop != NULL) {
773	int	disabled_mac_count;
774
775	if (isA_CFArray(disabled_mac_prop) == NULL) {
776	    fprintf(stderr, "DisabledMACAddresses isn't an array\n");
777	    goto failed;
778	}
779	if (my_CFStringArrayToEtherArray(disabled_mac_prop, NULL,
780					 &disabled_mac_space,
781					 &disabled_mac_count) == FALSE) {
782	    fprintf(stderr,
783		    "Couldn't calculate DisabledMACAddresses length\n");
784	    goto failed;
785	}
786	if (disabled_mac_count == 0) {
787	    disabled_mac_prop = NULL;
788	}
789	else {
790	    tail_space += disabled_mac_space;
791	}
792    }
793
794    switch (type_val) {
795    case kNBImageTypeClassic:
796	/* must have Shared */
797	shared_prop = CFDictionaryGetValue(plist, kNetBootImageInfoSharedImage);
798	if (isA_CFString(shared_prop) == NULL) {
799	    fprintf(stderr, "missing/invalid SharedImage property\n");
800	    goto failed;
801	}
802	shared_space = my_CFStringToCStringAndLength(shared_prop, tmp,
803						     sizeof(tmp));
804	if (stat_file(dir_path, tmp) == FALSE) {
805	    fprintf(stderr, "SharedImage does not exist\n");
806	    goto failed;
807	}
808	tail_space += shared_space;
809
810	/* may have Private */
811	private_prop
812	    = isA_CFString(CFDictionaryGetValue(plist,
813						kNetBootImageInfoPrivateImage));
814	if (private_prop != NULL) {
815	    private_space = my_CFStringToCStringAndLength(private_prop, tmp,
816							  sizeof(tmp));
817	    if (stat_file(dir_path, tmp)) {
818		tail_space += private_space;
819	    }
820	    else {
821		private_prop = NULL;
822	    }
823	}
824	break;
825    case kNBImageTypeNFS:
826	/* must have RootPath */
827	root_path_prop = CFDictionaryGetValue(plist, kNetBootImageInfoRootPath);
828	if (isA_CFString(root_path_prop) == NULL) {
829	    fprintf(stderr, "missing/invalid RootPath property\n");
830	    goto failed;
831	}
832	if (cfstring_to_cstring(root_path_prop, tmp, sizeof(tmp)) == FALSE) {
833	    fprintf(stderr, "RootPath could not be converted\n");
834	    goto failed;
835	}
836	if (stat_file(dir_path, tmp) == TRUE) {
837	    strlcpy(root_path, tmp, sizeof(root_path));
838	}
839	else if (parse_nfs_path(tmp, &server_ip, &mount_point,
840				&image_file) == TRUE) {
841	    if (image_file) {
842		snprintf(root_path, sizeof(root_path), "nfs:%s:%s:%s",
843			 inet_ntoa(server_ip), mount_point,
844			 image_file);
845	    }
846	    else {
847		snprintf(root_path, sizeof(root_path), "nfs:%s:%s",
848			 inet_ntoa(server_ip), mount_point);
849	    }
850	    indirect = TRUE;
851	}
852	else {
853	    goto failed;
854	}
855	root_path_len = strlen(root_path);
856	tail_space += root_path_len + 1;
857	break;
858    case kNBImageTypeHTTP:
859	/* must have RootPath */
860	root_path_prop = CFDictionaryGetValue(plist, kNetBootImageInfoRootPath);
861	if (isA_CFString(root_path_prop) == NULL) {
862	    fprintf(stderr, "missing/invalid RootPath property\n");
863	    goto failed;
864	}
865	if (cfstring_to_cstring(root_path_prop, tmp, sizeof(tmp)) == FALSE) {
866	    fprintf(stderr, "RootPath could not be converted\n");
867	    goto failed;
868	}
869	if (stat_file(dir_path, tmp) == TRUE) {
870	    strlcpy(root_path, tmp, sizeof(root_path));
871	    root_path_len = strlen(root_path);
872	    root_path_esc_len = escape_path(root_path, root_path_len,
873					    root_path_esc,
874					    sizeof(root_path_esc));
875	}
876	else if (parse_http_path(tmp, &server_ip, &server_username,
877				 &server_password, &server_port,
878				 &image_file) == TRUE) {
879	    if (server_username && server_password) {
880		if (server_port != 0) {
881		    snprintf(root_path, sizeof(root_path),
882			     "http://%s:%s@%s:%d/%s",
883			     server_username, server_password,
884			     inet_ntoa(server_ip),
885			     server_port, image_file);
886		}
887		else {
888		    snprintf(root_path, sizeof(root_path), "http://%s:%s@%s/%s",
889			     server_username, server_password,
890			     inet_ntoa(server_ip),
891			     image_file);
892		}
893	    }
894	    else {
895		if (server_port != 0) {
896		    snprintf(root_path, sizeof(root_path), "http://%s:%d/%s",
897			     inet_ntoa(server_ip), server_port,
898			     image_file);
899		}
900		else {
901		    snprintf(root_path, sizeof(root_path), "http://%s/%s",
902			     inet_ntoa(server_ip), image_file);
903		}
904	    }
905	    root_path_len = strlen(root_path);
906	    indirect = TRUE;
907	}
908	else {
909	    goto failed;
910	}
911	tail_space += root_path_len + 1;
912	if (root_path_esc_len != 0) {
913	    tail_space += root_path_esc_len + 1;
914	}
915	break;
916    case kNBImageTypeBootFileOnly:
917    default:
918	break;
919    }
920
921    entry = (NBImageEntryRef)malloc(sizeof(*entry) + tail_space);
922    if (entry == NULL) {
923	goto failed;
924    }
925    bzero(entry, sizeof(*entry));
926    entry->image_id = bsdp_image_id_make(idx_val, attr);
927    entry->type = type_val;
928    entry->is_default = image_is_default;
929    entry->diskless = diskless;
930    entry->filter_only = filter_only;
931    entry->load_balance_ip
932	= cfhost_to_ip(CFDictionaryGetValue(plist,
933					    kNetBootImageLoadBalanceServer));
934
935    offset = (char *)(entry + 1);
936
937    /* do pointer arrays + strings first */
938
939    /* archlist */
940    if (archlist_prop != NULL) {
941	entry->archlist = (const char * *)offset;
942	(void)my_CFStringArrayToCStringArray(archlist_prop, offset,
943					     &archlist_space,
944					     &entry->archlist_count);
945	offset += archlist_space;
946    }
947    else {
948	entry->arch = "ppc";
949	entry->archlist = &entry->arch;
950	entry->archlist_count = 1;
951    }
952
953    /* supported system ids */
954    if (sysids_prop != NULL) {
955	entry->sysids = (const char * *)offset;
956	(void)my_CFStringArrayToCStringArray(sysids_prop, offset,
957					     &sysids_space,
958					     &entry->sysids_count);
959	qsort(entry->sysids, entry->sysids_count, sizeof(char *),
960	      my_ptrstrcmp);
961	offset += sysids_space;
962    }
963
964    /* enabled MAC addresses */
965    if (enabled_mac_prop != NULL) {
966	entry->enabled_mac_addresses = (const struct ether_addr *)offset;
967	(void)my_CFStringArrayToEtherArray(enabled_mac_prop, offset,
968					   &enabled_mac_space,
969					   &entry->enabled_mac_addresses_count);
970	qsort((void *)entry->enabled_mac_addresses,
971	      entry->enabled_mac_addresses_count,
972	      sizeof(*entry->enabled_mac_addresses),
973	      (qsort_compare_func_t)ether_cmp);
974	offset += enabled_mac_space;
975    }
976
977    /* disabled MAC addresses */
978    if (disabled_mac_prop != NULL) {
979	entry->disabled_mac_addresses = (const struct ether_addr *)offset;
980	(void)my_CFStringArrayToEtherArray(disabled_mac_prop, offset,
981					   &disabled_mac_space,
982					   &entry->disabled_mac_addresses_count);
983	qsort((void *)entry->disabled_mac_addresses,
984	      entry->disabled_mac_addresses_count,
985	      sizeof(*entry->disabled_mac_addresses),
986	      (qsort_compare_func_t)ether_cmp);
987	offset += disabled_mac_space;
988    }
989
990    /* ... then just strings */
991
992    /* bootfile */
993    entry->bootfile = offset;
994    (void)my_CFStringToCStringAndLength(bootfile_prop, offset,
995					bootfile_space);
996    /* verify that bootfile exists for every defined architecture */
997    for (i = 0; i < entry->archlist_count; i++) {
998	char	path[PATH_MAX];
999	snprintf(path, sizeof(path), "%s/%s", entry->archlist[i],
1000		 entry->bootfile);
1001	if (stat_file(dir_path, path) == FALSE) {
1002	    if (strcmp(entry->archlist[i], "ppc") == 0
1003		&& stat_file(dir_path, entry->bootfile)) {
1004		entry->ppc_bootfile_no_subdir = TRUE;
1005	    }
1006	    else {
1007		fprintf(stderr, "BootFile does not exist\n");
1008		goto failed;
1009	    }
1010	}
1011    }
1012    offset += bootfile_space;
1013
1014    /* sharepoint */
1015    entry->sharepoint = sharepoint;
1016
1017    /* dir_name */
1018    entry->dir_name = offset;
1019    strlcpy(entry->dir_name, dir_name, dir_name_len + 1);
1020    offset += dir_name_len + 1;
1021
1022    /* dir_name_esc */
1023    if (dir_name_esc_len == 0) {
1024	entry->dir_name_esc = entry->dir_name;
1025    }
1026    else {
1027	entry->dir_name_esc = offset;
1028	strlcpy(entry->dir_name_esc, dir_name_esc, dir_name_esc_len + 1);
1029	offset += dir_name_esc_len + 1;
1030    }
1031
1032    /* name */
1033    entry->name = offset;
1034    (void)my_CFStringToCStringAndLengthExt(name_prop, offset,
1035					   name_space, TRUE);
1036    entry->name_length = name_space - 1;
1037    offset += name_space;
1038
1039    switch (type_val) {
1040    case kNBImageTypeClassic:
1041	entry->type_info.classic.shared = offset;
1042	(void)my_CFStringToCStringAndLength(shared_prop, offset, shared_space);
1043	offset += shared_space;
1044	if (private_prop != NULL) {
1045	    entry->type_info.classic.private = offset;
1046	    (void)my_CFStringToCStringAndLength(private_prop,
1047						offset, private_space);
1048	    offset += private_space;
1049	}
1050	break;
1051    case kNBImageTypeNFS:
1052	entry->type_info.nfs.root_path = offset;
1053	strlcpy((char *)entry->type_info.nfs.root_path, root_path,
1054		root_path_len + 1);
1055	offset += root_path_len + 1;
1056	entry->type_info.nfs.indirect = indirect;
1057	break;
1058    case kNBImageTypeHTTP:
1059	entry->type_info.http.root_path = offset;
1060	strlcpy((char *)entry->type_info.http.root_path, root_path,
1061		root_path_len + 1);
1062	offset += root_path_len + 1;
1063	if (root_path_esc_len == 0) {
1064	    entry->type_info.http.root_path_esc
1065		= entry->type_info.http.root_path;
1066	}
1067	else {
1068	    entry->type_info.http.root_path_esc = offset;
1069	    strlcpy((char *)entry->type_info.http.root_path_esc,
1070		    root_path_esc,
1071		    root_path_esc_len + 1);
1072	    offset += root_path_esc_len + 1;
1073	}
1074	entry->type_info.http.indirect = indirect;
1075	break;
1076    default:
1077	break;
1078    }
1079#if CHECK_TOTAL_SPACE
1080    printf("tail_space %d - actual %d = %d\n",
1081	   tail_space, (int)(offset - (char *)(entry + 1)),
1082	   tail_space - (int)(offset - (char *)(entry + 1)));
1083#endif /* CHECK_TOTAL_SPACE */
1084    my_CFRelease(&plist);
1085    return (entry);
1086
1087 failed:
1088    if (entry != NULL) {
1089	free(entry);
1090	entry = NULL;
1091    }
1092    my_CFRelease(&plist);
1093    return (entry);
1094}
1095
1096boolean_t
1097NBImageEntry_supported_sysid(NBImageEntryRef entry,
1098			     const char * arch,
1099			     const char * sysid,
1100			     const struct ether_addr * ether)
1101{
1102    boolean_t	found = FALSE;
1103    int		i;
1104
1105    for (i = 0; i < entry->archlist_count; i++) {
1106	if (strcmp(entry->archlist[i], arch) == 0) {
1107	    found = TRUE;
1108	    break;
1109	}
1110    }
1111    if (found == FALSE) {
1112	/* not a supported architecture */
1113	return (FALSE);
1114    }
1115    if (entry->sysids != NULL) {
1116	if (bsearch(&sysid, entry->sysids, entry->sysids_count,
1117		    sizeof(char *), my_ptrstrcmp) == NULL) {
1118	    /* not a supported system identifier */
1119	    return (FALSE);
1120	}
1121    }
1122    if (entry->disabled_mac_addresses != NULL) {
1123	if (bsearch(ether, entry->disabled_mac_addresses,
1124		    entry->disabled_mac_addresses_count,
1125		    sizeof(*ether), (qsort_compare_func_t)ether_cmp) != NULL) {
1126	    /* ethernet address explicitly disabled */
1127	    return (FALSE);
1128	}
1129    }
1130    if (entry->enabled_mac_addresses != NULL) {
1131	if (bsearch(ether, entry->enabled_mac_addresses,
1132		    entry->enabled_mac_addresses_count,
1133		    sizeof(*ether), (qsort_compare_func_t)ether_cmp) == NULL) {
1134	    /* ethernet address not explicitly enabled */
1135	    return (FALSE);
1136	}
1137    }
1138    return (TRUE);
1139}
1140
1141boolean_t
1142NBImageEntry_attributes_match(NBImageEntryRef entry,
1143			      const u_int16_t * attrs_list, int n_attrs_list)
1144{
1145    u_int16_t	attrs;
1146    int		i;
1147
1148    if (attrs_list == NULL) {
1149	return (!entry->filter_only);
1150    }
1151    attrs = bsdp_image_attributes(entry->image_id);
1152    for (i = 0; i < n_attrs_list; i++) {
1153	if (attrs_list[i] == attrs) {
1154	    return (TRUE);
1155	}
1156    }
1157    return (FALSE);
1158}
1159
1160static void
1161NBImageList_add_default_entry(NBImageListRef image_list,
1162			      NBImageEntryRef entry)
1163{
1164    dynarray_t *	dlist = &image_list->list;
1165    int 		i;
1166    int			count;
1167
1168    if (entry->sysids == NULL) {
1169	dynarray_insert(dlist, entry, 0);
1170	return;
1171    }
1172    count = dynarray_count(dlist);
1173    for (i = 0; i < count; i++) {
1174	NBImageEntryRef	scan = dynarray_element(dlist, i);
1175
1176	if (scan->is_default == FALSE
1177	    || scan->sysids != NULL) {
1178	    dynarray_insert(dlist, entry, i);
1179	    return;
1180	}
1181    }
1182    dynarray_add(dlist, entry);
1183    return;
1184}
1185
1186static void
1187NBImageList_add_entry(NBImageListRef image_list, NBImageEntryRef entry)
1188{
1189    NBImageEntryRef	scan;
1190
1191    scan = NBImageList_elementWithID(image_list, entry->image_id);
1192    if (scan != NULL) {
1193	fprintf(stderr,
1194		"Ignoring image with non-unique image index %d:\n",
1195		bsdp_image_index(entry->image_id));
1196	NBImageEntry_print(entry);
1197	free(entry);
1198	return;
1199    }
1200    if (entry->is_default) {
1201	NBImageList_add_default_entry(image_list, entry);
1202    }
1203    else {
1204	dynarray_add(&image_list->list, entry);
1205    }
1206    return;
1207}
1208
1209static void
1210NBImageList_add_images(NBImageListRef image_list, NBSPEntryRef sharepoint,
1211		       boolean_t allow_diskless)
1212{
1213    char		dir[PATH_MAX];
1214    DIR *		dir_p;
1215    NBImageEntryRef	entry;
1216    char		info_path[PATH_MAX];
1217    int			suffix_len;
1218    struct dirent *	scan;
1219    struct stat		sb;
1220
1221    dir_p = opendir(sharepoint->path);
1222    if (dir_p == NULL) {
1223	goto done;
1224    }
1225    suffix_len = strlen(NETBOOT_IMAGE_SUFFIX);
1226    while ((scan = readdir(dir_p)) != NULL) {
1227	int	entry_len = strlen(scan->d_name);
1228
1229	if (entry_len < suffix_len
1230	  || strcmp(scan->d_name + entry_len - suffix_len,
1231		    NETBOOT_IMAGE_SUFFIX) != 0) {
1232	    continue;
1233	}
1234	snprintf(dir, sizeof(dir), "%s/%s",
1235		 sharepoint->path, scan->d_name);
1236	if (stat(dir, &sb) != 0 || !S_ISDIR(sb.st_mode)) {
1237	    continue;
1238	}
1239	snprintf(info_path, sizeof(info_path),
1240		 "%s/" NETBOOT_IMAGE_INFO_PLIST, dir);
1241	if (stat(info_path, &sb) != 0 || (sb.st_mode & S_IFREG) == 0) {
1242	    continue;
1243	}
1244	entry = NBImageEntry_create(sharepoint, scan->d_name, dir, info_path,
1245				    allow_diskless);
1246	if (entry != NULL) {
1247	    NBImageList_add_entry(image_list, entry);
1248	}
1249    }
1250 done:
1251    if (dir_p)
1252	closedir(dir_p);
1253    return;
1254}
1255
1256NBImageEntryRef
1257NBImageList_default(NBImageListRef image_list,
1258		    const char * arch, const char * sysid,
1259		    const struct ether_addr * ether,
1260		    const u_int16_t * attrs, int n_attrs)
1261{
1262    int			count;
1263    dynarray_t *	dlist = &image_list->list;
1264    int			i;
1265
1266    count = dynarray_count(dlist);
1267    for (i = 0; i < count; i++) {
1268	NBImageEntryRef	scan = dynarray_element(dlist, i);
1269
1270	if (NBImageEntry_supported_sysid(scan, arch, sysid, ether)
1271	    && NBImageEntry_attributes_match(scan, attrs, n_attrs)) {
1272	    return (scan);
1273	}
1274    }
1275    return (NULL);
1276}
1277
1278NBImageListRef
1279NBImageList_init(NBSPListRef sharepoints, boolean_t allow_diskless)
1280{
1281    int				count;
1282    int				i;
1283    NBImageListRef		image_list = NULL;
1284
1285    image_list = (NBImageListRef)malloc(sizeof(*image_list));
1286    if (image_list == NULL) {
1287	goto done;
1288    }
1289    bzero(image_list, sizeof(*image_list));
1290    dynarray_init(&image_list->list, free, NULL);
1291
1292    count = NBSPList_count(sharepoints);
1293    for (i = 0; i < count; i++) {
1294	NBSPEntryRef	entry = NBSPList_element(sharepoints, i);
1295
1296	NBImageList_add_images(image_list, entry, allow_diskless);
1297    }
1298 done:
1299    if (image_list != NULL) {
1300	if (dynarray_count(&image_list->list) == 0) {
1301	    dynarray_free(&image_list->list);
1302	    free(image_list);
1303	    image_list = NULL;
1304	}
1305    }
1306    return (image_list);
1307}
1308
1309void
1310NBImageList_print(NBImageListRef image_list)
1311{
1312    int			count;
1313    int			i;
1314
1315    printf("%-12s %-35s %-35s %-10s %-9s %-12s Image(s)\n", "Sharepoint",
1316	   "Dir", "Name",
1317	   "Identifier", "Type", "BootFile");
1318
1319    count = dynarray_count(&image_list->list);
1320    for (i = 0; i < count; i++) {
1321	NBImageEntryRef	entry;
1322
1323	entry = (NBImageEntryRef)dynarray_element(&image_list->list, i);
1324	NBImageEntry_print(entry);
1325    }
1326    return;
1327}
1328
1329#ifdef TEST_NBIMAGES
1330
1331int
1332main(int argc, char * argv[])
1333{
1334    NBSPListRef		sharepoints;
1335
1336    sharepoints = NBSPList_init(NETBOOT_SHAREPOINT_LINK,
1337				NBSP_READONLY_OK);
1338    if (sharepoints != NULL) {
1339	NBImageListRef	images;
1340
1341	images = NBImageList_init(sharepoints, argc == 1);
1342	if (images != NULL) {
1343	    NBImageList_print(images);
1344	    NBImageList_free(&images);
1345	}
1346	NBSPList_free(&sharepoints);
1347    }
1348    exit(0);
1349}
1350
1351#endif /* TEST_NBIMAGES */
1352