1/* 2 * Copyright (c) 2006-2007, 2010 Apple Inc. All rights reserved. 3 * 4 * @APPLE_LICENSE_HEADER_START@ 5 * 6 * This file contains Original Code and/or Modifications of Original Code 7 * as defined in and that are subject to the Apple Public Source License 8 * Version 2.0 (the 'License'). You may not use this file except in 9 * compliance with the License. Please obtain a copy of the License at 10 * http://www.opensource.apple.com/apsl/ and read it before using this 11 * file. 12 * 13 * The Original Code and all software distributed under the License are 14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 18 * Please see the License for the specific language governing rights and 19 * limitations under the License. 20 * 21 * @APPLE_LICENSE_HEADER_END@ 22 */ 23#include <stdbool.h> 24#include <stdlib.h> 25#include <stdio.h> 26#include <dirent.h> 27#include <errno.h> 28#include <fcntl.h> 29#include <fts.h> 30#include <pthread.h> 31#include <pwd.h> 32#include <unistd.h> 33#include <sys/kauth.h> 34#include <sys/stat.h> 35#include <sys/sysctl.h> 36#include <sys/time.h> 37 38#include <mach/mach.h> 39#include <mach/mach_error.h> 40#include <servers/bootstrap.h> 41 42#include <bsm/libbsm.h> 43 44#include <asl.h> 45#include <membership.h> 46#include <launch.h> 47#include <dirhelper_priv.h> 48 49#include "dirhelper.h" 50#include "dirhelperServer.h" 51/* 52 * Uncomment the next line to define BOOTDEBUG, which will write timing 53 * info for clean_directories() to a file, /Debug. 54 */ 55//#define BOOTDEBUG 56#ifdef BOOTDEBUG 57#include <mach/mach_time.h> 58#endif //BOOTDEBUG 59 60// globals for idle exit 61struct idle_globals { 62 mach_port_t mp; 63 long timeout; 64 struct timeval lastmsg; 65}; 66 67struct idle_globals idle_globals; 68 69// argument structure for clean_thread 70struct clean_args { 71 const char** dirs; 72 int machineBoot; 73}; 74 75void* idle_thread(void* param __attribute__((unused))); 76void* clean_thread(void *); 77 78int file_check(const char* path, int mode, int uid, int gid, uid_t* owner, gid_t* group); 79#define is_file(x) file_check((x), S_IFREG, -1, -1, NULL, NULL) 80#define is_directory(x) file_check((x), S_IFDIR, -1, -1, NULL, NULL) 81#define is_directory_get_owner_group(x,o,g) file_check((x), S_IFDIR, -1, -1, (o), (g)) 82#define is_root_wheel_directory(x) file_check((x), S_IFDIR, 0, 0, NULL, NULL) 83 84int is_safeboot(void); 85 86void clean_files_older_than(const char* path, time_t when); 87void clean_directories(const char* names[], int); 88 89kern_return_t 90do___dirhelper_create_user_local( 91 mach_port_t server_port __attribute__((unused)), 92 audit_token_t au_tok) 93{ 94 int res = 0; 95 uid_t euid; 96 gid_t gid = 0; 97 struct passwd* pwd = NULL; 98 99 gettimeofday(&idle_globals.lastmsg, NULL); 100 101 audit_token_to_au32(au_tok, 102 NULL, // audit uid 103 &euid, // euid 104 NULL, // egid 105 NULL, // ruid 106 NULL, // rgid 107 NULL, // remote_pid 108 NULL, // asid 109 NULL); // aud_tid_t 110 111 // Look-up the primary gid of the user. We'll use this for chown(2) 112 // so that the created directory is owned by a group that the user 113 // belongs to, avoiding warnings if files are moved outside this dir. 114 pwd = getpwuid(euid); 115 if (pwd) gid = pwd->pw_gid; 116 117 do { // begin block 118 char path[PATH_MAX]; 119 char *next; 120 121 if (__user_local_dirname(euid, DIRHELPER_USER_LOCAL, path, sizeof(path)) == NULL) { 122 asl_log(NULL, NULL, ASL_LEVEL_ERR, 123 "__user_local_dirname: %s", strerror(errno)); 124 break; 125 } 126 127 // All dirhelper directories are now at the same level, so 128 // we need to remove the DIRHELPER_TOP_STR suffix to get the 129 // parent directory. 130 path[strlen(path) - (sizeof(DIRHELPER_TOP_STR) - 1)] = 0; 131 132 // 133 // 1. Starting with VAR_FOLDERS_PATH, make each subdirectory 134 // in path, ignoring failure if it already exists. 135 // 2. Change ownership of directory to the user. 136 // 137 next = path + strlen(VAR_FOLDERS_PATH); 138 while ((next = strchr(next, '/')) != NULL) { 139 *next = 0; // temporarily truncate 140 res = mkdir(path, 0755); 141 if (res != 0 && errno != EEXIST) { 142 asl_log(NULL, NULL, ASL_LEVEL_ERR, 143 "mkdir(%s): %s", path, strerror(errno)); 144 break; 145 } 146 *next++ = '/'; // restore the slash and increment 147 } 148 if(next || res) // an error occurred 149 break; 150 res = chown(path, euid, gid); 151 if (res != 0) { 152 asl_log(NULL, NULL, ASL_LEVEL_ERR, 153 "chown(%s): %s", path, strerror(errno)); 154 } 155 } while(0); // end block 156 return KERN_SUCCESS; 157} 158 159kern_return_t 160do___dirhelper_idle_exit( 161 mach_port_t server_port __attribute__((unused)), 162 audit_token_t au_tok __attribute__((unused))) { 163 164 struct timeval now; 165 gettimeofday(&now, NULL); 166 long delta = now.tv_sec - idle_globals.lastmsg.tv_sec; 167 if (delta >= idle_globals.timeout) { 168 asl_log(NULL, NULL, ASL_LEVEL_DEBUG, 169 "idle exit after %ld seconds", delta); 170 exit(EXIT_SUCCESS); 171 } 172 173 return KERN_SUCCESS; 174} 175 176void* 177idle_thread(void* param __attribute__((unused))) { 178 for(;;) { 179 struct timeval now; 180 gettimeofday(&now, NULL); 181 long delta = (now.tv_sec - idle_globals.lastmsg.tv_sec); 182 if (delta < idle_globals.timeout) { 183 // sleep for remainder of timeout 184 sleep((int)(idle_globals.timeout - delta)); 185 } else { 186 // timeout has elapsed, attempt to idle exit 187 __dirhelper_idle_exit(idle_globals.mp); 188 } 189 } 190 return NULL; 191} 192 193// If when == 0, all files are removed. Otherwise, only regular files that were both created _and_ last modified before `when`. 194void 195clean_files_older_than(const char* path, time_t when) { 196 FTS* fts; 197 198 char* path_argv[] = { (char*)path, NULL }; 199 fts = fts_open(path_argv, FTS_PHYSICAL | FTS_XDEV, NULL); 200 if (fts) { 201 FTSENT* ent; 202 asl_log(NULL, NULL, ASL_LEVEL_INFO, "Cleaning " VAR_FOLDERS_PATH "%s", path); 203 while ((ent = fts_read(fts))) { 204 switch(ent->fts_info) { 205 case FTS_F: 206 case FTS_DEFAULT: 207 if (when == 0) { 208#if DEBUG 209 asl_log(NULL, NULL, ASL_LEVEL_ALERT, "unlink(" VAR_FOLDERS_PATH "%s)", ent->fts_path); 210#endif 211 (void)unlink(ent->fts_path); 212 } else if (S_ISREG(ent->fts_statp->st_mode) && (ent->fts_statp->st_birthtime < when) && (ent->fts_statp->st_atime < when)) { 213 int fd = open(ent->fts_path, O_RDONLY | O_NONBLOCK); 214 if (fd != -1) { 215 // Obtain an exclusive lock so 216 // that we can avoid a race with other processes 217 // attempting to open or modify the file. 218 int res = flock(fd, LOCK_EX | LOCK_NB); 219 if (res == 0) { 220 struct stat sb; 221 res = fstat(fd, &sb); 222 if ((res == 0) && (sb.st_birthtime < when) && (sb.st_atime < when)) { 223#if DEBUG 224 asl_log(NULL, NULL, ASL_LEVEL_ALERT, "unlink(" VAR_FOLDERS_PATH "%s)", ent->fts_path); 225#endif 226 (void)unlink(ent->fts_path); 227 } 228 (void)flock(fd, LOCK_UN); 229 } 230 close(fd); 231 } 232 } 233 break; 234 235 case FTS_SL: 236 case FTS_SLNONE: 237 if (when == 0) { 238#if DEBUG 239 asl_log(NULL, NULL, ASL_LEVEL_ALERT, "unlink(" VAR_FOLDERS_PATH "%s)", ent->fts_path); 240#endif 241 (void)unlink(ent->fts_path); 242 } 243 break; 244 245 case FTS_DP: 246 if (when == 0) { 247#if DEBUG 248 asl_log(NULL, NULL, ASL_LEVEL_ALERT, "rmdir(" VAR_FOLDERS_PATH "%s)", ent->fts_path); 249#endif 250 (void)rmdir(ent->fts_path); 251 } 252 break; 253 254 case FTS_ERR: 255 case FTS_NS: 256 asl_log(NULL, NULL, ASL_LEVEL_ERR, VAR_FOLDERS_PATH "%s: %s", ent->fts_path, strerror(ent->fts_errno)); 257 break; 258 259 default: 260 break; 261 } 262 } 263 fts_close(fts); 264 } else { 265 asl_log(NULL, NULL, ASL_LEVEL_ERR, VAR_FOLDERS_PATH "%s: %s", path, strerror(errno)); 266 } 267} 268 269int 270file_check(const char* path, int mode, int uid, int gid, uid_t* owner, gid_t* group) { 271 int check = 1; 272 struct stat sb; 273 if (lstat(path, &sb) == 0) { 274 check = check && ((sb.st_mode & S_IFMT) == mode); 275 check = check && ((sb.st_uid == (uid_t)uid) || uid == -1); 276 check = check && ((sb.st_gid == (gid_t)gid) || gid == -1); 277 if (check) { 278 if (owner) *owner = sb.st_uid; 279 if (group) *group = sb.st_gid; 280 } 281 } else { 282 if (errno != ENOENT) { 283 /* This will print a shorter path after chroot() */ 284 asl_log(NULL, NULL, ASL_LEVEL_ERR, "%s: %s", path, strerror(errno)); 285 } 286 check = 0; 287 } 288 return check; 289} 290 291int 292is_safeboot(void) { 293 uint32_t sb = 0; 294 size_t sbsz = sizeof(sb); 295 296 if (sysctlbyname("kern.safeboot", &sb, &sbsz, NULL, 0) != 0) { 297 return 0; 298 } else { 299 return (int)sb; 300 } 301} 302 303void * 304clean_thread(void *a) { 305 struct clean_args* args = (struct clean_args*)a; 306 DIR* d; 307 time_t when = 0; 308 int i; 309 310 if (!args->machineBoot) { 311 struct timeval now; 312 long days = 3; 313 const char* str = getenv("CLEAN_FILES_OLDER_THAN_DAYS"); 314 if (str) { 315 days = strtol(str, NULL, 0); 316 } 317 (void)gettimeofday(&now, NULL); 318 for (i = 0; args->dirs[i]; i++) 319 asl_log(NULL, NULL, ASL_LEVEL_INFO, "Cleaning %s older than %ld days", args->dirs[i], days); 320 321 when = now.tv_sec - (days * 60 * 60 * 24); 322 } 323 324 // Look up the boot time 325 struct timespec boottime; 326 size_t len = sizeof(boottime); 327 if (sysctlbyname("kern.boottime", &boottime, &len, NULL, 0) == -1) { 328 asl_log(NULL, NULL, ASL_LEVEL_ERR, "%s: %s", "sysctl kern.boottime", strerror(errno)); 329 return NULL; 330 } 331 332 if (!is_root_wheel_directory(VAR_FOLDERS_PATH)) { 333 asl_log(NULL, NULL, ASL_LEVEL_ERR, "%s: %s", VAR_FOLDERS_PATH, "invalid ownership"); 334 return NULL; 335 } 336 337 if (chroot(VAR_FOLDERS_PATH)) { 338 asl_log(NULL, NULL, ASL_LEVEL_ERR, "chroot(%s) failed: %s", 339 VAR_FOLDERS_PATH, strerror(errno)); 340 } 341 chdir("/"); 342 if ((d = opendir("/"))) { 343 struct dirent* e; 344 char path[PATH_MAX]; 345 346 // /var/folders/* 347 while ((e = readdir(d))) { 348 if (strcmp(e->d_name, ".") == 0 || strcmp(e->d_name, "..") == 0) continue; 349 350 snprintf(path, sizeof(path), "%s%s", "/", e->d_name); 351 if (is_root_wheel_directory(path)) { 352 DIR* d2 = opendir(path); 353 if (d2) { 354 struct dirent* e2; 355 356 // /var/folders/*/* 357 while ((e2 = readdir(d2))) { 358 char dirbuf[PATH_MAX]; 359 uid_t owner; 360 gid_t group; 361 if (strcmp(e2->d_name, ".") == 0 || strcmp(e2->d_name, "..") == 0) continue; 362 snprintf(dirbuf, sizeof(dirbuf), 363 "%s/%s", path, e2->d_name); 364 if (!is_directory_get_owner_group(dirbuf, &owner, &group)) continue; 365 if (pthread_setugid_np(owner, group) != 0) { 366 asl_log(NULL, NULL, ASL_LEVEL_ERR, 367 "skipping %s: pthread_setugid_np(%u, %u): %s", 368 dirbuf, owner, group, strerror(errno)); 369 continue; 370 } 371 for (i = 0; args->dirs[i]; i++) { 372 const char *name = args->dirs[i]; 373 snprintf(dirbuf, sizeof(dirbuf), 374 "%s/%s/%s", path, e2->d_name, name); 375 if (is_directory(dirbuf)) { 376 // at boot time we clean all files, 377 // otherwise only clean regular files. 378 clean_files_older_than(dirbuf, when); 379 } 380 } 381 if (pthread_setugid_np(KAUTH_UID_NONE, KAUTH_GID_NONE) != 0) { 382 asl_log(NULL, NULL, ASL_LEVEL_ERR, 383 "%s: pthread_setugid_np(KAUTH_UID_NONE, KAUTH_GID_NONE): %s", 384 dirbuf, strerror(errno)); 385 } 386 } 387 closedir(d2); 388 } else { 389 asl_log(NULL, NULL, ASL_LEVEL_ERR, "%s: %s", path, strerror(errno)); 390 } 391 } 392 } 393 394 closedir(d); 395 } else { 396 asl_log(NULL, NULL, ASL_LEVEL_ERR, "%s: %s", VAR_FOLDERS_PATH, strerror(errno)); 397 } 398 return NULL; 399} 400 401void 402clean_directories(const char* dirs[], int machineBoot) { 403 struct clean_args args; 404 pthread_t t; 405 int ret; 406#ifdef BOOTDEBUG 407 double ratio; 408 struct mach_timebase_info info; 409 uint64_t begin, end; 410 FILE *debug; 411 412 mach_timebase_info(&info); 413 ratio = (double)info.numer / ((double)info.denom * NSEC_PER_SEC); 414 begin = mach_absolute_time(); 415 if((debug = fopen("/Debug", "a")) != NULL) { 416 fprintf(debug, "clean_directories: machineBoot=%d\n", machineBoot); 417 } 418#endif //BOOTDEBUG 419 420 args.dirs = dirs; 421 args.machineBoot = machineBoot; 422 ret = pthread_create(&t, NULL, clean_thread, &args); 423 if (ret == 0) { 424 ret = pthread_join(t, NULL); 425 if (ret) { 426 asl_log(NULL, NULL, ASL_LEVEL_ERR, "clean_directories: pthread_join: %s", 427 strerror(ret)); 428 } 429 } else { 430 asl_log(NULL, NULL, ASL_LEVEL_ERR, "clean_directories: pthread_create: %s", 431 strerror(ret)); 432 } 433#ifdef BOOTDEBUG 434 end = mach_absolute_time(); 435 if(debug) { 436 fprintf(debug, "clean_directories: %f secs\n", ratio * (end - begin)); 437 fclose(debug); 438 } 439#endif //BOOTDEBUG 440} 441 442int 443main(int argc, char* argv[]) { 444 mach_msg_size_t mxmsgsz = MAX_TRAILER_SIZE; 445 kern_return_t kr; 446 long idle_timeout = 30; // default 30 second timeout 447 448#ifdef BOOTDEBUG 449 { 450 FILE *debug; 451 int i; 452 if((debug = fopen("/Debug", "a")) != NULL) { 453 for(i = 0; i < argc; i++) { 454 fprintf(debug, " %s", argv[i]); 455 } 456 fputc('\n', debug); 457 fclose(debug); 458 } 459 } 460#endif //BOOTDEBUG 461 // Clean up TemporaryItems directory when launched at boot. 462 // It is safe to clean all file types at this time. 463 if (argc > 1 && strcmp(argv[1], "-machineBoot") == 0) { 464 const char *dirs[5]; 465 int i = 0; 466 dirs[i++] = DIRHELPER_TEMP_STR; 467 dirs[i++] = "TemporaryItems"; 468 dirs[i++] = "Cleanup At Startup"; 469 if (is_safeboot()) { 470 dirs[i++] = DIRHELPER_CACHE_STR; 471 } 472 dirs[i] = NULL; 473 clean_directories(dirs, 1); 474 exit(EXIT_SUCCESS); 475 } else if (argc > 1 && strcmp(argv[1], "-cleanTemporaryItems") == 0) { 476 const char *dirs[] = { 477 DIRHELPER_TEMP_STR, 478 "TemporaryItems", 479 NULL 480 }; 481 clean_directories(dirs, 0); 482 exit(EXIT_SUCCESS); 483 } else if (argc > 1) { 484 exit(EXIT_FAILURE); 485 } 486 487 launch_data_t config = NULL, checkin = NULL; 488 checkin = launch_data_new_string(LAUNCH_KEY_CHECKIN); 489 config = launch_msg(checkin); 490 if (!config || launch_data_get_type(config) == LAUNCH_DATA_ERRNO) { 491 asl_log(NULL, NULL, ASL_LEVEL_ERR, "launchd checkin failed"); 492 exit(EXIT_FAILURE); 493 } 494 495 launch_data_t tmv; 496 tmv = launch_data_dict_lookup(config, LAUNCH_JOBKEY_TIMEOUT); 497 if (tmv) { 498 idle_timeout = (long)launch_data_get_integer(tmv); 499 asl_log(NULL, NULL, ASL_LEVEL_DEBUG, 500 "idle timeout set: %ld seconds", idle_timeout); 501 } 502 503 launch_data_t svc; 504 svc = launch_data_dict_lookup(config, LAUNCH_JOBKEY_MACHSERVICES); 505 if (!svc) { 506 asl_log(NULL, NULL, ASL_LEVEL_ERR, "no mach services"); 507 exit(EXIT_FAILURE); 508 } 509 510 svc = launch_data_dict_lookup(svc, DIRHELPER_BOOTSTRAP_NAME); 511 if (!svc) { 512 asl_log(NULL, NULL, ASL_LEVEL_ERR, "no mach service: %s", 513 DIRHELPER_BOOTSTRAP_NAME); 514 exit(EXIT_FAILURE); 515 } 516 517 mach_port_t mp = launch_data_get_machport(svc); 518 if (mp == MACH_PORT_NULL) { 519 asl_log(NULL, NULL, ASL_LEVEL_ERR, "NULL mach service: %s", 520 DIRHELPER_BOOTSTRAP_NAME); 521 exit(EXIT_FAILURE); 522 } 523 524 // insert a send right so we can send our idle exit message 525 kr = mach_port_insert_right(mach_task_self(), mp, mp, 526 MACH_MSG_TYPE_MAKE_SEND); 527 if (kr != KERN_SUCCESS) { 528 asl_log(NULL, NULL, ASL_LEVEL_ERR, "send right failed: %s", 529 mach_error_string(kr)); 530 exit(EXIT_FAILURE); 531 } 532 533 // spawn a thread for our idle timeout 534 pthread_t thread; 535 idle_globals.mp = mp; 536 idle_globals.timeout = idle_timeout; 537 gettimeofday(&idle_globals.lastmsg, NULL); 538 pthread_create(&thread, NULL, &idle_thread, NULL); 539 540 // look to see if we have any messages queued. if not, assume 541 // we were launched because of the calendar interval, and attempt 542 // to clean the temporary items. 543 mach_msg_type_number_t status_count = MACH_PORT_RECEIVE_STATUS_COUNT; 544 mach_port_status_t status; 545 kr = mach_port_get_attributes(mach_task_self(), mp, 546 MACH_PORT_RECEIVE_STATUS, (mach_port_info_t)&status, &status_count); 547 if (kr == KERN_SUCCESS && status.mps_msgcount == 0) { 548 const char *dirs[] = { 549 DIRHELPER_TEMP_STR, 550 "TemporaryItems", 551 NULL 552 }; 553 clean_directories(dirs, 0); 554 exit(EXIT_SUCCESS); 555 } 556 557 // main event loop 558 559 kr = mach_msg_server(dirhelper_server, mxmsgsz, mp, 560 MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT) | 561 MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0)); 562 if (kr != KERN_SUCCESS) { 563 asl_log(NULL, NULL, ASL_LEVEL_ERR, 564 "mach_msg_server(mp): %s", mach_error_string(kr)); 565 exit(EXIT_FAILURE); 566 } 567 568 exit(EXIT_SUCCESS); 569} 570