/* * Copyright (c) 2013 Apple, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include #include #include #include #include #include #include #include #include #include #include #define FLAG_DELIM_CHAR '#' #define FLAG_DELIM_STR "#" /* * Some default propeteries for EAs we know about internally. */ struct defaultList { const char *eaName; const char *propList; int flags; // See below }; #define propFlagsPrefix 0x0001 // The name is a prefix, so only look at that part static const struct defaultList *defaultPropertyTable = NULL; static const struct defaultList defaultUnboxedPropertyTable[] = { { "com.apple.quarantine", "PCS", 0 }, // not public { "com.apple.TextEncoding", "CS", 0 }, // Content-dependent, public { "com.apple.metadata:", "PS", propFlagsPrefix }, // Don't export, keep for copy & safe save { "com.apple.security.", "S", propFlagsPrefix }, { XATTR_RESOURCEFORK_NAME, "PCS", 0 }, // Don't keep for safe save { XATTR_FINDERINFO_NAME, "PCS", 0 }, // Same as ResourceFork { 0, 0, 0 }, }; static const struct defaultList defaultSandboxedPropertyTable[] = { { "com.apple.quarantine", "PCS", 0 }, // not public { "com.apple.TextEncoding", "CS", 0 }, // Content-dependent, public { "com.apple.metadata:", "PS", propFlagsPrefix }, // Don't export, keep for copy & safe save { "com.apple.security.", "N", propFlagsPrefix }, { XATTR_RESOURCEFORK_NAME, "PCS", 0 }, // Don't keep for safe save { XATTR_FINDERINFO_NAME, "PCS", 0 }, // Same as ResourceFork { 0, 0, 0 }, }; /* * The property lists on an EA are set by having a suffix character, * and then a list of characters. In general, we're choosing upper-case * to indicate the property is set, and lower-case to indicate it's to be * cleared. */ struct propertyListMapping { char enable; // Character to enable char disable; // Character to disable -- usually lower-case of enable xattr_operation_intent_t value; }; static const struct propertyListMapping PropertyListMapTable[] = { { 'C', 'c', XATTR_FLAG_CONTENT_DEPENDENT }, { 'P', 'p', XATTR_FLAG_NO_EXPORT }, { 'N', 'n', XATTR_FLAG_NEVER_PRESERVE }, { 'S', 's', XATTR_FLAG_SYNCABLE }, { 0, 0, 0 }, }; /* * Given a converted property list (that is, converted to the * xattr_operation_intent_t type), and an intent, determine if * it should be preserved or not. * * I've chosen to use a block instead of a simple mask on the belief * that the question may be moderately complex. If it ends up not being * so, then this can simply be turned into a mask of which bits to check * as being exclusionary. */ static const struct divineIntent { xattr_operation_intent_t intent; int (^checker)(xattr_flags_t); } intentTable[] = { { XATTR_OPERATION_INTENT_COPY, ^(xattr_flags_t flags) { if (flags & XATTR_FLAG_NEVER_PRESERVE) return 0; return 1; } }, { XATTR_OPERATION_INTENT_SAVE, ^(xattr_flags_t flags) { if (flags & (XATTR_FLAG_CONTENT_DEPENDENT | XATTR_FLAG_NEVER_PRESERVE)) return 0; return 1; } }, { XATTR_OPERATION_INTENT_SHARE, ^(xattr_flags_t flags) { if ((flags & (XATTR_FLAG_NO_EXPORT | XATTR_FLAG_NEVER_PRESERVE)) != 0) return 0; return 1; } }, { XATTR_OPERATION_INTENT_SYNC, ^(xattr_flags_t flags) { return (flags & (XATTR_FLAG_SYNCABLE | XATTR_FLAG_NEVER_PRESERVE)) == XATTR_FLAG_SYNCABLE; } }, { 0, 0 }, }; /* * If an EA name is in the default list, find it, and return the property * list string for it. */ static const char * nameInDefaultList(const char *eaname) { const struct defaultList *retval; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ if (_xpc_runtime_is_app_sandboxed()) { defaultPropertyTable = defaultSandboxedPropertyTable; } else { defaultPropertyTable = defaultUnboxedPropertyTable; } }); for (retval = defaultPropertyTable; retval->eaName; retval++) { if ((retval->flags & propFlagsPrefix) != 0 && strncmp(retval->eaName, eaname, strlen(retval->eaName)) == 0) return retval->propList; if (strcmp(retval->eaName, eaname) == 0) return retval->propList; } return NULL; } /* * Given an EA name, see if it has a property list in it, and * return a pointer to it. All this is doing is looking for * the delimiter, and returning the string after that. Returns * NULL if the delimiter isn't found. Note that an empty string * is a valid property list, as far as we're concerned. */ static const char * findPropertyList(const char *eaname) { const char *ptr = strrchr(eaname, '#'); if (ptr) return ptr+1; return NULL; } /* * Convert a property list string (e.g., "pCd") into a * xattr_operation_intent_t type. */ static xattr_operation_intent_t stringToProperties(const char *proplist) { xattr_operation_intent_t retval = 0; const char *ptr; // A switch would be more efficient, but less generic. for (ptr = proplist; *ptr; ptr++) { const struct propertyListMapping *mapPtr; for (mapPtr = PropertyListMapTable; mapPtr->enable; mapPtr++) { if (*ptr == mapPtr->enable) { retval |= mapPtr->value; } else if (*ptr == mapPtr->disable) { retval &= ~mapPtr->value; } } } return retval; } /* * Given an EA name (e.g., "com.apple.lfs.hfs.test"), and a * xattr_operation_intent_t value (it's currently an integral value, so * just a bitmask), cycle through the list of known properties, and return * a string with the EA name, and the property list appended. E.g., we * might return "com.apple.lfs.hfs.test#pD". * * The tricky part of this funciton is that it will not append any letters * if the value is only the default properites. In that case, it will copy * the EA name, and return that. * * It returns NULL if there was an error. The two errors right now are * no memory (strdup failed), in which case it will set errno to ENOMEM; and * the resulting EA name is longer than XATTR_MAXNAMELEN, in which case it * sets errno to ENAMETOOLONG. * * (Note that it also uses ENAMETOOLONG if the buffer it's trying to set * gets too large. I honestly can't see how that would happen, but it's there * for sanity checking. That would require having more than 64 bits to use.) */ char * xattr_name_with_flags(const char *orig, xattr_flags_t propList) { char *retval = NULL; char suffix[66] = { 0 }; // 66: uint64_t for property types, plus '#', plus NUL char *cur = suffix; const struct propertyListMapping *mapPtr; *cur++ = '#'; for (mapPtr = PropertyListMapTable; mapPtr->enable; mapPtr++) { if ((propList & mapPtr->value) != 0) { *cur++ = mapPtr->enable; } if (cur >= (suffix + sizeof(suffix))) { errno = ENAMETOOLONG; return NULL; } } if (cur == suffix + 1) { // No changes made retval = strdup(orig); if (retval == NULL) errno = ENOMEM; } else { const char *defaultEntry = NULL; if ((defaultEntry = nameInDefaultList(orig)) != NULL && strcmp(defaultEntry, suffix + 1) == 0) { // Just use the name passed in retval = strdup(orig); } else { asprintf(&retval, "%s%s", orig, suffix); } if (retval == NULL) { errno = ENOMEM; } else { if (strlen(retval) > XATTR_MAXNAMELEN) { free(retval); retval = NULL; errno = ENAMETOOLONG; } } } return retval; } char * xattr_name_without_flags(const char *eaname) { char *retval = NULL; char *tmp; if ((tmp = strrchr(eaname, FLAG_DELIM_CHAR)) == NULL) { retval = strdup(eaname); } else { retval = calloc(tmp - eaname + 1, 1); if (retval) { strlcpy(retval, eaname, tmp - eaname + 1); } } if (retval == NULL) { errno = ENOMEM; } return retval; } int xattr_intent_with_flags(xattr_operation_intent_t intent, xattr_flags_t flags) { const struct divineIntent *ip; for (ip = intentTable; ip->intent; ip++) { if (ip->intent == intent) { return ip->checker(flags); } } if ((flags & XATTR_FLAG_NEVER_PRESERVE) != 0) return 0; // Special case, don't try to copy this one return 1; // Default } xattr_flags_t xattr_flags_from_name(const char *eaname) { xattr_flags_t retval = 0; const char *propList; propList = findPropertyList(eaname); if (propList == NULL) { propList = nameInDefaultList(eaname); } if (propList != NULL) { retval = stringToProperties(propList); } return retval; } /* * Indicate whether an EA should be preserved, when using the * given intent. * * This returns 0 if it should not be preserved, and 1 if it should. * * It simply looks through the tables we have above, and compares the * xattr_operation_intent_t for the EA with the intent. If the * EA doesn't have any properties, and it's not on the default list, the * default is to preserve it. */ int xattr_preserve_for_intent(const char *eaname, xattr_operation_intent_t intent) { xattr_flags_t flags = xattr_flags_from_name(eaname); return xattr_intent_with_flags(intent, flags); } #include "xattr_properties.h"