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