1/*
2 * Copyright (c) 2004 Apple Computer, 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// tokend - internal tracker for a tokend smartcard driver process
27//
28#include "tokend.h"
29#include <security_utilities/logging.h>
30
31
32//
33// Construct a TokenDaemon.
34// This will (try to) execute the actual tokend at 'path'; it will not communicate
35// with it beyond the standard securityd checkin mechanism.
36// The constructor will return if the tokend is either checked in and ready, or
37// it has died (or been unable to start at all). It's then our owner's responsibility
38// to manage us from there, including deleting us eventually.
39//
40TokenDaemon::TokenDaemon(RefPointer<Bundle> code,
41		const string &reader, const PCSC::ReaderState &readerState, TokenCache &cache)
42	: Tokend::ClientSession(Allocator::standard(), Allocator::standard()),
43	  mMe(code), mReaderName(reader), mState(readerState),
44	  mFaultRelay(NULL), mFaulted(false), mProbed(false),
45	  mUid(cache.tokendUid()), mGid(cache.tokendGid())
46{
47	this->fork();
48	switch (ServerChild::state()) {
49	case alive:
50		Tokend::ClientSession::servicePort(ServerChild::servicePort());
51		secdebug("tokend", "%p (pid %d) %s has launched", this, pid(), bundlePath().c_str());
52		break;
53	case dead:
54		// tokend died or quit before becoming ready
55		secdebug("tokend", "%p (pid %d) %s failed on startup", this, pid(), bundlePath().c_str());
56		break;
57	default:
58		assert(false);
59	}
60}
61
62
63//
64// The destructor for TokenDaemon *may* be called with tokend still alive.
65// We rely on ServerChild's destructor to kill it for us.
66// If we wanted to do something especally nice just for tokend (such as sending
67// a "go die" message), we'd do it here.
68//
69TokenDaemon::~TokenDaemon()
70{
71	secdebug("tokend", "%p (pid %d) %s is being destroyed", this, pid(), bundlePath().c_str());
72}
73
74
75//
76// Calculate a tokenUid as a concatenation of tokend identifier and uid
77//
78std::string TokenDaemon::tokenUid() const
79{
80	assert(hasTokenUid());
81	return mTokenUid;
82}
83
84
85//
86// Access to custom Info.plist fields
87//
88uint32 TokenDaemon::maxScore() const
89{
90	return cfNumber(CFNumberRef(mMe->infoPlistItem("TokendBestScore")), INT_MAX);
91}
92
93
94//
95// Our childAction is to launch tokend after preparing its environment
96//
97void TokenDaemon::childAction()
98{
99
100	// permanently relinquish high privilege
101#if defined(NDEBUG)
102	UnixError::check(::setgid(mGid));
103	UnixError::check(::setuid(mUid));
104#else //NDEBUG
105	// best effort, okay if not
106	::setgid(mGid);
107	::setuid(mUid);
108#endif //NDEBUG
109	secdebug("tokend", "uid=%d gid=%d", getuid(), getgid());
110
111	// go run the tokend
112	char protocol[20]; snprintf(protocol, sizeof(protocol), "%d", TDPROTOVERSION);
113	secdebug("tokend", "executing %s(\"%s\",%s)",
114		mMe->executablePath().c_str(), mReaderName.c_str(), protocol);
115	execl(mMe->executablePath().c_str(),
116		mMe->executablePath().c_str(),
117		protocol,									// #1: protocol version
118		mReaderName.c_str(),						// #2: reader name
119		CssmData::wrap(mState).toHex().c_str(),		// #3: PCSC reader state (hex)
120		NULL);
121}
122
123
124//
125// This will be called (by the UnixChild layer) when UNIX tells us that our tokend
126// has died. That means it's quite dead (a Zombie) already.
127//
128void TokenDaemon::dying()
129{
130	ServerChild::dying();					// honor prior engagement
131	fault(true, "token daemon has died");	// flag asynchronous fault
132}
133
134
135//
136// Declare a fault.
137//@@@ Semantics TBD.
138//
139void TokenDaemon::fault(bool async, const char *reason)
140{
141	if (!mFaulted) {
142		secdebug("tokend", "%p declaring %s FAULT condition: %s",
143			this, async ? "ASYNCHRONOUS" : "SYNCHRONOUS", reason);
144		Syslog::notice("card in reader %s has faulted (%s)",
145			mReaderName.c_str(), reason);
146		mFaulted = true;
147		if (mFaultRelay)
148			mFaultRelay->relayFault(async);
149	}
150	if (!async)
151		CssmError::throwMe(CSSM_ERRCODE_FUNCTION_FAILED);
152}
153
154
155//
156// A fault signalled from the ClientSession layer is just a (synchronous) fault
157// of TokenDaemon itself.
158//
159void TokenDaemon::fault()
160{
161	this->fault(false, "tokend service failed");
162}
163
164
165//
166// Overridden Tokend::ClientSession methods (to siphon off some return data).
167// Note that this does NOT include the Access magic; you still have to use
168// TokenDaemon::Access to mediate the call.
169//
170bool TokenDaemon::probe()
171{
172	secdebug("tokend", "%p probing", this);
173	ClientSession::probe(mScore, mTokenUid);
174	secdebug("tokend", "%p probed score=%d tokenUid=\"%s\"", this, mScore, mTokenUid.c_str());
175	mProbed = true;
176	return mScore > 0;
177}
178
179
180//
181// FaultRelay
182//
183FaultRelay::~FaultRelay()
184{ /* virtual */ }
185
186
187//
188// Debug dump support
189//
190#if defined(DEBUGDUMP)
191
192void TokenDaemon::dumpNode()
193{
194	PerGlobal::dumpNode();
195	if (mFaulted)
196		Debug::dump(" FAULT");
197	Debug::dump(" service=%d/%d",
198		ClientSession::servicePort().port(), ServerChild::servicePort().port());
199}
200
201#endif //DEBUGDUMP
202