1/*
2 * Copyright (c) 1999-2008 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 * macNC.c
25 * - macNC boot server
26 * - supports Mac OS 9 AKA Classic netboot clients
27 */
28
29/*
30 * Modification History:
31 *
32 * December 2, 1997	Dieter Siegmund (dieter@apple.com)
33 * - created
34 * February 1, 1999	Dieter Siegmund (dieter@apple.com)
35 * - create sharepoints at init time (and anytime we get a SIGHUP)
36 *   and ensure permissions are correct
37 * November 2, 2000	Dieter Siegmund (dieter@apple.com)
38 * - removed code that creates sharepoints
39 */
40
41#include <unistd.h>
42#include <stdlib.h>
43#include <sys/types.h>
44#include <sys/stat.h>
45#include <sys/socket.h>
46#include <sys/ioctl.h>
47#include <sys/file.h>
48#include <pwd.h>
49#include <net/if.h>
50#include <netinet/in.h>
51#include <netinet/in_systm.h>
52#include <netinet/ip.h>
53#include <netinet/udp.h>
54#include <netinet/bootp.h>
55#include <netinet/if_ether.h>
56#include <stdio.h>
57#include <strings.h>
58#include <errno.h>
59#include <fcntl.h>
60#include <ctype.h>
61#include <netdb.h>
62#include <syslog.h>
63#include <sys/param.h>
64#include <sys/mount.h>
65#include <arpa/inet.h>
66#include <mach/boolean.h>
67#include <sys/wait.h>
68#include <sys/resource.h>
69#include <ctype.h>
70#include <sys/attr.h>
71
72#include "dhcp.h"
73#include "netinfo.h"
74#include "rfc_options.h"
75#include "subnets.h"
76#include "interfaces.h"
77#include "bootpd.h"
78#include "bsdpd.h"
79#include "macnc_options.h"
80#include "macNC.h"
81#include "host_identifier.h"
82#include "nbsp.h"
83#include "nbimages.h"
84#include "NetBootServer.h"
85#include "util.h"
86
87/*
88 * Function: timestamp_syslog
89 *
90 * Purpose:
91 *   Log a timestamped event message to the syslog.
92 */
93static void
94S_timestamp_syslog(const char * msg)
95{
96    static struct timeval	tvp = {0,0};
97    struct timeval		tv;
98
99    gettimeofday(&tv, 0);
100    if (tvp.tv_sec) {
101	struct timeval result;
102
103	timeval_subtract(tv, tvp, &result);
104	syslog(LOG_INFO, "%ld.%06d (%ld.%06d): %s",
105	       tv.tv_sec, tv.tv_usec, result.tv_sec, result.tv_usec, msg);
106    }
107    else
108	syslog(LOG_INFO, "%ld.%06d (%d.%06d): %s",
109	       tv.tv_sec, tv.tv_usec, 0, 0, msg);
110    tvp = tv;
111}
112
113
114
115static __inline__ void
116S_timestamp(const char * msg)
117{
118    if (verbose)
119	S_timestamp_syslog(msg);
120}
121
122static boolean_t
123S_set_dimg_ddsk(const char * path)
124{
125    struct attrlist 	attrspec;
126    struct {
127	char	type_creator[8];
128	u_long	pad[6];
129    } finder =  {
130	{ 'd', 'i', 'm', 'g', 'd', 'd', 's', 'k' },
131	{ 0, 0, 0, 0, 0, 0 }
132    };
133
134    bzero(&attrspec, sizeof(attrspec));
135    attrspec.bitmapcount	= ATTR_BIT_MAP_COUNT;
136    attrspec.commonattr		= ATTR_CMN_FNDRINFO;
137    if (setattrlist(path, &attrspec, &finder, sizeof(finder), 0)) {
138	return (FALSE);
139    }
140    return (TRUE);
141}
142
143static boolean_t
144set_privs_no_stat(const char * path, struct stat * sb_p, uid_t uid, gid_t gid,
145		  mode_t mode, boolean_t unlock)
146{
147    boolean_t		needs_chown = FALSE;
148    boolean_t		needs_chmod = FALSE;
149
150    if (sb_p->st_uid != uid || sb_p->st_gid != gid)
151	needs_chown = TRUE;
152
153    if ((sb_p->st_mode & ACCESSPERMS) != mode)
154	needs_chmod = TRUE;
155
156    if (needs_chown || needs_chmod) {
157	if (sb_p->st_flags & UF_IMMUTABLE) {
158	    if (chflags(path, 0) < 0)
159		return (FALSE);
160	}
161	if (needs_chown) {
162	    if (chown(path, uid, gid) < 0)
163		return (FALSE);
164	}
165	if (needs_chmod) {
166	    if (chmod(path, mode) < 0)
167		return (FALSE);
168	}
169    }
170    else if (unlock) {
171	if (sb_p->st_flags & UF_IMMUTABLE) {
172	    if (chflags(path, 0) < 0)
173		return (FALSE);
174	}
175    }
176    return (TRUE);
177}
178
179boolean_t
180set_privs(const char * path, struct stat * sb_p, uid_t uid, gid_t gid,
181	  mode_t mode, boolean_t unlock)
182{
183    if (stat(path, sb_p) != 0) {
184	return (FALSE);
185    }
186    return (set_privs_no_stat(path, sb_p, uid, gid, mode, unlock));
187}
188
189/*
190 * Function: S_set_uid_gid
191 *
192 * Purpose:
193 *   Given a path to a file, make the owner of both the
194 *   enclosing directory and the file itself to user/group uid/gid.
195 */
196static int
197S_set_uid_gid(const char * file, uid_t uid, gid_t gid)
198{
199    char 	dir[PATH_MAX];
200    char *	last_slash = strrchr(file, '/');
201
202    if (file[0] != '/' || last_slash == NULL) {
203	if (debug)
204	    printf("path '%s' is not valid\n", file);
205	return (-1);
206    }
207
208    strncpy(dir, file, last_slash - file);
209    dir[last_slash - file] = '\0';
210    if (chown(dir, uid, gid) == -1)
211	return (-1);
212    if (chown(file, uid, gid) == -1)
213	return (-1);
214    return (0);
215}
216
217/**
218 ** Other local utility routines:
219 **/
220
221
222/*
223 * Function: S_get_client_info
224 *
225 * Purpose:
226 *   Retrieve the macNC client information from the given packet.
227 *   First try to parse the dhcp options, then look for the client
228 *   version tag and client info tag.  The client info tag will
229 *   contain "Apple MacNC".
230 *
231 * Returns:
232 *   TRUE and client_version if client version is present in packet
233 *   FALSE otherwise
234 */
235boolean_t
236macNC_get_client_info(struct dhcp * pkt, int pkt_size, dhcpol_t * options,
237		      u_int * client_version)
238{ /* get the client version info - if not present, not an NC */
239    void *		client_id;
240    int			opt_len;
241    void *		vers;
242
243    vers = dhcpol_find(options, macNCtag_client_version_e,
244		       &opt_len, NULL);
245    if (vers == NULL)
246	return (FALSE);
247
248    client_id = dhcpol_find(options, macNCtag_client_info_e,
249			    &opt_len, NULL);
250    if (client_id == NULL)
251	return (FALSE);
252    if (opt_len != strlen(MACNC_CLIENT_INFO)
253	|| bcmp(client_id, MACNC_CLIENT_INFO, opt_len)) {
254	return (FALSE);
255    }
256    if (client_version)
257	*client_version = ntohl(*((unsigned long *)vers));
258    return (TRUE);
259}
260
261/*
262 * Function: S_get_volpath
263 *
264 * Purpose:
265 *   Format a volume pathname given a volume, directory and file name.
266 */
267static void
268S_get_volpath(char * path, const NBSPEntry * entry, const char * dir,
269	      const char * file)
270{
271    snprintf(path, PATH_MAX, "%s%s%s%s%s",
272	     entry->path,
273	     (dir && *dir) ? "/" : "",
274	     (dir && *dir) ? (char *)dir : "",
275	     (file && *file) ? "/" : "",
276	     (file && *file) ? (char *)file : "");
277    return;
278}
279
280/*
281 * Function: S_create_volume_dir
282 *
283 * Purpose:
284 *   Create the given directory path on the given volume.
285 */
286static boolean_t
287S_create_volume_dir(NBSPEntry * entry, char * dirname, mode_t mode)
288{
289    char 		path[PATH_MAX];
290
291    S_get_volpath(path, entry, dirname, NULL);
292    if (create_path(path, mode) < 0) {
293	my_log(LOG_INFO, "macNC: create_volume_dir: create_path(%s)"
294	       " failed, %m",  path);
295	return (FALSE);
296    }
297    (void)chmod(path, mode);
298    return (TRUE);
299}
300
301/*
302 * Function: set_file_size
303 *
304 * Purpose:
305 *   Set a file to be a certain length.
306 */
307static int
308set_file_size(int fd, off_t size)
309{
310#ifdef F_SETSIZE
311    fcntl(fd, F_SETSIZE, &size);
312#endif /* F_SETSIZE */
313    return (ftruncate(fd, size));
314}
315
316/*
317 * Function: S_create_shadow_file
318 *
319 * Purpose:
320 *   Create a new empty file with the right permissions, and optionally,
321 *   set to type dimg/ddsk.
322 */
323static boolean_t
324S_create_shadow_file(const char * shadow_path, uid_t uid, gid_t gid,
325		     unsigned long long size, boolean_t set_dimg)
326{
327    int 		fd;
328
329    S_set_uid_gid(shadow_path, ROOT_UID, 0);
330
331    fd = open(shadow_path, O_CREAT | O_TRUNC | O_WRONLY, CLIENT_FILE_PERMS);
332    if (fd < 0) {
333	my_log(LOG_INFO, "macNC: couldn't create file '%s': %m", shadow_path);
334	return (FALSE);
335    }
336    if (set_file_size(fd, size)) {
337	my_log(LOG_INFO, "macNC: set_file_size '%s' failed: %m",
338	       shadow_path);
339	goto err;
340    }
341
342    if (set_dimg) {
343	if (S_set_dimg_ddsk(shadow_path) == FALSE) {
344	    my_log(LOG_INFO, "macNC: set type/creator '%s' failed, %m",
345		   shadow_path);
346	    goto err;
347	}
348    }
349
350    fchmod(fd, CLIENT_FILE_PERMS);
351    close(fd);
352
353    /* correct the owner of the path */
354    if (S_set_uid_gid(shadow_path, uid, gid)) {
355	my_log(LOG_INFO, "macNC: setuidgid '%s' to %ld,%ld failed: %m",
356	       shadow_path, uid, gid);
357	return (FALSE);
358    }
359    return (TRUE);
360
361  err:
362    close(fd);
363    return (FALSE);
364}
365
366static boolean_t
367S_add_afppath_option(struct in_addr servip, dhcpoa_t * options,
368		     NBSPEntry * entry, const char * dir, const char * file,
369		     int tag)
370{
371    char		buf[DHCP_OPTION_SIZE_MAX];
372    dhcpo_err_str_t	err;
373    int			len;
374    char		path[PATH_MAX];
375
376    if (dir && *dir)
377	snprintf(path, sizeof(path), "%s/%s", dir, file);
378    else {
379	snprintf(path, sizeof(path), "%s", file);
380    }
381
382    len = sizeof(buf);
383    if (macNCopt_encodeAFPPath(servip, AFP_PORT_NUMBER, entry->name,
384			       AFP_DIRID_NULL, AFP_PATHTYPE_LONG,
385			       path, '/', buf, &len, &err) == FALSE) {
386	my_log(LOG_INFO, "macNC: couldn't encode %s:%s, %s", entry->name, path,
387	       err.str);
388	return (FALSE);
389    }
390    if (dhcpoa_add(options, tag, len, buf) != dhcpoa_success_e) {
391	my_log(LOG_INFO, "macNC: couldn't add option %d failed: %s", tag,
392	       dhcpoa_err(options));
393	return (FALSE);
394    }
395    return (TRUE);
396}
397
398
399/*
400 * Function: S_stat_path_vol_file
401 *
402 * Purpose:
403 *   Return the stat structure for the given volume/dir/file.
404 */
405static __inline__ int
406S_stat_path_vol_file(char * path, NBSPEntry * entry,
407		     const char * dir, const char * file,
408		     struct stat * sb_p)
409{
410    S_get_volpath(path, entry, dir, file);
411    return (stat(path, sb_p));
412}
413
414
415static boolean_t
416S_freespace(const char * path, unsigned long long * size)
417{
418    struct statfs 	fsb;
419
420    if (statfs(path, &fsb) != 0) {
421	my_log(LOG_INFO, "macNC: statfs on '%s' failed %m", path);
422	return (FALSE);
423    }
424    *size = ((unsigned long long)fsb.f_bavail)
425	* ((unsigned long long)fsb.f_bsize);
426    if (debug)
427        printf("%s %qu x %u = %qu bytes\n", path,
428	       fsb.f_bavail, fsb.f_bsize, *size);
429    return (TRUE);
430}
431
432static NBSPEntry *
433S_find_volume_with_space(unsigned long long needspace, int def_vol_index,
434			 boolean_t need_hfs)
435{
436    unsigned long long	freespace;
437    int 		i;
438    NBSPEntry *	entry = NULL;
439    char		path[PATH_MAX];
440    int			vol_index;
441
442    for (i = 0, vol_index = def_vol_index; i < NBSPList_count(G_client_sharepoints);
443	 i++) {
444	NBSPEntry * shp = NBSPList_element(G_client_sharepoints, vol_index);
445
446	if (need_hfs == FALSE || shp->is_hfs == TRUE) {
447	    S_get_volpath(path, shp, NULL, NULL);
448	    if (S_freespace(path, &freespace) == TRUE) {
449#define SLOP_SPACE_BYTES	(20 * 1024 * 1024)
450		/* make sure there's some space left on the volume */
451		if (freespace >= (needspace + SLOP_SPACE_BYTES)) {
452		    entry = shp;
453		    if (debug)
454			printf("selected volume %s\n", entry->name);
455		    break; /* out of for */
456		}
457	    }
458	}
459	vol_index = (vol_index + 1) % NBSPList_count(G_client_sharepoints);
460    }
461    return (entry);
462}
463
464static boolean_t
465S_remove_shadow(const char * shadow_path, NBSPEntry * entry, const char * dir)
466{
467    char path[PATH_MAX];
468
469    /* remove the shadow file */
470    S_set_uid_gid(shadow_path, ROOT_UID, 0);
471    unlink(shadow_path);
472    S_get_volpath(path, entry, dir, NULL);
473    /* and its directory */
474    if (rmdir(path)) {
475	char new_path[PATH_MAX];
476	if (debug)
477	    perror(path);
478	S_get_volpath(new_path, entry, "Delete Me", NULL);
479	/* couldn't delete it, try to rename it */
480	if (rename(path, new_path)) {
481	    return (FALSE);
482	}
483    }
484    return (TRUE);
485}
486
487NBSPEntry *
488macNC_allocate_shadow(const char * machine_name, int host_number,
489		      uid_t uid, gid_t gid, const char * shadow_name)
490{
491    int			def_vol_index;
492    char		dir_path[PATH_MAX];
493    struct stat		dir_statb;
494    int			i;
495    char		nc_images_dir[PATH_MAX];
496    NBSPEntry *		nc_volume = NULL;
497    unsigned long long	needspace;
498    char		shadow_path[PATH_MAX];
499    int			vol_index;
500
501    if (G_client_sharepoints == NULL) {
502	syslog(LOG_NOTICE, "macNC_allocate_shadow: no client sharepoints");
503	return (NULL);
504    }
505
506    strncpy(nc_images_dir, machine_name, sizeof(nc_images_dir));
507
508    /* attempt to round-robin images across multiple volumes */
509    def_vol_index = (host_number - 1) % NBSPList_count(G_client_sharepoints);
510
511    /* check all volumes for a client image directory starting at default */
512    nc_volume = NULL;
513    for (i = 0, vol_index = def_vol_index; i < NBSPList_count(G_client_sharepoints);
514	 i++) {
515	NBSPEntry * entry = NBSPList_element(G_client_sharepoints, vol_index);
516	if (S_stat_path_vol_file(dir_path, entry,
517				 nc_images_dir, NULL, &dir_statb) == 0) {
518	    nc_volume = entry;
519	    break;
520	}
521	vol_index = (vol_index + 1) % NBSPList_count(G_client_sharepoints);
522    }
523#define ONE_MEG		(1024UL * 1024UL)
524    needspace = ((unsigned long long)G_shadow_size_meg) * ONE_MEG;
525    if (nc_volume != NULL) {
526	struct stat		sb_shadow;
527	boolean_t		set_file_size = FALSE;
528	boolean_t		set_owner_perms = FALSE;
529
530	S_get_volpath(shadow_path, nc_volume, nc_images_dir,
531		      shadow_name);
532	if (stat(shadow_path, &sb_shadow) == 0) { /* shadow exists */
533	    S_timestamp("shadow file exists");
534	    if (debug)
535		printf("shadow %qu need %qu\n",
536		       sb_shadow.st_size, needspace);
537
538	    if (sb_shadow.st_uid != uid
539		|| sb_shadow.st_gid != gid
540		|| (sb_shadow.st_mode & ACCESSPERMS) != CLIENT_FILE_PERMS)
541		set_owner_perms = TRUE;
542
543	    if (sb_shadow.st_size < needspace) {
544		unsigned long long  	difference;
545		unsigned long long	freespace = 0;
546
547		set_file_size = TRUE;
548		S_timestamp("shadow file needs to be grown");
549		/* check for enough space */
550		(void)S_freespace(shadow_path, &freespace);
551		difference = (needspace - sb_shadow.st_size);
552		if (freespace < difference) {
553		    my_log(LOG_INFO, "macNC: device full, "
554			   "attempting to relocate %s",
555			   shadow_path);
556		    /* blow away the shadow */
557		    if (S_remove_shadow(shadow_path, nc_volume,
558					nc_images_dir) == FALSE) {
559			my_log(LOG_INFO, "macNC: couldn't remove"
560			       " shadow %s, %m", shadow_path);
561			goto failed;
562		    }
563		    /* start fresh */
564		    nc_volume = NULL;
565		}
566	    }
567	}
568	else {
569	    /* start fresh */
570	    if (S_remove_shadow(shadow_path, nc_volume, nc_images_dir)
571		== FALSE) {
572		my_log(LOG_INFO, "macNC: couldn't remove"
573		       " shadow %s, %m", shadow_path);
574		goto failed;
575	    }
576	    nc_volume = NULL;
577	}
578	if (nc_volume != NULL) {
579	    if (set_file_size) {
580		S_timestamp("setting shadow file size");
581		if (S_create_shadow_file(shadow_path, uid, gid, needspace,
582					 FALSE) == FALSE) {
583		    my_log(LOG_INFO, "macNC: couldn't create %s, %m",
584			   shadow_path);
585		    goto failed;
586		}
587		S_timestamp("shadow file size set");
588	    }
589	    else if (set_owner_perms) {
590		S_timestamp("setting shadow file perms/owner");
591		chmod(shadow_path, CLIENT_FILE_PERMS);
592		S_set_uid_gid(shadow_path, uid, gid);
593		S_timestamp("shadow file perms/owner set");
594	    }
595	}
596    }
597    if (nc_volume == NULL) { /* locate the client's image dir */
598	nc_volume = S_find_volume_with_space(needspace, def_vol_index, FALSE);
599	if (nc_volume == NULL) {
600	    if (G_disk_space_warned == FALSE)
601		my_log(LOG_INFO, "macNC: can't create client image: "
602		       "OUT OF DISK SPACE");
603	    G_disk_space_warned = TRUE; /* don't keep complaining */
604	    goto failed;
605	}
606	S_get_volpath(shadow_path, nc_volume, nc_images_dir,
607		      shadow_name);
608	G_disk_space_warned = FALSE;
609	if (S_create_volume_dir(nc_volume, nc_images_dir,
610				CLIENT_DIR_PERMS) == FALSE) {
611	    goto failed;
612	}
613	if (S_create_shadow_file(shadow_path, uid, gid, needspace,
614				 FALSE) == FALSE) {
615	    my_log(LOG_INFO, "macNC: couldn't create %s, %m",
616		   shadow_path);
617	    goto failed;
618	}
619    }
620    return (nc_volume);
621
622 failed:
623    return (NULL);
624}
625
626
627/*
628 * Function: S_add_image_options
629 *
630 * Purpose:
631 *   Create/initialize image for client, format the paths into the
632 *   response options.
633 */
634static boolean_t
635S_add_image_options(NBImageEntryRef image_entry,
636		    uid_t uid, gid_t gid, struct in_addr servip,
637		    dhcpoa_t * options, int host_number,
638		    const char * afp_hostname)
639{
640    int			def_vol_index;
641    char		dir_path[PATH_MAX];
642    struct stat		dir_statb;
643    int			i;
644    char		nc_images_dir[PATH_MAX];
645    NBSPEntry *		nc_volume = NULL;
646    char		path[PATH_MAX];
647    struct stat		statb;
648    int			vol_index;
649
650    /* make sure the bootfile exists and the permissions are correct */
651    snprintf(path, sizeof(path), "%s/%s/%s",
652	     image_entry->sharepoint->path,
653	     image_entry->dir_name,
654	     image_entry->bootfile);
655    if (set_privs(path, &statb, ROOT_UID, G_admin_gid,
656		  SHARED_FILE_PERMS, FALSE) == FALSE) {
657	syslog(LOG_INFO, "macNC: '%s' does not exist", path);
658	return (FALSE);
659    }
660
661    snprintf(nc_images_dir, sizeof(nc_images_dir),
662	     "%s", afp_hostname);
663
664    /* attempt to round-robin images across multiple volumes */
665    def_vol_index = (host_number - 1) % NBSPList_count(G_client_sharepoints);
666
667    /* check all volumes for a client image directory starting at default */
668    nc_volume = NULL;
669    for (i = 0, vol_index = def_vol_index; i < NBSPList_count(G_client_sharepoints);
670	 i++) {
671	NBSPEntry * entry = NBSPList_element(G_client_sharepoints, vol_index);
672	if (entry->is_hfs == TRUE
673	    && S_stat_path_vol_file(dir_path, entry,
674				    nc_images_dir, NULL, &dir_statb) == 0) {
675	    nc_volume = entry;
676	    break;
677	}
678	vol_index = (vol_index + 1) % NBSPList_count(G_client_sharepoints);
679    }
680
681    /* if the client has its own private copy of the image file, use it */
682    if (nc_volume != NULL
683	&& S_stat_path_vol_file(path, nc_volume, nc_images_dir,
684				image_entry->type_info.classic.shared,
685				&statb) == 0) {
686	/* set the image file perms */
687	if (set_privs_no_stat(path, &statb, uid, gid, CLIENT_FILE_PERMS,
688			      TRUE) == FALSE) {
689	    my_log(LOG_INFO, "macNC: couldn't set permissions on path %s: %m",
690		   path);
691	    return (FALSE);
692	}
693	/* set the client dir perms */
694	if (set_privs_no_stat(dir_path, &dir_statb, uid, gid,
695			      CLIENT_DIR_PERMS, TRUE) == FALSE) {
696	    my_log(LOG_INFO, "macNC: couldn't set permissions on path %s: %m",
697		   dir_path);
698	    return (FALSE);
699	}
700	if (S_add_afppath_option(servip, options, nc_volume, nc_images_dir,
701				 image_entry->type_info.classic.shared,
702				 macNCtag_shared_system_file_e) == FALSE) {
703	    return (FALSE);
704	}
705	/* does the client have its own Private image? */
706	if (image_entry->type_info.classic.private != NULL) {
707	    if (S_stat_path_vol_file(path, nc_volume, nc_images_dir,
708				     image_entry->type_info.classic.private,
709				     &statb) == 0) {
710		if (set_privs_no_stat(path, &statb, uid, gid,
711				      CLIENT_FILE_PERMS, TRUE) == FALSE) {
712		    my_log(LOG_INFO,
713			   "macNC: couldn't set permissions on path %s: %m",
714			   path);
715		    return (FALSE);
716		}
717		/*
718		 * We use macNCtag_page_file_e instead of
719		 * macNCtag_private_system_file_e as you would expect.
720		 * The reason is that the client ROM software assumes
721		 * that the private_system_file is read-only. It also
722		 * assumes that page_file is read-write.  Since we don't
723		 * use page_file for anything else, we use that instead.
724		 * This is a hack/workaround.
725		 */
726		if (S_add_afppath_option(servip, options, nc_volume,
727					 nc_images_dir,
728					 image_entry->type_info.classic.private,
729					 macNCtag_page_file_e) == FALSE){
730		    return (FALSE);
731		}
732	    }
733	}
734    }
735    else { /* client gets shadow file(s) */
736	unsigned long long	needspace;
737	char 			private_path[PATH_MAX];
738	char 			shadow_path[PATH_MAX];
739	char 			shared_path[PATH_MAX];
740
741	snprintf(shared_path, sizeof(shared_path),
742		 "%s/%s/%s",
743		 image_entry->sharepoint->path,
744		 image_entry->dir_name,
745		 image_entry->type_info.classic.shared);
746	/* set the shared system image permissions */
747	if (set_privs(shared_path, &statb, ROOT_UID, G_admin_gid,
748		      SHARED_FILE_PERMS, FALSE) == FALSE) {
749	    syslog(LOG_INFO, "macNC: '%s' does not exist", shared_path);
750	    return (FALSE);
751	}
752
753	/* add the shared system image option */
754	if (S_add_afppath_option(servip, options,
755				 image_entry->sharepoint,
756				 image_entry->dir_name,
757				 image_entry->type_info.classic.shared,
758				 macNCtag_shared_system_file_e) == FALSE) {
759	    return (FALSE);
760	}
761	if (image_entry->type_info.classic.private != NULL) {
762	    /* check for the private system image, set its permissions */
763	    snprintf(private_path, sizeof(private_path),
764		     "%s/%s/%s",
765		     image_entry->sharepoint->path,
766		     image_entry->dir_name,
767		     image_entry->type_info.classic.private);
768	    if (set_privs(private_path, &statb, ROOT_UID, G_admin_gid,
769			  SHARED_FILE_PERMS, FALSE) == TRUE) {
770		/* add the private image option */
771		if (S_add_afppath_option(servip, options,
772					 image_entry->sharepoint,
773					 image_entry->dir_name,
774					 image_entry->type_info.classic.private,
775					 macNCtag_private_system_file_e)
776		    == FALSE) {
777		    return (FALSE);
778		}
779	    }
780	}
781
782#define ONE_MEG		(1024UL * 1024UL)
783	needspace = ((unsigned long long)G_shadow_size_meg) * ONE_MEG;
784	if (nc_volume != NULL) {
785	    struct stat		sb_shadow;
786	    boolean_t		set_file_size = FALSE;
787	    boolean_t		set_owner_perms = FALSE;
788
789	    S_get_volpath(shadow_path, nc_volume, nc_images_dir,
790			  kNetBootShadowName);
791	    if (stat(shadow_path, &sb_shadow) == 0) { /* shadow exists */
792		S_timestamp("shadow file exists");
793		if (debug)
794		    printf("shadow %qu need %qu\n",
795			   sb_shadow.st_size, needspace);
796
797		if (sb_shadow.st_uid != uid
798		    || sb_shadow.st_gid != gid
799		    || (sb_shadow.st_mode & ACCESSPERMS) != CLIENT_FILE_PERMS)
800		    set_owner_perms = TRUE;
801
802		if (sb_shadow.st_size < needspace) {
803		    unsigned long long  	difference;
804		    unsigned long long	freespace = 0;
805
806		    set_file_size = TRUE;
807		    S_timestamp("shadow file needs to be grown");
808		    /* check for enough space */
809		    (void)S_freespace(shadow_path, &freespace);
810		    difference = (needspace - sb_shadow.st_size);
811		    if (freespace < difference) {
812			my_log(LOG_INFO, "macNC: device full, "
813			       "attempting to relocate %s",
814			       shadow_path);
815			/* blow away the shadow */
816			if (S_remove_shadow(shadow_path, nc_volume,
817					    nc_images_dir) == FALSE) {
818			    my_log(LOG_INFO, "macNC: couldn't remove"
819				   " shadow %s, %m", shadow_path);
820			    return (FALSE);
821			}
822			/* start fresh */
823			nc_volume = NULL;
824		    }
825		}
826	    }
827	    else {
828		/* start fresh */
829		if (S_remove_shadow(shadow_path, nc_volume, nc_images_dir)
830		    == FALSE) {
831		    my_log(LOG_INFO, "macNC: couldn't remove"
832			   " shadow %s, %m", shadow_path);
833		    return (FALSE);
834		}
835		nc_volume = NULL;
836	    }
837	    if (nc_volume != NULL) {
838		if (set_file_size) {
839		    S_timestamp("setting shadow file size");
840		    if (S_create_shadow_file(shadow_path, uid, gid, needspace,
841					     TRUE)
842			== FALSE) {
843			my_log(LOG_INFO, "macNC: couldn't create %s, %m",
844			       shadow_path);
845			return (FALSE);
846		    }
847		    S_timestamp("shadow file size set");
848		}
849		else {
850		    if (set_owner_perms) {
851			S_timestamp("setting shadow file perms/owner");
852			chmod(shadow_path, CLIENT_FILE_PERMS);
853			S_set_uid_gid(shadow_path, uid, gid);
854		    }
855		    S_set_dimg_ddsk(shadow_path);
856		    S_timestamp("shadow file perms/owner set");
857		}
858	    }
859	}
860	if (nc_volume == NULL) { /* locate the client's image dir */
861	    nc_volume = S_find_volume_with_space(needspace, def_vol_index,
862						 TRUE);
863	    if (nc_volume == NULL) {
864		if (G_disk_space_warned == FALSE)
865		    my_log(LOG_INFO, "macNC: can't create client image: "
866			   "OUT OF DISK SPACE");
867		G_disk_space_warned = TRUE; /* don't keep complaining */
868		return (FALSE);
869	    }
870	    S_get_volpath(shadow_path, nc_volume, nc_images_dir,
871			  kNetBootShadowName);
872	    G_disk_space_warned = FALSE;
873	    if (S_create_volume_dir(nc_volume, nc_images_dir,
874				    CLIENT_DIR_PERMS) == FALSE) {
875		return (FALSE);
876	    }
877	    if (S_create_shadow_file(shadow_path, uid, gid, needspace,
878				     TRUE) == FALSE) {
879		my_log(LOG_INFO, "macNC: couldn't create %s, %m",
880		       shadow_path);
881		return (FALSE);
882	    }
883	}
884
885	/* add the shadow file option */
886	if (S_add_afppath_option(servip, options, nc_volume,
887				 nc_images_dir, kNetBootShadowName,
888				 macNCtag_shared_system_shadow_file_e)
889	    == FALSE) {
890	    return (FALSE);
891	}
892    }
893    return (TRUE);
894}
895
896boolean_t
897macNC_allocate(NBImageEntryRef image_entry,
898	       struct dhcp * reply, const char * hostname,
899	       struct in_addr servip, int host_number, dhcpoa_t * options,
900	       uid_t uid, const char * afp_user, const char * passwd)
901{
902    if (G_client_sharepoints == NULL) {
903	syslog(LOG_NOTICE, "macNC_allocate: no client sharepoints");
904	return (FALSE);
905    }
906
907    if (dhcpoa_add(options, macNCtag_user_name_e, strlen(afp_user),
908		   afp_user) != dhcpoa_success_e) {
909	my_log(LOG_INFO,
910	       "macNC: afp user name option add %s failed, %s",
911	       afp_user, dhcpoa_err(options));
912	return (FALSE);
913    }
914    /* add the Mac OS machine name option */
915    if (dhcpoa_add(options, macNCtag_MacOS_machine_name_e,
916		   strlen(hostname), hostname) != dhcpoa_success_e) {
917	my_log(LOG_INFO,
918	       "macNC: machine name option add client %s failed, %s",
919	       hostname, dhcpoa_err(options));
920	return (FALSE);
921    }
922    {
923	char	buf[16];
924	int	buf_len = sizeof(buf);
925
926	if (macNCopt_str_to_type(passwd, macNCtype_afp_password_e,
927				 buf, &buf_len, NULL) == FALSE
928	    || dhcpoa_add(options, macNCtag_password_e,
929			  buf_len, buf) != dhcpoa_success_e) {
930	    my_log(LOG_INFO, "macNC: failed add afp password for %d",
931		   host_number);
932	    return (FALSE);
933	}
934    }
935    if (S_add_image_options(image_entry, uid, G_admin_gid, servip,
936			    options, host_number, hostname) == FALSE) {
937	my_log(LOG_INFO,
938	       "macNC: S_add_image_options for %s failed", afp_user);
939	return (FALSE);
940    }
941    return (TRUE);
942}
943
944void
945macNC_unlink_shadow(int host_number, const char * hostname)
946{
947    int			def_vol_index;
948    int			i;
949    char		nc_images_dir[PATH_MAX];
950    NBSPEntry *	nc_volume = NULL;
951    struct stat		shadow_statb;
952    char		shadow_path[PATH_MAX];
953    int			vol_index;
954
955    if (G_client_sharepoints == NULL) {
956	return;
957    }
958    snprintf(nc_images_dir, sizeof(nc_images_dir),
959	     "%s", hostname);
960
961    def_vol_index = (host_number - 1) % NBSPList_count(G_client_sharepoints);
962
963    /* check all volumes for a client image directory starting at default */
964    nc_volume = NULL;
965    for (i = 0, vol_index = def_vol_index;
966	 i < NBSPList_count(G_client_sharepoints); i++) {
967	NBSPEntry * entry = NBSPList_element(G_client_sharepoints, vol_index);
968	if (S_stat_path_vol_file(shadow_path, entry, nc_images_dir,
969				 kNetBootShadowName, &shadow_statb) == 0) {
970	    if (unlink(shadow_path) < 0) {
971		my_log(LOG_DEBUG,
972		       "macNC: unlink(%s) failed, %m", shadow_path);
973	    }
974	    return;
975	}
976	vol_index = (vol_index + 1) % NBSPList_count(G_client_sharepoints);
977    }
978    return;
979}
980