#include "agentclient.h"
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <grp.h>
#include <security_agent_server/sa_reply.h> // for size of replies
#include <security_agent_client/sa_request.h>
#include <security_utilities/mach++.h>
#include <security_cdsa_utilities/walkers.h>
#include <security_cdsa_utilities/cssmwalkers.h>
#include <security_cdsa_utilities/AuthorizationWalkers.h>
#include <security_cdsa_utilities/AuthorizationData.h>
using Security::MachPlusPlus::VMGuard;
using LowLevelMemoryUtilities::increment;
using LowLevelMemoryUtilities::difference;
using Security::DataWalkers::walk;
using Authorization::AuthItemSet;
using Authorization::AuthItemRef;
using Authorization::AuthValueOverlay;
namespace SecurityAgent {
class CheckingReconstituteWalker {
public:
CheckingReconstituteWalker(void *ptr, void *base, size_t size)
: mBase(base), mLimit(increment(base, size)), mOffset(difference(ptr, base)) { }
template <class T>
void operator () (T &obj, size_t size = sizeof(T))
{ }
template <class T>
void operator () (T * &addr, size_t size = sizeof(T))
{
blob(addr, size);
}
template <class T>
void blob(T * &addr, size_t size)
{
DEBUGWALK("checkreconst:*");
if (addr) {
if (addr < mBase || increment(addr, size) > mLimit)
MacOSError::throwMe(errAuthorizationInternal);
addr = increment<T>(addr, mOffset);
}
}
static const bool needsRelinking = true;
static const bool needsSize = false;
private:
void *mBase; void *mLimit; off_t mOffset; };
template <class T>
void relocate(T *obj, T *base, size_t size)
{
if (obj) {
CheckingReconstituteWalker w(obj, base, size);
walk(w, base);
}
}
void Client::check(mach_msg_return_t returnCode)
{
switch (returnCode) {
case MACH_MSG_SUCCESS: break;
case MIG_SERVER_DIED: CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION);
default: MachPlusPlus::Error::throwMe(returnCode);
}
}
void Client::checkResult()
{
switch (result()) {
case kAuthorizationResultAllow: return;
case kAuthorizationResultDeny:
case kAuthorizationResultUserCanceled: CssmError::throwMe(CSSM_ERRCODE_USER_CANCELED);
default: MacOSError::throwMe(errAuthorizationInternal);
}
}
extern "C" boolean_t secagentreply_server(mach_msg_header_t *, mach_msg_header_t *);
#pragma mark administrative operations
Client::Client() :
mActive(false), mState(init), mDesktopUid(0), mKeepAlive(false), mAgentName("com.apple.SecurityAgent")
{
}
Client::Client(uid_t clientUID, Bootstrap clientBootstrap, const char *name) :
mActive(false), mState(init), mDesktopUid(clientUID), mKeepAlive(false), mAgentName(name),
mClientBootstrap(clientBootstrap)
{
}
void
Client::establishServer()
{
if (mServerPort = mClientBootstrap.lookupOptional(mAgentName.c_str()))
return;
#if defined(AGENTNAME) && defined(AGENTPATH)
Security::MachPlusPlus::StBootstrap bootSaver(mClientBootstrap);
switch (pid_t pid = fork()) {
case 0: {
unsetenv("USER");
unsetenv("LOGNAME");
unsetenv("HOME");
setenv("AGENTNAME", mAgentName.c_str(), 1);
if (mDesktopUid) {
struct group *grent = getgrnam("nobody");
gid_t desktopGid = grent ? grent->gr_gid : unsigned(-2); endgrent();
secdebug("SAclnt", "setgid(%d)", desktopGid);
setgid(desktopGid); secdebug("SAclnt", "setuid(%d)", mDesktopUid);
setuid(mDesktopUid); }
int maxDescriptors = getdtablesize ();
int i;
for (i = 3; i < maxDescriptors; ++i)
{
close (i);
}
char agentExecutable[PATH_MAX + 1];
const char *path = getenv("SECURITYAGENT");
if (!path)
path = AGENTPATH;
snprintf(agentExecutable, sizeof(agentExecutable), "%s/Contents/MacOS/" AGENTNAME, path);
secdebug("SAclnt", "execl(%s)", agentExecutable);
execl(agentExecutable, agentExecutable, NULL);
secdebug("SAclnt", "execl of SecurityAgent failed, errno=%d", errno);
#if 1
_exit(1);
#else
setuid(0);
abort();
#endif
}
case -1: UnixError::throwMe();
default: {
static const int timeout = 300;
secdebug("SAclnt", "Starting security agent (%d seconds timeout)", timeout);
struct timespec rqtp;
memset(&rqtp, 0, sizeof(rqtp));
rqtp.tv_nsec = 100000000;
for (int n = timeout; n > 0; nanosleep(&rqtp, NULL), n--) {
if (mServerPort = mClientBootstrap.lookupOptional(mAgentName.c_str()))
break;
int status;
switch (IFDEBUG(pid_t rc =) waitpid(pid, &status, WNOHANG)) {
case 0: continue;
case -1: switch (errno) {
case EINTR:
case EAGAIN: continue;
case ECHILD: secdebug("SAclnt", "child is dead (reaped elsewhere)");
CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION);
default:
secdebug("SAclnt", "waitpid failed: errno=%d", errno);
UnixError::throwMe();
}
default:
assert(rc == pid);
secdebug("SAclnt", "child died without claiming the SecurityAgent port");
CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION);
}
}
if (mServerPort == 0) { secdebug("SAclnt", "Autolaunch failed");
CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION);
}
secdebug("SAclnt", "SecurityAgent located");
return;
}
}
#endif
secdebug("SAclnt", "Cannot contact SecurityAgent");
CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION); }
OSStatus
Client::activate()
{
if (!mActive)
{
establishServer();
if (!mServerPort)
MacOSError::throwMe(errAuthorizationInternal);
mClientPort.allocate();
mClientPort.insertRight(MACH_MSG_TYPE_MAKE_SEND);
mServerPort.requestNotify(mClientPort, MACH_NOTIFY_DEAD_NAME, true);
Clients::gClients().insert(this);
mActive = true;
}
return noErr;
}
Client::~Client()
{
teardown();
}
void Client::setState(PluginState inState)
{
{
mState = inState;
}
}
int Client::state()
{
return mState;
}
void Client::teardown() throw()
{
Clients::gClients().remove(this);
if (mActive) {
mServerPort.deallocate();
mStagePort.deallocate();
mClientPort.destroy();
mActive = false;
}
}
AuthItemSet
Client::clientHints(CodeSigning::OSXCode *clientCode, pid_t clientPid, uid_t clientUid)
{
AuthItemSet clientHints;
string encodedBundle = clientCode->encode();
char bundleType = (encodedBundle.c_str())[0]; SecurityAgent::RequestorType requestorType;
string bundlePath = clientCode->canonicalPath();
switch(bundleType)
{
case 'b': requestorType = SecurityAgent::bundle; break;
case 't': requestorType = SecurityAgent::tool; break;
default: requestorType = SecurityAgent::unknown; break;
}
clientHints.insert(AuthItemRef(AGENT_HINT_CLIENT_TYPE, AuthValueOverlay(sizeof(requestorType), &requestorType)));
clientHints.insert(AuthItemRef(AGENT_HINT_CLIENT_PATH, AuthValueOverlay(bundlePath)));
clientHints.insert(AuthItemRef(AGENT_HINT_CLIENT_PID, AuthValueOverlay(sizeof(clientPid), &clientPid)));
clientHints.insert(AuthItemRef(AGENT_HINT_CLIENT_UID, AuthValueOverlay(sizeof(clientUid), &clientUid)));
return clientHints;
}
#pragma mark request operations
OSStatus Client::create(const char *inPluginId, const char *inMechanismId, const SessionId inSessionId )
{
activate();
secdebug("agentclient", "asking server at port %d to create %s:%s; replies to %d", mServerPort.port(), inPluginId, inMechanismId, mClientPort.port()); kern_return_t ret = sa_request_client_create(mServerPort, mClientPort, inSessionId, inPluginId, inMechanismId);
if (ret)
{
return ret;
}
try {
receive();
}
catch (...)
{
return errAuthorizationInternal;
}
if (state() == created)
return noErr;
if (state() == dead)
return Client::getError();
secdebug("agentclient", "we got an error on create"); return errAuthorizationInternal;
}
OSStatus Client::invoke(
const Authorization::AuthValueVector& inArguments,
const Authorization::AuthItemSet& inHints,
const Authorization::AuthItemSet& inContext)
{
if ((state() != created) &&
(state() != active))
return errAuthorizationInternal;
AuthorizationValueVector *arguments;
AuthorizationItemSet *hints, *context;
size_t argumentSize, hintSize, contextSize;
inHints.copy(hints, hintSize);
inContext.copy(context, contextSize);
inArguments.copy(&arguments, &argumentSize);
check(sa_request_client_invoke(mStagePort.port(),
arguments, argumentSize, arguments, hints, hintSize, hints,
context, contextSize, context));
receive();
switch(state())
{
case active:
switch(result())
{
case kAuthorizationResultUndefined:
MacOSError::throwMe(errAuthorizationInternal);
default:
return noErr;
}
case dead:
return mErrorState;
}
return errAuthorizationInternal;
}
OSStatus
Client::deactivate()
{
if (state() != current)
return errAuthorizationInternal;
secdebug("agentclient", "deactivating mechanism at request port %d", mStagePort.port());
check(sa_request_client_deactivate(mStagePort.port()));
setState(deactivating);
receive();
return noErr;
}
OSStatus
Client::destroy()
{
if ((state() == current) && deactivate())
return errAuthorizationInternal;
if (state() == active || state() == created)
{
secdebug("agentclient", "destroying mechanism at request port %d", mStagePort.port());
check(sa_request_client_destroy(mStagePort.port()));
setState(dead);
return noErr;
}
return errAuthorizationInternal;
}
OSStatus
Client::terminate()
{
check(sa_request_client_terminate(mServerPort.port()));
return noErr;
}
void
Client::receive()
{
try
{
Message in(sizeof(union __ReplyUnion__sa_reply_client_secagentreply_subsystem));
Message out(sizeof(union __ReplyUnion__sa_reply_client_secagentreply_subsystem));
in.receive(mClientPort, 0, 0);
if (!secagentreply_server(in, out))
{
MacOSError::throwMe(errAuthorizationInternal);
}
}
catch (Security::MachPlusPlus::Error &e)
{
secdebug("agentclient", "interpret error %ul", e.error);
check(e.error);
}
catch (...)
{
MacOSError::throwMe(errAuthorizationInternal);
}
}
#pragma mark result operations
void Client::setResult(const AuthorizationResult inResult, const AuthorizationItemSet *inHints, const AuthorizationItemSet *inContext)
{
mHints = (*inHints);
mContext = (*inContext);
mResult = inResult;
setState(active);
}
void Client::setError(const OSStatus inMechanismError)
{
setState(dead);
mErrorState = inMechanismError;
}
OSStatus Client::getError()
{
return mErrorState;
}
void Client::requestInterrupt()
{
setState(interrupting);
setState(created);
}
void Client::didDeactivate()
{
setState(active);
}
void Client::didCreate(const mach_port_t inStagePort)
{
setStagePort(inStagePort);
setState(created);
}
#pragma mark client instances
ModuleNexus<Clients> Clients::gClients;
bool
Clients::compare(const Client * client, mach_port_t instance)
{
if (client->instance() == instance) return true;
return false;
}
Client&
Clients::find(mach_port_t instanceReplyPort) const
{
StLock<Mutex> _(mLock);
for (set<Client*>::const_iterator foundClient = mClients.begin();
foundClient != mClients.end();
foundClient++)
{
Client *client = *foundClient;
if (client->instance() == instanceReplyPort)
return *client;
}
MacOSError::throwMe(errAuthorizationInternal);
}
#pragma mark demux requests replies
extern "C" {
#define COPY_IN(type,name) type *name, mach_msg_type_number_t name##Length, type *name##Base
kern_return_t sa_reply_server_didCreate(mach_port_t instanceReplyPort, mach_port_t instanceRequestPort)
{
secdebug("agentclient", "got didCreate at port %u; requests go to port %u", instanceReplyPort, instanceRequestPort);
Clients::gClients().find(instanceReplyPort).didCreate(instanceRequestPort);
return KERN_SUCCESS;
}
kern_return_t sa_reply_server_setResult(mach_port_t instanceReplyPort, AuthorizationResult result,
COPY_IN(AuthorizationItemSet,inHints) ,
COPY_IN(AuthorizationItemSet,inContext) )
{
VMGuard _(inHints, inHintsLength);
VMGuard _2(inContext, inContextLength);
secdebug("agentclient", "got setResult at port %u; result %ld", instanceReplyPort, result);
try { relocate(inHints, inHintsBase, inHintsLength); }
catch (MacOSError &e) { return e.osStatus(); }
catch (...) { return errAuthorizationInternal; }
try { relocate(inContext, inContextBase, inContextLength); }
catch (MacOSError &e) { return e.osStatus(); }
catch (...) { return errAuthorizationInternal; }
Clients::gClients().find(instanceReplyPort).setResult(result, inHints, inContext);
return KERN_SUCCESS;
}
kern_return_t sa_reply_server_requestInterrupt(mach_port_t instanceReplyPort)
{
secdebug("agentclient", "got requestInterrupt at port %u", instanceReplyPort);
Clients::gClients().find(instanceReplyPort).requestInterrupt();
return KERN_SUCCESS;
}
kern_return_t sa_reply_server_didDeactivate(mach_port_t instanceReplyPort)
{
secdebug("agentclient", "got didDeactivate at port %u", instanceReplyPort);
Clients::gClients().find(instanceReplyPort).didDeactivate();
return KERN_SUCCESS;
}
kern_return_t sa_reply_server_reportError(mach_port_t instanceReplyPort, OSStatus status)
{
secdebug("agentclient", "got reportError at port %u; error is %ld", instanceReplyPort, status);
Clients::gClients().find(instanceReplyPort).setError(status);
return KERN_SUCCESS;
}
}
}