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