]> git.saurik.com Git - apple/libc.git/blob - darwin/_dirhelper.c
bfd9959211c4e2918a6c6a68c5a94e265a855705
[apple/libc.git] / darwin / _dirhelper.c
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_EMBEDDED
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_EMBEDDED
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 * now for subdirectories, create it with the appropriate permissions
308 * if it doesn't already exist. On OS X, if we're under App Sandbox, we
309 * rely on xpchelper having created the subdir for us.
310 */
311 #if !TARGET_OS_EMBEDDED
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_EMBEDDED
318 if (_xpc_runtime_is_app_sandboxed()) {
319 /*
320 * if xpchelper didn't make the subdir for us, bail since we don't have
321 * permission to create it ourselves.
322 */
323 if(stat(path, &sb) < 0) {
324 errno = EPERM;
325 return NULL;
326 }
327
328 /*
329 * sandboxed applications get per-application directories named
330 * after the container
331 */
332 char *container_id = getenv(XPC_ENV_SANDBOX_CONTAINER_ID);
333 if(!container_id) {
334 errno = EINVAL;
335 return NULL;
336 }
337
338 /*
339 * container ID doesn't end in a slash, so +2 is for slash and \0
340 */
341 if (pathlen < strlen(path) + strlen(container_id) + 2) {
342 errno = EINVAL;
343 return NULL; /* buffer too small */
344 }
345
346 strcat(path, container_id);
347 strcat(path, "/");
348
349 /*
350 * create per-app subdirectory with the appropriate permissions
351 * if it doesn't already exist.
352 */
353 if(mkdir(path, modes[which]) != 0 && errno != EEXIST)
354 return NULL;
355 }
356 #endif
357
358 return path;
359 }