]>
Commit | Line | Data |
---|---|---|
224c7076 | 1 | /* |
1f2f436a | 2 | * Copyright (c) 2006, 2007, 2010 Apple Inc. All rights reserved. |
224c7076 A |
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> | |
511daa4c | 40 | #include <TargetConditionals.h> |
1f2f436a A |
41 | #include <xpc/xpc.h> |
42 | #include <xpc/private.h> | |
224c7076 A |
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 | ||
1f2f436a A |
52 | // Use 5 bits per character, to avoid uppercase and shell magic characters |
53 | #define ENCODEBITS 5 | |
224c7076 | 54 | #define ENCODEDSIZE ((8 * UUID_UID_SIZE + ENCODEBITS - 1) / ENCODEBITS) |
1f2f436a | 55 | #define MASK(x) ((1 << (x)) - 1) |
224c7076 A |
56 | #define UUID_UID_SIZE (sizeof(uuid_t) + sizeof(uid_t)) |
57 | ||
58 | extern int __is_threaded; | |
59 | ||
60 | static const mode_t modes[] = { | |
1f2f436a | 61 | 0755, /* user */ |
224c7076 A |
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 | ||
1f2f436a A |
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"; | |
224c7076 A |
78 | |
79 | static void | |
1f2f436a | 80 | encode_uuid_uid(const uuid_t uuid, uid_t uid, char *str) |
224c7076 A |
81 | { |
82 | unsigned char buf[UUID_UID_SIZE + 1]; | |
83 | unsigned char *bp = buf; | |
1f2f436a | 84 | int i; |
224c7076 A |
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 | |
1f2f436a A |
91 | for(i = 0; i < ENCODEDSIZE; i++) { |
92 | // 5 bits has 8 states | |
93 | switch(i % 8) { | |
224c7076 A |
94 | case 0: |
95 | n = *bp++; | |
1f2f436a | 96 | *str++ = encode[n >> 3]; |
224c7076 A |
97 | break; |
98 | case 1: | |
1f2f436a A |
99 | n = ((n & MASK(3)) << 8) | *bp++; |
100 | *str++ = encode[n >> 6]; | |
224c7076 A |
101 | break; |
102 | case 2: | |
1f2f436a A |
103 | n &= MASK(6); |
104 | *str++ = encode[n >> 1]; | |
224c7076 A |
105 | break; |
106 | case 3: | |
1f2f436a A |
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)]; | |
224c7076 A |
124 | break; |
125 | } | |
224c7076 A |
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 | { | |
ad3c9f2a | 133 | #if TARGET_OS_IPHONE |
511daa4c A |
134 | char *tmpdir; |
135 | #else | |
224c7076 A |
136 | uuid_t uuid; |
137 | char str[ENCODEDSIZE + 1]; | |
511daa4c | 138 | #endif |
224c7076 A |
139 | int res; |
140 | ||
141 | if(which < 0 || which > DIRHELPER_USER_LOCAL_LAST) { | |
142 | errno = EINVAL; | |
143 | return NULL; | |
144 | } | |
145 | ||
ad3c9f2a | 146 | #if TARGET_OS_IPHONE |
1f2f436a A |
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 { | |
511daa4c A |
161 | errno = EINVAL; |
162 | return NULL; | |
163 | } | |
511daa4c | 164 | #else |
224c7076 A |
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 | |
1f2f436a | 173 | // many users in a single directory. With 1024 buckets, we |
224c7076 | 174 | // could scale to 1,000,000 users while keeping the average |
1f2f436a | 175 | // number of files in a single directory around 1000 |
224c7076 A |
176 | // |
177 | encode_uuid_uid(uuid, uid, str); | |
178 | res = snprintf(path, pathlen, | |
179 | "%s%.*s/%s/%s", | |
1f2f436a | 180 | VAR_FOLDERS_PATH, BUCKETLEN, str, str + BUCKETLEN, subdirs[which]); |
511daa4c | 181 | #endif |
224c7076 A |
182 | if(res >= pathlen) { |
183 | errno = EINVAL; | |
184 | return NULL; /* buffer too small */ | |
185 | } | |
186 | return path; | |
187 | } | |
188 | ||
34e8f829 A |
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 | ||
1f2f436a A |
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 | ||
224c7076 A |
222 | __private_extern__ char * |
223 | _dirhelper(dirhelper_which_t which, char *path, size_t pathlen) | |
224 | { | |
224c7076 | 225 | static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; |
224c7076 A |
226 | struct stat sb; |
227 | ||
228 | if(which < 0 || which > DIRHELPER_USER_LOCAL_LAST) { | |
229 | errno = EINVAL; | |
230 | return NULL; | |
231 | } | |
232 | ||
1f2f436a A |
233 | if (pthread_once(&userdir_control, userdir_allocate) |
234 | || !userdir) { | |
235 | errno = ENOMEM; | |
236 | return NULL; | |
237 | } | |
238 | ||
224c7076 A |
239 | if(!*userdir) { |
240 | MUTEX_LOCK(&lock); | |
34e8f829 A |
241 | if (!*userdir) { |
242 | ||
1f2f436a | 243 | if(__user_local_dirname(geteuid(), DIRHELPER_USER_LOCAL, userdir, PATH_MAX) == NULL) { |
224c7076 A |
244 | MUTEX_UNLOCK(&lock); |
245 | return NULL; | |
246 | } | |
1f2f436a A |
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; | |
224c7076 | 253 | /* |
34e8f829 A |
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). | |
224c7076 A |
258 | */ |
259 | if(stat(userdir, &sb) < 0) { | |
34e8f829 A |
260 | mach_port_t mp; |
261 | ||
224c7076 A |
262 | if(errno != ENOENT) { /* some unknown error */ |
263 | *userdir = 0; | |
34e8f829 A |
264 | MUTEX_UNLOCK(&lock); |
265 | return NULL; | |
224c7076 | 266 | } |
34e8f829 A |
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); | |
224c7076 A |
293 | } |
294 | } | |
224c7076 A |
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 | /* | |
ad3c9f2a A |
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. | |
224c7076 | 310 | */ |
ad3c9f2a | 311 | #if !TARGET_OS_IPHONE |
1f2f436a A |
312 | if (!_xpc_runtime_is_app_sandboxed()) |
313 | #endif | |
314 | if(mkdir(path, modes[which]) != 0 && errno != EEXIST) | |
315 | return NULL; | |
316 | ||
ad3c9f2a A |
317 | #if !TARGET_OS_IPHONE |
318 | char *userdir_suffix = NULL; | |
319 | ||
1f2f436a A |
320 | if (_xpc_runtime_is_app_sandboxed()) { |
321 | /* | |
ad3c9f2a | 322 | * if the subdir wasn't made for us, bail since we probably don't have |
1f2f436a A |
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 | */ | |
ad3c9f2a A |
334 | userdir_suffix = getenv(XPC_ENV_SANDBOX_CONTAINER_ID); |
335 | if (!userdir_suffix) { | |
1f2f436a A |
336 | errno = EINVAL; |
337 | return NULL; | |
338 | } | |
ad3c9f2a A |
339 | } else |
340 | userdir_suffix = getenv(DIRHELPER_ENV_USER_DIR_SUFFIX); | |
1f2f436a | 341 | |
ad3c9f2a | 342 | if (userdir_suffix) { |
1f2f436a | 343 | /* |
ad3c9f2a | 344 | * suffix (usually container ID) doesn't end in a slash, so +2 is for slash and \0 |
1f2f436a | 345 | */ |
ad3c9f2a | 346 | if (pathlen < strlen(path) + strlen(userdir_suffix) + 2) { |
1f2f436a A |
347 | errno = EINVAL; |
348 | return NULL; /* buffer too small */ | |
349 | } | |
350 | ||
ad3c9f2a | 351 | strcat(path, userdir_suffix); |
1f2f436a A |
352 | strcat(path, "/"); |
353 | ||
354 | /* | |
ad3c9f2a | 355 | * create suffix subdirectory with the appropriate permissions |
1f2f436a A |
356 | * if it doesn't already exist. |
357 | */ | |
ad3c9f2a | 358 | if (mkdir(path, modes[which]) != 0 && errno != EEXIST) |
1f2f436a | 359 | return NULL; |
ad3c9f2a A |
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 | } | |
224c7076 | 369 | } |
1f2f436a | 370 | #endif |
224c7076 A |
371 | |
372 | return path; | |
373 | } |