]>
Commit | Line | Data |
---|---|---|
11f767b3 A |
1 | /* |
2 | * Copyright (c) 2013 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 <stdio.h> | |
25 | #include <stdlib.h> | |
26 | #include <string.h> | |
27 | #include <unistd.h> | |
28 | #include <err.h> | |
29 | #include <errno.h> | |
30 | #include <sys/types.h> | |
31 | #include <sys/xattr.h> | |
618b37c8 A |
32 | #include <dispatch/dispatch.h> |
33 | #include <xpc/private.h> | |
11f767b3 | 34 | |
618b37c8 A |
35 | #include <xattr_flags.h> |
36 | ||
37 | #define FLAG_DELIM_CHAR '#' | |
38 | #define FLAG_DELIM_STR "#" | |
11f767b3 A |
39 | |
40 | /* | |
41 | * Some default propeteries for EAs we know about internally. | |
42 | */ | |
43 | struct defaultList { | |
44 | const char *eaName; | |
45 | const char *propList; | |
46 | int flags; // See below | |
47 | }; | |
48 | ||
49 | #define propFlagsPrefix 0x0001 // The name is a prefix, so only look at that part | |
50 | ||
618b37c8 A |
51 | static const struct defaultList *defaultPropertyTable = NULL; |
52 | ||
11f767b3 | 53 | static const struct defaultList |
618b37c8 A |
54 | defaultUnboxedPropertyTable[] = { |
55 | { "com.apple.quarantine", "PCS", 0 }, // not public | |
56 | { "com.apple.TextEncoding", "CS", 0 }, // Content-dependent, public | |
57 | { "com.apple.metadata:", "PS", propFlagsPrefix }, // Don't export, keep for copy & safe save | |
58 | { "com.apple.security.", "S", propFlagsPrefix }, | |
59 | { XATTR_RESOURCEFORK_NAME, "PCS", 0 }, // Don't keep for safe save | |
60 | { XATTR_FINDERINFO_NAME, "PCS", 0 }, // Same as ResourceFork | |
87564055 | 61 | { "com.apple.root.installed", "PC", 0}, // Don't share or sync. Copyable by entitled callers |
11f767b3 A |
62 | { 0, 0, 0 }, |
63 | }; | |
64 | ||
618b37c8 A |
65 | static const struct defaultList |
66 | defaultSandboxedPropertyTable[] = { | |
67 | { "com.apple.quarantine", "PCS", 0 }, // not public | |
68 | { "com.apple.TextEncoding", "CS", 0 }, // Content-dependent, public | |
69 | { "com.apple.metadata:", "PS", propFlagsPrefix }, // Don't export, keep for copy & safe save | |
70 | { "com.apple.security.", "N", propFlagsPrefix }, | |
71 | { XATTR_RESOURCEFORK_NAME, "PCS", 0 }, // Don't keep for safe save | |
72 | { XATTR_FINDERINFO_NAME, "PCS", 0 }, // Same as ResourceFork | |
87564055 | 73 | { "com.apple.root.installed", "PC", 0}, // Don't share or sync. Copyable by entitled callers |
618b37c8 A |
74 | { 0, 0, 0 }, |
75 | }; | |
76 | ||
11f767b3 A |
77 | /* |
78 | * The property lists on an EA are set by having a suffix character, | |
79 | * and then a list of characters. In general, we're choosing upper-case | |
80 | * to indicate the property is set, and lower-case to indicate it's to be | |
81 | * cleared. | |
82 | */ | |
83 | struct propertyListMapping { | |
84 | char enable; // Character to enable | |
85 | char disable; // Character to disable -- usually lower-case of enable | |
618b37c8 | 86 | xattr_operation_intent_t value; |
11f767b3 A |
87 | }; |
88 | static const struct propertyListMapping | |
89 | PropertyListMapTable[] = { | |
618b37c8 A |
90 | { 'C', 'c', XATTR_FLAG_CONTENT_DEPENDENT }, |
91 | { 'P', 'p', XATTR_FLAG_NO_EXPORT }, | |
92 | { 'N', 'n', XATTR_FLAG_NEVER_PRESERVE }, | |
93 | { 'S', 's', XATTR_FLAG_SYNCABLE }, | |
11f767b3 A |
94 | { 0, 0, 0 }, |
95 | }; | |
96 | ||
97 | /* | |
98 | * Given a converted property list (that is, converted to the | |
618b37c8 | 99 | * xattr_operation_intent_t type), and an intent, determine if |
11f767b3 A |
100 | * it should be preserved or not. |
101 | * | |
102 | * I've chosen to use a block instead of a simple mask on the belief | |
103 | * that the question may be moderately complex. If it ends up not being | |
104 | * so, then this can simply be turned into a mask of which bits to check | |
105 | * as being exclusionary. | |
106 | */ | |
107 | static const struct divineIntent { | |
618b37c8 A |
108 | xattr_operation_intent_t intent; |
109 | int (^checker)(xattr_flags_t); | |
11f767b3 | 110 | } intentTable[] = { |
618b37c8 A |
111 | { XATTR_OPERATION_INTENT_COPY, ^(xattr_flags_t flags) { |
112 | if (flags & XATTR_FLAG_NEVER_PRESERVE) | |
11f767b3 A |
113 | return 0; |
114 | return 1; | |
115 | } }, | |
618b37c8 A |
116 | { XATTR_OPERATION_INTENT_SAVE, ^(xattr_flags_t flags) { |
117 | if (flags & (XATTR_FLAG_CONTENT_DEPENDENT | XATTR_FLAG_NEVER_PRESERVE)) | |
11f767b3 A |
118 | return 0; |
119 | return 1; | |
120 | } }, | |
618b37c8 A |
121 | { XATTR_OPERATION_INTENT_SHARE, ^(xattr_flags_t flags) { |
122 | if ((flags & (XATTR_FLAG_NO_EXPORT | XATTR_FLAG_NEVER_PRESERVE)) != 0) | |
11f767b3 A |
123 | return 0; |
124 | return 1; | |
125 | } }, | |
618b37c8 A |
126 | { XATTR_OPERATION_INTENT_SYNC, ^(xattr_flags_t flags) { |
127 | return (flags & (XATTR_FLAG_SYNCABLE | XATTR_FLAG_NEVER_PRESERVE)) == XATTR_FLAG_SYNCABLE; | |
128 | } }, | |
11f767b3 A |
129 | { 0, 0 }, |
130 | }; | |
131 | ||
132 | ||
133 | /* | |
134 | * If an EA name is in the default list, find it, and return the property | |
135 | * list string for it. | |
136 | */ | |
137 | static const char * | |
138 | nameInDefaultList(const char *eaname) | |
139 | { | |
140 | const struct defaultList *retval; | |
618b37c8 A |
141 | static dispatch_once_t onceToken; |
142 | ||
143 | dispatch_once(&onceToken, ^{ | |
144 | if (_xpc_runtime_is_app_sandboxed()) { | |
145 | defaultPropertyTable = defaultSandboxedPropertyTable; | |
146 | } else { | |
147 | defaultPropertyTable = defaultUnboxedPropertyTable; | |
148 | } | |
149 | }); | |
11f767b3 A |
150 | |
151 | for (retval = defaultPropertyTable; retval->eaName; retval++) { | |
152 | if ((retval->flags & propFlagsPrefix) != 0 && | |
153 | strncmp(retval->eaName, eaname, strlen(retval->eaName)) == 0) | |
154 | return retval->propList; | |
155 | if (strcmp(retval->eaName, eaname) == 0) | |
156 | return retval->propList; | |
157 | } | |
158 | return NULL; | |
159 | } | |
160 | ||
161 | /* | |
162 | * Given an EA name, see if it has a property list in it, and | |
163 | * return a pointer to it. All this is doing is looking for | |
164 | * the delimiter, and returning the string after that. Returns | |
165 | * NULL if the delimiter isn't found. Note that an empty string | |
166 | * is a valid property list, as far as we're concerned. | |
167 | */ | |
168 | static const char * | |
169 | findPropertyList(const char *eaname) | |
170 | { | |
171 | const char *ptr = strrchr(eaname, '#'); | |
172 | if (ptr) | |
173 | return ptr+1; | |
174 | return NULL; | |
175 | } | |
176 | ||
177 | /* | |
178 | * Convert a property list string (e.g., "pCd") into a | |
618b37c8 | 179 | * xattr_operation_intent_t type. |
11f767b3 | 180 | */ |
618b37c8 | 181 | static xattr_operation_intent_t |
11f767b3 A |
182 | stringToProperties(const char *proplist) |
183 | { | |
618b37c8 | 184 | xattr_operation_intent_t retval = 0; |
11f767b3 A |
185 | const char *ptr; |
186 | ||
187 | // A switch would be more efficient, but less generic. | |
188 | for (ptr = proplist; *ptr; ptr++) { | |
189 | const struct propertyListMapping *mapPtr; | |
190 | for (mapPtr = PropertyListMapTable; mapPtr->enable; mapPtr++) { | |
191 | if (*ptr == mapPtr->enable) { | |
192 | retval |= mapPtr->value; | |
193 | } else if (*ptr == mapPtr->disable) { | |
194 | retval &= ~mapPtr->value; | |
195 | } | |
196 | } | |
197 | } | |
198 | return retval; | |
199 | } | |
200 | ||
201 | /* | |
202 | * Given an EA name (e.g., "com.apple.lfs.hfs.test"), and a | |
618b37c8 | 203 | * xattr_operation_intent_t value (it's currently an integral value, so |
11f767b3 A |
204 | * just a bitmask), cycle through the list of known properties, and return |
205 | * a string with the EA name, and the property list appended. E.g., we | |
206 | * might return "com.apple.lfs.hfs.test#pD". | |
207 | * | |
208 | * The tricky part of this funciton is that it will not append any letters | |
209 | * if the value is only the default properites. In that case, it will copy | |
210 | * the EA name, and return that. | |
211 | * | |
212 | * It returns NULL if there was an error. The two errors right now are | |
213 | * no memory (strdup failed), in which case it will set errno to ENOMEM; and | |
214 | * the resulting EA name is longer than XATTR_MAXNAMELEN, in which case it | |
215 | * sets errno to ENAMETOOLONG. | |
216 | * | |
217 | * (Note that it also uses ENAMETOOLONG if the buffer it's trying to set | |
218 | * gets too large. I honestly can't see how that would happen, but it's there | |
219 | * for sanity checking. That would require having more than 64 bits to use.) | |
220 | */ | |
221 | char * | |
618b37c8 | 222 | xattr_name_with_flags(const char *orig, xattr_flags_t propList) |
11f767b3 A |
223 | { |
224 | char *retval = NULL; | |
225 | char suffix[66] = { 0 }; // 66: uint64_t for property types, plus '#', plus NUL | |
226 | char *cur = suffix; | |
227 | const struct propertyListMapping *mapPtr; | |
228 | ||
229 | *cur++ = '#'; | |
230 | for (mapPtr = PropertyListMapTable; mapPtr->enable; mapPtr++) { | |
231 | if ((propList & mapPtr->value) != 0) { | |
232 | *cur++ = mapPtr->enable; | |
233 | } | |
234 | if (cur >= (suffix + sizeof(suffix))) { | |
235 | errno = ENAMETOOLONG; | |
236 | return NULL; | |
237 | } | |
238 | ||
239 | } | |
240 | ||
241 | ||
242 | if (cur == suffix + 1) { | |
243 | // No changes made | |
244 | retval = strdup(orig); | |
245 | if (retval == NULL) | |
246 | errno = ENOMEM; | |
247 | } else { | |
248 | const char *defaultEntry = NULL; | |
249 | if ((defaultEntry = nameInDefaultList(orig)) != NULL && | |
250 | strcmp(defaultEntry, suffix + 1) == 0) { | |
251 | // Just use the name passed in | |
252 | retval = strdup(orig); | |
253 | } else { | |
254 | asprintf(&retval, "%s%s", orig, suffix); | |
255 | } | |
256 | if (retval == NULL) { | |
257 | errno = ENOMEM; | |
258 | } else { | |
259 | if (strlen(retval) > XATTR_MAXNAMELEN) { | |
260 | free(retval); | |
261 | retval = NULL; | |
262 | errno = ENAMETOOLONG; | |
263 | } | |
264 | } | |
265 | } | |
266 | return retval; | |
267 | } | |
268 | ||
618b37c8 A |
269 | char * |
270 | xattr_name_without_flags(const char *eaname) | |
271 | { | |
272 | char *retval = NULL; | |
273 | char *tmp; | |
274 | ||
275 | if ((tmp = strrchr(eaname, FLAG_DELIM_CHAR)) == NULL) { | |
276 | retval = strdup(eaname); | |
277 | } else { | |
278 | retval = calloc(tmp - eaname + 1, 1); | |
279 | if (retval) { | |
280 | strlcpy(retval, eaname, tmp - eaname + 1); | |
281 | } | |
282 | } | |
283 | if (retval == NULL) { | |
284 | errno = ENOMEM; | |
285 | } | |
286 | return retval; | |
287 | } | |
288 | ||
289 | int | |
290 | xattr_intent_with_flags(xattr_operation_intent_t intent, xattr_flags_t flags) | |
291 | { | |
292 | const struct divineIntent *ip; | |
293 | ||
294 | for (ip = intentTable; ip->intent; ip++) { | |
295 | if (ip->intent == intent) { | |
296 | return ip->checker(flags); | |
297 | } | |
298 | } | |
299 | if ((flags & XATTR_FLAG_NEVER_PRESERVE) != 0) | |
300 | return 0; // Special case, don't try to copy this one | |
301 | ||
302 | return 1; // Default | |
303 | } | |
304 | ||
305 | xattr_flags_t | |
306 | xattr_flags_from_name(const char *eaname) | |
11f767b3 | 307 | { |
618b37c8 | 308 | xattr_flags_t retval = 0; |
11f767b3 A |
309 | const char *propList; |
310 | ||
311 | propList = findPropertyList(eaname); | |
312 | if (propList == NULL) { | |
618b37c8 | 313 | propList = nameInDefaultList(eaname); |
11f767b3 A |
314 | } |
315 | if (propList != NULL) { | |
316 | retval = stringToProperties(propList); | |
317 | } | |
318 | ||
319 | return retval; | |
320 | } | |
321 | ||
322 | /* | |
323 | * Indicate whether an EA should be preserved, when using the | |
324 | * given intent. | |
325 | * | |
326 | * This returns 0 if it should not be preserved, and 1 if it should. | |
327 | * | |
328 | * It simply looks through the tables we have above, and compares the | |
618b37c8 | 329 | * xattr_operation_intent_t for the EA with the intent. If the |
11f767b3 A |
330 | * EA doesn't have any properties, and it's not on the default list, the |
331 | * default is to preserve it. | |
332 | */ | |
333 | ||
334 | int | |
618b37c8 | 335 | xattr_preserve_for_intent(const char *eaname, xattr_operation_intent_t intent) |
11f767b3 | 336 | { |
618b37c8 | 337 | xattr_flags_t flags = xattr_flags_from_name(eaname); |
11f767b3 | 338 | |
618b37c8 | 339 | return xattr_intent_with_flags(intent, flags); |
11f767b3 | 340 | } |
618b37c8 A |
341 | |
342 | #include "xattr_properties.h" |