1/* 2 * Copyright (c) 2014 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 <dirent.h> 25#include <unistd.h> 26#include <security_utilities/cfutilities.h> 27#include <security_utilities/debugging.h> 28#include "dirscanner.h" 29 30namespace Security { 31namespace CodeSigning { 32 33 34DirScanner::DirScanner(const char *path) 35 : init(false) 36{ 37 this->path = std::string(path); 38 this->initialize(); 39} 40 41DirScanner::DirScanner(string path) 42 : init(false) 43{ 44 this->path = path; 45 this->initialize(); 46} 47 48DirScanner::~DirScanner() 49{ 50 if (this->dp != NULL) 51 (void) closedir(this->dp); 52} 53 54void DirScanner::initialize() 55{ 56 if (this->dp == NULL) { 57 errno = 0; 58 if ((this->dp = opendir(this->path.c_str())) == NULL) { 59 if (errno == ENOENT) { 60 init = false; 61 } else { 62 UnixError::check(-1); 63 } 64 } else 65 init = true; 66 } else 67 MacOSError::throwMe(errSecInternalError); 68} 69 70struct dirent * DirScanner::getNext() 71{ 72 return readdir(this->dp); 73} 74 75bool DirScanner::initialized() 76{ 77 return this->init; 78} 79 80 81DirValidator::~DirValidator() 82{ 83 for (Rules::iterator it = mRules.begin(); it != mRules.end(); ++it) 84 delete *it; 85} 86 87void DirValidator::validate(const string &root, OSStatus error) 88{ 89 std::set<Rule *> reqMatched; 90 FTS fts(root); 91 while (FTSENT *ent = fts_read(fts)) { 92 const char *relpath = ent->fts_path + root.size() + 1; // skip prefix + "/" 93 bool executable = ent->fts_statp->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH); 94 Rule *rule = NULL; 95 switch (ent->fts_info) { 96 case FTS_F: 97 secdebug("dirval", "file %s", ent->fts_path); 98 rule = match(relpath, file, executable); 99 break; 100 case FTS_SL: { 101 secdebug("dirval", "symlink %s", ent->fts_path); 102 char target[PATH_MAX]; 103 ssize_t len = ::readlink(ent->fts_accpath, target, sizeof(target)-1); 104 if (len < 0) 105 UnixError::throwMe(); 106 target[len] = '\0'; 107 rule = match(relpath, symlink, executable, target); 108 break; 109 } 110 case FTS_D: 111 secdebug("dirval", "entering %s", ent->fts_path); 112 if (ent->fts_level == FTS_ROOTLEVEL) 113 continue; // skip root directory 114 rule = match(relpath, directory, executable); 115 if (!rule || !(rule->flags & descend)) 116 fts_set(fts, ent, FTS_SKIP); // do not descend 117 break; 118 case FTS_DP: 119 secdebug("dirval", "leaving %s", ent->fts_path); 120 continue; 121 default: 122 secdebug("dirval", "type %d (errno %d): %s", ent->fts_info, ent->fts_errno, ent->fts_path); 123 MacOSError::throwMe(error); // not a file, symlink, or directory 124 } 125 if (!rule) 126 MacOSError::throwMe(error); // no match 127 else if (rule->flags & required) 128 reqMatched.insert(rule); 129 } 130 if (reqMatched.size() != mRequireCount) { 131 secdebug("dirval", "matched %d of %d required rules", reqMatched.size(), mRequireCount); 132 MacOSError::throwMe(error); // not all required rules were matched 133 } 134} 135 136DirValidator::Rule * DirValidator::match(const char *path, uint32_t flags, bool executable, const char *target) 137{ 138 for (Rules::iterator it = mRules.begin(); it != mRules.end(); ++it) { 139 Rule *rule = *it; 140 if ((rule->flags & flags) 141 && !(executable && (rule->flags & noexec)) 142 && rule->match(path) 143 && (!target || rule->matchTarget(path, target))) 144 return rule; 145 } 146 return NULL; 147} 148 149DirValidator::FTS::FTS(const string &path, int options) 150{ 151 const char * paths[2] = { path.c_str(), NULL }; 152 mFTS = fts_open((char * const *)paths, options, NULL); 153 if (!mFTS) 154 UnixError::throwMe(); 155} 156 157DirValidator::FTS::~FTS() 158{ 159 fts_close(mFTS); 160} 161 162DirValidator::Rule::Rule(const string &pattern, uint32_t flags, TargetPatternBuilder targetBlock) 163 : ResourceBuilder::Rule(pattern, 0, flags), mTargetBlock(NULL) 164{ 165 if (targetBlock) 166 mTargetBlock = Block_copy(targetBlock); 167} 168 169DirValidator::Rule::~Rule() 170{ 171 if (mTargetBlock) 172 Block_release(mTargetBlock); 173} 174 175bool DirValidator::Rule::matchTarget(const char *path, const char *target) const 176{ 177 if (!mTargetBlock) 178 MacOSError::throwMe(errSecCSInternalError); 179 string pattern = mTargetBlock(path, target); 180 if (pattern.empty()) 181 return true; // always match empty pattern 182 secdebug("dirval", "%s: match target %s against %s", path, target, pattern.c_str()); 183 regex_t re; 184 if (::regcomp(&re, pattern.c_str(), REG_EXTENDED | REG_NOSUB)) 185 MacOSError::throwMe(errSecCSInternalError); 186 switch (::regexec(&re, target, 0, NULL, 0)) { 187 case 0: 188 return true; 189 case REG_NOMATCH: 190 return false; 191 default: 192 MacOSError::throwMe(errSecCSInternalError); 193 } 194} 195 196 197} // end namespace CodeSigning 198} // end namespace Security 199