1/*
2 * Copyright (c) 2006-2011 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#include "bundlediskrep.h"
24#include "filediskrep.h"
25#include "dirscanner.h"
26#include <CoreFoundation/CFURLAccess.h>
27#include <CoreFoundation/CFBundlePriv.h>
28#include <security_utilities/cfmunge.h>
29#include <copyfile.h>
30#include <fts.h>
31
32
33namespace Security {
34namespace CodeSigning {
35
36using namespace UnixPlusPlus;
37
38
39//
40// Local helpers
41//
42static std::string findDistFile(const std::string &directory);
43
44
45//
46// We make a CFBundleRef immediately, but everything else is lazy
47//
48BundleDiskRep::BundleDiskRep(const char *path, const Context *ctx)
49	: mBundle(CFBundleCreate(NULL, CFTempURL(path)))
50{
51	if (!mBundle)
52		MacOSError::throwMe(errSecCSBadBundleFormat);
53	setup(ctx);
54	CODESIGN_DISKREP_CREATE_BUNDLE_PATH(this, (char*)path, (void*)ctx, mExecRep);
55}
56
57BundleDiskRep::BundleDiskRep(CFBundleRef ref, const Context *ctx)
58{
59	mBundle = ref;		// retains
60	setup(ctx);
61	CODESIGN_DISKREP_CREATE_BUNDLE_REF(this, ref, (void*)ctx, mExecRep);
62}
63
64BundleDiskRep::~BundleDiskRep()
65{
66}
67
68// common construction code
69void BundleDiskRep::setup(const Context *ctx)
70{
71	mInstallerPackage = false;	// default
72
73	// capture the path of the main executable before descending into a specific version
74	CFRef<CFURLRef> mainExecBefore = CFBundleCopyExecutableURL(mBundle);
75
76	// validate the bundle root; fish around for the desired framework version
77	string root = cfStringRelease(copyCanonicalPath());
78	string contents = root + "/Contents";
79	string version = root + "/Versions/"
80		+ ((ctx && ctx->version) ? ctx->version : "Current")
81		+ "/.";
82	if (::access(contents.c_str(), F_OK) == 0) {	// not shallow
83		DirValidator val;
84		val.require("^Contents$", DirValidator::directory);	 // duh
85		val.allow("^(\\.LSOverride|\\.DS_Store|Icon\r|\\.SoftwareDepot\\.tracking)$", DirValidator::file | DirValidator::noexec);
86		try {
87			val.validate(root, errSecCSUnsealedAppRoot);
88		} catch (const MacOSError &err) {
89			recordStrictError(err.error);
90		}
91	} else if (::access(version.c_str(), F_OK) == 0) {	// versioned bundle
92		if (CFBundleRef versionBundle = CFBundleCreate(NULL, CFTempURL(version)))
93			mBundle.take(versionBundle);	// replace top bundle ref
94		else
95			MacOSError::throwMe(errSecCSStaticCodeNotFound);
96		validateFrameworkRoot(root);
97	} else {
98		if (ctx && ctx->version)	// explicitly specified
99			MacOSError::throwMe(errSecCSStaticCodeNotFound);
100	}
101
102	CFDictionaryRef infoDict = CFBundleGetInfoDictionary(mBundle);
103	assert(infoDict);	// CFBundle will always make one up for us
104	CFTypeRef mainHTML = CFDictionaryGetValue(infoDict, CFSTR("MainHTML"));
105	CFTypeRef packageVersion = CFDictionaryGetValue(infoDict, CFSTR("IFMajorVersion"));
106
107	// conventional executable bundle: CFBundle identifies an executable for us
108	if (CFRef<CFURLRef> mainExec = CFBundleCopyExecutableURL(mBundle))		// if CFBundle claims an executable...
109		if (mainHTML == NULL) {												// ... and it's not a widget
110
111			// Note that this check is skipped if there is a specific framework version checked.
112			// That's because you know what you are doing if you are looking at a specific version.
113			// This check is designed to stop someone who did a verification on an app root, from mistakenly
114			// verifying a framework
115			if (mainExecBefore && (!ctx || !ctx->version)) {
116				char main_exec_before[PATH_MAX];
117				char main_exec[PATH_MAX];
118				// The realpath call is important because alot of Framework bundles have a symlink
119				// to their "Current" version binary in the main bundle
120				if (realpath(cfString(mainExecBefore).c_str(), main_exec_before) == NULL ||
121					realpath(cfString(mainExec).c_str(), main_exec) == NULL)
122					MacOSError::throwMe(errSecCSInternalError);
123
124				if (strcmp(main_exec_before, main_exec) != 0)
125					recordStrictError(errSecCSAmbiguousBundleFormat);
126			}
127
128			mMainExecutableURL = mainExec;
129			mExecRep = DiskRep::bestFileGuess(this->mainExecutablePath(), ctx);
130			if (!mExecRep->fd().isPlainFile(this->mainExecutablePath()))
131				recordStrictError(errSecCSRegularFile);
132			mFormat = "bundle with " + mExecRep->format();
133			return;
134		}
135
136	// widget
137	if (mainHTML) {
138		if (CFGetTypeID(mainHTML) != CFStringGetTypeID())
139			MacOSError::throwMe(errSecCSBadBundleFormat);
140		mMainExecutableURL.take(makeCFURL(cfString(CFStringRef(mainHTML)), false,
141			CFRef<CFURLRef>(CFBundleCopySupportFilesDirectoryURL(mBundle))));
142		if (!mMainExecutableURL)
143			MacOSError::throwMe(errSecCSBadBundleFormat);
144		mExecRep = new FileDiskRep(this->mainExecutablePath().c_str());
145		if (!mExecRep->fd().isPlainFile(this->mainExecutablePath()))
146			recordStrictError(errSecCSRegularFile);
147		mFormat = "widget bundle";
148		return;
149	}
150
151	// do we have a real Info.plist here?
152	if (CFRef<CFURLRef> infoURL = _CFBundleCopyInfoPlistURL(mBundle)) {
153		// focus on the Info.plist (which we know exists) as the nominal "main executable" file
154		mMainExecutableURL = infoURL;
155		mExecRep = new FileDiskRep(this->mainExecutablePath().c_str());
156		if (!mExecRep->fd().isPlainFile(this->mainExecutablePath()))
157			recordStrictError(errSecCSRegularFile);
158		if (packageVersion) {
159			mInstallerPackage = true;
160			mFormat = "installer package bundle";
161		} else {
162			mFormat = "bundle";
163		}
164		return;
165	}
166
167	// we're getting desperate here. Perhaps an oldish-style installer package? Look for a *.dist file
168	std::string distFile = findDistFile(this->resourcesRootPath());
169	if (!distFile.empty()) {
170		mMainExecutableURL = makeCFURL(distFile);
171		mExecRep = new FileDiskRep(this->mainExecutablePath().c_str());
172		if (!mExecRep->fd().isPlainFile(this->mainExecutablePath()))
173			recordStrictError(errSecCSRegularFile);
174		mInstallerPackage = true;
175		mFormat = "installer package bundle";
176		return;
177	}
178
179	// this bundle cannot be signed
180	MacOSError::throwMe(errSecCSBadBundleFormat);
181}
182
183
184//
185// Return the full path to the one-and-only file named something.dist in a directory.
186// Return empty string if none; throw an exception if multiple. Do not descend into subdirectories.
187//
188static std::string findDistFile(const std::string &directory)
189{
190	std::string found;
191	char *paths[] = {(char *)directory.c_str(), NULL};
192	FTS *fts = fts_open(paths, FTS_PHYSICAL | FTS_NOCHDIR | FTS_NOSTAT, NULL);
193	bool root = true;
194	while (FTSENT *ent = fts_read(fts)) {
195		switch (ent->fts_info) {
196		case FTS_F:
197		case FTS_NSOK:
198			if (!strcmp(ent->fts_path + ent->fts_pathlen - 5, ".dist")) {	// found plain file foo.dist
199				if (found.empty())	// first found
200					found = ent->fts_path;
201				else				// multiple *.dist files (bad)
202					MacOSError::throwMe(errSecCSBadBundleFormat);
203			}
204			break;
205		case FTS_D:
206			if (!root)
207				fts_set(fts, ent, FTS_SKIP);	// don't descend
208			root = false;
209			break;
210		default:
211			break;
212		}
213	}
214	fts_close(fts);
215	return found;
216}
217
218
219//
220// Create a path to a bundle signing resource, by name.
221// If the BUNDLEDISKREP_DIRECTORY directory exists in the bundle's support directory, files
222// will be read and written there. Otherwise, they go directly into the support directory.
223//
224string BundleDiskRep::metaPath(const char *name)
225{
226	if (mMetaPath.empty()) {
227		string support = cfStringRelease(CFBundleCopySupportFilesDirectoryURL(mBundle));
228		mMetaPath = support + "/" BUNDLEDISKREP_DIRECTORY;
229		if (::access(mMetaPath.c_str(), F_OK) == 0) {
230			mMetaExists = true;
231		} else {
232			mMetaPath = support;
233			mMetaExists = false;
234		}
235	}
236	return mMetaPath + "/" + name;
237}
238
239
240//
241// Try to create the meta-file directory in our bundle.
242// Does nothing if the directory already exists.
243// Throws if an error occurs.
244//
245void BundleDiskRep::createMeta()
246{
247	string meta = metaPath(BUNDLEDISKREP_DIRECTORY);
248	if (!mMetaExists) {
249		if (::mkdir(meta.c_str(), 0755) == 0) {
250			copyfile(cfStringRelease(copyCanonicalPath()).c_str(), meta.c_str(), NULL, COPYFILE_SECURITY);
251			mMetaPath = meta;
252			mMetaExists = true;
253		} else if (errno != EEXIST)
254			UnixError::throwMe();
255	}
256}
257
258//
259// Load's a CFURL and makes sure that it is a regular file and not a symlink (or fifo, etc.)
260//
261CFDataRef BundleDiskRep::loadRegularFile(CFURLRef url)
262{
263	assert(url);
264
265	CFDataRef data = NULL;
266
267	std::string path(cfString(url));
268
269	AutoFileDesc fd(path);
270
271	if (!fd.isPlainFile(path))
272		recordStrictError(errSecCSRegularFile);
273
274	data = cfLoadFile(fd, fd.fileSize());
275
276	if (!data) {
277		secdebug(__PRETTY_FUNCTION__, "failed to load %s", cfString(url).c_str());
278		MacOSError::throwMe(errSecCSInternalError);
279	}
280
281	return data;
282}
283
284//
285// Load and return a component, by slot number.
286// Info.plist components come from the bundle, always (we don't look
287// for Mach-O embedded versions).
288// Everything else comes from the embedded blobs of a Mach-O image, or from
289// files located in the Contents directory of the bundle.
290//
291CFDataRef BundleDiskRep::component(CodeDirectory::SpecialSlot slot)
292{
293	switch (slot) {
294	// the Info.plist comes from the magic CFBundle-indicated place and ONLY from there
295	case cdInfoSlot:
296		if (CFRef<CFURLRef> info = _CFBundleCopyInfoPlistURL(mBundle))
297			return loadRegularFile(info);
298		else
299			return NULL;
300	// by default, we take components from the executable image or files
301	default:
302		if (CFDataRef data = mExecRep->component(slot))
303			return data;
304		// falling through
305	// but the following always come from files
306	case cdResourceDirSlot:
307		if (const char *name = CodeDirectory::canonicalSlotName(slot))
308			return metaData(name);
309		else
310			return NULL;
311	}
312}
313
314
315//
316// The binary identifier is taken directly from the main executable.
317//
318CFDataRef BundleDiskRep::identification()
319{
320	return mExecRep->identification();
321}
322
323
324//
325// Various aspects of our DiskRep personality.
326//
327CFURLRef BundleDiskRep::copyCanonicalPath()
328{
329	if (CFURLRef url = CFBundleCopyBundleURL(mBundle))
330		return url;
331	CFError::throwMe();
332}
333
334string BundleDiskRep::mainExecutablePath()
335{
336	return cfString(mMainExecutableURL);
337}
338
339string BundleDiskRep::resourcesRootPath()
340{
341	return cfStringRelease(CFBundleCopySupportFilesDirectoryURL(mBundle));
342}
343
344void BundleDiskRep::adjustResources(ResourceBuilder &builder)
345{
346	// exclude entire contents of meta directory
347	builder.addExclusion("^" BUNDLEDISKREP_DIRECTORY "$");
348	builder.addExclusion("^" CODERESOURCES_LINK "$");	// ancient-ish symlink into it
349
350	// exclude the store manifest directory
351	builder.addExclusion("^" STORE_RECEIPT_DIRECTORY "$");
352
353	// exclude the main executable file
354	string resources = resourcesRootPath();
355	if (resources.compare(resources.size() - 2, 2, "/.") == 0)	// chop trailing /.
356		resources = resources.substr(0, resources.size()-2);
357	string executable = mainExecutablePath();
358	if (!executable.compare(0, resources.length(), resources, 0, resources.length())
359		&& executable[resources.length()] == '/')	// is proper directory prefix
360		builder.addExclusion(string("^")
361			+ ResourceBuilder::escapeRE(executable.substr(resources.length()+1)) + "$");
362}
363
364
365
366Universal *BundleDiskRep::mainExecutableImage()
367{
368	return mExecRep->mainExecutableImage();
369}
370
371size_t BundleDiskRep::signingBase()
372{
373	return mExecRep->signingBase();
374}
375
376size_t BundleDiskRep::signingLimit()
377{
378	return mExecRep->signingLimit();
379}
380
381string BundleDiskRep::format()
382{
383	return mFormat;
384}
385
386CFArrayRef BundleDiskRep::modifiedFiles()
387{
388	CFMutableArrayRef files = CFArrayCreateMutableCopy(NULL, 0, mExecRep->modifiedFiles());
389	checkModifiedFile(files, cdCodeDirectorySlot);
390	checkModifiedFile(files, cdSignatureSlot);
391	checkModifiedFile(files, cdResourceDirSlot);
392	checkModifiedFile(files, cdEntitlementSlot);
393	return files;
394}
395
396void BundleDiskRep::checkModifiedFile(CFMutableArrayRef files, CodeDirectory::SpecialSlot slot)
397{
398	if (CFDataRef data = mExecRep->component(slot))	// provided by executable file
399		CFRelease(data);
400	else if (const char *resourceName = CodeDirectory::canonicalSlotName(slot)) {
401		string file = metaPath(resourceName);
402		if (::access(file.c_str(), F_OK) == 0)
403			CFArrayAppendValue(files, CFTempURL(file));
404	}
405}
406
407FileDesc &BundleDiskRep::fd()
408{
409	return mExecRep->fd();
410}
411
412void BundleDiskRep::flush()
413{
414	mExecRep->flush();
415}
416
417
418//
419// Defaults for signing operations
420//
421string BundleDiskRep::recommendedIdentifier(const SigningContext &)
422{
423	if (CFStringRef identifier = CFBundleGetIdentifier(mBundle))
424		return cfString(identifier);
425	if (CFDictionaryRef infoDict = CFBundleGetInfoDictionary(mBundle))
426		if (CFStringRef identifier = CFStringRef(CFDictionaryGetValue(infoDict, kCFBundleNameKey)))
427			return cfString(identifier);
428
429	// fall back to using the canonical path
430	return canonicalIdentifier(cfStringRelease(this->copyCanonicalPath()));
431}
432
433string BundleDiskRep::resourcesRelativePath()
434{
435	// figure out the resource directory base. Clean up some gunk inserted by CFBundle in frameworks
436	string rbase = this->resourcesRootPath();
437	size_t pos = rbase.find("/./");	// gratuitously inserted by CFBundle in some frameworks
438	while (pos != std::string::npos) {
439		rbase = rbase.replace(pos, 2, "", 0);
440		pos = rbase.find("/./");
441	}
442	if (rbase.substr(rbase.length()-2, 2) == "/.")	// produced by versioned bundle implicit "Current" case
443		rbase = rbase.substr(0, rbase.length()-2);	// ... so take it off for this
444
445	// find the resources directory relative to the resource base
446	string resources = cfStringRelease(CFBundleCopyResourcesDirectoryURL(mBundle));
447	if (resources == rbase)
448		resources = "";
449	else if (resources.compare(0, rbase.length(), rbase, 0, rbase.length()) != 0)	// Resources not in resource root
450		MacOSError::throwMe(errSecCSBadBundleFormat);
451	else
452		resources = resources.substr(rbase.length() + 1) + "/";	// differential path segment
453
454	return resources;
455}
456
457CFDictionaryRef BundleDiskRep::defaultResourceRules(const SigningContext &ctx)
458{
459	string resources = this->resourcesRelativePath();
460
461	// installer package rules
462	if (mInstallerPackage)
463		return cfmake<CFDictionaryRef>("{rules={"
464			"'^.*' = #T"							// include everything, but...
465			"%s = {optional=#T, weight=1000}"		// make localizations optional
466			"'^.*/.*\\.pkg/' = {omit=#T, weight=10000}" // and exclude all nested packages (by name)
467			"}}",
468			(string("^") + resources + ".*\\.lproj/").c_str()
469		);
470
471	// old (V1) executable bundle rules - compatible with before
472	if (ctx.signingFlags() & kSecCSSignV1)				// *** must be exactly the same as before ***
473		return cfmake<CFDictionaryRef>("{rules={"
474			"'^version.plist$' = #T"                    // include version.plist
475			"%s = #T"                                   // include Resources
476			"%s = {optional=#T, weight=1000}"           // make localizations optional
477			"%s = {omit=#T, weight=1100}"               // exclude all locversion.plist files
478			"}}",
479			(string("^") + resources).c_str(),
480			(string("^") + resources + ".*\\.lproj/").c_str(),
481			(string("^") + resources + ".*\\.lproj/locversion.plist$").c_str()
482		);
483
484	// FMJ (everything is a resource) rules
485	if (ctx.signingFlags() & kSecCSSignOpaque)			// Full Metal Jacket - everything is a resource file
486		return cfmake<CFDictionaryRef>("{rules={"
487			"'^.*' = #T"								// everything is a resource
488			"'^Info\\.plist$' = {omit=#T,weight=10}"	// explicitly exclude this for backward compatibility
489		"}}");
490
491	// new (V2) executable bundle rules
492	return cfmake<CFDictionaryRef>("{"					// *** the new (V2) world ***
493		"rules={"										// old (V1; legacy) version
494			"'^version.plist$' = #T"					// include version.plist
495			"%s = #T"									// include Resources
496			"%s = {optional=#T, weight=1000}"			// make localizations optional
497			"%s = {omit=#T, weight=1100}"				// exclude all locversion.plist files
498		"},rules2={"
499			"'^.*' = #T"								// include everything as a resource, with the following exceptions
500			"'^[^/]+$' = {nested=#T, weight=10}"		// files directly in Contents
501			"'^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/' = {nested=#T, weight=10}" // dynamic repositories
502			"'.*\\.dSYM($|/)' = {weight=11}"			// but allow dSYM directories in code locations (parallel to their code)
503			"'^(.*/)?\\.DS_Store$' = {omit=#T,weight=2000}"	// ignore .DS_Store files
504			"'^Info\\.plist$' = {omit=#T, weight=20}"	// excluded automatically now, but old systems need to be told
505			"'^version\\.plist$' = {weight=20}"			// include version.plist as resource
506			"'^embedded\\.provisionprofile$' = {weight=20}"	// include embedded.provisionprofile as resource
507			"'^PkgInfo$' = {omit=#T, weight=20}"		// traditionally not included
508			"%s = {weight=20}"							// Resources override default nested (widgets)
509			"%s = {optional=#T, weight=1000}"			// make localizations optional
510			"%s = {omit=#T, weight=1100}"				// exclude all locversion.plist files
511		"}}",
512
513		(string("^") + resources).c_str(),
514		(string("^") + resources + ".*\\.lproj/").c_str(),
515		(string("^") + resources + ".*\\.lproj/locversion.plist$").c_str(),
516
517		(string("^") + resources).c_str(),
518		(string("^") + resources + ".*\\.lproj/").c_str(),
519		(string("^") + resources + ".*\\.lproj/locversion.plist$").c_str()
520	);
521}
522
523
524CFArrayRef BundleDiskRep::allowedResourceOmissions()
525{
526	return cfmake<CFArrayRef>("["
527		"'^(.*/)?\\.DS_Store$'"
528		"'^Info\\.plist$'"
529		"'^PkgInfo$'"
530		"%s"
531		"]",
532		(string("^") + this->resourcesRelativePath() + ".*\\.lproj/locversion.plist$").c_str()
533	);
534}
535
536
537const Requirements *BundleDiskRep::defaultRequirements(const Architecture *arch, const SigningContext &ctx)
538{
539	return mExecRep->defaultRequirements(arch, ctx);
540}
541
542size_t BundleDiskRep::pageSize(const SigningContext &ctx)
543{
544	return mExecRep->pageSize(ctx);
545}
546
547
548//
549// Strict validation.
550// Takes an array of CFNumbers of errors to tolerate.
551//
552void BundleDiskRep::strictValidate(const ToleratedErrors& tolerated)
553{
554	std::vector<OSStatus> fatalErrors;
555	set_difference(mStrictErrors.begin(), mStrictErrors.end(), tolerated.begin(), tolerated.end(), back_inserter(fatalErrors));
556	if (!fatalErrors.empty())
557		MacOSError::throwMe(fatalErrors[0]);
558	mExecRep->strictValidate(tolerated);
559}
560
561void BundleDiskRep::recordStrictError(OSStatus error)
562{
563	mStrictErrors.insert(error);
564}
565
566
567//
568// Check framework root for unsafe symlinks and unsealed content.
569//
570void BundleDiskRep::validateFrameworkRoot(string root)
571{
572	// build regex element that matches either the "Current" symlink, or the name of the current version
573	string current = "Current";
574	char currentVersion[PATH_MAX];
575	ssize_t len = ::readlink((root + "/Versions/Current").c_str(), currentVersion, sizeof(currentVersion)-1);
576	if (len > 0) {
577		currentVersion[len] = '\0';
578		current = string("(Current|") + ResourceBuilder::escapeRE(currentVersion) + ")";
579	}
580
581	DirValidator val;
582	val.require("^Versions$", DirValidator::directory | DirValidator::descend);	// descend into Versions directory
583	val.require("^Versions/[^/]+$", DirValidator::directory);					// require at least one version
584	val.require("^Versions/Current$", DirValidator::symlink,					// require Current symlink...
585		"^(\\./)?(\\.\\.[^/]+|\\.?[^\\./][^/]*)$");								// ...must point to a version
586	val.allow("^(Versions/)?\\.DS_Store$", DirValidator::file | DirValidator::noexec); // allow .DS_Store files
587	val.allow("^[^/]+$", DirValidator::symlink, ^ string (const string &name, const string &target) {
588		// top-level symlinks must point to namesake in current version
589		return string("^(\\./)?Versions/") + current + "/" + ResourceBuilder::escapeRE(name) + "$";
590	});
591	// module.map must be regular non-executable file, or symlink to module.map in current version
592	val.allow("^module\\.map$", DirValidator::file | DirValidator::noexec | DirValidator::symlink,
593		string("^(\\./)?Versions/") + current + "/module\\.map$");
594
595	try {
596		val.validate(root, errSecCSUnsealedFrameworkRoot);
597	} catch (const MacOSError &err) {
598		recordStrictError(err.error);
599	}
600}
601
602
603//
604// Writers
605//
606DiskRep::Writer *BundleDiskRep::writer()
607{
608	return new Writer(this);
609}
610
611BundleDiskRep::Writer::Writer(BundleDiskRep *r)
612	: rep(r), mMadeMetaDirectory(false)
613{
614	execWriter = rep->mExecRep->writer();
615}
616
617
618//
619// Write a component.
620// Note that this isn't concerned with Mach-O writing; this is handled at
621// a much higher level. If we're called, we write to a file in the Bundle's meta directory.
622//
623void BundleDiskRep::Writer::component(CodeDirectory::SpecialSlot slot, CFDataRef data)
624{
625	switch (slot) {
626	default:
627		if (!execWriter->attribute(writerLastResort))	// willing to take the data...
628			return execWriter->component(slot, data);	// ... so hand it through
629		// execWriter doesn't want the data; store it as a resource file (below)
630	case cdResourceDirSlot:
631		// the resource directory always goes into a bundle file
632		if (const char *name = CodeDirectory::canonicalSlotName(slot)) {
633			rep->createMeta();
634			string path = rep->metaPath(name);
635			AutoFileDesc fd(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
636			fd.writeAll(CFDataGetBytePtr(data), CFDataGetLength(data));
637		} else
638			MacOSError::throwMe(errSecCSBadBundleFormat);
639	}
640}
641
642
643//
644// Remove all signature data
645//
646void BundleDiskRep::Writer::remove()
647{
648	// remove signature from the executable
649	execWriter->remove();
650
651	// remove signature files from bundle
652	for (CodeDirectory::SpecialSlot slot = 0; slot < cdSlotCount; slot++)
653		remove(slot);
654	remove(cdSignatureSlot);
655}
656
657void BundleDiskRep::Writer::remove(CodeDirectory::SpecialSlot slot)
658{
659	if (const char *name = CodeDirectory::canonicalSlotName(slot))
660		if (::unlink(rep->metaPath(name).c_str()))
661			switch (errno) {
662			case ENOENT:		// not found - that's okay
663				break;
664			default:
665				UnixError::throwMe();
666			}
667}
668
669
670void BundleDiskRep::Writer::flush()
671{
672	execWriter->flush();
673}
674
675
676} // end namespace CodeSigning
677} // end namespace Security
678