5 * Copyright (c) 2002,2008 Apple Inc.. All rights reserved.
13 For now all the calls into agentclient will be synchronous, with timeouts
15 On 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.
17 If we receive a reply that is not confirming attempts to abort, we'll process these and return them to the caller.
19 Alternatively, there can be an answer that isn't the answer we expected: setError, where the server aborts the transaction.
21 We can't support interrupt() with a synchronous interface unless we add some notification that let's the client know that the "server" is dead
24 #include <sys/types.h>
28 #include <security_agent_server/sa_reply.h> // for size of replies
29 #include <security_agent_client/sa_request.h>
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>
37 #include <security_utilities/logging.h>
39 using LowLevelMemoryUtilities::increment
;
40 using LowLevelMemoryUtilities::difference
;
41 using Security::DataWalkers::walk
;
43 using Authorization::AuthItemSet
;
44 using Authorization::AuthItemRef
;
45 using Authorization::AuthValueOverlay
;
47 #include "agentclient.h"
49 namespace SecurityAgent
{
51 class CheckingReconstituteWalker
{
53 CheckingReconstituteWalker(void *ptr
, void *base
, size_t size
)
54 : mBase(base
), mLimit(increment(base
, size
)), mOffset(difference(ptr
, base
)) { }
57 void operator () (T
&obj
, size_t size
= sizeof(T
))
61 void operator () (T
* &addr
, size_t size
= sizeof(T
))
67 void blob(T
* &addr
, size_t size
)
69 DEBUGWALK("checkreconst:*");
71 if (addr
< mBase
|| increment(addr
, size
) > mLimit
)
72 MacOSError::throwMe(errAuthorizationInternal
);
73 addr
= increment
<T
>(addr
, mOffset
);
77 static const bool needsRelinking
= true;
78 static const bool needsSize
= false;
81 void *mBase
; // old base address
82 void *mLimit
; // old last byte address + 1
83 off_t mOffset
; // relocation offset
87 void relocate(T
*obj
, T
*base
, size_t size
)
90 CheckingReconstituteWalker
w(obj
, base
, size
);
95 void Client::check(mach_msg_return_t returnCode
)
97 // first check the Mach IPC return code
99 case MACH_MSG_SUCCESS
: // peachy
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
);
108 void Client::checkResult()
110 // now check the OSStatus return from the server side
112 case kAuthorizationResultAllow
: return;
113 case kAuthorizationResultDeny
:
114 case kAuthorizationResultUserCanceled
: CssmError::throwMe(CSSM_ERRCODE_USER_CANCELED
);
115 default: MacOSError::throwMe(errAuthorizationInternal
);
121 #pragma mark administrative operations
123 Client::Client() : mState(init
), mTerminateOnSleep(false)
126 mClientPort
.allocate(); //implicit MACH_PORT_RIGHT_RECEIVE
128 // register with agentclients
129 Clients::gClients().insert(this);
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);
137 Client::activate(Port serverPort
)
140 MacOSError::throwMe(errAuthorizationInternal
);
142 secdebug("agentclient", "using server at port %d", serverPort
.port());
143 mServerPort
= serverPort
;
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.
159 Client::startTransaction(Port serverPort
)
162 return errAuthorizationInternal
;
163 kern_return_t ret
= sa_request_client_txStart(serverPort
);
164 secdebug("agentclient", "Transaction started (port %u)", serverPort
.port());
169 Client::endTransaction(Port serverPort
)
172 return errAuthorizationInternal
;
173 secdebug("agentclient", "Requesting end of transaction (port %u)", serverPort
.port());
174 return sa_request_client_txEnd(serverPort
);
178 Client::setState(PluginState inState
)
180 // validate state transition: might be more useful where change is requested if that implies anything to interpreting what to do.
185 void Client::teardown() throw()
187 // remove this from the global list
189 StLock
<Mutex
> _(Clients::gAllClientsMutex());
190 Clients::allClients().erase(this);
193 Clients::gClients().remove(this);
197 mStagePort
.destroy();
199 mClientPort
.destroy();
200 } catch (...) { secdebug("agentclient", "ignoring problems tearing down ports for client %p", this); }
205 Client::clientHints(SecurityAgent::RequestorType type
, std::string
&path
, pid_t clientPid
, uid_t clientUid
)
207 AuthItemSet clientHints
;
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
)));
218 #pragma mark request operations
220 OSStatus
Client::contact(mach_port_t jobId
, Bootstrap processBootstrap
, mach_port_t userPrefs
)
222 kern_return_t ret
= sa_request_client_contact(mServerPort
, mClientPort
, jobId
, processBootstrap
, userPrefs
);
225 Syslog::error("SecurityAgent::Client::contact(): kern_return error %s",
226 mach_error_string(ret
));
231 OSStatus
Client::create(const char *inPluginId
, const char *inMechanismId
, const SessionId inSessionId
)
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.
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
);
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
);
248 // wait for message (either didCreate, reportError)
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
); }
255 while ((state() != created
) &&
258 // verify that we got didCreate
259 if (state() == created
)
262 // and not reportError
264 return Client::getError();
266 // something we don't deal with
267 secdebug("agentclient", "we got an error on create"); // XXX/cs
268 return errAuthorizationInternal
;
271 // client maintains their own copy of the current data
272 OSStatus
Client::invoke()
274 if ((state() != created
) &&
275 (state() != active
) &&
276 (state() != interrupting
))
277 return errAuthorizationInternal
;
279 AuthorizationValueVector
*arguments
;
280 AuthorizationItemSet
*hints
, *context
;
281 size_t argumentSize
, hintSize
, contextSize
;
283 mInHints
.copy(hints
, hintSize
);
284 mInContext
.copy(context
, contextSize
);
285 mArguments
.copy(&arguments
, &argumentSize
);
289 check(sa_request_client_invoke(mStagePort
.port(),
290 arguments
, argumentSize
, arguments
, // data, size, offset
291 hints
, hintSize
, hints
,
292 context
, contextSize
, context
));
305 case kAuthorizationResultUndefined
:
306 MacOSError::throwMe(errAuthorizationInternal
);
317 return errAuthorizationInternal
;
323 // check state is current
324 if (state() != current
)
325 return errAuthorizationInternal
;
327 secdebug("agentclient", "deactivating mechanism at request port %d", mStagePort
.port());
329 // tell mechanism to deactivate
330 check(sa_request_client_deactivate(mStagePort
.port()));
332 setState(deactivating
);
336 // if failed destroy it
343 if (state() == active
|| state() == created
|| state() == current
)
345 secdebug("agentclient", "destroying mechanism at request port %d", mStagePort
.port());
346 // tell mechanism to destroy
348 sa_request_client_destroy(mStagePort
.port());
355 return errAuthorizationInternal
;
358 // kill host: do not pass go, do not collect $200
362 check(sa_request_client_terminate(mServerPort
.port()));
370 bool gotReply
= false;
372 gotReply
= Clients::gClients().receive();
375 #pragma mark result operations
377 void Client::setResult(const AuthorizationResult inResult
, const AuthorizationItemSet
*inHints
, const AuthorizationItemSet
*inContext
)
379 if (state() != current
)
381 // construct AuthItemSet for hints and context (deep copy - previous contents are released)
382 mOutHints
= (*inHints
);
383 mOutContext
= (*inContext
);
388 void Client::setError(const OSStatus inMechanismError
)
392 mErrorState
= inMechanismError
;
395 OSStatus
Client::getError()
400 void Client::requestInterrupt()
402 if (state() != active
)
405 setState(interrupting
);
408 void Client::didDeactivate()
410 if (state() != deactivating
)
413 // check state for deactivating
418 void Client::setStagePort(const mach_port_t inStagePort
)
420 mStagePort
= Port(inStagePort
);
421 mStagePort
.requestNotify(mClientPort
, MACH_NOTIFY_DEAD_NAME
, 0);
425 void Client::didCreate(const mach_port_t inStagePort
)
427 // it can be dead, because the host died, we'll always try to revive it once
428 if ((state() != init
) && (state() != dead
))
431 setStagePort(inStagePort
);
435 #pragma mark client instances
437 ThreadNexus
<Clients
> Clients::gClients
;
438 ModuleNexus
<set
<Client
*> > Clients::allClients
;
441 Clients::compare(const Client
* client
, mach_port_t instance
)
443 if (client
->instance() == instance
) return true;
447 // throw so the agent client operation is aborted
449 Clients::find(mach_port_t instanceReplyPort
) const
451 StLock
<Mutex
> _(mLock
);
452 for (set
<Client
*>::const_iterator foundClient
= mClients
.begin();
453 foundClient
!= mClients
.end();
456 Client
*client
= *foundClient
;
457 if (client
->instance() == instanceReplyPort
)
461 // can't be receiving for a client we didn't create
462 MacOSError::throwMe(errAuthorizationInternal
);
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
));
474 in
.receive(mClientPortSet
, 0, 0);
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
))
480 // port death notification
481 if (MACH_NOTIFY_DEAD_NAME
== in
.msgId())
483 find(in
.remotePort()).setError(errAuthorizationInternal
);
492 catch (Security::MachPlusPlus::Error
&e
)
494 secdebug("agentclient", "interpret error %ul", e
.error
);
496 case MACH_MSG_SUCCESS
: // peachy
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
);
506 MacOSError::throwMe(errAuthorizationInternal
);
511 ModuleNexus
<RecursiveMutex
> Clients::gAllClientsMutex
;
514 Clients::killAllClients()
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());
519 set
<Client
*>::iterator clientIterator
= allClients().begin();
520 while (clientIterator
!= allClients().end())
522 set
<Client
*>::iterator thisClient
= clientIterator
++;
523 if ((*thisClient
)->getTerminateOnSleep())
525 (*thisClient
)->terminate();
532 } /* end namesapce SecurityAgent */
534 #pragma mark demux requests replies
535 // external C symbols for the mig message handling code to call into
537 #define COPY_IN(type,name) type *name, mach_msg_type_number_t name##Length, type *name##Base
539 // callbacks that key off instanceReplyPort to find the right agentclient instance
540 // to deliver the message to.
542 // they make the data readable to the receiver (relocate internal references)
544 kern_return_t
sa_reply_server_didCreate(mach_port_t instanceReplyPort
, mach_port_t instanceRequestPort
)
546 secdebug("agentclient", "got didCreate at port %u; requests go to port %u", instanceReplyPort
, instanceRequestPort
);
547 SecurityAgent::Clients::gClients().find(instanceReplyPort
).didCreate(instanceRequestPort
);
551 kern_return_t
sa_reply_server_setResult(mach_port_t instanceReplyPort
, AuthorizationResult result
,
552 COPY_IN(AuthorizationItemSet
,inHints
) ,
553 COPY_IN(AuthorizationItemSet
,inContext
) )
555 secdebug("agentclient", "got setResult at port %u; result %u", instanceReplyPort
, (unsigned int)result
);
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
; }
562 try { SecurityAgent::relocate(inContext
, inContextBase
, inContextLength
); }
563 catch (MacOSError
&e
) { return e
.osStatus(); }
564 catch (...) { return errAuthorizationInternal
; }
566 SecurityAgent::Clients::gClients().find(instanceReplyPort
).setResult(result
, inHints
, inContext
);
571 kern_return_t
sa_reply_server_requestInterrupt(mach_port_t instanceReplyPort
)
573 secdebug("agentclient", "got requestInterrupt at port %u", instanceReplyPort
);
574 SecurityAgent::Clients::gClients().find(instanceReplyPort
).requestInterrupt();
578 kern_return_t
sa_reply_server_didDeactivate(mach_port_t instanceReplyPort
)
580 secdebug("agentclient", "got didDeactivate at port %u", instanceReplyPort
);
581 SecurityAgent::Clients::gClients().find(instanceReplyPort
).didDeactivate();
585 kern_return_t
sa_reply_server_reportError(mach_port_t instanceReplyPort
, OSStatus status
)
587 secdebug("agentclient", "got reportError at port %u; error is %u", instanceReplyPort
, (unsigned int)status
);
588 SecurityAgent::Clients::gClients().find(instanceReplyPort
).setError(status
);
592 kern_return_t
sa_reply_server_didStartTx(mach_port_t replyPort
, kern_return_t retval
)
594 // no instance ports here: this goes straight to server
595 secdebug("agentclient", "got didStartTx");