1/*
2 * Copyright (c) 2005-2007 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 *  modeNetboot.c
25 *  bless
26 *
27 *  Created by Shantonu Sen on 10/10/05.
28 *  Copyright 2005-2007 Apple Inc. All Rights Reserved.
29 *
30 */
31
32#include <stdlib.h>
33#include <unistd.h>
34#include <string.h>
35#include <sys/socket.h>
36#include <net/if.h>
37#include <arpa/nameser.h>
38#include <netinet/in.h>
39#include <arpa/inet.h>
40
41
42#include "enums.h"
43#include "structs.h"
44
45#include "bless.h"
46#include "bless_private.h"
47#include "protos.h"
48
49static bool validateAddress(const char *host);
50
51static int parseURL(BLContextPtr context,
52                    const char *url,
53                    char * scheme,
54                    char * interface,
55                    char * host,
56                    char * path,
57                    bool   requiresPath,
58                    const char *requiresScheme,
59                    bool	useBackslash);
60
61
62int modeNetboot(BLContextPtr context, struct clarg actargs[klast])
63{
64    int ret;
65    BLPreBootEnvType	preboot;
66    char                interface[IF_NAMESIZE];
67    char                host[NS_MAXDNAME];
68    char                path[MAXPATHLEN];
69	char				scheme[128];
70	bool				useBackslash = false;
71
72    if(!( actargs[kserver].present || actargs[kbooter].present)) {
73        blesscontextprintf(context, kBLLogLevelError,
74                           "No NetBoot server specification provided\n");
75        return 1;
76    }
77
78
79	ret = BLGetPreBootEnvironmentType(context, &preboot);
80    if(ret)
81        return 2;
82
83	if(preboot == kBLPreBootEnvType_OpenFirmware) {
84		useBackslash = true;
85	}
86
87
88    if (actargs[kserver].present) {
89        blesscontextprintf(context, kBLLogLevelVerbose,
90                           "NetBoot server specification given as: %s\n",
91                           actargs[kserver].argument);
92
93        ret = parseURL(context,
94                       actargs[kserver].argument,
95					   scheme,
96                       interface,
97                       host,
98                       path,
99                       false,
100                       NULL,
101					   useBackslash);
102
103    } else {
104        blesscontextprintf(context, kBLLogLevelVerbose,
105                           "NetBoot booter given as: %s\n",
106                           actargs[kbooter].argument);
107
108        ret = parseURL(context,
109                       actargs[kbooter].argument,
110					   scheme,
111                       interface,
112                       host,
113                       path,
114                       true,
115                       "tftp",
116					   useBackslash);
117
118    }
119
120    if(ret)
121        return 1;
122
123    if(preboot == kBLPreBootEnvType_OpenFirmware) {
124        // XXX temporary stub
125#define NVRAM "/usr/sbin/nvram"
126        char * OFSettings[6];
127
128        char bootdevice[1024];
129        char bootfile[1024];
130        char bootcommand[1024];
131        char bootargs[1024];
132		char ofstring[1024];
133
134        pid_t p;
135        int status;
136
137		if (0 != strcmp(scheme, "bsdp") && 0 != strcmp(scheme, "tftp")) {
138			blesscontextprintf(context, kBLLogLevelError,
139                               "Netboot scheme %s not supported on Open Firmware systems\n",
140							   scheme);
141		}
142
143		ret = BLGetOpenFirmwareBootDeviceForNetworkPath(context,
144                                                        interface,
145                                                        strcmp(host, "255.255.255.255") == 0 ? NULL : host,
146                                                        path,
147                                                        ofstring);
148		if(ret)
149			return 3;
150
151		sprintf(bootdevice, "boot-device=%s", ofstring);
152
153        if(actargs[kkernel].present) {
154            ret = parseURL(context,
155                           actargs[kkernel].argument,
156						   scheme,
157                           interface,
158                           host,
159                           path,
160                           true,
161                           "tftp",
162						   useBackslash);
163            if(ret)
164                return 1;
165
166			ret = BLGetOpenFirmwareBootDeviceForNetworkPath(context,
167                                                            interface,
168                                                            strcmp(host, "255.255.255.255") == 0 ? NULL : host,
169                                                            path,
170                                                            ofstring);
171			if(ret)
172				return 4;
173
174            sprintf(bootfile, "boot-file=%s", ofstring);
175        } else {
176            sprintf(bootfile, "boot-file=");
177        }
178
179        if(actargs[kmkext].present) {
180            blesscontextprintf(context, kBLLogLevelError,
181                               "mkext option not supported on Open Firmware systems\n");
182            return 5;
183        }
184
185        sprintf(bootcommand, "boot-command=mac-boot");
186        sprintf(bootargs, "boot-args=");
187
188        OFSettings[0] = NVRAM;
189        OFSettings[1] = bootdevice;
190        OFSettings[2] = bootfile;
191        OFSettings[3] = bootcommand;
192        OFSettings[4] = bootargs;
193        OFSettings[5] = NULL;
194
195        blesscontextprintf(context, kBLLogLevelVerbose,  "OF Setings:\n" );
196        blesscontextprintf(context, kBLLogLevelVerbose,  "\t\tprogram: %s\n", OFSettings[0] );
197        blesscontextprintf(context, kBLLogLevelVerbose,  "\t\t%s\n", OFSettings[1] );
198        blesscontextprintf(context, kBLLogLevelVerbose,  "\t\t%s\n", OFSettings[2] );
199        blesscontextprintf(context, kBLLogLevelVerbose,  "\t\t%s\n", OFSettings[3] );
200        blesscontextprintf(context, kBLLogLevelVerbose,  "\t\t%s\n", OFSettings[4] );
201
202        p = fork();
203        if (p == 0) {
204            int ret = execv(NVRAM, OFSettings);
205            if(ret == -1) {
206                blesscontextprintf(context, kBLLogLevelError,  "Could not exec %s\n", NVRAM );
207            }
208            _exit(1);
209        }
210
211        do {
212            p = wait(&status);
213        } while (p == -1 && errno == EINTR);
214
215        if(p == -1 || status) {
216            blesscontextprintf(context, kBLLogLevelError,  "%s returned non-0 exit status\n", NVRAM );
217            return 3;
218        }
219
220    } else if(preboot == kBLPreBootEnvType_EFI) {
221        CFStringRef booterXML = NULL, kernelXML = NULL, mkextXML = NULL, kernelcacheXML = NULL;
222		BLNetBootProtocolType protocol = kBLNetBootProtocol_Unknown;
223
224		if (0 == strcmp(scheme, "bsdp") || 0 == strcmp(scheme, "tftp")) {
225			protocol = kBLNetBootProtocol_BSDP;
226		} else if (0 == strcmp(scheme, "pxe")) {
227			protocol = kBLNetBootProtocol_PXE;
228
229			if (0 != strcmp(host, "255.255.255.255")) {
230				blesscontextprintf(context, kBLLogLevelError,
231								   "PXE requires broadcast address 255.255.255.255\n");
232				return 3;
233			}
234
235		} else {
236			blesscontextprintf(context, kBLLogLevelError,
237                               "Netboot scheme %s not supported on EFI systems\n",
238							   scheme);
239			return 3;
240		}
241
242
243        ret = BLCreateEFIXMLRepresentationForNetworkPath(context,
244														 protocol,
245                                                         interface,
246                                                         strcmp(host, "255.255.255.255") == 0 ? NULL : host,
247                                                         strlen(path) > 0 ? path : NULL,
248                                                         actargs[koptions].present ? actargs[koptions].argument : NULL,
249                                                         &booterXML);
250        if(ret)
251            return 3;
252
253
254        if(actargs[kkernel].present) {
255            ret = parseURL(context,
256                           actargs[kkernel].argument,
257						   scheme,
258                           interface,
259                           host,
260                           path,
261                           true,
262                           "tftp",
263						   false);
264            if(ret)
265                return 1;
266
267            ret = BLCreateEFIXMLRepresentationForNetworkPath(context,
268															 protocol,
269                                                             interface,
270                                                             host,
271                                                             path,
272                                                             NULL,
273                                                             &kernelXML);
274            if(ret)
275                return 3;
276        }
277
278
279        if(actargs[kmkext].present) {
280            ret = parseURL(context,
281                           actargs[kmkext].argument,
282						   scheme,
283                           interface,
284                           host,
285                           path,
286                           true,
287                           "tftp",
288						   false);
289            if(ret)
290                return 1;
291
292            ret = BLCreateEFIXMLRepresentationForNetworkPath(context,
293															 protocol,
294                                                             interface,
295                                                             host,
296                                                             path,
297                                                             NULL,
298                                                             &mkextXML);
299            if(ret)
300                return 3;
301        }
302
303        if(actargs[kkernelcache].present) {
304            ret = parseURL(context,
305                           actargs[kkernelcache].argument,
306						   scheme,
307                           interface,
308                           host,
309                           path,
310                           true,
311                           "tftp",
312						   false);
313            if(ret)
314                return 1;
315
316            ret = BLCreateEFIXMLRepresentationForNetworkPath(context,
317															 protocol,
318                                                             interface,
319                                                             host,
320                                                             path,
321                                                             NULL,
322                                                             &kernelcacheXML);
323            if(ret)
324                return 3;
325        }
326
327
328        ret = setefinetworkpath(context, booterXML, kernelXML, mkextXML, kernelcacheXML,
329                                actargs[knextonly].present);
330
331		if(ret) {
332			blesscontextprintf(context, kBLLogLevelError,  "Can't set EFI\n" );
333			return 1;
334		} else {
335			blesscontextprintf(context, kBLLogLevelVerbose,  "EFI set successfully\n" );
336		}
337    } else {
338        blesscontextprintf(context, kBLLogLevelError,
339                           "NetBoot not supported on this system\n");
340        return 3;
341    }
342
343    return 0;
344}
345
346static bool validateAddress(const char *host)
347{
348    in_addr_t addr;
349    int ret;
350
351    ret = inet_pton(PF_INET, host, &addr);
352    if(ret == 1)
353        return true;
354    else
355        return false;
356}
357
358static int parseURL(BLContextPtr context,
359                    const char *url,
360					char * scheme,
361                    char * interface,
362                    char * host,
363                    char * path,
364                    bool   requiresPath,
365                    const char *requiresScheme,
366					bool	useBackslash)
367{
368    int                 ret;
369
370    CFURLRef            serverURL;
371    CFStringRef         schemeString, interfaceString, pathString, hostString;
372    CFStringRef         requiredScheme = NULL;
373
374    serverURL = CFURLCreateAbsoluteURLWithBytes(kCFAllocatorDefault,
375                                                (const UInt8 *)url,
376                                                strlen(url),
377                                                kCFStringEncodingUTF8,
378                                                NULL, false);
379
380    if(!serverURL || !CFURLCanBeDecomposed(serverURL)) {
381        if(serverURL) CFRelease(serverURL);
382
383        blesscontextprintf(context, kBLLogLevelError,
384                           "Could not interpret %s as a URL\n", url);
385        return 2;
386    }
387
388    schemeString = CFURLCopyScheme(serverURL);
389    if (requiresScheme) {
390        requiredScheme = CFStringCreateWithCString(kCFAllocatorDefault,requiresScheme,kCFStringEncodingUTF8);
391    }
392    if(!schemeString || (requiredScheme && !CFEqual(schemeString, requiredScheme))) {
393
394        blesscontextprintf(context, kBLLogLevelError,
395                           "Unrecognized scheme %s\n", BLGetCStringDescription(schemeString));
396
397        if(schemeString) CFRelease(schemeString);
398        if(requiredScheme) CFRelease(requiredScheme);
399
400        return 2;
401    }
402
403	if(!CFStringGetCString(schemeString,scheme,128,kCFStringEncodingUTF8)) {
404		blesscontextprintf(context, kBLLogLevelError,
405						   "Can't interpret scheme as string\n");
406		return 3;
407	}
408
409    if(requiredScheme) CFRelease(requiredScheme);
410    CFRelease(schemeString);
411
412    interfaceString = CFURLCopyUserName(serverURL);
413
414    if(interfaceString == NULL) {
415
416        ret = BLGetPreferredNetworkInterface(context, interface);
417        if(ret) {
418            blesscontextprintf(context, kBLLogLevelError,
419                               "Failed to determine preferred network interface\n");
420            return 1;
421        } else {
422            blesscontextprintf(context, kBLLogLevelVerbose,
423                               "Preferred network interface is %s\n", interface);
424        }
425    } else {
426        if(!CFStringGetCString(interfaceString,interface,IF_NAMESIZE,kCFStringEncodingUTF8)) {
427            CFRelease(interfaceString);
428            blesscontextprintf(context, kBLLogLevelError,
429                               "Can't interpret interface as string\n");
430            return 3;
431        }
432
433        if(!BLIsValidNetworkInterface(context, interface)) {
434            blesscontextprintf(context, kBLLogLevelError,
435                               "%s is not a valid interface\n", interface);
436            return 4;
437        }
438
439        CFRelease(interfaceString);
440    }
441
442    blesscontextprintf(context, kBLLogLevelVerbose,
443                       "Using interface %s\n", interface);
444
445    pathString = CFURLCopyStrictPath(serverURL, NULL);
446
447    // path component must be NULL or empty
448    if(requiresPath) {
449        if(pathString == NULL || CFEqual(pathString, CFSTR(""))) {
450            if(pathString) CFRelease(pathString);
451            blesscontextprintf(context, kBLLogLevelError,
452                               "Specification must contain a path\n");
453            return 5;
454        }
455        if(!CFStringGetCString(pathString,path,MAXPATHLEN,kCFStringEncodingUTF8)) {
456            CFRelease(pathString);
457            blesscontextprintf(context, kBLLogLevelError,
458                               "Can't interpret path as string\n");
459            return 5;
460        }
461    } else {
462        if(!(pathString == NULL || CFEqual(pathString, CFSTR("")))) {
463            CFRelease(pathString);
464            blesscontextprintf(context, kBLLogLevelError,
465                               "Specification can't contain a path\n");
466            return 5;
467        }
468        path[0] = '\0';
469    }
470
471    if(pathString) CFRelease(pathString);
472
473	if(useBackslash) {
474		int i, len;
475		for(i=0, len=strlen(path); i < len; i++) {
476			if(path[i] == '/') {
477				path[i] = '\\';
478			}
479		}
480	}
481
482
483    hostString = CFURLCopyHostName(serverURL);
484
485    // host must be present
486    if(hostString == NULL || CFEqual(hostString, CFSTR(""))) {
487        if(hostString) CFRelease(hostString);
488        blesscontextprintf(context, kBLLogLevelError,
489                           "Specification doesn't contain host\n");
490        return 5;
491    }
492
493    if(!CFStringGetCString(hostString,host,NS_MAXDNAME,kCFStringEncodingUTF8)) {
494        CFRelease(hostString);
495        blesscontextprintf(context, kBLLogLevelError,
496                           "Can't interpret host as string\n");
497        return 5;
498    }
499
500    CFRelease(hostString);
501
502    if(!validateAddress(host)) {
503        blesscontextprintf(context, kBLLogLevelError,
504                           "Can't interpret host %s as an IPv4 address\n",
505                           host);
506        return 6;
507    }
508
509    return 0;
510}
511