/* * Copyright (c) 2004 Apple Computer, 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 #include "Manifest.h" ModuleNexus CSSMInitializer::mInstance; CSSMInitializer::CSSMInitializer () : mModule (gGuidAppleCSP), mCSP (mModule) { } CSSMInitializer::~CSSMInitializer () { } CssmClient::CSP* CSSMInitializer::GetCSP () { return &mInstance().mCSP; } //========================== MANIFEST ITEM LIST ========================== ManifestItemList::ManifestItemList () { } ManifestItemList::~ManifestItemList () { // throw away all of the items in the list iterator it = begin (); while (it != end ()) { delete *it++; } } // return the path portion of a URL after checking to see if we support its scheme void ManifestItemList::DecodeURL (CFURLRef url, char *pathBuffer, CFIndex maxBufLen) { // get the scheme from the url and check to make sure it is a "file" scheme CFRef scheme (CFURLCopyScheme (url)); if (CFStringCompare (scheme, CFSTR("file"), 0) != 0) { // we only support file URL's MacOSError::throwMe (errSecManifestNotSupported); } // convert the url into a "real" path name if (!CFURLGetFileSystemRepresentation (url, false, (UInt8*) pathBuffer, maxBufLen)) { MacOSError::throwMe (errSecManifestNotEqual); } } void ManifestItemList::AddFileSystemObject (char* path, StringSet& exceptions, bool isRoot) { // see if our path is in the exception list. If it is, do nothing else StringSet::iterator it = exceptions.find (path); if (it != exceptions.end ()) { secdebug ("manifest", "Did not add %s to the manifest.", path); return; } // now that we have the path, do a stat and see what we have struct stat nodeStat; int result = lstat (path, &nodeStat); UnixError::check (result); FileSystemEntryItem* mItem; // by default, include the user, group, and node in the digested info for the file bool includeUserAndGroup = true; switch (nodeStat.st_mode & S_IFMT) { case S_IFDIR: // are we a directory? { ManifestDirectoryItem* dirItem = new ManifestDirectoryItem (); dirItem->SetPath (path, exceptions, isRoot); mItem = dirItem; } break; case S_IFREG: { ManifestFileItem* fileItem = new ManifestFileItem (); fileItem->SetPath (path); fileItem->ComputeRepresentations (nodeStat); mItem = fileItem; } break; case S_IFLNK: { ManifestSymLinkItem* symItem = new ManifestSymLinkItem (); symItem->SetPath (path); symItem->ComputeRepresentation (); mItem = symItem; nodeStat.st_mode = S_IFLNK; includeUserAndGroup = false; } break; default: { ManifestOtherItem* otherItem = new ManifestOtherItem (); otherItem->SetPath (path); mItem = otherItem; } break; } if (includeUserAndGroup) // should we set the info? { mItem->SetUID (nodeStat.st_uid); mItem->SetGID (nodeStat.st_gid); } mItem->SetMode (nodeStat.st_mode); push_back (mItem); } void ManifestItemList::AddDataObject (CFDataRef object) { // reconstruct the pointer CssmClient::Digest d (*CSSMInitializer::GetCSP (), CSSM_ALGID_SHA1); const UInt8* data = CFDataGetBytePtr (object); CFIndex length = CFDataGetLength (object); CssmData dd ((void*) data, length); d.digest (dd); ManifestDataBlobItem* db = new ManifestDataBlobItem (); CssmData dResult = d (); db->SetDigest ((SHA1Digest*) dResult.data ()); db->SetLength (length); push_back (db); } void ManifestItemList::ConvertToStringSet (const char* path, CFArrayRef exceptionList, StringSet &exceptions) { if (exceptionList != NULL) { std::string prefix = path; // put us in canonical form if (prefix[prefix.length () - 1] != '/') { prefix += '/'; } // enumerate the list CFIndex max = CFArrayGetCount (exceptionList); CFIndex n; for (n = 0; n < max; ++n) { CFTypeRef dataRef = CFArrayGetValueAtIndex (exceptionList, n); if (CFGetTypeID (dataRef) != CFStringGetTypeID ()) { MacOSError::throwMe (errSecManifestInvalidException); } // always prepend the prefix -- the spec says that all items in the exception list are relative to the root std::string s = prefix + cfString (CFStringRef (dataRef)); secdebug ("manifest", "Uncanonicalized path is %s", s.c_str ()); // canonicalize the path and insert if successful. char realPath [PATH_MAX]; if (realpath (s.c_str (), realPath) != NULL) { secdebug ("manifest", "Inserted path %s as an exception", realPath); exceptions.insert (realPath); } } } } void ManifestItemList::AddObject (CFTypeRef object, CFArrayRef exceptionList) { // get the type of the object CFTypeID objectID = CFGetTypeID (object); if (objectID == CFDataGetTypeID ()) { AddDataObject ((CFDataRef) object); } else if (objectID == CFURLGetTypeID ()) { StringSet exceptions; // get the path from the URL char path [PATH_MAX]; DecodeURL ((CFURLRef) object, path, sizeof (path)); // canonicalize char realPath [PATH_MAX]; if (realpath (path, realPath) == NULL) { UnixError::throwMe (); } ConvertToStringSet (realPath, exceptionList, exceptions); AddFileSystemObject (realPath, exceptions, true); } else { MacOSError::throwMe (errSecManifestNotEqual); } } void RootItemList::Compare (RootItemList& item, bool compareOwnerAndGroup) { // the number of items in the list has to be the same unsigned numItems = size (); if (numItems != item.size ()) { MacOSError::throwMe (errSecManifestNotEqual); } // for a root item list, items in the manifest MUST have the same creation order unsigned i; for (i = 0; i < numItems; ++i) { ManifestItem* item1 = (*this)[i]; ManifestItem* item2 = item[i]; if (item1->GetItemType () != item2->GetItemType ()) { MacOSError::throwMe (errSecManifestNotEqual); } item1->Compare (item2, compareOwnerAndGroup); } } class CompareManifestFileItems { public: bool operator () (ManifestItem *a, ManifestItem *b); }; bool CompareManifestFileItems::operator () (ManifestItem *a, ManifestItem *b) { FileSystemEntryItem *aa = static_cast(a); FileSystemEntryItem *bb = static_cast(b); return strcmp (aa->GetName (), bb->GetName ()) < 0; } void FileSystemItemList::Compare (FileSystemItemList &a, bool compareOwnerAndGroup) { unsigned numItems = size (); if (numItems != a.size ()) { MacOSError::throwMe (errSecManifestNotEqual); } // sort the two lists sort (begin (), end (), CompareManifestFileItems ()); sort (a.begin (), a.end (), CompareManifestFileItems ()); // compare each item in the list unsigned i; for (i = 0; i < numItems; ++i) { ManifestItem *thisListPtr = (*this)[i]; ManifestItem *aListPtr = a[i]; if (thisListPtr->GetItemType () != aListPtr->GetItemType ()) { MacOSError::throwMe (errSecManifestNotEqual); } thisListPtr->Compare (aListPtr, compareOwnerAndGroup); } } //========================== MANIFEST ========================== ManifestInternal::ManifestInternal () { } ManifestInternal::~ManifestInternal () { secdebug ("manifest", "Destroyed manifest internal %p", this); } void ManifestInternal::CompareManifests (ManifestInternal& m1, ManifestInternal& m2, SecManifestCompareOptions options) { if ((options & ~kSecManifestVerifyOwnerAndGroup) != 0) { MacOSError::throwMe (unimpErr); // we don't support these options } m1.mManifestItems.Compare (m2.mManifestItems, (bool) options & kSecManifestVerifyOwnerAndGroup); } //========================== MANIFEST ITEM ========================== ManifestItem::~ManifestItem () { } //========================== DATA BLOB ITEM ========================== ManifestDataBlobItem::ManifestDataBlobItem () { } ManifestDataBlobItem::~ManifestDataBlobItem () { } ManifestItemType ManifestDataBlobItem::GetItemType () { return kManifestDataBlobItemType; } const SHA1Digest* ManifestDataBlobItem::GetDigest () { return &mSHA1Digest; } void ManifestDataBlobItem::SetDigest (const SHA1Digest *sha1Digest) { memcpy (&mSHA1Digest, sha1Digest, sizeof (SHA1Digest)); } size_t ManifestDataBlobItem::GetLength () { return mLength; } void ManifestDataBlobItem::SetLength (size_t length) { mLength = length; } void ManifestDataBlobItem::Compare (ManifestItem* item, bool compareOwnerAndGroup) { ManifestDataBlobItem* i = static_cast(item); if (memcmp (&i->mSHA1Digest, &mSHA1Digest, sizeof (SHA1Digest)) != 0) { MacOSError::throwMe (errSecManifestNotEqual); } } //========================== FILE SYSTEM ENTRY ITEM ========================== FileSystemEntryItem::FileSystemEntryItem () : mUserID (0), mGroupID (0), mMode (0) { } FileSystemEntryItem::~FileSystemEntryItem () { } void FileSystemEntryItem::SetName (char* name) { mName = name; } static char* StringTail (char* path) { char* finger = path + strlen (path) - 1; while (finger != path && *finger != '/') { finger -= 1; } if (finger != path) // did find a separator { finger += 1; } return finger; } void FileSystemEntryItem::SetPath (char* path) { // save off the path mPath = path; // while we are at it, extract that last name of the path name and save it off as the name mName = StringTail (path); secdebug ("manifest", "Created file item for %s with name %s", mPath.c_str (), mName.c_str ()); } void FileSystemEntryItem::SetUID (uid_t uid) { mUserID = uid; } void FileSystemEntryItem::SetGID (gid_t gid) { mGroupID = gid; } void FileSystemEntryItem::SetMode (mode_t mode) { mMode = mode; } uid_t FileSystemEntryItem::GetUID () const { return mUserID; } gid_t FileSystemEntryItem::GetGID () const { return mGroupID; } mode_t FileSystemEntryItem::GetMode () const { return mMode; } const char* FileSystemEntryItem::GetName () const { return (char*) mName.c_str (); } void FileSystemEntryItem::Compare (ManifestItem *aa, bool compareOwnerAndGroup) { FileSystemEntryItem* a = static_cast(aa); if (mName != a->mName || mMode != a->mMode) { MacOSError::throwMe (errSecManifestNotEqual); } if (compareOwnerAndGroup) { if (mUserID != a->mUserID || mGroupID != a->mGroupID) { MacOSError::throwMe (errSecManifestNotEqual); } } } //========================== MANIFEST FILE ITEM ========================== bool ManifestFileItem::FileSystemHasTrueForks (char* pathToFile) { // return true if volume to which path points supports true forked files struct statfs st; int result = statfs (pathToFile, &st); if (result != 0) { secdebug ("manifest", "Could not get statfs (error was %s)", strerror (errno)); UnixError::throwMe (); } return strcmp (st.f_fstypename, "afpfs") == 0 || strcmp (st.f_fstypename, "hfs") == 0; } std::string ManifestFileItem::ResourceFileName (char* path) { std::string filePath; if (FileSystemHasTrueForks (path)) { filePath = path; filePath += "/rsrc"; } else { // extract the local portion of the path int len = strlen (path) - 1; // skip any trailing / characters while (len >= 0 && path[len] == '/') { len -= 1; } if (len == 0) // handle the root case { return "/dev/null"; } int start = len - 1; while (start > 0 && path[start] != '/') { start -= 1; } if (path[start] == '/') { start += 1; } // create the full path to the resource fork std::string local (path + start, len - start + 1); std::string prefix (path, start); filePath = prefix + "._" + local; } return filePath; } bool ManifestFileItem::HasResourceFork (char* pathToFile, std::string &result, struct stat &st) { // try to get the stat on the file. If it works, the file exists result = ResourceFileName (pathToFile); int stresult = lstat (result.c_str (), &st); if (stresult == 0) { return st.st_size != 0; } return false; } ManifestFileItem::ManifestFileItem () : mNumForks (1) { } ManifestFileItem::~ManifestFileItem () { secdebug ("manifest", "Destroyed manifest item %p for path %s", this, mPath.c_str ()); } ManifestItemType ManifestFileItem::GetItemType () { return kManifestFileItemType; } u_int32_t ManifestFileItem::GetNumberOfForks () { return mNumForks; } void ManifestFileItem::SetNumberOfForks (u_int32_t numForks) { mNumForks = numForks; } bool ManifestFileItem::FileIsMachOBinary (char* path) { return false; } void ManifestFileItem::SetForkLength (int which, size_t length) { mFileLengths[which] = length; } size_t ManifestFileItem::GetForkLength (int which) { return mFileLengths[which]; } void ManifestFileItem::ComputeRepresentations (struct stat &st) { // digest the data fork mNumForks = 1; ComputeDigestForFile ((char*) mPath.c_str (), mDigest[0], mFileLengths[0], st); struct stat stat2; std::string resourceForkName; if (HasResourceFork ((char*) mPath.c_str (), resourceForkName, stat2)) { mNumForks = 2; ComputeDigestForFile ((char*) resourceForkName.c_str (), mDigest[1], mFileLengths[1], stat2); } } static const int kReadChunkSize = 4096 * 4; void ManifestFileItem::ComputeDigestForFile (char* name, SHA1Digest &digest, size_t &fileLength, struct stat &st) { secdebug ("manifest", "Creating digest for %s", name); // create a context for the digest operation CssmClient::Digest d (*CSSMInitializer::GetCSP (), CSSM_ALGID_SHA1); int fileNo = open (name, O_RDONLY, 0); if (fileNo == -1) { UnixError::throwMe (); } fileLength = st.st_size; if (st.st_size != 0) { // read the file char buffer[kReadChunkSize]; ssize_t bytesRead; while ((bytesRead = read (fileNo, buffer, kReadChunkSize)) != 0) { // digest the read data CssmData data (buffer, bytesRead); d.digest (data); } // compute the SHA1 hash CssmData dd = d (); memmove (&digest, (void*) dd.Data, dd.Length); } close (fileNo); } void ManifestFileItem::GetItemRepresentation (int whichFork, void* &itemRep, size_t &size) { itemRep = (void*) &mDigest[whichFork]; size = kSHA1DigestSize; } void ManifestFileItem::SetItemRepresentation (int whichFork, const void* itemRep, size_t size) { memcpy ((void*) &mDigest[whichFork], itemRep, size); } void ManifestFileItem::Compare (ManifestItem *manifestItem, bool compareOwnerAndGroup) { FileSystemEntryItem::Compare (manifestItem, compareOwnerAndGroup); ManifestFileItem* item = static_cast< ManifestFileItem*>(manifestItem); secdebug ("manifest", "Comparing file item %s against %s", GetName (), item->GetName ()); // the number of forks should be equal if (mNumForks != item->mNumForks) { MacOSError::throwMe (errSecManifestNotEqual); } // compare file lengths int i; for (i = 0; i < mNumForks; ++i) { if (mFileLengths[i] != item->mFileLengths[i]) { MacOSError::throwMe (errSecManifestNotEqual); } if (memcmp (&mDigest[i], item->mDigest[i], kSHA1DigestSize) != 0) { MacOSError::throwMe (errSecManifestNotEqual); } } } //========================== MANIFEST DIRECTORY ITEM ========================== ManifestDirectoryItem::ManifestDirectoryItem () { } ManifestDirectoryItem::~ManifestDirectoryItem () { secdebug ("manifest", "Destroyed directory item %p for path %s", this, mPath.c_str ()); } void ManifestDirectoryItem::SetPath (char* path, StringSet &exceptions, bool isRoot) { if (isRoot) { mName = "/"; mPath = path; } else { FileSystemEntryItem::SetPath (path); } secdebug ("manifest", "Added directory entry for %s with name %s", mPath.c_str (), mName.c_str ()); // enumerate the contents of the directory. DIR* thisDir = opendir (path); if (thisDir == NULL) // huh? The file disappeared or isn't a directory any more { UnixError::throwMe (); } struct dirent* dirEnt; while ((dirEnt = readdir (thisDir)) != NULL) { // throw out . and .. if (strcmp (dirEnt->d_name, ".") == 0 || strcmp (dirEnt->d_name, "..") == 0) { continue; } // figure out what this is pointing to. std::string fileName = mPath + "/" + dirEnt->d_name; mDirectoryItems.AddFileSystemObject ((char*) fileName.c_str(), exceptions, false); } } ManifestItemType ManifestDirectoryItem::GetItemType () { return kManifestDirectoryItemType; } void ManifestDirectoryItem::Compare (ManifestItem* a, bool compareOwnerAndGroup) { FileSystemEntryItem::Compare (a, compareOwnerAndGroup); ManifestDirectoryItem* aa = static_cast(a); secdebug ("manifest", "Comparing directory item %s against %s", GetName (), aa->GetName ()); mDirectoryItems.Compare (aa->mDirectoryItems, compareOwnerAndGroup); } //========================== MANIFEST SYMLINK ITEM ========================== ManifestSymLinkItem::ManifestSymLinkItem () { } ManifestSymLinkItem::~ManifestSymLinkItem () { secdebug ("manifest", "Destroyed symlink item for %s", mPath.c_str ()); } void ManifestSymLinkItem::ComputeRepresentation () { char path [FILENAME_MAX]; int result = readlink (mPath.c_str (), path, sizeof (path)); secdebug ("manifest", "Read content %s for %s", path, mPath.c_str ()); CssmClient::Digest d (*CSSMInitializer::GetCSP (), CSSM_ALGID_SHA1); CssmData dd (path, result); d.digest (dd); CssmData digest = d (); memmove (&mDigest, digest.Data, digest.Length); UnixError::check (result); } const SHA1Digest* ManifestSymLinkItem::GetDigest () { return &mDigest; } void ManifestSymLinkItem::SetDigest (const SHA1Digest* digest) { memcpy (mDigest, digest, sizeof (SHA1Digest)); } ManifestItemType ManifestSymLinkItem::GetItemType () { return kManifestSymLinkItemType; } void ManifestSymLinkItem::Compare (ManifestItem *a, bool compareOwnerAndGroup) { FileSystemEntryItem::Compare (a, compareOwnerAndGroup); ManifestSymLinkItem* aa = static_cast(a); secdebug ("manifest", "Comparing symlink item %s against %s", GetName (), aa->GetName ()); // now compare the data if (memcmp (&mDigest, &aa->mDigest, kSHA1DigestSize) != 0) { MacOSError::throwMe (errSecManifestNotEqual); } } //========================== MANIFEST OTHER ITEM ========================== ManifestOtherItem::ManifestOtherItem () { } ManifestOtherItem::~ManifestOtherItem () { secdebug ("manifest", "Destroyed other item for path %s", mPath.c_str ()); } ManifestItemType ManifestOtherItem::GetItemType () { return kManifestOtherType; } void ManifestOtherItem::Compare (ManifestItem *a, bool compareOwnerAndGroup) { FileSystemEntryItem::Compare (a, compareOwnerAndGroup); secdebug ("manifest", "Comparing other item %s against %s", GetName (), static_cast(a)->GetName ()); }