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