]>
Commit | Line | Data |
---|---|---|
34d340d7 A |
1 | /* |
2 | * Copyright (c) 2006-2007 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/stat.h> | |
34 | #include <sys/sysctl.h> | |
35 | #include <sys/time.h> | |
36 | ||
37 | #include <mach/mach.h> | |
38 | #include <mach/mach_error.h> | |
39 | #include <servers/bootstrap.h> | |
40 | ||
41 | #include <bsm/libbsm.h> | |
42 | ||
43 | #include <asl.h> | |
44 | #include <membership.h> | |
45 | #include <launch.h> | |
46 | #include <dirhelper_priv.h> | |
47 | ||
916eb79e | 48 | #include "dirhelper_server.h" |
34d340d7 A |
49 | |
50 | // globals for idle exit | |
51 | struct idle_globals { | |
52 | mach_port_t mp; | |
53 | long timeout; | |
54 | struct timeval lastmsg; | |
55 | }; | |
56 | ||
57 | struct idle_globals idle_globals; | |
58 | ||
59 | void* idle_thread(void* param __attribute__((unused))); | |
60 | ||
61 | int file_check(const char* path, int mode, int uid, int gid); | |
62 | #define is_file(x) file_check((x), S_IFREG, -1, -1) | |
63 | #define is_directory(x) file_check((x), S_IFDIR, -1, -1) | |
64 | #define is_root_wheel_directory(x) file_check((x), S_IFDIR, 0, 0) | |
65 | ||
66 | int is_safeboot(void); | |
67 | ||
68 | void clean_files_older_than(const char* path, time_t when); | |
ef8ad44b | 69 | void clean_directories(const char* names[], int); |
34d340d7 A |
70 | |
71 | kern_return_t | |
72 | do___dirhelper_create_user_local( | |
73 | mach_port_t server_port __attribute__((unused)), | |
74 | audit_token_t au_tok) | |
75 | { | |
76 | int res = 0; | |
77 | uid_t euid; | |
78 | gid_t gid = 0; | |
79 | struct passwd* pwd = NULL; | |
80 | ||
81 | gettimeofday(&idle_globals.lastmsg, NULL); | |
82 | ||
83 | audit_token_to_au32(au_tok, | |
84 | NULL, // audit uid | |
85 | &euid, // euid | |
86 | NULL, // egid | |
87 | NULL, // ruid | |
88 | NULL, // rgid | |
89 | NULL, // remote_pid | |
90 | NULL, // asid | |
91 | NULL); // aud_tid_t | |
92 | ||
93 | // Look-up the primary gid of the user. We'll use this for chown(2) | |
94 | // so that the created directory is owned by a group that the user | |
95 | // belongs to, avoiding warnings if files are moved outside this dir. | |
96 | pwd = getpwuid(euid); | |
97 | if (pwd) gid = pwd->pw_gid; | |
98 | ||
99 | do { // begin block | |
100 | char path[PATH_MAX]; | |
101 | char *next; | |
102 | ||
103 | if (__user_local_dirname(euid, DIRHELPER_USER_LOCAL, path, sizeof(path)) == NULL) { | |
104 | asl_log(NULL, NULL, ASL_LEVEL_ERR, | |
105 | "__user_local_dirname: %s", strerror(errno)); | |
106 | break; | |
107 | } | |
108 | ||
109 | // | |
110 | // 1. Starting with VAR_FOLDERS_PATH, make each subdirectory | |
111 | // in path, ignoring failure if it already exists. | |
112 | // 2. Change ownership of directory to the user. | |
113 | // | |
114 | next = path + strlen(VAR_FOLDERS_PATH); | |
115 | while ((next = strchr(next, '/')) != NULL) { | |
116 | *next = 0; // temporarily truncate | |
117 | res = mkdir(path, 0755); | |
118 | if (res != 0 && errno != EEXIST) { | |
119 | asl_log(NULL, NULL, ASL_LEVEL_ERR, | |
120 | "mkdir(%s): %s", path, strerror(errno)); | |
121 | break; | |
122 | } | |
123 | *next++ = '/'; // restore the slash and increment | |
124 | } | |
125 | if(next || res) // an error occurred | |
126 | break; | |
127 | res = chown(path, euid, gid); | |
128 | if (res != 0) { | |
129 | asl_log(NULL, NULL, ASL_LEVEL_ERR, | |
130 | "chown(%s): %s", path, strerror(errno)); | |
131 | } | |
132 | } while(0); // end block | |
133 | return KERN_SUCCESS; | |
134 | } | |
135 | ||
136 | kern_return_t | |
137 | do___dirhelper_idle_exit( | |
138 | mach_port_t server_port __attribute__((unused)), | |
139 | audit_token_t au_tok __attribute__((unused))) { | |
140 | ||
141 | struct timeval now; | |
142 | gettimeofday(&now, NULL); | |
143 | long delta = now.tv_sec - idle_globals.lastmsg.tv_sec; | |
144 | if (delta >= idle_globals.timeout) { | |
145 | asl_log(NULL, NULL, ASL_LEVEL_DEBUG, | |
146 | "idle exit after %ld seconds", delta); | |
147 | exit(EXIT_SUCCESS); | |
148 | } | |
149 | ||
150 | return KERN_SUCCESS; | |
151 | } | |
152 | ||
153 | void* | |
154 | idle_thread(void* param __attribute__((unused))) { | |
155 | for(;;) { | |
156 | struct timeval now; | |
157 | gettimeofday(&now, NULL); | |
158 | long delta = (now.tv_sec - idle_globals.lastmsg.tv_sec); | |
159 | if (delta < idle_globals.timeout) { | |
160 | // sleep for remainder of timeout | |
161 | sleep(idle_globals.timeout - delta); | |
162 | } else { | |
163 | // timeout has elapsed, attempt to idle exit | |
164 | __dirhelper_idle_exit(idle_globals.mp); | |
165 | } | |
166 | } | |
167 | return NULL; | |
168 | } | |
169 | ||
170 | // If when == 0, all files are removed. Otherwise, only regular files older than when. | |
171 | void | |
172 | clean_files_older_than(const char* path, time_t when) { | |
173 | FTS* fts; | |
174 | ||
175 | char* path_argv[] = { (char*)path, NULL }; | |
176 | fts = fts_open(path_argv, FTS_PHYSICAL | FTS_XDEV, NULL); | |
177 | if (fts) { | |
178 | FTSENT* ent; | |
ef8ad44b | 179 | asl_log(NULL, NULL, ASL_LEVEL_INFO, "Cleaning " VAR_FOLDERS_PATH "%s", path); |
34d340d7 A |
180 | while ((ent = fts_read(fts))) { |
181 | switch(ent->fts_info) { | |
182 | case FTS_F: | |
183 | case FTS_DEFAULT: | |
184 | // Unlink the file if it has not been accessed since | |
185 | // the specified time. Obtain an exclusive lock so | |
186 | // that we can avoid a race with other processes | |
187 | // attempting to open the file. | |
188 | if (when == 0) { | |
ef8ad44b A |
189 | #if DEBUG |
190 | asl_log(NULL, NULL, ASL_LEVEL_ALERT, "unlink(" VAR_FOLDERS_PATH "%s)", ent->fts_path); | |
191 | #endif | |
34d340d7 A |
192 | (void)unlink(ent->fts_path); |
193 | } else if (S_ISREG(ent->fts_statp->st_mode) && ent->fts_statp->st_atime < when) { | |
194 | int fd = open(ent->fts_path, O_RDONLY | O_NONBLOCK); | |
195 | if (fd != -1) { | |
196 | int res = flock(fd, LOCK_EX | LOCK_NB); | |
197 | if (res == 0) { | |
198 | struct stat sb; | |
199 | res = fstat(fd, &sb); | |
200 | if (res == 0 && sb.st_atime < when) { | |
ef8ad44b A |
201 | #if DEBUG |
202 | asl_log(NULL, NULL, ASL_LEVEL_ALERT, "unlink(" VAR_FOLDERS_PATH "%s)", ent->fts_path); | |
203 | #endif | |
34d340d7 A |
204 | (void)unlink(ent->fts_path); |
205 | } | |
206 | (void)flock(fd, LOCK_UN); | |
207 | } | |
208 | close(fd); | |
209 | } | |
210 | } | |
211 | break; | |
212 | ||
213 | case FTS_SL: | |
214 | case FTS_SLNONE: | |
215 | if (when == 0) { | |
ef8ad44b A |
216 | #if DEBUG |
217 | asl_log(NULL, NULL, ASL_LEVEL_ALERT, "unlink(" VAR_FOLDERS_PATH "%s)", ent->fts_path); | |
218 | #endif | |
34d340d7 A |
219 | (void)unlink(ent->fts_path); |
220 | } | |
221 | break; | |
222 | ||
223 | case FTS_DP: | |
224 | if (when == 0) { | |
ef8ad44b A |
225 | #if DEBUG |
226 | asl_log(NULL, NULL, ASL_LEVEL_ALERT, "rmdir(" VAR_FOLDERS_PATH "%s)", ent->fts_path); | |
227 | #endif | |
34d340d7 A |
228 | (void)rmdir(ent->fts_path); |
229 | } | |
230 | break; | |
231 | ||
232 | case FTS_ERR: | |
233 | case FTS_NS: | |
ef8ad44b | 234 | asl_log(NULL, NULL, ASL_LEVEL_ERR, VAR_FOLDERS_PATH "%s: %s", ent->fts_path, strerror(ent->fts_errno)); |
34d340d7 A |
235 | break; |
236 | ||
237 | default: | |
238 | break; | |
239 | } | |
240 | } | |
241 | fts_close(fts); | |
242 | } else { | |
ef8ad44b | 243 | asl_log(NULL, NULL, ASL_LEVEL_ERR, VAR_FOLDERS_PATH "%s: %s", path, strerror(errno)); |
34d340d7 A |
244 | } |
245 | } | |
246 | ||
247 | int | |
248 | file_check(const char* path, int mode, int uid, int gid) { | |
249 | int check = 1; | |
250 | struct stat sb; | |
251 | if (lstat(path, &sb) == 0) { | |
252 | check = check && ((sb.st_mode & S_IFMT) == mode); | |
253 | check = check && ((sb.st_uid == (uid_t)uid) || uid == -1); | |
254 | check = check && ((sb.st_gid == (gid_t)gid) || gid == -1); | |
255 | } else { | |
256 | if (errno != ENOENT) { | |
ef8ad44b | 257 | /* This will print a shorter path after chroot() */ |
34d340d7 A |
258 | asl_log(NULL, NULL, ASL_LEVEL_ERR, "%s: %s", path, strerror(errno)); |
259 | } | |
260 | check = 0; | |
261 | } | |
262 | return check; | |
263 | } | |
264 | ||
265 | int | |
266 | is_safeboot(void) { | |
267 | uint32_t sb = 0; | |
268 | size_t sbsz = sizeof(sb); | |
269 | ||
270 | if (sysctlbyname("kern.safeboot", &sb, &sbsz, NULL, 0) != 0) { | |
271 | return 0; | |
272 | } else { | |
273 | return (int)sb; | |
274 | } | |
275 | } | |
276 | ||
277 | void | |
ef8ad44b | 278 | clean_directories(const char* dirs[], int machineBoot) { |
34d340d7 A |
279 | DIR* d; |
280 | time_t when = 0; | |
ef8ad44b | 281 | int i; |
34d340d7 A |
282 | |
283 | if (!machineBoot) { | |
284 | struct timeval now; | |
285 | long days = 3; | |
286 | const char* str = getenv("CLEAN_FILES_OLDER_THAN_DAYS"); | |
287 | if (str) { | |
288 | days = strtol(str, NULL, 0); | |
289 | } | |
290 | (void)gettimeofday(&now, NULL); | |
ef8ad44b A |
291 | for (i = 0; dirs[i]; i++) |
292 | asl_log(NULL, NULL, ASL_LEVEL_INFO, "Cleaning %s older than %ld days", dirs[i], days); | |
34d340d7 A |
293 | |
294 | when = now.tv_sec - (days * 60 * 60 * 24); | |
295 | } | |
296 | ||
297 | // Look up the boot time | |
298 | struct timespec boottime; | |
299 | size_t len = sizeof(boottime); | |
300 | if (sysctlbyname("kern.boottime", &boottime, &len, NULL, 0) == -1) { | |
301 | asl_log(NULL, NULL, ASL_LEVEL_ERR, "%s: %s", "sysctl kern.boottime", strerror(errno)); | |
302 | return; | |
303 | } | |
304 | ||
305 | if (!is_root_wheel_directory(VAR_FOLDERS_PATH)) { | |
306 | asl_log(NULL, NULL, ASL_LEVEL_ERR, "%s: %s", VAR_FOLDERS_PATH, "invalid ownership"); | |
307 | return; | |
308 | } | |
ef8ad44b A |
309 | |
310 | if (chroot(VAR_FOLDERS_PATH)) { | |
311 | asl_log(NULL, NULL, ASL_LEVEL_ERR, "chroot(%s) failed: %s", | |
312 | VAR_FOLDERS_PATH, strerror(errno)); | |
313 | } | |
314 | if ((d = opendir("/"))) { | |
34d340d7 A |
315 | struct dirent* e; |
316 | char path[PATH_MAX]; | |
317 | ||
318 | // /var/folders/* | |
319 | while ((e = readdir(d))) { | |
320 | if (strcmp(e->d_name, ".") == 0 || strcmp(e->d_name, "..") == 0) continue; | |
321 | ||
ef8ad44b | 322 | snprintf(path, sizeof(path), "%s%s", "/", e->d_name); |
34d340d7 A |
323 | if (is_root_wheel_directory(path)) { |
324 | DIR* d2 = opendir(path); | |
325 | if (d2) { | |
326 | struct dirent* e2; | |
327 | ||
328 | // /var/folders/*/* | |
329 | while ((e2 = readdir(d2))) { | |
330 | char temporary_items[PATH_MAX]; | |
331 | if (strcmp(e2->d_name, ".") == 0 || strcmp(e2->d_name, "..") == 0) continue; | |
ef8ad44b A |
332 | for (i = 0; dirs[i]; i++) { |
333 | const char *name = dirs[i]; | |
334 | snprintf(temporary_items, sizeof(temporary_items), | |
335 | "%s/%s/%s", path, e2->d_name, name); | |
336 | if (is_directory(temporary_items)) { | |
337 | // at boot time we clean all files, | |
338 | // otherwise only clean regular files. | |
339 | clean_files_older_than(temporary_items, when); | |
340 | } | |
34d340d7 A |
341 | } |
342 | } | |
34d340d7 A |
343 | closedir(d2); |
344 | } else { | |
345 | asl_log(NULL, NULL, ASL_LEVEL_ERR, "%s: %s", path, strerror(errno)); | |
346 | } | |
347 | } | |
348 | } | |
349 | ||
350 | closedir(d); | |
351 | } else { | |
352 | asl_log(NULL, NULL, ASL_LEVEL_ERR, "%s: %s", VAR_FOLDERS_PATH, strerror(errno)); | |
353 | } | |
354 | } | |
355 | ||
356 | int | |
357 | main(int argc, char* argv[]) { | |
358 | mach_msg_size_t mxmsgsz = MAX_TRAILER_SIZE; | |
359 | kern_return_t kr; | |
360 | long idle_timeout = 30; // default 30 second timeout | |
361 | ||
362 | // Clean up TemporaryItems directory when launched at boot. | |
363 | // It is safe to clean all file types at this time. | |
364 | if (argc > 1 && strcmp(argv[1], "-machineBoot") == 0) { | |
ef8ad44b A |
365 | const char *dirs[5]; |
366 | int i = 0; | |
367 | dirs[i++] = DIRHELPER_TEMP_STR; | |
368 | dirs[i++] = "TemporaryItems"; | |
369 | dirs[i++] = "Cleanup At Startup"; | |
370 | if (is_safeboot()) { | |
371 | dirs[i++] = DIRHELPER_CACHE_STR; | |
372 | } | |
373 | dirs[i] = NULL; | |
374 | clean_directories(dirs, 1); | |
34d340d7 A |
375 | exit(EXIT_SUCCESS); |
376 | } else if (argc > 1 && strcmp(argv[1], "-cleanTemporaryItems") == 0) { | |
ef8ad44b A |
377 | const char *dirs[] = { |
378 | DIRHELPER_TEMP_STR, | |
379 | "TemporaryItems", | |
380 | NULL | |
381 | }; | |
382 | clean_directories(dirs, 0); | |
34d340d7 A |
383 | exit(EXIT_SUCCESS); |
384 | } else if (argc > 1) { | |
385 | exit(EXIT_FAILURE); | |
386 | } | |
387 | ||
388 | launch_data_t config = NULL, checkin = NULL; | |
389 | checkin = launch_data_new_string(LAUNCH_KEY_CHECKIN); | |
390 | config = launch_msg(checkin); | |
391 | if (!config || launch_data_get_type(config) == LAUNCH_DATA_ERRNO) { | |
392 | asl_log(NULL, NULL, ASL_LEVEL_ERR, "launchd checkin failed"); | |
393 | exit(EXIT_FAILURE); | |
394 | } | |
395 | ||
396 | launch_data_t tmv; | |
397 | tmv = launch_data_dict_lookup(config, LAUNCH_JOBKEY_TIMEOUT); | |
398 | if (tmv) { | |
399 | idle_timeout = launch_data_get_integer(tmv); | |
400 | asl_log(NULL, NULL, ASL_LEVEL_DEBUG, | |
401 | "idle timeout set: %ld seconds", idle_timeout); | |
402 | } | |
403 | ||
404 | launch_data_t svc; | |
405 | svc = launch_data_dict_lookup(config, LAUNCH_JOBKEY_MACHSERVICES); | |
406 | if (!svc) { | |
407 | asl_log(NULL, NULL, ASL_LEVEL_ERR, "no mach services"); | |
408 | exit(EXIT_FAILURE); | |
409 | } | |
410 | ||
411 | svc = launch_data_dict_lookup(svc, DIRHELPER_BOOTSTRAP_NAME); | |
412 | if (!svc) { | |
413 | asl_log(NULL, NULL, ASL_LEVEL_ERR, "no mach service: %s", | |
414 | DIRHELPER_BOOTSTRAP_NAME); | |
415 | exit(EXIT_FAILURE); | |
416 | } | |
417 | ||
418 | mach_port_t mp = launch_data_get_machport(svc); | |
419 | if (mp == MACH_PORT_NULL) { | |
420 | asl_log(NULL, NULL, ASL_LEVEL_ERR, "NULL mach service: %s", | |
421 | DIRHELPER_BOOTSTRAP_NAME); | |
422 | exit(EXIT_FAILURE); | |
423 | } | |
424 | ||
425 | // insert a send right so we can send our idle exit message | |
426 | kr = mach_port_insert_right(mach_task_self(), mp, mp, | |
427 | MACH_MSG_TYPE_MAKE_SEND); | |
428 | if (kr != KERN_SUCCESS) { | |
429 | asl_log(NULL, NULL, ASL_LEVEL_ERR, "send right failed: %s", | |
430 | mach_error_string(kr)); | |
431 | exit(EXIT_FAILURE); | |
432 | } | |
433 | ||
434 | // spawn a thread for our idle timeout | |
435 | pthread_t thread; | |
436 | idle_globals.mp = mp; | |
437 | idle_globals.timeout = idle_timeout; | |
438 | gettimeofday(&idle_globals.lastmsg, NULL); | |
439 | pthread_create(&thread, NULL, &idle_thread, NULL); | |
440 | ||
441 | // look to see if we have any messages queued. if not, assume | |
442 | // we were launched because of the calendar interval, and attempt | |
443 | // to clean the temporary items. | |
444 | mach_msg_type_number_t status_count = MACH_PORT_RECEIVE_STATUS_COUNT; | |
445 | mach_port_status_t status; | |
446 | kr = mach_port_get_attributes(mach_task_self(), mp, | |
447 | MACH_PORT_RECEIVE_STATUS, (mach_port_info_t)&status, &status_count); | |
448 | if (kr == KERN_SUCCESS && status.mps_msgcount == 0) { | |
ef8ad44b A |
449 | const char *dirs[] = { |
450 | DIRHELPER_TEMP_STR, | |
451 | "TemporaryItems", | |
452 | NULL | |
453 | }; | |
454 | clean_directories(dirs, 0); | |
455 | exit(EXIT_SUCCESS); | |
34d340d7 A |
456 | } |
457 | ||
458 | // main event loop | |
459 | ||
460 | kr = mach_msg_server(dirhelper_server, mxmsgsz, mp, | |
461 | MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT) | | |
462 | MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0)); | |
463 | if (kr != KERN_SUCCESS) { | |
464 | asl_log(NULL, NULL, ASL_LEVEL_ERR, | |
465 | "mach_msg_server(mp): %s", mach_error_string(kr)); | |
466 | exit(EXIT_FAILURE); | |
467 | } | |
468 | ||
469 | exit(EXIT_SUCCESS); | |
470 | } |