1/*
2 * Copyright (c) 2012-2013 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//
25//  SecFileLocations.c
26//  utilities
27//
28
29/*
30    This file incorporates code from securityd_files.c (iOS) and iOSforOSX.c (OSX).
31 */
32
33#include <TargetConditionals.h>
34#include <AssertMacros.h>
35#include <CoreFoundation/CFPriv.h>
36#include <CoreFoundation/CFString.h>
37#include <CoreFoundation/CFURL.h>
38#include <CoreFoundation/CFUtilities.h>
39#include <utilities/SecCFWrappers.h>
40#include <utilities/SecCFRelease.h>
41#include <sys/stat.h>
42#include <uuid/uuid.h>
43#include <copyfile.h>
44
45#include "SecFileLocations.h"
46
47static CFURLRef sCustomHomeURL = NULL;
48
49static CFURLRef SecCopyHomeURL(void)
50{
51    // This returns a CFURLRef so that it can be passed as the second parameter
52    // to CFURLCreateCopyAppendingPathComponent
53
54    CFURLRef homeURL = sCustomHomeURL;
55    if (homeURL) {
56        CFRetain(homeURL);
57    } else {
58#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE))
59        // We would use CFCopyHomeDirectoryURL but it doesn't exist on MACOS.
60        // This does the same.
61        homeURL = CFCopyHomeDirectoryURLForUser(NULL);
62#else
63        homeURL = CFCopyHomeDirectoryURL();
64#endif
65    }
66
67    return homeURL;
68}
69
70#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE))
71static const char * get_host_uuid()
72{
73    static uuid_string_t hostuuid = {};
74    static dispatch_once_t onceToken;
75    dispatch_once(&onceToken, ^{
76        struct timespec timeout = {30, 0};
77        uuid_t uuid = {};
78        if (gethostuuid(uuid, &timeout) == 0) {
79            uuid_unparse(uuid, hostuuid);
80        } else {
81            secerror("failed to get host uuid");
82        }
83    });
84
85    return hostuuid;
86}
87
88static CFStringRef copy_keychain_uuid_path(CFURLRef keyChainBaseURL)
89{
90    CFStringRef baseURLString = NULL;
91    CFStringRef uuid_path = NULL;
92
93    require(keyChainBaseURL, done);
94
95    baseURLString = CFURLCopyFileSystemPath(keyChainBaseURL, kCFURLPOSIXPathStyle);
96    require(baseURLString, done);
97
98    uuid_path = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@/%s"), baseURLString, get_host_uuid());
99
100done:
101    CFReleaseSafe(baseURLString);
102    return uuid_path;
103}
104
105// See _kb_verify_create_path in securityd
106static bool keychain_verify_create_path(const char *keychainBasePath)
107{
108    bool created = false;
109    struct stat st_info = {};
110    char new_path[PATH_MAX] = {};
111    char kb_path[PATH_MAX] = {};
112    snprintf(kb_path, sizeof(kb_path), "%s", keychainBasePath);
113    if (lstat(kb_path, &st_info) == 0) {
114        if (S_ISDIR(st_info.st_mode)) {
115            created = true;
116        } else {
117            secerror("invalid directory at '%s' moving aside", kb_path);
118            snprintf(new_path, sizeof(new_path), "%s-invalid", kb_path);
119            unlink(new_path);
120            if (rename(kb_path, new_path) != 0) {
121                secerror("failed to rename file: %s (%s)", kb_path, strerror(errno));
122                goto done;
123            }
124        }
125    }
126    if (!created) {
127        require_action(mkpath_np(kb_path, 0700) == 0, done, secerror("could not create path: %s (%s)", kb_path, strerror(errno)));
128        created = true;
129    }
130
131done:
132    return created;
133}
134#endif /*(TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) */
135
136static CFURLRef SecCopyBaseFilesURL()
137{
138    CFURLRef baseURL = sCustomHomeURL;
139    if (baseURL) {
140        CFRetain(baseURL);
141    } else {
142#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED))
143        baseURL = SecCopyHomeURL();
144#else
145        baseURL = CFURLCreateWithFileSystemPath(NULL, CFSTR("/"), kCFURLPOSIXPathStyle, true);
146#endif
147    }
148    return baseURL;
149}
150
151static CFURLRef SecCopyURLForFileInBaseDirectory(CFStringRef directoryPath, CFStringRef fileName)
152{
153    CFURLRef fileURL = NULL;
154    CFStringRef suffix = NULL;
155    CFURLRef homeURL = SecCopyBaseFilesURL();
156
157    if (fileName)
158        suffix = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@/%@"), directoryPath, fileName);
159    else
160    if (directoryPath)
161        suffix = CFStringCreateCopy(kCFAllocatorDefault, directoryPath);
162
163    if (homeURL && suffix)
164        fileURL = CFURLCreateCopyAppendingPathComponent(kCFAllocatorDefault, homeURL, suffix, false);
165    CFReleaseSafe(suffix);
166    CFReleaseSafe(homeURL);
167    return fileURL;
168}
169
170CFURLRef SecCopyURLForFileInKeychainDirectory(CFStringRef fileName)
171{
172#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE))
173    // need to tack on uuid here
174    Boolean isDirectory = (fileName == NULL);
175    CFURLRef resultURL = NULL;
176    CFStringRef resultStr = NULL;
177    __block bool directoryExists = false;
178
179    CFURLRef keyChainBaseURL = SecCopyURLForFileInBaseDirectory(CFSTR("Library/Keychains"), NULL);
180    CFStringRef uuid_path = copy_keychain_uuid_path(keyChainBaseURL);
181    CFStringPerformWithCString(uuid_path, ^(const char *utf8Str) {
182        directoryExists = keychain_verify_create_path(utf8Str);
183    });
184    require(directoryExists, done);
185    if (fileName)
186        resultStr = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@/%@"), uuid_path, fileName);
187    else
188        resultStr = CFStringCreateCopy(kCFAllocatorDefault, uuid_path);
189
190done:
191    CFReleaseSafe(uuid_path);
192    CFReleaseSafe(keyChainBaseURL);
193    if (resultStr)
194    {
195        resultURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, resultStr, kCFURLPOSIXPathStyle, isDirectory);
196        CFRelease(resultStr);
197    }
198    return resultURL;
199#else
200    return SecCopyURLForFileInBaseDirectory(CFSTR("Library/Keychains"), fileName);
201#endif
202}
203
204CFURLRef SecCopyURLForFileInPreferencesDirectory(CFStringRef fileName)
205{
206    return SecCopyURLForFileInBaseDirectory(CFSTR("Library/Preferences"), fileName);
207}
208
209void WithPathInKeychainDirectory(CFStringRef fileName, void(^operation)(const char *utf8String))
210{
211    CFURLRef fileURL = SecCopyURLForFileInKeychainDirectory(fileName);
212    UInt8 buffer[MAXPATHLEN];
213    CFURLGetFileSystemRepresentation(fileURL, false, buffer, sizeof(buffer));
214
215    operation((const char*)buffer);
216    CFRelease(fileURL);
217}
218
219void SetCustomHomeURLString(CFStringRef home_path)
220{
221    CFReleaseNull(sCustomHomeURL);
222    if (home_path) {
223        sCustomHomeURL = CFURLCreateWithFileSystemPath(NULL, home_path, kCFURLPOSIXPathStyle, true);
224    }
225}
226
227void SetCustomHomeURL(const char* path)
228{
229    if (path) {
230        CFStringRef path_cf = CFStringCreateWithCStringNoCopy(NULL, path, kCFStringEncodingUTF8, kCFAllocatorNull);
231        SetCustomHomeURLString(path_cf);
232        CFReleaseSafe(path_cf);
233    } else {
234        SetCustomHomeURLString(NULL);
235    }
236}
237
238
239