]>
Commit | Line | Data |
---|---|---|
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 | ||
24 | #include <mach/mach.h> | |
25 | #include <mach/mach_error.h> | |
26 | #include <servers/bootstrap.h> | |
27 | #include <unistd.h> | |
28 | #include <stdio.h> | |
29 | #include <stdlib.h> | |
30 | #include <stdbool.h> | |
31 | #include <membership.h> | |
32 | #include <pthread.h> | |
33 | #include <errno.h> | |
34 | #include <sys/types.h> | |
35 | #include <sys/stat.h> | |
36 | #include <sys/param.h> | |
37 | #include <uuid/uuid.h> | |
38 | #include <string.h> | |
39 | #include <libkern/OSByteOrder.h> | |
40 | #include <TargetConditionals.h> | |
41 | #include <xpc/xpc.h> | |
42 | #include <xpc/private.h> | |
43 | ||
44 | #include "dirhelper.h" | |
45 | #include "dirhelper_priv.h" | |
46 | ||
47 | #define BUCKETLEN 2 | |
48 | ||
49 | #define MUTEX_LOCK(x) if(__is_threaded) pthread_mutex_lock(x) | |
50 | #define MUTEX_UNLOCK(x) if(__is_threaded) pthread_mutex_unlock(x) | |
51 | ||
52 | // Use 5 bits per character, to avoid uppercase and shell magic characters | |
53 | #define ENCODEBITS 5 | |
54 | #define ENCODEDSIZE ((8 * UUID_UID_SIZE + ENCODEBITS - 1) / ENCODEBITS) | |
55 | #define MASK(x) ((1 << (x)) - 1) | |
56 | #define UUID_UID_SIZE (sizeof(uuid_t) + sizeof(uid_t)) | |
57 | ||
58 | extern int __is_threaded; | |
59 | ||
60 | static const mode_t modes[] = { | |
61 | 0755, /* user */ | |
62 | 0700, /* temp */ | |
63 | 0700, /* cache */ | |
64 | }; | |
65 | ||
66 | static const char *subdirs[] = { | |
67 | DIRHELPER_TOP_STR, | |
68 | DIRHELPER_TEMP_STR, | |
69 | DIRHELPER_CACHE_STR, | |
70 | }; | |
71 | ||
72 | static pthread_once_t userdir_control = PTHREAD_ONCE_INIT; | |
73 | static char *userdir = NULL; | |
74 | ||
75 | // lower case letter (minus vowels), plus numbers and _, making | |
76 | // 32 characters. | |
77 | static const char encode[] = "0123456789_bcdfghjklmnpqrstvwxyz"; | |
78 | ||
79 | static void | |
80 | encode_uuid_uid(const uuid_t uuid, uid_t uid, char *str) | |
81 | { | |
82 | unsigned char buf[UUID_UID_SIZE + 1]; | |
83 | unsigned char *bp = buf; | |
84 | int i; | |
85 | unsigned int n; | |
86 | ||
87 | memcpy(bp, uuid, sizeof(uuid_t)); | |
88 | uid = OSSwapHostToBigInt32(uid); | |
89 | memcpy(bp + sizeof(uuid_t), &uid, sizeof(uid_t)); | |
90 | bp[UUID_UID_SIZE] = 0; // this ensures the last encoded byte will have trailing zeros | |
91 | for(i = 0; i < ENCODEDSIZE; i++) { | |
92 | // 5 bits has 8 states | |
93 | switch(i % 8) { | |
94 | case 0: | |
95 | n = *bp++; | |
96 | *str++ = encode[n >> 3]; | |
97 | break; | |
98 | case 1: | |
99 | n = ((n & MASK(3)) << 8) | *bp++; | |
100 | *str++ = encode[n >> 6]; | |
101 | break; | |
102 | case 2: | |
103 | n &= MASK(6); | |
104 | *str++ = encode[n >> 1]; | |
105 | break; | |
106 | case 3: | |
107 | n = ((n & MASK(1)) << 8) | *bp++; | |
108 | *str++ = encode[n >> 4]; | |
109 | break; | |
110 | case 4: | |
111 | n = ((n & MASK(4)) << 8) | *bp++; | |
112 | *str++ = encode[n >> 7]; | |
113 | break; | |
114 | case 5: | |
115 | n &= MASK(7); | |
116 | *str++ = encode[n >> 2]; | |
117 | break; | |
118 | case 6: | |
119 | n = ((n & MASK(2)) << 8) | *bp++; | |
120 | *str++ = encode[n >> 5]; | |
121 | break; | |
122 | case 7: | |
123 | *str++ = encode[n & MASK(5)]; | |
124 | break; | |
125 | } | |
126 | } | |
127 | *str = 0; | |
128 | } | |
129 | ||
130 | char * | |
131 | __user_local_dirname(uid_t uid, dirhelper_which_t which, char *path, size_t pathlen) | |
132 | { | |
133 | #if TARGET_OS_IPHONE | |
134 | char *tmpdir; | |
135 | #else | |
136 | uuid_t uuid; | |
137 | char str[ENCODEDSIZE + 1]; | |
138 | #endif | |
139 | int res; | |
140 | ||
141 | if(which < 0 || which > DIRHELPER_USER_LOCAL_LAST) { | |
142 | errno = EINVAL; | |
143 | return NULL; | |
144 | } | |
145 | ||
146 | #if TARGET_OS_IPHONE | |
147 | /* We only support DIRHELPER_USER_LOCAL_TEMP on embedded. | |
148 | * This interface really doesn't map from OSX to embedded, | |
149 | * and clients of this interface will need to adapt when | |
150 | * porting their applications to embedded. | |
151 | * See: <rdar://problem/7515613> | |
152 | */ | |
153 | if(which == DIRHELPER_USER_LOCAL_TEMP) { | |
154 | tmpdir = getenv("TMPDIR"); | |
155 | if(!tmpdir) { | |
156 | errno = EINVAL; | |
157 | return NULL; | |
158 | } | |
159 | res = snprintf(path, pathlen, "%s", tmpdir); | |
160 | } else { | |
161 | errno = EINVAL; | |
162 | return NULL; | |
163 | } | |
164 | #else | |
165 | res = mbr_uid_to_uuid(uid, uuid); | |
166 | if(res != 0) { | |
167 | errno = res; | |
168 | return NULL; | |
169 | } | |
170 | ||
171 | // | |
172 | // We partition the namespace so that we don't end up with too | |
173 | // many users in a single directory. With 1024 buckets, we | |
174 | // could scale to 1,000,000 users while keeping the average | |
175 | // number of files in a single directory around 1000 | |
176 | // | |
177 | encode_uuid_uid(uuid, uid, str); | |
178 | res = snprintf(path, pathlen, | |
179 | "%s%.*s/%s/%s", | |
180 | VAR_FOLDERS_PATH, BUCKETLEN, str, str + BUCKETLEN, subdirs[which]); | |
181 | #endif | |
182 | if(res >= pathlen) { | |
183 | errno = EINVAL; | |
184 | return NULL; /* buffer too small */ | |
185 | } | |
186 | return path; | |
187 | } | |
188 | ||
189 | char * | |
190 | __user_local_mkdir_p(char *path) | |
191 | { | |
192 | char *next; | |
193 | int res; | |
194 | ||
195 | next = path + strlen(VAR_FOLDERS_PATH); | |
196 | while ((next = strchr(next, '/')) != NULL) { | |
197 | *next = 0; // temporarily truncate | |
198 | res = mkdir(path, 0755); | |
199 | if (res != 0 && errno != EEXIST) | |
200 | return NULL; | |
201 | *next++ = '/'; // restore the slash and increment | |
202 | } | |
203 | return path; | |
204 | } | |
205 | ||
206 | static void userdir_allocate(void) | |
207 | { | |
208 | userdir = calloc(PATH_MAX, sizeof(char)); | |
209 | } | |
210 | ||
211 | /* | |
212 | * 9407258: Invalidate the dirhelper cache (userdir) of the child after fork. | |
213 | * There is a rare case when launchd will have userdir set, and child process | |
214 | * will sometimes inherit this cached value. | |
215 | */ | |
216 | __private_extern__ void | |
217 | _dirhelper_fork_child(void) | |
218 | { | |
219 | if(userdir) *userdir = 0; | |
220 | } | |
221 | ||
222 | __private_extern__ char * | |
223 | _dirhelper(dirhelper_which_t which, char *path, size_t pathlen) | |
224 | { | |
225 | static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; | |
226 | struct stat sb; | |
227 | ||
228 | if(which < 0 || which > DIRHELPER_USER_LOCAL_LAST) { | |
229 | errno = EINVAL; | |
230 | return NULL; | |
231 | } | |
232 | ||
233 | if (pthread_once(&userdir_control, userdir_allocate) | |
234 | || !userdir) { | |
235 | errno = ENOMEM; | |
236 | return NULL; | |
237 | } | |
238 | ||
239 | if(!*userdir) { | |
240 | MUTEX_LOCK(&lock); | |
241 | if (!*userdir) { | |
242 | ||
243 | if(__user_local_dirname(geteuid(), DIRHELPER_USER_LOCAL, userdir, PATH_MAX) == NULL) { | |
244 | MUTEX_UNLOCK(&lock); | |
245 | return NULL; | |
246 | } | |
247 | /* | |
248 | * All dirhelper directories are now at the same level, so | |
249 | * we need to remove the DIRHELPER_TOP_STR suffix to get the | |
250 | * parent directory. | |
251 | */ | |
252 | userdir[strlen(userdir) - (sizeof(DIRHELPER_TOP_STR) - 1)] = 0; | |
253 | /* | |
254 | * check if userdir exists, and if not, either do the work | |
255 | * ourself if we are root, or call | |
256 | * __dirhelper_create_user_local to create it (we have to | |
257 | * check again afterwards). | |
258 | */ | |
259 | if(stat(userdir, &sb) < 0) { | |
260 | mach_port_t mp; | |
261 | ||
262 | if(errno != ENOENT) { /* some unknown error */ | |
263 | *userdir = 0; | |
264 | MUTEX_UNLOCK(&lock); | |
265 | return NULL; | |
266 | } | |
267 | /* | |
268 | * If we are root, lets do what dirhelper does for us. | |
269 | */ | |
270 | if (geteuid() == 0) { | |
271 | if (__user_local_mkdir_p(userdir) == NULL) { | |
272 | *userdir = 0; | |
273 | MUTEX_UNLOCK(&lock); | |
274 | return NULL; | |
275 | } | |
276 | } else { | |
277 | if(bootstrap_look_up(bootstrap_port, DIRHELPER_BOOTSTRAP_NAME, &mp) != KERN_SUCCESS) { | |
278 | errno = EPERM; | |
279 | server_error: | |
280 | mach_port_deallocate(mach_task_self(), mp); | |
281 | MUTEX_UNLOCK(&lock); | |
282 | return NULL; | |
283 | } | |
284 | if(__dirhelper_create_user_local(mp) != KERN_SUCCESS) { | |
285 | errno = EPERM; | |
286 | goto server_error; | |
287 | } | |
288 | /* double check that the directory really got created */ | |
289 | if(stat(userdir, &sb) < 0) { | |
290 | goto server_error; | |
291 | } | |
292 | mach_port_deallocate(mach_task_self(), mp); | |
293 | } | |
294 | } | |
295 | } | |
296 | MUTEX_UNLOCK(&lock); | |
297 | } | |
298 | ||
299 | if(pathlen < strlen(userdir) + strlen(subdirs[which]) + 1) { | |
300 | errno = EINVAL; | |
301 | return NULL; /* buffer too small */ | |
302 | } | |
303 | strcpy(path, userdir); | |
304 | strcat(path, subdirs[which]); | |
305 | ||
306 | /* | |
307 | * create the subdir with the appropriate permissions if it doesn't already | |
308 | * exist. On OS X, if we're under App Sandbox, we rely on the subdir having | |
309 | * been already created for us. | |
310 | */ | |
311 | #if !TARGET_OS_IPHONE | |
312 | if (!_xpc_runtime_is_app_sandboxed()) | |
313 | #endif | |
314 | if(mkdir(path, modes[which]) != 0 && errno != EEXIST) | |
315 | return NULL; | |
316 | ||
317 | #if !TARGET_OS_IPHONE | |
318 | char *userdir_suffix = NULL; | |
319 | ||
320 | if (_xpc_runtime_is_app_sandboxed()) { | |
321 | /* | |
322 | * if the subdir wasn't made for us, bail since we probably don't have | |
323 | * permission to create it ourselves. | |
324 | */ | |
325 | if(stat(path, &sb) < 0) { | |
326 | errno = EPERM; | |
327 | return NULL; | |
328 | } | |
329 | ||
330 | /* | |
331 | * sandboxed applications get per-application directories named | |
332 | * after the container | |
333 | */ | |
334 | userdir_suffix = getenv(XPC_ENV_SANDBOX_CONTAINER_ID); | |
335 | if (!userdir_suffix) { | |
336 | errno = EINVAL; | |
337 | return NULL; | |
338 | } | |
339 | } else | |
340 | userdir_suffix = getenv(DIRHELPER_ENV_USER_DIR_SUFFIX); | |
341 | ||
342 | if (userdir_suffix) { | |
343 | /* | |
344 | * suffix (usually container ID) doesn't end in a slash, so +2 is for slash and \0 | |
345 | */ | |
346 | if (pathlen < strlen(path) + strlen(userdir_suffix) + 2) { | |
347 | errno = EINVAL; | |
348 | return NULL; /* buffer too small */ | |
349 | } | |
350 | ||
351 | strcat(path, userdir_suffix); | |
352 | strcat(path, "/"); | |
353 | ||
354 | /* | |
355 | * create suffix subdirectory with the appropriate permissions | |
356 | * if it doesn't already exist. | |
357 | */ | |
358 | if (mkdir(path, modes[which]) != 0 && errno != EEXIST) | |
359 | return NULL; | |
360 | ||
361 | /* | |
362 | * update TMPDIR if necessary | |
363 | */ | |
364 | if (which == DIRHELPER_USER_LOCAL_TEMP) { | |
365 | char *tmpdir = getenv("TMPDIR"); | |
366 | if (!tmpdir || strncmp(tmpdir, path, strlen(path))) | |
367 | setenv("TMPDIR", path, 1); | |
368 | } | |
369 | } | |
370 | #endif | |
371 | ||
372 | return path; | |
373 | } |