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), mTerminateOnSleep(false)
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    // add this client to the global list so that we can kill it if we have to
132    StLock<Mutex> _(Clients::gAllClientsMutex());
133    Clients::allClients().insert(this);
134}
135
136void
137Client::activate(Port serverPort)
138{
139	if (!serverPort)
140		MacOSError::throwMe(errAuthorizationInternal);
141
142	secdebug("agentclient", "using server at port %d", serverPort.port());
143	mServerPort = serverPort;
144}
145
146
147Client::~Client()
148{
149    teardown();
150}
151
152
153// start/endTransaction calls stand outside the usual client protocol: they
154// don't participate in the state-management or multiplexing-by-port tangoes.
155// (These calls could take advantage of activate(), but callers would be
156// instantiating an entire Client object for the sake of mServerPort.)
157// Conversely, SecurityAgent::Client does not cache transaction state.
158OSStatus
159Client::startTransaction(Port serverPort)
160{
161    if (!serverPort)
162        return errAuthorizationInternal;
163    kern_return_t ret = sa_request_client_txStart(serverPort);
164    secdebug("agentclient", "Transaction started (port %u)", serverPort.port());
165    return ret;
166}
167
168OSStatus
169Client::endTransaction(Port serverPort)
170{
171    if (!serverPort)
172        return errAuthorizationInternal;
173    secdebug("agentclient", "Requesting end of transaction (port %u)", serverPort.port());
174    return sa_request_client_txEnd(serverPort);
175}
176
177void
178Client::setState(PluginState inState)
179{
180	// validate state transition: might be more useful where change is requested if that implies anything to interpreting what to do.
181	// Mutex
182	mState = inState;
183}
184
185void Client::teardown() throw()
186{
187    // remove this from the global list
188    {
189        StLock<Mutex> _(Clients::gAllClientsMutex());
190        Clients::allClients().erase(this);
191    }
192
193	Clients::gClients().remove(this);
194
195	try {
196		if (mStagePort)
197			mStagePort.destroy();
198		if (mClientPort)
199			mClientPort.destroy();
200	} catch (...) { secdebug("agentclient", "ignoring problems tearing down ports for client %p", this); }
201}
202
203
204AuthItemSet
205Client::clientHints(SecurityAgent::RequestorType type, std::string &path, pid_t clientPid, uid_t clientUid)
206{
207    AuthItemSet clientHints;
208
209	clientHints.insert(AuthItemRef(AGENT_HINT_CLIENT_TYPE, AuthValueOverlay(sizeof(type), &type)));
210	clientHints.insert(AuthItemRef(AGENT_HINT_CLIENT_PATH, AuthValueOverlay(path)));
211	clientHints.insert(AuthItemRef(AGENT_HINT_CLIENT_PID, AuthValueOverlay(sizeof(clientPid), &clientPid)));
212	clientHints.insert(AuthItemRef(AGENT_HINT_CLIENT_UID, AuthValueOverlay(sizeof(clientUid), &clientUid)));
213
214    return clientHints;
215}
216
217
218#pragma mark request operations
219
220OSStatus Client::contact(mach_port_t jobId, Bootstrap processBootstrap, mach_port_t userPrefs)
221{
222    kern_return_t ret = sa_request_client_contact(mServerPort, mClientPort, jobId, processBootstrap, userPrefs);
223    if (ret)
224    {
225        Syslog::error("SecurityAgent::Client::contact(): kern_return error %s",
226                      mach_error_string(ret));
227    }
228    return ret;
229}
230
231OSStatus Client::create(const char *inPluginId, const char *inMechanismId, const SessionId inSessionId)
232{
233	// 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.
234	{
235		kern_return_t ret;
236		mach_port_t old_port = MACH_PORT_NULL;
237		ret = mach_port_request_notification(mach_task_self(), mServerPort, MACH_NOTIFY_DEAD_NAME, 0, mClientPort, MACH_MSG_TYPE_MAKE_SEND_ONCE, &old_port);
238		if (!ret && (MACH_PORT_NULL != old_port))
239			mach_port_deallocate(mach_task_self(), old_port);
240	}
241
242	secdebug("agentclient", "asking server at port %d to create %s:%s; replies to %d", mServerPort.port(), inPluginId, inMechanismId, mClientPort.port()); // XXX/cs
243	kern_return_t ret = sa_request_client_create(mServerPort, mClientPort, inSessionId, inPluginId, inMechanismId);
244
245	if (ret)
246		return ret;
247
248	// wait for message (either didCreate, reportError)
249	do
250	{
251		// one scenario that could happen here (and only here) is:
252		// host died before create finished - in which case we'll get a DPN
253		try { receive(); } catch (...) { setState(dead); }
254	}
255	while ((state() != created) &&
256			(state() != dead));
257
258    // verify that we got didCreate
259	if (state() == created)
260		return noErr;
261
262    // and not reportError
263	if (state() == dead)
264		return Client::getError();
265
266	// something we don't deal with
267	secdebug("agentclient", "we got an error on create"); // XXX/cs
268    return errAuthorizationInternal;
269}
270
271// client maintains their own copy of the current data
272OSStatus Client::invoke()
273{
274	if ((state() != created) &&
275        (state() != active) &&
276        (state() != interrupting))
277		return errAuthorizationInternal;
278
279    AuthorizationValueVector *arguments;
280    AuthorizationItemSet *hints, *context;
281    size_t argumentSize, hintSize, contextSize;
282
283    mInHints.copy(hints, hintSize);
284    mInContext.copy(context, contextSize);
285    mArguments.copy(&arguments, &argumentSize);
286
287    setState(current);
288
289    check(sa_request_client_invoke(mStagePort.port(),
290                    arguments, argumentSize, arguments, // data, size, offset
291                    hints, hintSize, hints,
292                    context, contextSize, context));
293
294    receive();
295
296	free (arguments);
297	free (hints);
298	free (context);
299
300    switch(state())
301    {
302        case active:
303            switch(result())
304            {
305                case kAuthorizationResultUndefined:
306                    MacOSError::throwMe(errAuthorizationInternal);
307                default:
308                    return noErr;
309            }
310        case dead:
311            return mErrorState;
312        case current:
313            return noErr;
314        default:
315            break;
316    }
317    return errAuthorizationInternal;
318}
319
320OSStatus
321Client::deactivate()
322{
323	// check state is current
324	if (state() != current)
325		return errAuthorizationInternal;
326
327	secdebug("agentclient", "deactivating mechanism at request port %d", mStagePort.port());
328
329	// tell mechanism to deactivate
330	check(sa_request_client_deactivate(mStagePort.port()));
331
332	setState(deactivating);
333
334	receive();
335
336	// if failed destroy it
337	return noErr;
338}
339
340OSStatus
341Client::destroy()
342{
343	if (state() == active || state() == created || state() == current)
344	{
345		secdebug("agentclient", "destroying mechanism at request port %d", mStagePort.port());
346		// tell mechanism to destroy
347		if (mStagePort)
348			sa_request_client_destroy(mStagePort.port());
349
350		setState(dead);
351
352		return noErr;
353	}
354
355	return errAuthorizationInternal;
356}
357
358// kill host: do not pass go, do not collect $200
359OSStatus
360Client::terminate()
361{
362	check(sa_request_client_terminate(mServerPort.port()));
363
364    return noErr;
365}
366
367void
368Client::receive()
369{
370    bool gotReply = false;
371    while (!gotReply)
372	gotReply = Clients::gClients().receive();
373}
374
375#pragma mark result operations
376
377void Client::setResult(const AuthorizationResult inResult, const AuthorizationItemSet *inHints, const AuthorizationItemSet *inContext)
378{
379    if (state() != current)
380		return;
381    // construct AuthItemSet for hints and context (deep copy - previous contents are released)
382	mOutHints = (*inHints);
383	mOutContext = (*inContext);
384    mResult = inResult;
385    setState(active);
386}
387
388void Client::setError(const OSStatus inMechanismError)
389{
390	setState(dead);
391
392    mErrorState = inMechanismError;
393}
394
395OSStatus Client::getError()
396{
397    return mErrorState;
398}
399
400void Client::requestInterrupt()
401{
402	if (state() != active)
403		return;
404
405	setState(interrupting);
406}
407
408void Client::didDeactivate()
409{
410	if (state() != deactivating)
411		return;
412
413	// check state for deactivating
414	// change state
415	setState(active);
416}
417
418void Client::setStagePort(const mach_port_t inStagePort)
419{
420    mStagePort = Port(inStagePort);
421    mStagePort.requestNotify(mClientPort, MACH_NOTIFY_DEAD_NAME, 0);
422}
423
424
425void Client::didCreate(const mach_port_t inStagePort)
426{
427	// it can be dead, because the host died, we'll always try to revive it once
428	if ((state() != init) && (state() != dead))
429		return;
430
431	setStagePort(inStagePort);
432	setState(created);
433}
434
435#pragma mark client instances
436
437ThreadNexus<Clients> Clients::gClients;
438ModuleNexus<set<Client*> > Clients::allClients;
439
440bool
441Clients::compare(const Client * client, mach_port_t instance)
442{
443    if (client->instance() == instance) return true;
444    return false;
445}
446
447// throw so the agent client operation is aborted
448Client&
449Clients::find(mach_port_t instanceReplyPort) const
450{
451	StLock<Mutex> _(mLock);
452    for (set<Client*>::const_iterator foundClient = mClients.begin();
453        foundClient != mClients.end();
454        foundClient++)
455        {
456            Client *client = *foundClient;
457            if (client->instance() == instanceReplyPort)
458                return *client;
459        }
460
461    // can't be receiving for a client we didn't create
462	MacOSError::throwMe(errAuthorizationInternal);
463}
464
465bool
466Clients::receive()
467{
468	try
469	{
470		// maximum known message size (variable sized elements are already forced OOL)
471		Message in(sizeof(union __ReplyUnion__sa_reply_client_secagentreply_subsystem));
472		Message out(sizeof(union __ReplyUnion__sa_reply_client_secagentreply_subsystem));
473
474		in.receive(mClientPortSet, 0, 0);
475
476        // got the message, now demux it; call secagentreply_server to handle any call
477        // this is asynchronous, so no reply message, although not apparent
478        if (!::secagentreply_server(in, out))
479        {
480    		// port death notification
481            if (MACH_NOTIFY_DEAD_NAME == in.msgId())
482            {
483                find(in.remotePort()).setError(errAuthorizationInternal);
484                return true;
485            }
486	    return false;
487
488        }
489	else
490		return true;
491    }
492    catch (Security::MachPlusPlus::Error &e)
493    {
494        secdebug("agentclient", "interpret error %ul", e.error);
495        switch (e.error) {
496			case MACH_MSG_SUCCESS:				// peachy
497				break;
498			case MIG_SERVER_DIED:			// explicit can't-send-it's-dead
499				CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION);
500			default:						// some random Mach error
501				MachPlusPlus::Error::throwMe(e.error);
502		}
503    }
504    catch (...)
505    {
506        MacOSError::throwMe(errAuthorizationInternal);
507    }
508       return false;
509}
510
511ModuleNexus<RecursiveMutex> Clients::gAllClientsMutex;
512
513void
514Clients::killAllClients()
515{
516    // grab the lock for the client list -- we need to make sure no one modifies the structure while we are iterating it.
517    StLock<Mutex> _(gAllClientsMutex());
518
519    set<Client*>::iterator clientIterator = allClients().begin();
520    while (clientIterator != allClients().end())
521    {
522        set<Client*>::iterator thisClient = clientIterator++;
523        if ((*thisClient)->getTerminateOnSleep())
524        {
525            (*thisClient)->terminate();
526        }
527    }
528}
529
530
531
532} /* end namesapce SecurityAgent */
533
534#pragma mark demux requests replies
535// external C symbols for the mig message handling code to call into
536
537#define COPY_IN(type,name)	type *name, mach_msg_type_number_t name##Length, type *name##Base
538
539// callbacks that key off instanceReplyPort to find the right agentclient instance
540// to deliver the message to.
541
542// they make the data readable to the receiver (relocate internal references)
543
544kern_return_t sa_reply_server_didCreate(mach_port_t instanceReplyPort, mach_port_t instanceRequestPort)
545{
546	secdebug("agentclient", "got didCreate at port %u; requests go to port %u", instanceReplyPort, instanceRequestPort);
547	SecurityAgent::Clients::gClients().find(instanceReplyPort).didCreate(instanceRequestPort);
548	return KERN_SUCCESS;
549}
550
551kern_return_t sa_reply_server_setResult(mach_port_t instanceReplyPort, AuthorizationResult result,
552	COPY_IN(AuthorizationItemSet,inHints) ,
553	COPY_IN(AuthorizationItemSet,inContext) )
554{
555	secdebug("agentclient", "got setResult at port %u; result %u", instanceReplyPort, (unsigned int)result);
556
557	// relink internal references according to current place in memory
558	try { SecurityAgent::relocate(inHints, inHintsBase, inHintsLength); }
559	catch (MacOSError &e) {	return e.osStatus(); }
560	catch (...) { return errAuthorizationInternal; }
561
562	try { SecurityAgent::relocate(inContext, inContextBase, inContextLength); }
563	catch (MacOSError &e) {	return e.osStatus(); }
564	catch (...) { return errAuthorizationInternal; }
565
566	SecurityAgent::Clients::gClients().find(instanceReplyPort).setResult(result, inHints, inContext);
567
568	return KERN_SUCCESS;
569}
570
571kern_return_t sa_reply_server_requestInterrupt(mach_port_t instanceReplyPort)
572{
573	secdebug("agentclient", "got requestInterrupt at port %u", instanceReplyPort);
574	SecurityAgent::Clients::gClients().find(instanceReplyPort).requestInterrupt();
575	return KERN_SUCCESS;
576}
577
578kern_return_t sa_reply_server_didDeactivate(mach_port_t instanceReplyPort)
579{
580	secdebug("agentclient", "got didDeactivate at port %u", instanceReplyPort);
581	SecurityAgent::Clients::gClients().find(instanceReplyPort).didDeactivate();
582	return KERN_SUCCESS;
583}
584
585kern_return_t sa_reply_server_reportError(mach_port_t instanceReplyPort, OSStatus status)
586{
587	secdebug("agentclient", "got reportError at port %u; error is %u", instanceReplyPort, (unsigned int)status);
588	SecurityAgent::Clients::gClients().find(instanceReplyPort).setError(status);
589	return KERN_SUCCESS;
590}
591
592kern_return_t sa_reply_server_didStartTx(mach_port_t replyPort, kern_return_t retval)
593{
594    // no instance ports here: this goes straight to server
595    secdebug("agentclient", "got didStartTx");
596    return retval;
597}
598