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