]> git.saurik.com Git - apple/security.git/blob - OSX/libsecurity_codesigning/lib/dirscanner.cpp
Security-59754.41.1.tar.gz
[apple/security.git] / OSX / libsecurity_codesigning / lib / 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