1/*
2 *  agentclient.cpp
3 *  SecurityAgent
4 *
5 *  Copyright (c) 2002,2008 Apple Inc.. All rights reserved.
6 *
7 */
8
9
10#include <stdio.h>
11
12/*
13For now all the calls into agentclient will be synchronous, with timeouts
14
15On a timeout, we will return control to the client, but we really need to send the appropriate abort right there and then, otherwise they'll need to call the same method again to check that the reply still isn't there.
16
17If we receive a reply that is not confirming attempts to abort, we'll process these and return them to the caller.
18
19Alternatively, there can be an answer that isn't the answer we expected: setError, where the server aborts the transaction.
20
21We can't support interrupt() with a synchronous interface unless we add some notification that let's the client know that the "server" is dead
22*/
23
24#include <sys/types.h>
25#include <sys/wait.h>
26#include <grp.h>
27
28#include <security_agent_server/sa_reply.h> // for size of replies
29#include <security_agent_client/sa_request.h>
30
31#include <security_utilities/mach++.h>
32#include <security_cdsa_utilities/walkers.h>
33#include <security_cdsa_utilities/cssmwalkers.h>
34#include <security_cdsa_utilities/AuthorizationWalkers.h>
35#include <security_cdsa_utilities/AuthorizationData.h>
36
37#include <security_utilities/logging.h>
38
39using LowLevelMemoryUtilities::increment;
40using LowLevelMemoryUtilities::difference;
41using Security::DataWalkers::walk;
42
43using Authorization::AuthItemSet;
44using Authorization::AuthItemRef;
45using Authorization::AuthValueOverlay;
46
47#include "agentclient.h"
48
49namespace SecurityAgent {
50
51class CheckingReconstituteWalker {
52public:
53    CheckingReconstituteWalker(void *ptr, void *base, size_t size)
54    : mBase(base), mLimit(increment(base, size)), mOffset(difference(ptr, base)) { }
55
56	template <class T>
57	void operator () (T &obj, size_t size = sizeof(T))
58{ }
59
60    template <class T>
61    void operator () (T * &addr, size_t size = sizeof(T))
62{
63		blob(addr, size);
64}
65
66template <class T>
67void blob(T * &addr, size_t size)
68{
69	DEBUGWALK("checkreconst:*");
70	if (addr) {
71		if (addr < mBase || increment(addr, size) > mLimit)
72			MacOSError::throwMe(errAuthorizationInternal);
73		addr = increment<T>(addr, mOffset);
74	}
75}
76
77static const bool needsRelinking = true;
78static const bool needsSize = false;
79
80private:
81void *mBase;			// old base address
82void *mLimit;			// old last byte address + 1
83off_t mOffset;			// relocation offset
84};
85
86template <class T>
87void relocate(T *obj, T *base, size_t size)
88{
89    if (obj) {
90        CheckingReconstituteWalker w(obj, base, size);
91        walk(w, base);
92    }
93}
94
95void Client::check(mach_msg_return_t returnCode)
96{
97	// first check the Mach IPC return code
98	switch (returnCode) {
99		case MACH_MSG_SUCCESS:				// peachy
100			break;
101		case MIG_SERVER_DIED:			// explicit can't-send-it's-dead
102			CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION);
103		default:						// some random Mach error
104			MachPlusPlus::Error::throwMe(returnCode);
105	}
106}
107
108void Client::checkResult()
109{
110        // now check the OSStatus return from the server side
111        switch (result()) {
112            case kAuthorizationResultAllow: return;
113            case kAuthorizationResultDeny:
114            case kAuthorizationResultUserCanceled: CssmError::throwMe(CSSM_ERRCODE_USER_CANCELED);
115            default: MacOSError::throwMe(errAuthorizationInternal);
116        }
117}
118
119
120
121#pragma mark administrative operations
122
123Client::Client() : mState(init)
124{
125	// create reply port
126	mClientPort.allocate(); //implicit MACH_PORT_RIGHT_RECEIVE
127
128	// register with agentclients
129	Clients::gClients().insert(this);
130}
131
132void
133Client::activate(Port serverPort)
134{
135	if (!serverPort)
136		MacOSError::throwMe(errAuthorizationInternal);
137
138	secdebug("agentclient", "using server at port %d", serverPort.port());
139	mServerPort = serverPort;
140}
141
142
143Client::~Client()
144{
145    teardown();
146}
147
148
149// start/endTransaction calls stand outside the usual client protocol: they
150// don't participate in the state-management or multiplexing-by-port tangoes.
151// (These calls could take advantage of activate(), but callers would be
152// instantiating an entire Client object for the sake of mServerPort.)
153// Conversely, SecurityAgent::Client does not cache transaction state.
154OSStatus
155Client::startTransaction(Port serverPort)
156{
157    if (!serverPort)
158        return errAuthorizationInternal;
159    kern_return_t ret = sa_request_client_txStart(serverPort);
160    secdebug("agentclient", "Transaction started (port %u)", serverPort.port());
161    return ret;
162}
163
164OSStatus
165Client::endTransaction(Port serverPort)
166{
167    if (!serverPort)
168        return errAuthorizationInternal;
169    secdebug("agentclient", "Requesting end of transaction (port %u)", serverPort.port());
170    return sa_request_client_txEnd(serverPort);
171}
172
173void
174Client::setState(PluginState inState)
175{
176	// validate state transition: might be more useful where change is requested if that implies anything to interpreting what to do.
177	// Mutex
178	mState = inState;
179}
180
181void Client::teardown() throw()
182{
183	Clients::gClients().remove(this);
184
185	try {
186		if (mStagePort)
187			mStagePort.destroy();
188		if (mClientPort)
189			mClientPort.destroy();
190	} catch (...) { secdebug("agentclient", "ignoring problems tearing down ports for client %p", this); }
191}
192
193
194AuthItemSet
195Client::clientHints(SecurityAgent::RequestorType type, std::string &path, pid_t clientPid, uid_t clientUid)
196{
197    AuthItemSet clientHints;
198
199	clientHints.insert(AuthItemRef(AGENT_HINT_CLIENT_TYPE, AuthValueOverlay(sizeof(type), &type)));
200	clientHints.insert(AuthItemRef(AGENT_HINT_CLIENT_PATH, AuthValueOverlay(path)));
201	clientHints.insert(AuthItemRef(AGENT_HINT_CLIENT_PID, AuthValueOverlay(sizeof(clientPid), &clientPid)));
202	clientHints.insert(AuthItemRef(AGENT_HINT_CLIENT_UID, AuthValueOverlay(sizeof(clientUid), &clientUid)));
203
204    return clientHints;
205}
206
207
208#pragma mark request operations
209
210OSStatus Client::contact(mach_port_t jobId, Bootstrap processBootstrap, mach_port_t userPrefs)
211{
212    kern_return_t ret = sa_request_client_contact(mServerPort, mClientPort, jobId, processBootstrap, userPrefs);
213    if (ret)
214    {
215        Syslog::error("SecurityAgent::Client::contact(): kern_return error %s",
216                      mach_error_string(ret));
217    }
218    return ret;
219}
220
221OSStatus Client::create(const char *inPluginId, const char *inMechanismId, const SessionId inSessionId)
222{
223	// securityd is already notified when the agent/authhost dies through SIGCHILD, and we only really care about the stage port, but we will track if dying happens during create with a DPN.  If two threads are both in the time between the create message and the didcreate answer and the host dies, one will be stuck - too risky and I will win the lottery before that: Chablis.
224	{
225		kern_return_t ret;
226		mach_port_t old_port = MACH_PORT_NULL;
227		ret = mach_port_request_notification(mach_task_self(), mServerPort, MACH_NOTIFY_DEAD_NAME, 0, mClientPort, MACH_MSG_TYPE_MAKE_SEND_ONCE, &old_port);
228		if (!ret && (MACH_PORT_NULL != old_port))
229			mach_port_deallocate(mach_task_self(), old_port);
230	}
231
232	secdebug("agentclient", "asking server at port %d to create %s:%s; replies to %d", mServerPort.port(), inPluginId, inMechanismId, mClientPort.port()); // XXX/cs
233	kern_return_t ret = sa_request_client_create(mServerPort, mClientPort, inSessionId, inPluginId, inMechanismId);
234
235	if (ret)
236		return ret;
237
238	// wait for message (either didCreate, reportError)
239	do
240	{
241		// one scenario that could happen here (and only here) is:
242		// host died before create finished - in which case we'll get a DPN
243		try { receive(); } catch (...) { setState(dead); }
244	}
245	while ((state() != created) &&
246			(state() != dead));
247
248    // verify that we got didCreate
249	if (state() == created)
250		return noErr;
251
252    // and not reportError
253	if (state() == dead)
254		return Client::getError();
255
256	// something we don't deal with
257	secdebug("agentclient", "we got an error on create"); // XXX/cs
258    return errAuthorizationInternal;
259}
260
261// client maintains their own copy of the current data
262OSStatus Client::invoke()
263{
264	if ((state() != created) &&
265        (state() != active) &&
266        (state() != interrupting))
267		return errAuthorizationInternal;
268
269    AuthorizationValueVector *arguments;
270    AuthorizationItemSet *hints, *context;
271    size_t argumentSize, hintSize, contextSize;
272
273    mInHints.copy(hints, hintSize);
274    mInContext.copy(context, contextSize);
275    mArguments.copy(&arguments, &argumentSize);
276
277    setState(current);
278
279    check(sa_request_client_invoke(mStagePort.port(),
280                    arguments, argumentSize, arguments, // data, size, offset
281                    hints, hintSize, hints,
282                    context, contextSize, context));
283
284    receive();
285
286	free (arguments);
287	free (hints);
288	free (context);
289
290    switch(state())
291    {
292        case active:
293            switch(result())
294            {
295                case kAuthorizationResultUndefined:
296                    MacOSError::throwMe(errAuthorizationInternal);
297                default:
298                    return noErr;
299            }
300        case dead:
301            return mErrorState;
302        case current:
303            return noErr;
304        default:
305            break;
306    }
307    return errAuthorizationInternal;
308}
309
310OSStatus
311Client::deactivate()
312{
313	// check state is current
314	if (state() != current)
315		return errAuthorizationInternal;
316
317	secdebug("agentclient", "deactivating mechanism at request port %d", mStagePort.port());
318
319	// tell mechanism to deactivate
320	check(sa_request_client_deactivate(mStagePort.port()));
321
322	setState(deactivating);
323
324	receive();
325
326	// if failed destroy it
327	return noErr;
328}
329
330OSStatus
331Client::destroy()
332{
333	if (state() == active || state() == created || state() == current)
334	{
335		secdebug("agentclient", "destroying mechanism at request port %d", mStagePort.port());
336		// tell mechanism to destroy
337		if (mStagePort)
338			sa_request_client_destroy(mStagePort.port());
339
340		setState(dead);
341
342		return noErr;
343	}
344
345	return errAuthorizationInternal;
346}
347
348// kill host: do not pass go, do not collect $200
349OSStatus
350Client::terminate()
351{
352	check(sa_request_client_terminate(mServerPort.port()));
353
354    return noErr;
355}
356
357void
358Client::receive()
359{
360    bool gotReply = false;
361    while (!gotReply)
362	gotReply = Clients::gClients().receive();
363}
364
365#pragma mark result operations
366
367void Client::setResult(const AuthorizationResult inResult, const AuthorizationItemSet *inHints, const AuthorizationItemSet *inContext)
368{
369    if (state() != current)
370		return;
371    // construct AuthItemSet for hints and context (deep copy - previous contents are released)
372	mOutHints = (*inHints);
373	mOutContext = (*inContext);
374    mResult = inResult;
375    setState(active);
376}
377
378void Client::setError(const OSStatus inMechanismError)
379{
380	setState(dead);
381
382    mErrorState = inMechanismError;
383}
384
385OSStatus Client::getError()
386{
387    return mErrorState;
388}
389
390void Client::requestInterrupt()
391{
392	if (state() != active)
393		return;
394
395	setState(interrupting);
396}
397
398void Client::didDeactivate()
399{
400	if (state() != deactivating)
401		return;
402
403	// check state for deactivating
404	// change state
405	setState(active);
406}
407
408void Client::setStagePort(const mach_port_t inStagePort)
409{
410    mStagePort = Port(inStagePort);
411    mStagePort.requestNotify(mClientPort, MACH_NOTIFY_DEAD_NAME, 0);
412}
413
414
415void Client::didCreate(const mach_port_t inStagePort)
416{
417	// it can be dead, because the host died, we'll always try to revive it once
418	if ((state() != init) && (state() != dead))
419		return;
420
421	setStagePort(inStagePort);
422	setState(created);
423}
424
425#pragma mark client instances
426
427ThreadNexus<Clients> Clients::gClients;
428
429bool
430Clients::compare(const Client * client, mach_port_t instance)
431{
432    if (client->instance() == instance) return true;
433    return false;
434}
435
436// throw so the agent client operation is aborted
437Client&
438Clients::find(mach_port_t instanceReplyPort) const
439{
440	StLock<Mutex> _(mLock);
441    for (set<Client*>::const_iterator foundClient = mClients.begin();
442        foundClient != mClients.end();
443        foundClient++)
444        {
445            Client *client = *foundClient;
446            if (client->instance() == instanceReplyPort)
447                return *client;
448        }
449
450    // can't be receiving for a client we didn't create
451	MacOSError::throwMe(errAuthorizationInternal);
452}
453
454bool
455Clients::receive()
456{
457	try
458	{
459		// maximum known message size (variable sized elements are already forced OOL)
460		Message in(sizeof(union __ReplyUnion__sa_reply_client_secagentreply_subsystem));
461		Message out(sizeof(union __ReplyUnion__sa_reply_client_secagentreply_subsystem));
462
463		in.receive(mClientPortSet, 0, 0);
464
465        // got the message, now demux it; call secagentreply_server to handle any call
466        // this is asynchronous, so no reply message, although not apparent
467        if (!::secagentreply_server(in, out))
468        {
469    		// port death notification
470            if (MACH_NOTIFY_DEAD_NAME == in.msgId())
471            {
472                find(in.remotePort()).setError(errAuthorizationInternal);
473                return true;
474            }
475	    return false;
476
477        }
478	else
479		return true;
480    }
481    catch (Security::MachPlusPlus::Error &e)
482    {
483        secdebug("agentclient", "interpret error %ul", e.error);
484        switch (e.error) {
485			case MACH_MSG_SUCCESS:				// peachy
486				break;
487			case MIG_SERVER_DIED:			// explicit can't-send-it's-dead
488				CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION);
489			default:						// some random Mach error
490				MachPlusPlus::Error::throwMe(e.error);
491		}
492    }
493    catch (...)
494    {
495        MacOSError::throwMe(errAuthorizationInternal);
496    }
497       return false;
498}
499
500} /* end namesapce SecurityAgent */
501
502#pragma mark demux requests replies
503// external C symbols for the mig message handling code to call into
504
505#define COPY_IN(type,name)	type *name, mach_msg_type_number_t name##Length, type *name##Base
506
507// callbacks that key off instanceReplyPort to find the right agentclient instance
508// to deliver the message to.
509
510// they make the data readable to the receiver (relocate internal references)
511
512kern_return_t sa_reply_server_didCreate(mach_port_t instanceReplyPort, mach_port_t instanceRequestPort)
513{
514	secdebug("agentclient", "got didCreate at port %u; requests go to port %u", instanceReplyPort, instanceRequestPort);
515	SecurityAgent::Clients::gClients().find(instanceReplyPort).didCreate(instanceRequestPort);
516	return KERN_SUCCESS;
517}
518
519kern_return_t sa_reply_server_setResult(mach_port_t instanceReplyPort, AuthorizationResult result,
520	COPY_IN(AuthorizationItemSet,inHints) ,
521	COPY_IN(AuthorizationItemSet,inContext) )
522{
523	secdebug("agentclient", "got setResult at port %u; result %u", instanceReplyPort, (unsigned int)result);
524
525	// relink internal references according to current place in memory
526	try { SecurityAgent::relocate(inHints, inHintsBase, inHintsLength); }
527	catch (MacOSError &e) {	return e.osStatus(); }
528	catch (...) { return errAuthorizationInternal; }
529
530	try { SecurityAgent::relocate(inContext, inContextBase, inContextLength); }
531	catch (MacOSError &e) {	return e.osStatus(); }
532	catch (...) { return errAuthorizationInternal; }
533
534	SecurityAgent::Clients::gClients().find(instanceReplyPort).setResult(result, inHints, inContext);
535
536	return KERN_SUCCESS;
537}
538
539kern_return_t sa_reply_server_requestInterrupt(mach_port_t instanceReplyPort)
540{
541	secdebug("agentclient", "got requestInterrupt at port %u", instanceReplyPort);
542	SecurityAgent::Clients::gClients().find(instanceReplyPort).requestInterrupt();
543	return KERN_SUCCESS;
544}
545
546kern_return_t sa_reply_server_didDeactivate(mach_port_t instanceReplyPort)
547{
548	secdebug("agentclient", "got didDeactivate at port %u", instanceReplyPort);
549	SecurityAgent::Clients::gClients().find(instanceReplyPort).didDeactivate();
550	return KERN_SUCCESS;
551}
552
553kern_return_t sa_reply_server_reportError(mach_port_t instanceReplyPort, OSStatus status)
554{
555	secdebug("agentclient", "got reportError at port %u; error is %u", instanceReplyPort, (unsigned int)status);
556	SecurityAgent::Clients::gClients().find(instanceReplyPort).setError(status);
557	return KERN_SUCCESS;
558}
559
560kern_return_t sa_reply_server_didStartTx(mach_port_t replyPort, kern_return_t retval)
561{
562    // no instance ports here: this goes straight to server
563    secdebug("agentclient", "got didStartTx");
564    return retval;
565}
566