1f1b9d127SSheldon Hearn /* 2f1b9d127SSheldon Hearn * Copyright (c) 2000, Boris Popov 3f1b9d127SSheldon Hearn * All rights reserved. 4f1b9d127SSheldon Hearn * 5f1b9d127SSheldon Hearn * Redistribution and use in source and binary forms, with or without 6f1b9d127SSheldon Hearn * modification, are permitted provided that the following conditions 7f1b9d127SSheldon Hearn * are met: 8f1b9d127SSheldon Hearn * 1. Redistributions of source code must retain the above copyright 9f1b9d127SSheldon Hearn * notice, this list of conditions and the following disclaimer. 10f1b9d127SSheldon Hearn * 2. Redistributions in binary form must reproduce the above copyright 11f1b9d127SSheldon Hearn * notice, this list of conditions and the following disclaimer in the 12f1b9d127SSheldon Hearn * documentation and/or other materials provided with the distribution. 13f1b9d127SSheldon Hearn * 3. All advertising materials mentioning features or use of this software 14f1b9d127SSheldon Hearn * must display the following acknowledgement: 15f1b9d127SSheldon Hearn * This product includes software developed by Boris Popov. 16f1b9d127SSheldon Hearn * 4. Neither the name of the author nor the names of any co-contributors 17f1b9d127SSheldon Hearn * may be used to endorse or promote products derived from this software 18f1b9d127SSheldon Hearn * without specific prior written permission. 19f1b9d127SSheldon Hearn * 20f1b9d127SSheldon Hearn * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 21f1b9d127SSheldon Hearn * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22f1b9d127SSheldon Hearn * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23f1b9d127SSheldon Hearn * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 24f1b9d127SSheldon Hearn * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25f1b9d127SSheldon Hearn * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26f1b9d127SSheldon Hearn * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27f1b9d127SSheldon Hearn * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28f1b9d127SSheldon Hearn * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29f1b9d127SSheldon Hearn * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30f1b9d127SSheldon Hearn * SUCH DAMAGE. 31f1b9d127SSheldon Hearn * 32df3342d6SSheldon Hearn * $Id: subr.c,v 1.12 2001/08/22 03:31:37 bp Exp $ 33f1b9d127SSheldon Hearn */ 34f1b9d127SSheldon Hearn 35f1b9d127SSheldon Hearn #include <sys/param.h> 36f1b9d127SSheldon Hearn #include <sys/types.h> 37f1b9d127SSheldon Hearn #include <sys/errno.h> 38f1b9d127SSheldon Hearn #include <sys/sysctl.h> 39f1b9d127SSheldon Hearn #include <sys/syscall.h> 40f1b9d127SSheldon Hearn #include <unistd.h> 41f1b9d127SSheldon Hearn #include <ctype.h> 42f1b9d127SSheldon Hearn #include <string.h> 43f1b9d127SSheldon Hearn #include <stdio.h> 44f1b9d127SSheldon Hearn #include <stdlib.h> 45f1b9d127SSheldon Hearn #include <stdarg.h> 46f1b9d127SSheldon Hearn #include <err.h> 47f1b9d127SSheldon Hearn 48f1b9d127SSheldon Hearn #include <netsmb/netbios.h> 49f1b9d127SSheldon Hearn #include <netsmb/smb_lib.h> 50f1b9d127SSheldon Hearn #include <netsmb/nb_lib.h> 51f1b9d127SSheldon Hearn #include <cflib.h> 52f1b9d127SSheldon Hearn 53df3342d6SSheldon Hearn #ifdef APPLE 54df3342d6SSheldon Hearn #include <sysexits.h> 55df3342d6SSheldon Hearn #include <sys/wait.h> 56df3342d6SSheldon Hearn #include <mach/mach.h> 57df3342d6SSheldon Hearn #include <mach/mach_error.h> 58df3342d6SSheldon Hearn 59df3342d6SSheldon Hearn uid_t real_uid, eff_uid; 60df3342d6SSheldon Hearn #endif 61df3342d6SSheldon Hearn 62f1b9d127SSheldon Hearn extern char *__progname; 63f1b9d127SSheldon Hearn 64f1b9d127SSheldon Hearn static int smblib_initialized; 65f1b9d127SSheldon Hearn 66f1b9d127SSheldon Hearn struct rcfile *smb_rc; 67f1b9d127SSheldon Hearn 68f1b9d127SSheldon Hearn int 69f1b9d127SSheldon Hearn smb_lib_init(void) 70f1b9d127SSheldon Hearn { 71f1b9d127SSheldon Hearn int error; 72f1b9d127SSheldon Hearn int kv; 73f1b9d127SSheldon Hearn size_t kvlen = sizeof(kv); 74f1b9d127SSheldon Hearn 75f1b9d127SSheldon Hearn if (smblib_initialized) 76f1b9d127SSheldon Hearn return 0; 77f1b9d127SSheldon Hearn #if __FreeBSD_version > 400000 78f1b9d127SSheldon Hearn error = sysctlbyname("net.smb.version", &kv, &kvlen, NULL, 0); 79f1b9d127SSheldon Hearn if (error) { 80f1b9d127SSheldon Hearn warnx("%s: can't find kernel module\n", __FUNCTION__); 81f1b9d127SSheldon Hearn return error; 82f1b9d127SSheldon Hearn } 83f1b9d127SSheldon Hearn if (NSMB_VERSION != kv) { 84f1b9d127SSheldon Hearn warnx("%s: kernel module version(%d) don't match library(%d).\n", __FUNCTION__, kv, NSMB_VERSION); 85f1b9d127SSheldon Hearn return EINVAL; 86f1b9d127SSheldon Hearn } 87f1b9d127SSheldon Hearn #endif 88f1b9d127SSheldon Hearn if ((error = nls_setlocale("")) != 0) { 89f1b9d127SSheldon Hearn warnx("%s: can't initialise locale\n", __FUNCTION__); 90f1b9d127SSheldon Hearn return error; 91f1b9d127SSheldon Hearn } 92f1b9d127SSheldon Hearn smblib_initialized++; 93f1b9d127SSheldon Hearn return 0; 94f1b9d127SSheldon Hearn } 95f1b9d127SSheldon Hearn 96f1b9d127SSheldon Hearn /* 97f1b9d127SSheldon Hearn * Print a (descriptive) error message 98f1b9d127SSheldon Hearn * error values: 99f1b9d127SSheldon Hearn * 0 - no specific error code available; 100f1b9d127SSheldon Hearn * 1..32767 - system error 101f1b9d127SSheldon Hearn */ 102f1b9d127SSheldon Hearn void 103f1b9d127SSheldon Hearn smb_error(const char *fmt, int error,...) { 104f1b9d127SSheldon Hearn va_list ap; 105f1b9d127SSheldon Hearn const char *cp; 106f1b9d127SSheldon Hearn int errtype = error & SMB_ERRTYPE_MASK; 107f1b9d127SSheldon Hearn 108f1b9d127SSheldon Hearn fprintf(stderr, "%s: ", __progname); 109f1b9d127SSheldon Hearn va_start(ap, error); 110f1b9d127SSheldon Hearn vfprintf(stderr, fmt, ap); 111f1b9d127SSheldon Hearn va_end(ap); 112f1b9d127SSheldon Hearn if (error == -1) 113f1b9d127SSheldon Hearn error = errno; 114f1b9d127SSheldon Hearn else 115f1b9d127SSheldon Hearn error &= ~SMB_ERRTYPE_MASK; 116f1b9d127SSheldon Hearn switch (errtype) { 117f1b9d127SSheldon Hearn case SMB_SYS_ERROR: 118f1b9d127SSheldon Hearn if (error) 119f1b9d127SSheldon Hearn fprintf(stderr, ": syserr = %s\n", strerror(error)); 120f1b9d127SSheldon Hearn else 121f1b9d127SSheldon Hearn fprintf(stderr, "\n"); 122f1b9d127SSheldon Hearn break; 123f1b9d127SSheldon Hearn case SMB_RAP_ERROR: 124f1b9d127SSheldon Hearn fprintf(stderr, ": raperr = %d (0x%04x)\n", error, error); 125f1b9d127SSheldon Hearn break; 126f1b9d127SSheldon Hearn case SMB_NB_ERROR: 127f1b9d127SSheldon Hearn cp = nb_strerror(error); 128f1b9d127SSheldon Hearn if (cp == NULL) 129f1b9d127SSheldon Hearn fprintf(stderr, ": nberr = unknown (0x%04x)\n", error); 130f1b9d127SSheldon Hearn else 131f1b9d127SSheldon Hearn fprintf(stderr, ": nberr = %s\n", cp); 132f1b9d127SSheldon Hearn break; 133f1b9d127SSheldon Hearn default: 134f1b9d127SSheldon Hearn fprintf(stderr, "\n"); 135f1b9d127SSheldon Hearn } 136f1b9d127SSheldon Hearn } 137f1b9d127SSheldon Hearn 138f1b9d127SSheldon Hearn char * 139f1b9d127SSheldon Hearn smb_printb(char *dest, int flags, const struct smb_bitname *bnp) { 140f1b9d127SSheldon Hearn int first = 1; 141f1b9d127SSheldon Hearn 142f1b9d127SSheldon Hearn strcpy(dest, "<"); 143f1b9d127SSheldon Hearn for(; bnp->bn_bit; bnp++) { 144f1b9d127SSheldon Hearn if (flags & bnp->bn_bit) { 145f1b9d127SSheldon Hearn strcat(dest, bnp->bn_name); 146f1b9d127SSheldon Hearn first = 0; 147f1b9d127SSheldon Hearn } 148f1b9d127SSheldon Hearn if (!first && (flags & bnp[1].bn_bit)) 149f1b9d127SSheldon Hearn strcat(dest, "|"); 150f1b9d127SSheldon Hearn } 151f1b9d127SSheldon Hearn strcat(dest, ">"); 152f1b9d127SSheldon Hearn return dest; 153f1b9d127SSheldon Hearn } 154f1b9d127SSheldon Hearn 155f1b9d127SSheldon Hearn /* 156f1b9d127SSheldon Hearn * first read ~/.smbrc, next try to merge SMB_CFG_FILE 157f1b9d127SSheldon Hearn */ 158f1b9d127SSheldon Hearn int 159f1b9d127SSheldon Hearn smb_open_rcfile(void) 160f1b9d127SSheldon Hearn { 161f1b9d127SSheldon Hearn char *home, *fn; 162f1b9d127SSheldon Hearn int error; 163f1b9d127SSheldon Hearn 164f1b9d127SSheldon Hearn home = getenv("HOME"); 165f1b9d127SSheldon Hearn if (home) { 166f1b9d127SSheldon Hearn fn = malloc(strlen(home) + 20); 167f1b9d127SSheldon Hearn sprintf(fn, "%s/.nsmbrc", home); 168f1b9d127SSheldon Hearn error = rc_open(fn, "r", &smb_rc); 169f1b9d127SSheldon Hearn free(fn); 170f1b9d127SSheldon Hearn } 171f1b9d127SSheldon Hearn error = rc_merge(SMB_CFG_FILE, &smb_rc); 172f1b9d127SSheldon Hearn if (smb_rc == NULL) { 173f1b9d127SSheldon Hearn printf("Warning: no cfg file(s) found.\n"); 174f1b9d127SSheldon Hearn return ENOENT; 175f1b9d127SSheldon Hearn } 176f1b9d127SSheldon Hearn return 0; 177f1b9d127SSheldon Hearn } 178f1b9d127SSheldon Hearn 179f1b9d127SSheldon Hearn void * 180f1b9d127SSheldon Hearn smb_dumptree(void) 181f1b9d127SSheldon Hearn { 182f1b9d127SSheldon Hearn size_t len; 183f1b9d127SSheldon Hearn void *p; 184f1b9d127SSheldon Hearn int error; 185f1b9d127SSheldon Hearn 186df3342d6SSheldon Hearn #ifdef APPLE 187df3342d6SSheldon Hearn seteuid(eff_uid); /* restore setuid root briefly */ 188df3342d6SSheldon Hearn #endif 189f1b9d127SSheldon Hearn error = sysctlbyname("net.smb.treedump", NULL, &len, NULL, 0); 190df3342d6SSheldon Hearn #ifdef APPLE 191df3342d6SSheldon Hearn seteuid(real_uid); /* and back to real user */ 192df3342d6SSheldon Hearn #endif 193f1b9d127SSheldon Hearn if (error) 194f1b9d127SSheldon Hearn return NULL; 195f1b9d127SSheldon Hearn p = malloc(len); 196f1b9d127SSheldon Hearn if (p == NULL) 197f1b9d127SSheldon Hearn return NULL; 198df3342d6SSheldon Hearn #ifdef APPLE 199df3342d6SSheldon Hearn seteuid(eff_uid); /* restore setuid root briefly */ 200df3342d6SSheldon Hearn #endif 201f1b9d127SSheldon Hearn error = sysctlbyname("net.smb.treedump", p, &len, NULL, 0); 202df3342d6SSheldon Hearn #ifdef APPLE 203df3342d6SSheldon Hearn seteuid(real_uid); /* and back to real user */ 204df3342d6SSheldon Hearn #endif 205f1b9d127SSheldon Hearn if (error) { 206f1b9d127SSheldon Hearn free(p); 207f1b9d127SSheldon Hearn return NULL; 208f1b9d127SSheldon Hearn } 209f1b9d127SSheldon Hearn return p; 210f1b9d127SSheldon Hearn } 211f1b9d127SSheldon Hearn 212df3342d6SSheldon Hearn char * 213f1b9d127SSheldon Hearn smb_simplecrypt(char *dst, const char *src) 214f1b9d127SSheldon Hearn { 215f1b9d127SSheldon Hearn int ch, pos; 216df3342d6SSheldon Hearn char *dp; 217f1b9d127SSheldon Hearn 218df3342d6SSheldon Hearn if (dst == NULL) { 219df3342d6SSheldon Hearn dst = malloc(4 + 2 * strlen(src)); 220df3342d6SSheldon Hearn if (dst == NULL) 221df3342d6SSheldon Hearn return NULL; 222df3342d6SSheldon Hearn } 223df3342d6SSheldon Hearn dp = dst; 224f1b9d127SSheldon Hearn *dst++ = '$'; 225f1b9d127SSheldon Hearn *dst++ = '$'; 226f1b9d127SSheldon Hearn *dst++ = '1'; 227f1b9d127SSheldon Hearn pos = 27; 228f1b9d127SSheldon Hearn while (*src) { 229f1b9d127SSheldon Hearn ch = *src++; 230f1b9d127SSheldon Hearn if (isascii(ch)) 231f1b9d127SSheldon Hearn ch = (isupper(ch) ? ('A' + (ch - 'A' + 13) % 26) : 232f1b9d127SSheldon Hearn islower(ch) ? ('a' + (ch - 'a' + 13) % 26) : ch); 233f1b9d127SSheldon Hearn ch ^= pos; 234f1b9d127SSheldon Hearn pos += 13; 235f1b9d127SSheldon Hearn sprintf(dst, "%02x", ch); 236f1b9d127SSheldon Hearn dst += 2; 237f1b9d127SSheldon Hearn } 238f1b9d127SSheldon Hearn *dst = 0; 239df3342d6SSheldon Hearn return dp; 240f1b9d127SSheldon Hearn } 241f1b9d127SSheldon Hearn 242f1b9d127SSheldon Hearn int 243f1b9d127SSheldon Hearn smb_simpledecrypt(char *dst, const char *src) 244f1b9d127SSheldon Hearn { 245f1b9d127SSheldon Hearn char *ep, hexval[3]; 246f1b9d127SSheldon Hearn int len, ch, pos; 247f1b9d127SSheldon Hearn 248f1b9d127SSheldon Hearn if (strncmp(src, "$$1", 3) != 0) 249f1b9d127SSheldon Hearn return EINVAL; 250f1b9d127SSheldon Hearn src += 3; 251f1b9d127SSheldon Hearn len = strlen(src); 252f1b9d127SSheldon Hearn if (len & 1) 253f1b9d127SSheldon Hearn return EINVAL; 254f1b9d127SSheldon Hearn len /= 2; 255f1b9d127SSheldon Hearn hexval[2] = 0; 256f1b9d127SSheldon Hearn pos = 27; 257f1b9d127SSheldon Hearn while (len--) { 258f1b9d127SSheldon Hearn hexval[0] = *src++; 259f1b9d127SSheldon Hearn hexval[1] = *src++; 260f1b9d127SSheldon Hearn ch = strtoul(hexval, &ep, 16); 261f1b9d127SSheldon Hearn if (*ep != 0) 262f1b9d127SSheldon Hearn return EINVAL; 263f1b9d127SSheldon Hearn ch ^= pos; 264f1b9d127SSheldon Hearn pos += 13; 265f1b9d127SSheldon Hearn if (isascii(ch)) 266f1b9d127SSheldon Hearn ch = (isupper(ch) ? ('A' + (ch - 'A' + 13) % 26) : 267f1b9d127SSheldon Hearn islower(ch) ? ('a' + (ch - 'a' + 13) % 26) : ch); 268f1b9d127SSheldon Hearn *dst++ = ch; 269f1b9d127SSheldon Hearn } 270f1b9d127SSheldon Hearn *dst = 0; 271f1b9d127SSheldon Hearn return 0; 272f1b9d127SSheldon Hearn } 273df3342d6SSheldon Hearn 274df3342d6SSheldon Hearn 275df3342d6SSheldon Hearn #ifdef APPLE 276df3342d6SSheldon Hearn static int 277df3342d6SSheldon Hearn safe_execv(char *args[]) 278df3342d6SSheldon Hearn { 279df3342d6SSheldon Hearn int pid; 280df3342d6SSheldon Hearn union wait status; 281df3342d6SSheldon Hearn 282df3342d6SSheldon Hearn pid = fork(); 283df3342d6SSheldon Hearn if (pid == 0) { 284df3342d6SSheldon Hearn (void)execv(args[0], args); 285df3342d6SSheldon Hearn errx(EX_OSERR, "%s: execv %s failed, %s\n", __progname, 286df3342d6SSheldon Hearn args[0], strerror(errno)); 287df3342d6SSheldon Hearn } 288df3342d6SSheldon Hearn if (pid == -1) { 289df3342d6SSheldon Hearn fprintf(stderr, "%s: fork failed, %s\n", __progname, 290df3342d6SSheldon Hearn strerror(errno)); 291df3342d6SSheldon Hearn return (1); 292df3342d6SSheldon Hearn } 293df3342d6SSheldon Hearn if (wait4(pid, (int *)&status, 0, NULL) != pid) { 294df3342d6SSheldon Hearn fprintf(stderr, "%s: BUG executing %s command\n", __progname, 295df3342d6SSheldon Hearn args[0]); 296df3342d6SSheldon Hearn return (1); 297df3342d6SSheldon Hearn } else if (!WIFEXITED(status)) { 298df3342d6SSheldon Hearn fprintf(stderr, "%s: %s command aborted by signal %d\n", 299df3342d6SSheldon Hearn __progname, args[0], WTERMSIG(status)); 300df3342d6SSheldon Hearn return (1); 301df3342d6SSheldon Hearn } else if (WEXITSTATUS(status)) { 302df3342d6SSheldon Hearn fprintf(stderr, "%s: %s command failed, exit status %d: %s\n", 303df3342d6SSheldon Hearn __progname, args[0], WEXITSTATUS(status), 304df3342d6SSheldon Hearn strerror(WEXITSTATUS(status))); 305df3342d6SSheldon Hearn return (1); 306df3342d6SSheldon Hearn } 307df3342d6SSheldon Hearn return (0); 308df3342d6SSheldon Hearn } 309df3342d6SSheldon Hearn 310df3342d6SSheldon Hearn 311df3342d6SSheldon Hearn void 312df3342d6SSheldon Hearn dropsuid() 313df3342d6SSheldon Hearn { 314df3342d6SSheldon Hearn /* drop setuid root privs asap */ 315df3342d6SSheldon Hearn eff_uid = geteuid(); 316df3342d6SSheldon Hearn real_uid = getuid(); 317df3342d6SSheldon Hearn seteuid(real_uid); 318df3342d6SSheldon Hearn return; 319df3342d6SSheldon Hearn } 320df3342d6SSheldon Hearn 321df3342d6SSheldon Hearn 322df3342d6SSheldon Hearn static int 323df3342d6SSheldon Hearn kextisloaded(char * kextname) 324df3342d6SSheldon Hearn { 325df3342d6SSheldon Hearn mach_port_t kernel_port; 326df3342d6SSheldon Hearn kmod_info_t *k, *loaded_modules = 0; 327df3342d6SSheldon Hearn int err, loaded_count = 0; 328df3342d6SSheldon Hearn 329df3342d6SSheldon Hearn /* on error return not loaded - to make loadsmbvfs fail */ 330df3342d6SSheldon Hearn 331df3342d6SSheldon Hearn err = task_for_pid(mach_task_self(), 0, &kernel_port); 332df3342d6SSheldon Hearn if (err) { 333df3342d6SSheldon Hearn fprintf(stderr, "%s: %s: %s\n", __progname, 334df3342d6SSheldon Hearn "unable to get kernel task port", 335df3342d6SSheldon Hearn mach_error_string(err)); 336df3342d6SSheldon Hearn return (0); 337df3342d6SSheldon Hearn } 338df3342d6SSheldon Hearn err = kmod_get_info(kernel_port, (void *)&loaded_modules, 339df3342d6SSheldon Hearn &loaded_count); /* never freed */ 340df3342d6SSheldon Hearn if (err) { 341df3342d6SSheldon Hearn fprintf(stderr, "%s: %s: %s\n", __progname, 342df3342d6SSheldon Hearn "kmod_get_info() failed", 343df3342d6SSheldon Hearn mach_error_string(err)); 344df3342d6SSheldon Hearn return (0); 345df3342d6SSheldon Hearn } 346df3342d6SSheldon Hearn for (k = loaded_modules; k; k = k->next ? k+1 : 0) 347df3342d6SSheldon Hearn if (!strcmp(k->name, kextname)) 348df3342d6SSheldon Hearn return (1); 349df3342d6SSheldon Hearn return (0); 350df3342d6SSheldon Hearn } 351df3342d6SSheldon Hearn 352df3342d6SSheldon Hearn 353df3342d6SSheldon Hearn #define KEXTLOAD_COMMAND "/sbin/kextload" 354df3342d6SSheldon Hearn #define FS_KEXT_DIR "/System/Library/Extensions/smbfs.kext" 355df3342d6SSheldon Hearn #define FULL_KEXTNAME "com.apple.filesystems.smbfs" 356df3342d6SSheldon Hearn 357df3342d6SSheldon Hearn 358df3342d6SSheldon Hearn int 359df3342d6SSheldon Hearn loadsmbvfs() 360df3342d6SSheldon Hearn { 361df3342d6SSheldon Hearn const char *kextargs[] = {KEXTLOAD_COMMAND, FS_KEXT_DIR, NULL}; 362df3342d6SSheldon Hearn int error = 0; 363df3342d6SSheldon Hearn 364df3342d6SSheldon Hearn /* 365df3342d6SSheldon Hearn * temporarily revert to root (required for kextload) 366df3342d6SSheldon Hearn */ 367df3342d6SSheldon Hearn seteuid(eff_uid); 368df3342d6SSheldon Hearn if (!kextisloaded(FULL_KEXTNAME)) { 369df3342d6SSheldon Hearn error = safe_execv(kextargs); 370df3342d6SSheldon Hearn if (!error) 371df3342d6SSheldon Hearn error = !kextisloaded(FULL_KEXTNAME); 372df3342d6SSheldon Hearn } 373df3342d6SSheldon Hearn seteuid(real_uid); /* and back to real user */ 374df3342d6SSheldon Hearn return (error); 375df3342d6SSheldon Hearn } 376df3342d6SSheldon Hearn #endif /* APPLE */ 377