1/*
2 * Copyright (c) 2004-2006,2008 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//
26// tokencache - persistent (on-disk) hardware token directory
27//
28// Here's the basic disk layout, rooted at /var/db/TokenCache (or $TOKENCACHE):
29//  TBA
30//
31#include "tokencache.h"
32#include <security_utilities/unix++.h>
33#include <pwd.h>
34#include <grp.h>
35
36using namespace UnixPlusPlus;
37
38
39//
40// Here are the uid/gid values we assign to token daemons and their cache files
41//
42#define TOKEND_UID			"tokend"
43#define TOKEND_GID			"tokend"
44#define TOKEND_UID_FALLBACK	uid_t(-2)
45#define TOKEND_GID_FALLBACK	gid_t(-2)
46
47
48//
49// Fixed relative file paths
50//
51
52// relative to cache root (use cache->path())
53static const char configDir[] = "config";
54static const char lastSSIDFile[] = "config/lastSSID";
55static const char tokensDir[] = "tokens";
56
57// relative to token directory (use token->path())
58static const char ssidFile[] = "SSID";
59static const char workDir[] = "work";
60static const char cacheDir[] = "cache";
61
62
63//
64// Internal file I/O helpers. These read/write entire files.
65// Note that the defaulted read functions do NOT write the default
66// to disk; they work fine in read-only disk areas.
67//
68static unsigned long getFile(const string &path, unsigned long defaultValue)
69{
70	try {
71		AutoFileDesc fd(path, O_RDONLY, FileDesc::modeMissingOk);
72		if (fd) {
73			string s; fd.readAll(s);
74			unsigned long value; sscanf(s.c_str(), "%lu", &value);
75			return value;
76		}
77	} catch (...) {
78	}
79	return defaultValue;
80}
81
82static string getFile(const string &path, const string &defaultValue)
83{
84	try {
85		AutoFileDesc fd(path, O_RDONLY, FileDesc::modeMissingOk);
86		if (fd) {
87			string s; fd.readAll(s);
88			return s;
89		}
90	} catch (...) {
91	}
92	return defaultValue;
93}
94
95
96static void putFile(const string &path, uint32 value)
97{
98	char buffer[64];
99	snprintf(buffer, sizeof(buffer), "%ld\n", value);
100	AutoFileDesc(path, O_WRONLY | O_CREAT | O_TRUNC).writeAll(buffer);
101}
102
103static void putFile(const string &path, const string &value)
104{
105	AutoFileDesc(path, O_WRONLY | O_CREAT | O_TRUNC).writeAll(value);
106}
107
108
109//
110// The "rooted tree" utility class
111//
112void Rooted::root(const string &r)
113{
114	assert(mRoot.empty());	// can't re-set this
115	mRoot = r;
116}
117
118string Rooted::path(const char *sub) const
119{
120	if (sub == NULL)
121		return mRoot;
122	return mRoot + "/" + sub;
123}
124
125
126//
127// Open a TokenCache.
128// If the cache does not exist at the path given, initialize it.
129// If that fails, throw an exception.
130//
131TokenCache::TokenCache(const char *where)
132	: Rooted(where), mLastSubservice(0)
133{
134	makedir(root(), O_CREAT, 0711, securityd);
135	makedir(path(configDir), O_CREAT, 0700, securityd);
136	makedir(path(tokensDir), O_CREAT, 0711, securityd);
137
138	mLastSubservice = getFile(path(lastSSIDFile), 1);
139
140	// identify uid/gid for token daemons
141	struct passwd *pw = getpwnam(TOKEND_UID);
142	mTokendUid = pw ? pw->pw_uid : TOKEND_UID_FALLBACK;
143	struct group *gr = getgrnam(TOKEND_GID);
144	mTokendGid = gr ? gr->gr_gid : TOKEND_GID_FALLBACK;
145
146	secdebug("tokencache", "token cache rooted at %s (last ssid=%ld, uid/gid=%d/%d)",
147		root().c_str(), mLastSubservice, mTokendUid, mTokendGid);
148}
149
150TokenCache::~TokenCache()
151{
152}
153
154
155//
156// Get a new, unused subservice id number.
157// Update the tracking file so we won't hand it out again (ever) within this cache.
158//
159uint32 TokenCache::allocateSubservice()
160{
161	putFile(path(lastSSIDFile), ++mLastSubservice);
162	return mLastSubservice;
163}
164
165
166//
167// A slightly souped-up UnixPlusPlus::makedir
168//
169void TokenCache::makedir(const char *path, int flags, mode_t mode, Owner owner)
170{
171	UnixPlusPlus::makedir(path, flags, mode);
172	switch(owner) {
173	case securityd:
174		// leave it alone; we own it alrady
175		break;
176	case tokend:
177		::chown(path, tokendUid(), tokendGid());
178		break;
179	}
180}
181
182
183//
184// Make a cache entry from a valid tokenUid.
185// This will locate an existing entry or make a new one.
186//
187TokenCache::Token::Token(TokenCache &c, const string &tokenUid)
188	: Rooted(c.path(string(tokensDir) + "/" + tokenUid)), cache(c)
189{
190	cache.makedir(root(), O_CREAT, 0711, securityd);
191	if (mSubservice = getFile(path(ssidFile), 0)) {
192		secdebug("tokencache", "found token \"%s\" ssid=%ld", tokenUid.c_str(), mSubservice);
193		init(existing);
194	} else {
195		mSubservice = cache.allocateSubservice();   // allocate new, unique ssid...
196		putFile(path(ssidFile), mSubservice);			// ... and save it in cache
197		secdebug("tokencache", "new token \"%s\" ssid=%ld", tokenUid.c_str(), mSubservice);
198		init(created);
199	}
200}
201
202
203//
204// Make a cache entry that is temporary and will never be reused.
205//
206TokenCache::Token::Token(TokenCache &c)
207	: cache(c)
208{
209	mSubservice = cache.allocateSubservice();	// new, unique id
210	char rootForm[30]; snprintf(rootForm, sizeof(rootForm),
211		"%s/temporary:%ld", tokensDir, mSubservice);
212	root(cache.path(rootForm));
213	cache.makedir(root(), O_CREAT | O_EXCL, 0711, securityd);
214	putFile(path(ssidFile), mSubservice);			// ... and save it in cache
215	secdebug("tokencache", "temporary token \"%s\" ssid=%ld", rootForm, mSubservice);
216	init(temporary);
217}
218
219
220//
221// Common constructor setup code
222//
223void TokenCache::Token::init(Type type)
224{
225	mType = type;
226	cache.makedir(workPath(), O_CREAT, 0700, tokend);
227	cache.makedir(cachePath(), O_CREAT, 0700, tokend);
228}
229
230
231//
232// The Token destructor might clean or preen a bit, but shouldn't take
233// too long (or too much effort).
234//
235TokenCache::Token::~Token()
236{
237	if (type() == temporary)
238		secdebug("tokencache", "@@@ should delete the cache directory here...");
239}
240
241
242//
243// Attributes of TokenCache::Tokens
244//
245string TokenCache::Token::workPath() const
246{
247	return path("Work");
248}
249
250string TokenCache::Token::cachePath() const
251{
252	return path("Cache");
253}
254
255
256string TokenCache::Token::printName() const
257{
258	return getFile(path("PrintName"), "");
259}
260
261void TokenCache::Token::printName(const string &name)
262{
263	putFile(path("PrintName"), name);
264}
265