/ OSX / libsecurity_codesigning / lib / dirscanner.cpp
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