1/*
2 *  webdavd.c
3 *  webdavfs
4 *
5 *  Created by William Conway on 12/15/06.
6 *  Copyright 2006 Apple Computer, Inc. All rights reserved.
7 *
8 */
9
10#include "webdavd.h"
11#include "LogMessage.h"
12
13#include <mach/mach.h>
14#include <mach/mach_error.h>
15#include <servers/bootstrap.h>
16#include <IOKit/kext/KextManager.h>
17#include <sys/types.h>
18#include <sys/sysctl.h>
19#include <sys/errno.h>
20#include <sys/wait.h>
21#include <sys/syslog.h>
22#include <sys/un.h>
23#include <sys/resource.h>
24#include <sys/param.h>
25#include <sys/mount.h>
26#include <sys/syslimits.h>
27#include <sys/socket.h>
28#include <asl.h>
29
30#include <netinet/in.h>
31
32#include <stdlib.h>
33#include <stdio.h>
34#include <unistd.h>
35#include <string.h>
36#include <pthread.h>
37#include <err.h>
38#include <fcntl.h>
39#include <paths.h>
40#include <readpassphrase.h>
41#include <signal.h>
42#include <time.h>
43#include <notify.h>
44#include <sandbox.h>
45#include <libxml/parser.h>
46#include <libxml/xmlmemory.h>
47
48#include <CoreServices/CoreServices.h>
49#include <SystemConfiguration/SystemConfiguration.h>
50
51#include <mntopts.h>
52#include "webdav_authcache.h"
53#include "webdav_network.h"
54#include "webdav_requestqueue.h"
55#include "webdav_cache.h"
56#include "webdav_cookie.h"
57#include "webdav_utils.h"
58
59/*****************************************************************************/
60
61/*
62 * shared globals
63 */
64unsigned int gtimeout_val;		/* the pulse_thread runs at double this rate */
65char *gtimeout_string;			/* the length of time LOCKs are held on on the server */
66int gWebdavfsDebug = FALSE;		/* TRUE if the WEBDAVFS_DEBUG environment variable is set */
67uid_t gProcessUID = -1;			/* the daemon's UID */
68int gSuppressAllUI = FALSE;		/* if TRUE, the mount requested that all UI be supressed */
69int gSecureServerAuth = FALSE;		/* if TRUE, the authentication for server challenges must be sent securely (not clear-text) */
70char gWebdavCachePath[MAXPATHLEN + 1] = ""; /* the current path to the cache directory */
71int gSecureConnection = FALSE;	/* if TRUE, the connection is secure */
72CFURLRef gBaseURL = NULL;		/* the base URL for this mount */
73CFStringRef gBasePath = NULL;	/* the base path (from gBaseURL) for this mount */
74char gBasePathStr[MAXPATHLEN];	/* gBasePath as a c-string */
75uint32_t gServerIdent = 0;		/* identifies some (not all) types of servers we are connected to (i.e. WEBDAV_IDISK_SERVER) */
76fsid_t	g_fsid;					/* file system id */
77char g_mountPoint[MAXPATHLEN];	/* path to our mount point */
78
79/*
80 * mount_webdav.c file globals
81 */
82static int wakeupFDs[2] = { -1, -1 };	/* used by webdav_kill() to communicate with main select loop */
83static char mntfromname[MNAMELEN];		/* the mntfromname */
84
85/*
86 * We want getmntopts() to simply ignore
87 * unrecognized mount options.
88 */
89int getmnt_silent = 1;
90
91// The maximum size of an upload or download to allow the
92// system to cache.
93uint64_t webdavCacheMaximumSize = WEBDAV_DEFAULT_CACHE_MAX_SIZE;
94
95// Sets the maximum size of an upload or download to allow the
96// system to cache, based on the amount of physical memory in
97// the system.
98static void setCacheMaximumSize(void);
99
100#define CFENVFORMATSTRING "__CF_USER_TEXT_ENCODING=0x%X:0:0"
101
102/*****************************************************************************/
103
104void webdav_debug_assert(const char *componentNameString, const char *assertionString,
105	const char *exceptionLabelString, const char *errorString,
106	const char *fileName, long lineNumber, uint64_t errorCode)
107{
108	#pragma unused(componentNameString)
109
110	if ( (assertionString != NULL) && (*assertionString != '\0') )
111	{
112		if ( errorCode != 0 )
113		{
114			syslog(WEBDAV_LOG_LEVEL, "(%s) failed with %d%s%s%s%s; file: %s; line: %ld",
115			assertionString,
116			(int)errorCode,
117			(errorString != NULL) ? "; " : "",
118			(errorString != NULL) ? errorString : "",
119			(exceptionLabelString != NULL) ? "; going to " : "",
120			(exceptionLabelString != NULL) ? exceptionLabelString : "",
121			fileName,
122			lineNumber);
123		}
124		else
125		{
126			syslog(WEBDAV_LOG_LEVEL, "(%s) failed%s%s%s%s; file: %s; line: %ld",
127			assertionString,
128			(errorString != NULL) ? "; " : "",
129			(errorString != NULL) ? errorString : "",
130			(exceptionLabelString != NULL) ? "; going to " : "",
131			(exceptionLabelString != NULL) ? exceptionLabelString : "",
132			fileName,
133			lineNumber);
134		}
135	}
136	else
137	{
138		syslog(WEBDAV_LOG_LEVEL, "%s; file: %s; line: %ld",
139		errorString,
140		fileName,
141		lineNumber);
142	}
143}
144
145/*****************************************************************************/
146
147static void usage(void)
148{
149	(void)fprintf(stderr,
150		"usage: mount_webdav [-i] [-s] [-S] [-o options] [-v <volume name>]\n");
151	(void)fprintf(stderr,
152		"\t<WebDAV_URL> node\n");
153}
154
155/*****************************************************************************/
156
157static
158char *GetMountURI(char *arguri, int *isHTTPS)
159{
160	int hasScheme;
161	int hasTrailingSlash;
162	size_t argURILength;
163	char httpStr[] = "http://";
164	size_t httpLength;
165	char *uri;
166	size_t URILength;
167
168	argURILength = strlen(arguri);
169	httpLength = strlen(httpStr);
170
171	*isHTTPS = (strncasecmp(arguri, "https://", strlen("https://")) == 0);
172
173	/* if there's no scheme, we'll have to add "http://" */
174	hasScheme = ((strncasecmp(arguri, httpStr, httpLength) == 0) || *isHTTPS);
175
176	/* if there's no trailing slash, we'll have to add one */
177	hasTrailingSlash = arguri[argURILength - 1] == '/';
178
179	/* allocate space for url */
180	URILength = argURILength + (hasScheme ? 0 : httpLength) + (hasTrailingSlash ? 0 : 1);
181	uri = malloc(URILength + 1);
182	require(uri != NULL, malloc_uri);
183
184	/* copy arguri adding scheme and trailing slash if needed */
185	if ( !hasScheme )
186	{
187		strlcpy(uri, "http://", URILength + 1);
188	}
189	else
190	{
191		*uri = '\0';
192	}
193	strlcat(uri, arguri, URILength + 1);
194
195	if ( !hasTrailingSlash )
196	{
197		strlcat(uri, "/", URILength + 1);
198	}
199
200malloc_uri:
201
202	return ( uri );
203}
204
205/*****************************************************************************/
206
207static OSStatus KeychainItemCopyAccountPassword(SecKeychainItemRef itemRef,
208                                                char *username,
209                                                size_t user_size,
210                                                char *password,
211                                                size_t pass_size)
212{
213    OSStatus                            result;
214    SecKeychainAttribute                attr;
215    SecKeychainAttributeList            attrList;
216    UInt32                              length;
217    void                                *outData;
218
219    /* the attribute we want is the account name */
220    attr.tag = kSecAccountItemAttr;
221    attr.length = 0;
222    attr.data = NULL;
223
224    attrList.count = 1;
225    attrList.attr = &attr;
226
227    result = SecKeychainItemCopyContent(itemRef, NULL, &attrList, &length, &outData);
228    if ( result == noErr )
229	{
230		/* attr.data is the account (username) and outdata is the password */
231		if ( attr.length >= user_size || length >= pass_size ) {
232			syslog(LOG_ERR, "%s: keychain username or password is too long!", __FUNCTION__);
233			result = ENAMETOOLONG;
234		} else {
235			(void)strlcpy(username, attr.data, user_size);
236			(void)strlcpy(password, outData, pass_size);
237		}
238
239		(void) SecKeychainItemFreeContent(&attrList, outData);
240	}
241    return ( result );
242}
243
244/*****************************************************************************/
245
246static SecProtocolType getSecurityProtocol(CFStringRef str)
247{
248	if ( CFStringCompare(str, CFSTR("http"), kCFCompareCaseInsensitive) == 0 )
249		return kSecProtocolTypeHTTP;
250	else if ( CFStringCompare(str, CFSTR("https"), kCFCompareCaseInsensitive) == 0 )
251		return kSecProtocolTypeHTTPS;
252	else
253		return kSecProtocolTypeAny;
254}
255
256/*****************************************************************************/
257
258static int get_keychain_credentials(char* in_url,
259									char* out_user,
260									size_t out_user_size,
261									char* out_pass,
262									size_t out_pass_size)
263
264{
265	CFStringRef        cfhostName = NULL;
266	CFStringRef        cfpath = NULL;
267	CFStringRef        cfscheme = NULL;
268	CFURLRef           cf_url = NULL;
269	SecKeychainItemRef itemRef = NULL;
270	SecProtocolType    protocol;
271	char*              path = NULL;
272	char*              hostName = NULL;
273	int	               result;
274
275	/* Validate input */
276	if ( in_url == NULL|| out_user == NULL || out_pass == NULL)
277		return EINVAL;
278
279	cf_url = CFURLCreateWithBytes(NULL, (const UInt8 *)in_url, strlen(in_url), kCFStringEncodingUTF8, NULL);
280	if ( cf_url == NULL )
281		return ENOMEM;
282
283	cfscheme = CFURLCopyScheme(cf_url);
284	if ( cfscheme == NULL ) {
285		result = ENOMEM;
286		goto cleanup_exit;
287	}
288	protocol = getSecurityProtocol(cfscheme);
289
290	cfhostName = CFURLCopyHostName(cf_url);
291	if ( cfhostName == NULL || (hostName = createUTF8CStringFromCFString(cfhostName)) == NULL) {
292		result = ENOMEM;
293		goto cleanup_exit;
294	}
295
296	cfpath = CFURLCopyPath(cf_url);
297	if ( cfpath == NULL || (path = createUTF8CStringFromCFString(cfpath)) == NULL) {
298		result = ENOMEM;
299		goto cleanup_exit;
300	}
301
302	result = SecKeychainFindInternetPassword(NULL,                                                      /* default keychain */
303                                             (UInt32)strlen(hostName), hostName,                        /* serverName */
304                                             0, NULL,                                                   /* no securityDomain */
305                                             0, NULL,													/* no accountName */
306                                             (UInt32)strlen(path), path,                                /* path */
307                                             0,                                                         /* port */
308                                             protocol,                                                  /* protocol */
309                                             kSecAuthenticationTypeAny,                                 /* authenticationType */
310                                             0, NULL,                                                   /* no password */
311                                             &itemRef);
312
313	/* if it doesn't work the first time, try with a NULL path. This is what NetAuth does. */
314	if ( result != noErr ) {
315		result = SecKeychainFindInternetPassword(NULL,                                                      /* default keychain */
316												 (UInt32)strlen(hostName), hostName,                        /* serverName */
317												 0, NULL,                                                   /* no securityDomain */
318												 0, NULL,													/* no accountName */
319												 0, NULL,													/* no path */
320												 0,                                                         /* port */
321												 protocol,                                                  /* protocol */
322												 kSecAuthenticationTypeAny,                                 /* authenticationType */
323												 0, NULL,                                                   /* no password */
324												 &itemRef);
325
326	}
327
328	if ( result == noErr ) {
329		/* Success, copy the user & pass out */
330		result = KeychainItemCopyAccountPassword(itemRef, out_user, out_user_size, out_pass, out_pass_size);
331	}
332
333cleanup_exit:
334
335	CFReleaseNull(cf_url);
336	CFReleaseNull(cfscheme);
337	CFReleaseNull(cfhostName);
338	CFReleaseNull(cfpath);
339	CFReleaseNull(itemRef);
340	if (path)     free(path);
341	if (hostName) free(hostName);
342
343	return result;
344}
345
346/*****************************************************************************/
347
348/*
349 * webdav_kill lets the select loop know to call webdav_force_unmount by feeding
350 * the wakeupFDs pipe. This is the signal handler for signals that should
351 * terminate us.
352 */
353void webdav_kill(int message)
354{
355	/* if there's a read end of the pipe*/
356	if (wakeupFDs[0] != -1)
357	{
358		/* if there's a write end of the  pipe */
359		if (wakeupFDs[1] != -1)
360		{
361			/* write the message */
362			verify(write(wakeupFDs[1], &message, sizeof(int)) == sizeof(int));
363		}
364		/* else we are already in the process of force unmounting */
365	}
366	else
367	{
368		/* there's no read end so just exit */
369		exit(EXIT_FAILURE);
370	}
371}
372
373/*****************************************************************************/
374
375/*
376 * webdav_force_unmount
377 *
378 * webdav_force_unmount is called from our select loop when the mount_webdav
379 * process receives a signal, or hits some unrecoverable condition which
380 * requires a force unmount.
381 */
382static void webdav_force_unmount(char *mntpt)
383{
384	int pid, terminated_pid;
385	int result = -1;
386	union wait status;
387
388	pid = fork();
389	if (pid == 0)
390	{
391		char CFUserTextEncodingEnvSetting[sizeof(CFENVFORMATSTRING) + 20];
392		char *env[] = {CFUserTextEncodingEnvSetting, "", (char *) 0 };
393
394		/*
395		 * Create a new environment with a definition of __CF_USER_TEXT_ENCODING to work
396		 * around CF's interest in the user's home directory (which could be networked,
397		 * causing recursive references through automount). Make sure we include the uid
398		 * since CF will check for this when deciding if to look in the home directory.
399		 */
400		snprintf(CFUserTextEncodingEnvSetting, sizeof(CFUserTextEncodingEnvSetting), CFENVFORMATSTRING, getuid());
401
402		result = execle(PRIVATE_UNMOUNT_COMMAND, PRIVATE_UNMOUNT_COMMAND,
403			PRIVATE_UNMOUNT_FLAGS, mntpt,  (char *) 0, env);
404		/* We can only get here if the exec failed */
405		goto Return;
406	}
407
408	require(pid != -1, Return);
409
410	/* wait for completion here */
411	while ( (terminated_pid = wait4(pid, (int *)&status, 0, NULL)) < 0 )
412	{
413		/* retry if EINTR, else break out with error */
414		if ( errno != EINTR )
415		{
416			break;
417		}
418	}
419
420Return:
421
422	/* execution will not reach this point unless umount fails */
423	check_noerr_string(errno, strerror(errno));
424
425	_exit(EXIT_FAILURE);
426}
427
428/*****************************************************************************/
429
430/* start up a new thread to call webdav_force_unmount() */
431static void create_unmount_thread(void)
432{
433	int error;
434	pthread_t unmount_thread;
435	pthread_attr_t unmount_thread_attr;
436
437	error = pthread_attr_init(&unmount_thread_attr);
438	require_noerr(error, pthread_attr_init);
439
440	error = pthread_attr_setdetachstate(&unmount_thread_attr, PTHREAD_CREATE_DETACHED);
441	require_noerr(error, pthread_attr_setdetachstate);
442
443	error = pthread_create(&unmount_thread, &unmount_thread_attr,
444		(void *)webdav_force_unmount, (void *)mntfromname);
445	require_noerr(error, pthread_create);
446
447	return;
448
449pthread_create:
450pthread_attr_setdetachstate:
451pthread_attr_init:
452
453	exit(error);
454}
455
456/*****************************************************************************/
457
458/* start up a new thread to call network_update_proxy() */
459static void create_change_thread(void)
460{
461	int error;
462	pthread_t change_thread;
463	pthread_attr_t change_thread_attr;
464
465	error = pthread_attr_init(&change_thread_attr);
466	require_noerr(error, pthread_attr_init);
467
468	error = pthread_attr_setdetachstate(&change_thread_attr, PTHREAD_CREATE_DETACHED);
469	require_noerr(error, pthread_attr_setdetachstate);
470
471	error = pthread_create(&change_thread, &change_thread_attr,
472		(void *)network_update_proxy, (void *)NULL);
473	require_noerr(error, pthread_create);
474
475	return;
476
477pthread_create:
478pthread_attr_setdetachstate:
479pthread_attr_init:
480
481	exit(error);
482}
483
484/*****************************************************************************/
485
486// determine the cacheMaximumSize based on the physical memory size of the system
487static void setCacheMaximumSize(void)
488{
489	int		mib[2];
490	size_t	len;
491	int		result;
492	uint64_t	memsize;
493
494	/* get the physical ram size */
495	mib[0] = CTL_HW;
496	mib[1] = HW_MEMSIZE;
497	len = sizeof(uint64_t);
498	result = sysctl(mib, 2, &memsize, &len, 0, 0);
499	if (result == 0) {
500		if (memsize <= WEBDAV_ONE_GIGABYTE) {
501			// limit to 1/8 physical ram for systems with 1G or less
502			webdavCacheMaximumSize = memsize / 8;
503		}
504		else {
505			// limit to 1/4 physical ram for larger systems
506			webdavCacheMaximumSize = memsize / 4;
507		}
508	}
509	else {
510		// limit to kDefaultCacheMaximumSize if for some bizarre reason the physical ram size cannot be determined
511		webdavCacheMaximumSize = WEBDAV_DEFAULT_CACHE_MAX_SIZE;
512	}
513}
514
515/*****************************************************************************/
516
517#define TMP_WEBDAV_UDS _PATH_TMP ".webdavUDS.XXXXXX"	/* Scratch socket name */
518
519/* maximum length of username and password */
520#define WEBDAV_MAX_USERNAME_LEN 256
521#define WEBDAV_MAX_PASSWORD_LEN 256
522#define PASS_PROMPT "Password: "
523#define USER_PROMPT "Username: "
524
525/*****************************************************************************/
526
527static boolean_t readCredentialsFromFile(int fd, char *userName, char *userPassword,
528										 char *proxyUserName, char *proxyUserPassword)
529{
530	boolean_t success;
531	uint32_t be_len;
532	size_t len1;
533	ssize_t rlen;
534
535	success = TRUE;
536
537	if (fd < 0) {
538		syslog(LOG_ERR, "%s: invalid file descriptor arg", __FUNCTION__);
539		return FALSE;
540	}
541
542	// seek to beginnning
543	if (lseek(fd, 0LL, SEEK_SET) == -1) {
544		return FALSE;
545	}
546
547	// read username length
548	rlen = read(fd, &be_len, sizeof(be_len));
549	if (rlen != sizeof(be_len)) {
550		return FALSE;
551	}
552	len1 = ntohl(be_len);
553	if (len1 >= WEBDAV_MAX_USERNAME_LEN) {
554		return FALSE;
555	}
556
557	// read username (if length not zero)
558	if (len1) {
559		rlen = read(fd, userName, len1);
560		if (rlen < 0) {
561			return FALSE;
562		}
563	}
564
565	// read password length
566	rlen = read(fd, &be_len, sizeof(be_len));
567	if (rlen != sizeof(be_len)) {
568		return FALSE;
569	}
570	len1 = ntohl(be_len);
571	if (len1 >= WEBDAV_MAX_USERNAME_LEN) {
572		return FALSE;
573	}
574
575	// read password (if length not zero)
576	if (len1) {
577		rlen = read(fd, userPassword, len1);
578		if (rlen < 0) {
579			return FALSE;
580		}
581	}
582
583	// read proxy username length
584	rlen = read(fd, &be_len, sizeof(be_len));
585	if (rlen != sizeof(be_len)) {
586		return FALSE;
587	}
588	len1 = ntohl(be_len);
589	if (len1 >= WEBDAV_MAX_USERNAME_LEN) {
590		return FALSE;
591	}
592
593	// read proxy username (if length not zero)
594	if (len1) {
595		rlen = read(fd, proxyUserName, len1);
596		if (rlen < 0) {
597			return FALSE;
598		}
599	}
600
601	// read proxy password length
602	rlen = read(fd, &be_len, sizeof(be_len));
603	if (rlen != sizeof(be_len)) {
604		return FALSE;
605	}
606	len1 = ntohl(be_len);
607	if (len1 >= WEBDAV_MAX_USERNAME_LEN) {
608		return FALSE;
609	}
610
611	// read proxy password (if length not zero)
612	if (len1) {
613		rlen = read(fd, proxyUserPassword, len1);
614		if (rlen < 0) {
615			return FALSE;
616		}
617	}
618
619	/* zero contents of file and close it if
620	 * fd is not STDIN_FILENO, STDOUT_FILENO or STDERR_FILENO
621	 */
622	if ( (fd != STDIN_FILENO) &&
623		(fd != STDOUT_FILENO) &&
624		(fd != STDERR_FILENO) )
625	{
626		struct stat statb;
627		off_t bytes_to_overwrite;
628		size_t bytes_to_write;
629		int zero;
630
631		zero = 0;
632
633		if (fstat(fd, &statb) != -1) {
634			bytes_to_overwrite = statb.st_size;
635			(void)lseek(fd, 0LL, SEEK_SET);
636			while (bytes_to_overwrite != 0) {
637				if (bytes_to_overwrite > (off_t)sizeof(zero))
638					bytes_to_write = sizeof(zero);
639				else
640					bytes_to_write = (size_t)bytes_to_overwrite;
641				if (write(fd, &zero, bytes_to_write) < 0)
642				{
643					break;
644				}
645				bytes_to_overwrite -= bytes_to_write;
646			}
647			(void)fsync(fd);
648		}
649		(void)close(fd);
650	}
651
652	return TRUE;
653}
654
655/*****************************************************************************/
656
657int main(int argc, char *argv[])
658{
659	struct webdav_args args;
660	struct sockaddr_un un;
661	struct statfs *buffer;
662	int mntflags;
663	int servermntflags;
664	struct vfsconf vfc;
665	mode_t mode_mask;
666	int return_code;
667	int listen_socket;
668	int store_notify_fd;
669	int lowdisk_notify_fd;
670	int out_token;
671	int error;
672	int ch;
673	int i;
674	int count;
675	unsigned int mntfromnameLength;
676	struct rlimit rlp;
677	struct node_entry *root_node;
678	char volumeName[NAME_MAX + 1] = "";
679	char *uri;
680	int mirrored_mount;
681	int isMounted = FALSE;			/* TRUE if we make it past mount(2) */
682	int checkKeychain = TRUE;       /* set to false for -i and -a */
683	mntoptparse_t mp;
684
685	char user[WEBDAV_MAX_USERNAME_LEN];
686	char pass[WEBDAV_MAX_PASSWORD_LEN];
687	char proxy_user[WEBDAV_MAX_USERNAME_LEN];
688	char proxy_pass[WEBDAV_MAX_PASSWORD_LEN];
689
690	struct webdav_request_statfs request_statfs;
691	struct webdav_reply_statfs reply_statfs;
692	int tempError;
693	struct statfs buf;
694	boolean_t result;
695	kern_return_t status;
696	char	  *errorbuf = NULL;
697
698
699
700	error = 0;
701	g_fsid.val[0] = -1;
702	g_fsid.val[1] = -1;
703
704	/* store our UID */
705	gProcessUID = getuid();
706
707	// init auth arrays
708	memset(user, 0, sizeof(user));
709	memset(pass, 0, sizeof(pass));
710	memset(proxy_user, 0, sizeof(proxy_user));
711	memset(proxy_pass, 0, sizeof(proxy_pass));
712
713	mntflags = 0;
714	/*
715	 * Crack command line args
716	 */
717	while ((ch = getopt(argc, argv, "sSa:io:v:")) != -1)
718	{
719		switch (ch)
720		{
721			case 'a':	/* get user and password credentials from URLMount */
722				{
723					int fd = atoi(optarg); 		/* fd from URLMount */
724
725					/* we're reading in from URLMOUNT, ignore keychain */
726					checkKeychain = FALSE;
727
728					result = readCredentialsFromFile(fd, user, pass,
729													 proxy_user, proxy_pass);
730					if (result == FALSE) {
731						syslog(LOG_DEBUG, "%s: readCredentials returned FALSE", __FUNCTION__);
732						user[0] = '\0';
733						pass[0] = '\0';
734						proxy_user[0] = '\0';
735						proxy_pass[0] = '\0';
736					}
737					break;
738				}
739
740			case 'i': /* called from mount_webdav, get user/pass interactively */
741				/* Ignore keychain when entering user/pass */
742				checkKeychain = FALSE;
743				/* Set RPP_REQUIRE_TTY so that we get an error if this is called
744				 * from the NetFS plugin.
745				 */
746				if (readpassphrase(USER_PROMPT, user, sizeof(user), RPP_REQUIRE_TTY | RPP_ECHO_ON) == NULL) {
747					user[0] = '\0';
748					error = errno;
749				}
750				else if (readpassphrase(PASS_PROMPT, pass, sizeof(pass), RPP_ECHO_OFF) == NULL) {
751					pass[0] = '\0';
752					error = errno;
753				}
754				break;
755
756			case 'S':	/* Suppress ALL dialogs and notifications */
757				gSuppressAllUI = 1;
758				break;
759
760			case 's':	/* authentication to server must be secure */
761				gSecureServerAuth = TRUE;
762				break;
763
764			case 'o':	/* Get the mount options */
765				{
766					const struct mntopt mopts[] = {
767						MOPT_USERQUOTA,
768						MOPT_GROUPQUOTA,
769						MOPT_FSTAB_COMPAT,
770						MOPT_NODEV,
771						MOPT_NOEXEC,
772						MOPT_NOSUID,
773						MOPT_RDONLY,
774						MOPT_UNION,
775						MOPT_BROWSE,
776						MOPT_AUTOMOUNTED,
777						MOPT_QUARANTINE,
778						{ NULL, 0, 0, 0 }
779					};
780
781					mp = getmntopts(optarg, mopts, &mntflags, 0);
782					if (mp == NULL)
783						error = 1;
784					else
785						freemntopts(mp);
786				}
787				break;
788
789			case 'v':	/* Use argument as volume name instead of parsing
790						 * the mount point path for the volume name
791						 */
792				if ( strlen(optarg) <= NAME_MAX )
793				{
794					strlcpy(volumeName, optarg, NAME_MAX + 1);
795				}
796				else
797				{
798					error = 1;
799				}
800				break;
801
802			default:
803				error = 1;
804				break;
805		}
806	}
807
808	if (!error)
809	{
810		if (optind != (argc - 2) || strlen(argv[optind]) > MAXPATHLEN)
811		{
812			error = 1;
813		}
814	}
815	else if (error == 1) {
816		/* Generic error was set, map to EINVAL.  Otherwise should be set to useful errno */
817		error = EINVAL;
818	}
819
820	require_noerr_action_quiet(error, error_exit, usage());
821
822	/* does this look like a mirrored mount (UI suppressed and not browseable) */
823	mirrored_mount = gSuppressAllUI && (mntflags & MNT_DONTBROWSE);
824
825	/* get the mount point */
826	require_action(realpath(argv[optind + 1], g_mountPoint) != NULL, error_exit, error = ENOENT);
827
828	/* derive the volume name from the mount point if needed */
829	if ( *volumeName == '\0' )
830	{
831		/* the volume name wasn't passed on the command line so use the
832		 * last path segment of mountPoint
833		 */
834		strlcpy(volumeName, strrchr(g_mountPoint, '/') + 1, NAME_MAX + 1);
835	}
836
837	/* Get uri (fix it up if needed) */
838	uri = GetMountURI(argv[optind], &gSecureConnection);
839	require_action_quiet(uri != NULL, error_exit, error = EINVAL);
840
841	/* Create a mntfromname from the uri. Make sure the string is no longer than MNAMELEN */
842	strlcpy(mntfromname, uri , MNAMELEN);
843
844	/* Check to see if this mntfromname is already used by a mount point by the
845	 * current user. Sure, someone could mount using the DNS name one time and
846	 * the IP address the next, or they could  munge the path with escaped characters,
847	 * but this check will catch the obvious duplicates.
848	 */
849	count = getmntinfo(&buffer, MNT_NOWAIT);
850	mntfromnameLength = (unsigned int)strlen(mntfromname);
851	for (i = 0; i < count; i++)
852	{
853		/* Is mntfromname already being used as a mntfromname for a webdav mount
854		 * owned by this user?
855		 */
856		if ( (buffer[i].f_owner == gProcessUID) &&
857			 (strcmp("webdav", buffer[i].f_fstypename) == 0) &&
858			 (strlen(buffer[i].f_mntfromname) == mntfromnameLength) &&
859			 (strncasecmp(buffer[i].f_mntfromname, mntfromname, mntfromnameLength) == 0) )
860		{
861			/* Handle MNT_DONTBROWSE separately per:
862			 * <rdar://problem/5322867> iDisk gets mounted multiple timesif kMarkDontBrowse is set
863			 */
864			if(((buffer[i].f_flags & MNT_DONTBROWSE) && (mntflags & MNT_DONTBROWSE)) ||
865				(!(buffer[i].f_flags & MNT_DONTBROWSE) && !(mntflags & MNT_DONTBROWSE)))
866			{
867				/* Yes, this mntfromname is in use - return EBUSY
868				 * (the same error that you'd get if you tried mount a disk device twice).
869				 */
870				syslog(LOG_ERR, "%s is already mounted: %s", mntfromname, strerror(EBUSY));
871				error = EBUSY;
872				goto error_exit;
873			}
874		}
875	}
876
877	/* is WEBDAVFS_DEBUG environment variable set? */
878	gWebdavfsDebug = (getenv("WEBDAVFS_DEBUG") != NULL);
879
880	/* detach from controlling tty and start a new process group */
881	if ( setsid() < 0 )
882	{
883		debug_string("setsid() failed");
884	}
885
886	(void)chdir("/");
887
888	if ( !gWebdavfsDebug )
889	{
890		/* redirect standard input, standard output, and standard error to /dev/null if not debugging */
891		int fd;
892
893		fd = open(_PATH_DEVNULL, O_RDWR, 0);
894		if (fd != -1)
895		{
896			(void)dup2(fd, STDIN_FILENO);
897			(void)dup2(fd, STDOUT_FILENO);
898			(void)dup2(fd, STDERR_FILENO);
899			if (fd > 2)
900			{
901				(void)close(fd);
902			}
903		}
904	}
905
906	/*
907	 * Set the default timeout value
908	 */
909	gtimeout_string = WEBDAV_PULSE_TIMEOUT;
910	gtimeout_val = atoi(gtimeout_string);
911
912	/* get the current maximum number of open files for this process */
913	error = getrlimit(RLIMIT_NOFILE, &rlp);
914	require_noerr_action(error, error_exit, error = EINVAL);
915
916	/* Close any open file descriptors we may have inherited from our
917	 * parent caller.  This excludes the first three.  We don't close
918	 * STDIN_FILENO, STDOUT_FILENO or STDERR_FILENO. Note, this has to
919	 * be done before we open any other file descriptors, but after we
920	 * check for a file containing authentication in /tmp.
921	 */
922	closelog();
923	for (i = 0; i < (int)rlp.rlim_cur; ++i)
924	{
925		switch (i)
926		{
927		case STDIN_FILENO:
928		case STDOUT_FILENO:
929		case STDERR_FILENO:
930			break;
931		default:
932			(void)close(i);
933		}
934	}
935
936	/* Start logging (and change name) */
937	openlog("webdavfs_agent", LOG_CONS | LOG_PID, LOG_DAEMON);
938
939	/* Workaround for daemon/mach ports problem... */
940	CFRunLoopGetCurrent();
941
942	// Set default maximum file upload or download size to allow
943	// the file system to cache
944	setCacheMaximumSize();
945
946	/* raise the maximum number of open files for this process if needed */
947	if ( rlp.rlim_cur < WEBDAV_RLIMIT_NOFILE )
948	{
949		rlp.rlim_cur = WEBDAV_RLIMIT_NOFILE;
950		error = setrlimit(RLIMIT_NOFILE, &rlp);
951		require_noerr_action(error, error_exit, error = EINVAL);
952	}
953
954	/* Set global signal handling to protect us from SIGPIPE */
955	signal(SIGPIPE, SIG_IGN);
956
957	/* Make sure our kext and filesystem are loaded */
958	vfc.vfc_typenum = -1;
959	error = getvfsbyname("webdav", &vfc);
960	if (error)
961	{
962		status = KextManagerLoadKextWithIdentifier(CFSTR("com.apple.filesystems.webdav") ,NULL);
963		if (status != KERN_SUCCESS) {
964			syslog(LOG_ERR, "Loading com.apple.filesystems.webdav status = %d", status);
965		}
966		error = getvfsbyname("webdav", &vfc);
967	}
968	require_noerr_action(error, error_exit, error = EINVAL);
969
970	error = nodecache_init(strlen(uri), uri, &root_node);
971	require_noerr_action_quiet(error, error_exit, error = EINVAL);
972
973	if ( checkKeychain == TRUE ) {
974		error = get_keychain_credentials(argv[optind], user, sizeof(user), pass, sizeof(pass));
975		if ( error != 0 )
976			syslog(LOG_INFO, "%s: get_keychain_credentials exited with result: %d", __FUNCTION__, error);
977	}
978
979	error = authcache_init(user, pass, proxy_user, proxy_pass, NULL);
980	require_noerr_action_quiet(error, error_exit, error = EINVAL);
981
982	cookies_init();
983
984	bzero(user, sizeof(user));
985	bzero(pass, sizeof(pass));
986	bzero(proxy_user, sizeof(proxy_user));
987	bzero(proxy_pass, sizeof(proxy_pass));
988
989	error = network_init((const UInt8 *)uri, strlen(uri), &store_notify_fd, mirrored_mount);
990	free(uri);	/* all done with uri */
991	uri = NULL;
992	require_noerr_action_quiet(error, error_exit, error = EINVAL);
993
994	error = filesystem_init(vfc.vfc_typenum);
995	require_noerr_action_quiet(error, error_exit, error = EINVAL);
996
997	error = requestqueue_init();
998	require_noerr_action_quiet(error, error_exit, error = EINVAL);
999
1000	/*
1001	 * Check out the server and get the mount flags
1002	 */
1003	servermntflags = 0;
1004	error = filesystem_mount(&servermntflags);
1005	require_noerr_quiet(error, error_exit);
1006
1007	/*
1008	 * OR in the mnt flags forced on us by the server
1009	 */
1010	mntflags |= servermntflags;
1011
1012	/*
1013	 * Construct the listening socket
1014	 */
1015	listen_socket = socket(PF_LOCAL, SOCK_STREAM, 0);
1016	require_action(listen_socket >= 0, error_exit, error = EINVAL);
1017
1018	bzero(&un, sizeof(un));
1019	un.sun_len = sizeof(un);
1020	un.sun_family = AF_LOCAL;
1021	require_action(sizeof(TMP_WEBDAV_UDS) < sizeof(un.sun_path), error_exit, error = EINVAL);
1022
1023	strlcpy(un.sun_path, TMP_WEBDAV_UDS, sizeof(un.sun_path));
1024	require_action(mktemp(un.sun_path) != NULL, error_exit, error = EINVAL);
1025
1026	/* bind socket with owner write-only access (S_IWUSR) which is all that's needed for the kext to connect */
1027	mode_mask = umask(0577);
1028	require_action(bind(listen_socket, (struct sockaddr *)&un, (socklen_t)sizeof(un)) >= 0, error_exit, error = EINVAL);
1029	(void)umask(mode_mask);
1030
1031	/* make it hard for anyone to unlink the name */
1032	require_action(chflags(un.sun_path, UF_IMMUTABLE) == 0, error_exit, error = EINVAL);
1033
1034	/* listen with plenty of backlog */
1035	require_noerr_action(listen(listen_socket, WEBDAV_MAX_KEXT_CONNECTIONS), error_exit, error = EINVAL);
1036
1037	/*
1038	 * Ok, we are about to set up the mount point so set the signal handlers
1039	 * so that we know if someone is trying to kill us.
1040	 */
1041
1042	/* open the wakeupFDs pipe */
1043	require_action(pipe(wakeupFDs) == 0, error_exit, wakeupFDs[0] = wakeupFDs[1] = -1; error = EINVAL);
1044
1045	/* set the signal handler to webdav_kill for the signals that aren't ignored by default */
1046
1047	/*
1048	 * Catch signals which suggest we shut down cleanly.  Other signals
1049	 * will kill webdavfs_agent and will be appropriately handled by
1050	 * CrashReporter.
1051	 *
1052	 * SIGTERM is set to webdav_kill after we've successfully mounted.
1053	 */
1054	signal(SIGHUP, webdav_kill);
1055	signal(SIGINT, webdav_kill);
1056	signal(SIGQUIT, webdav_kill);
1057
1058	/* prepare mount args */
1059	args.pa_mntfromname = mntfromname;
1060	args.pa_version = kCurrentWebdavArgsVersion;
1061	args.pa_socket_namelen = (int)sizeof(un);
1062	args.pa_socket_name = (struct sockaddr *)&un;
1063	args.pa_vol_name = volumeName;
1064	args.pa_flags = 0;
1065	if ( gSuppressAllUI )
1066	{
1067		args.pa_flags |= WEBDAV_SUPPRESSALLUI;
1068	}
1069	if ( gSecureConnection )
1070	{
1071		args.pa_flags |= WEBDAV_SECURECONNECTION;
1072	}
1073
1074	args.pa_server_ident = gServerIdent;	/* gServerIdent is set in filesytem_mount() */
1075	args.pa_root_id = root_node->nodeid;
1076	args.pa_root_fileid = WEBDAV_ROOTFILEID;
1077	args.pa_uid = geteuid();	/* effective uid of user mounting this volume */
1078	args.pa_gid = getegid();	/* effective gid of user mounting this volume */
1079	args.pa_dir_size = WEBDAV_DIR_SIZE;
1080	/* pathconf values: >=0 to return value; -1 if not supported */
1081	args.pa_link_max = 1;			/* 1 for file systems that do not support link counts */
1082	args.pa_name_max = NAME_MAX;	/* The maximum number of bytes in a file name */
1083	args.pa_path_max = PATH_MAX;	/* The maximum number of bytes in a pathname */
1084	args.pa_pipe_buf = -1;			/* no support for pipes */
1085	args.pa_chown_restricted = (int)_POSIX_CHOWN_RESTRICTED; /* appropriate privileges are required for the chown(2) */
1086	args.pa_no_trunc = (int)_POSIX_NO_TRUNC; /* file names longer than KERN_NAME_MAX are truncated */
1087
1088	/* need to get the statfs information before we can call mount */
1089	bzero(&reply_statfs, sizeof(struct webdav_reply_statfs));
1090
1091	request_statfs.pcr.pcr_uid = getuid();
1092
1093	request_statfs.root_obj_id = root_node->nodeid;
1094
1095	tempError = filesystem_statfs(&request_statfs, &reply_statfs);
1096
1097	memset (&args.pa_vfsstatfs, 0, sizeof (args.pa_vfsstatfs));
1098
1099	if (tempError == 0) {
1100		args.pa_vfsstatfs.f_bsize = (uint32_t)reply_statfs.fs_attr.f_bsize;
1101		args.pa_vfsstatfs.f_iosize = (uint32_t)reply_statfs.fs_attr.f_iosize;
1102		args.pa_vfsstatfs.f_blocks = reply_statfs.fs_attr.f_blocks;
1103		args.pa_vfsstatfs.f_bfree = reply_statfs.fs_attr.f_bfree;
1104		args.pa_vfsstatfs.f_bavail = reply_statfs.fs_attr.f_bavail;
1105		args.pa_vfsstatfs.f_files = reply_statfs.fs_attr.f_files;
1106		args.pa_vfsstatfs.f_ffree = reply_statfs.fs_attr.f_ffree;
1107	}
1108
1109	/* since we just read/write to a local cached file, set the iosize to be the same size as the local filesystem */
1110	tempError = statfs(_PATH_TMP, &buf);
1111	if ( tempError == 0 )
1112		args.pa_vfsstatfs.f_iosize = (uint32_t)buf.f_iosize;
1113
1114	/* mount the volume */
1115	return_code = mount(vfc.vfc_name, g_mountPoint, mntflags, &args);
1116	require_noerr_action(return_code, error_exit, error = errno);
1117
1118	/* we're mounted so kill our parent so it will call parentexit and exit with success */
1119 	kill(getppid(), SIGTERM);
1120
1121	isMounted = TRUE;
1122
1123	/* and set SIGTERM to webdav_kill */
1124	signal(SIGTERM, webdav_kill);
1125
1126	syslog(LOG_INFO, "%s mounted", g_mountPoint);
1127
1128	/*
1129	 * This code is needed so that the network reachability code doesn't have to
1130	 * be scheduled for every synchronous CFNetwork transaction. This is accomplished
1131	 * by setting a dummy callback on the main thread's CFRunLoop.
1132	 */
1133	{
1134		struct sockaddr_in socket_address;
1135		SCNetworkReachabilityRef reachabilityRef;
1136
1137		bzero(&socket_address, sizeof(socket_address));
1138		socket_address.sin_len = sizeof(socket_address);
1139		socket_address.sin_family = AF_INET;
1140		socket_address.sin_port = 0;
1141		socket_address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
1142
1143		reachabilityRef = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (struct sockaddr*)&socket_address);
1144		if (reachabilityRef != NULL)
1145		{
1146			SCNetworkReachabilityScheduleWithRunLoop(reachabilityRef, CFRunLoopGetCurrent(), CFSTR("_nonexistent_"));
1147		}
1148	}
1149
1150	/* register for lowdiskspace notifications on the system (boot) volume */
1151	tempError = notify_register_file_descriptor("com.apple.system.lowdiskspace.system", &lowdisk_notify_fd, 0, &out_token);
1152	if (tempError != NOTIFY_STATUS_OK)
1153	{
1154		syslog(LOG_ERR, "failed to register for low disk space notifications: err = %u\n", tempError);
1155		lowdisk_notify_fd = -1;
1156	}
1157	xmlInitParser();
1158	/* Put on seatbelt */
1159
1160	if (sandbox_init("webdav_agent", SANDBOX_NAMED, &errorbuf) == -1) {
1161		asl_log(NULL,NULL,ASL_LEVEL_ERR, "sandbox_init: %s\n", errorbuf);
1162		sandbox_free_error(errorbuf);
1163		exit(errno);
1164	}
1165
1166	/*
1167	 * Just loop waiting for new connections and activating them
1168	 */
1169	while ( TRUE )
1170	{
1171		int accept_socket;
1172		fd_set readfds;
1173		int max_select_fd;
1174
1175		/*
1176		 * Accept a new connection
1177		 * Will get EINTR if a signal has arrived, so just
1178		 * ignore that error code
1179		 */
1180		FD_ZERO(&readfds);
1181		FD_SET(listen_socket, &readfds);
1182		FD_SET(store_notify_fd, &readfds);
1183		max_select_fd = MAX(listen_socket, store_notify_fd);
1184
1185		if ( lowdisk_notify_fd != -1 )
1186		{
1187			/* This allows low disk notifications to wake up the select() */
1188			FD_SET(lowdisk_notify_fd, &readfds);
1189			max_select_fd = MAX(lowdisk_notify_fd, max_select_fd);
1190		}
1191
1192		if (wakeupFDs[0] != -1)
1193		{
1194			/* This allows writing to wakeupFDs[1] to wake up the select() */
1195			FD_SET(wakeupFDs[0], &readfds);
1196			max_select_fd = MAX(wakeupFDs[0], max_select_fd);
1197		}
1198
1199		++max_select_fd;
1200
1201		return_code = select(max_select_fd, &readfds, (fd_set *)0, (fd_set *)0, (struct timeval *)0);
1202		if (return_code <= 0)
1203		{
1204			/* if select isn't working, then exit here */
1205			error = errno;
1206			if (error != EINTR)
1207				syslog(LOG_ERR, "%s: exiting on select errno %d for %s\n",
1208					   __FUNCTION__, error, g_mountPoint);
1209			require(error == EINTR, error_exit); /* EINTR is OK */
1210			continue;
1211		}
1212
1213		/* Did we receive (SIGINT | SIGTERM | SIGHUP | SIGQUIT)? */
1214		if ( (wakeupFDs[0] != -1) && FD_ISSET(wakeupFDs[0], &readfds) )
1215		{
1216			int message;
1217
1218			/* read the message number out of the pipe */
1219			message = -1;
1220			(void)read(wakeupFDs[0], &message, sizeof(int));
1221
1222			/* do nothing with SIGHUP -- we might want to use it to start some debug logging in the future */
1223			if (message == SIGHUP)
1224			{
1225			}
1226			else
1227			{
1228				/* time to force an unmount */
1229				wakeupFDs[0] = -1; /* don't look for anything else from the pipe */
1230
1231				if ( message >= 0 )
1232				{
1233					/* positive messages are signal numbers */
1234					syslog(LOG_ERR, "%s: received signal: %d. Unmounting %s\n",
1235						   __FUNCTION__, message, g_mountPoint);
1236				}
1237				else
1238				{
1239					if ( message == -2 )
1240					{
1241						/* this is the normal way out of the select loop */
1242						syslog(LOG_DEBUG, "%s: received unmount message for %s\n",
1243							   __FUNCTION__, g_mountPoint);
1244						break;
1245					}
1246					else
1247					{
1248						syslog(LOG_ERR, "%s: received message: %d. Force unmounting %s\n",
1249							   __FUNCTION__, message, g_mountPoint);
1250					}
1251				}
1252
1253				/* start a new thread to unmount */
1254				create_unmount_thread();
1255			}
1256		}
1257
1258		/* was a message from the webdav kext received? */
1259		if ( FD_ISSET(listen_socket, &readfds) )
1260		{
1261			struct sockaddr_un addr;
1262			socklen_t addrlen;
1263
1264			addrlen = (socklen_t)sizeof(addr);
1265			accept_socket = accept(listen_socket, (struct sockaddr *)&addr, &addrlen);
1266			if (accept_socket < 0)
1267			{
1268				error = errno;
1269				if (error != EINTR)
1270				syslog(LOG_ERR, "%s: exiting on select errno %d for %s\n",
1271						__FUNCTION__, error, g_mountPoint);
1272				require(error == EINTR, error_exit);
1273				continue;
1274			}
1275
1276			/*
1277			 * Now put a new element on the thread queue so that a thread
1278			 *  will handle this.
1279			 */
1280			error = requestqueue_enqueue_request(accept_socket);
1281			require_noerr_quiet(error, error_exit);
1282		}
1283
1284		/* was a message from the dynamic store received? */
1285		if ( FD_ISSET(store_notify_fd, &readfds) )
1286		{
1287			ssize_t bytes_read;
1288			int32_t buf;
1289
1290			/* read the identifier off the fd and do nothing with it */
1291			bytes_read = read(store_notify_fd, &buf, sizeof(buf));
1292
1293			/* pass the change notification off to another thread to handle */
1294			create_change_thread();
1295		}
1296
1297		/* was a low disk notification received? */
1298		if ( lowdisk_notify_fd != -1 )
1299		{
1300			if ( FD_ISSET(lowdisk_notify_fd, &readfds) )
1301			{
1302				ssize_t bytes_read;
1303
1304				/* read the token off the fd and do nothing with it */
1305				bytes_read = read(lowdisk_notify_fd, &out_token, sizeof(out_token));
1306
1307				/* try to help the system out by purging any closed cache files */
1308				(void) requestqueue_purge_cache_files();
1309			}
1310		}
1311	}
1312
1313	syslog(LOG_DEBUG, "%s unmounted\n", g_mountPoint);
1314
1315	/* attempt to delete the cache directory (if any) and the bound socket name */
1316	if (*gWebdavCachePath != '\0')
1317	{
1318		(void) rmdir(gWebdavCachePath);
1319	}
1320
1321	/* clear the immutable flag and unlink the name from the listening socket */
1322	(void) chflags(un.sun_path, 0);
1323	(void) unlink(un.sun_path);
1324
1325	xmlCleanupParser();
1326	exit(EXIT_SUCCESS);
1327
1328error_exit:
1329
1330	/* is we aren't mounted, return the set of error codes things expect mounts to return */
1331	if ( !isMounted )
1332	{
1333		switch (error)
1334		{
1335			/* The server directory could not be mounted by mount_webdav because the node path is invalid. */
1336			case ENOENT:
1337				break;
1338
1339			/* Could not connect to the server because the name or password is not correct */
1340			case EAUTH:
1341				break;
1342
1343			/* Could not connect to the server because the name or password is not correct and the user canceled */
1344			case ECANCELED:
1345				break;
1346
1347			/* You are already connected to this server volume */
1348			case EBUSY:
1349				break;
1350
1351			/* You cannot connect to this server because it cannot be found on the network. Try again later or try a different URL. */
1352			case ENODEV:
1353				break;
1354
1355			/* Map everything else to a generic unexpected error */
1356			default:
1357				error = EINVAL;
1358				break;
1359		}
1360	}
1361	xmlCleanupParser();
1362	exit(error);
1363}
1364