SecurityAgentClient.cpp [plain text]
#include "SecurityAgentClient.h"
#include "secagent.h"
#include <Security/utilities.h>
#include <Security/Authorization.h>
#include <cstdio>
#include <unistd.h>
#include <mach/mach_error.h>
#include <mach/ndr.h>
#include <Security/debugging.h>
#include <sys/wait.h>
#include <sys/syslimits.h>
#include <time.h>
#include <signal.h>
#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
extern "C" int nanosleep(const struct timespec *rqtp, struct timespec *rmtp);
namespace Security {
namespace SecurityAgent {
using namespace Security;
using namespace MachPlusPlus;
#define COPY(copy) copy, copy.length(), copy
#define COPY_OUT(copy) ©, ©##Length, ©##Base
#define COPY_OUT_DECL(type,name) type *name, *name##Base; mach_msg_type_number_t name##Length
class Requestor {
public:
Requestor(const OSXCode *code) { if (code) extForm = code->encode(); }
operator const char * () const { return extForm.c_str(); }
private:
string extForm;
};
void Client::check(kern_return_t error)
{
switch (error) {
case KERN_SUCCESS: break;
case MIG_SERVER_DIED: stage = mainStage;
CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION);
default: stage = mainStage;
MachPlusPlus::Error::throwMe(error);
}
switch (status) {
case noErr:
case errAuthorizationDenied:
break;
case userCanceledErr:
unstage();
CssmError::throwMe(CSSM_ERRCODE_USER_CANCELED);
default:
unstage();
MacOSError::throwMe(status);
}
}
void Client::unstage()
{
if (stage != mainStage) {
mStagePort.deallocate();
stage = mainStage;
}
}
#if defined(NOSA)
#include <cstdarg>
static void getNoSA(char *buffer, size_t bufferSize, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vfprintf(stdout, fmt, args);
va_end(args);
memset(buffer, 0, bufferSize);
const char *nosa = getenv("NOSA");
if (!strcmp(nosa, "-")) {
if (fgets(buffer, bufferSize-1, stdin) == NULL)
CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION);
buffer[strlen(buffer)-1] = '\0'; if (!isatty(fileno(stdin)))
printf("%s\n", buffer); } else {
strncpy(buffer, nosa, bufferSize-1);
printf("%s\n", buffer);
}
if (buffer[0] == '\0') CssmError::throwMe(CSSM_ERRCODE_USER_CANCELED);
}
#endif //NOSA
Client::Client() : mActive(false), mUsePBS(true), mKeepAlive(false), stage(mainStage)
{
}
Client::Client(uid_t clientUID, Bootstrap clientBootstrap) :
mActive(false), desktopUid(clientUID), mUsePBS(false),
mClientBootstrap(clientBootstrap), mKeepAlive(false), stage(mainStage)
{
setClientGroupID();
debug("SAclnt", "Desktop: uid %d, gid %d", desktopUid, desktopGid);
}
Client::~Client()
{
terminate();
}
void Client::activate(const char *name)
{
if (!mActive) {
establishServer(name ? name : "SecurityAgent");
mClientPort.allocate(MACH_PORT_RIGHT_RECEIVE);
mClientPort.insertRight(MACH_MSG_TYPE_MAKE_SEND);
mServerPort.requestNotify(mClientPort, MACH_NOTIFY_DEAD_NAME, true);
mActive = true;
}
}
void Client::terminate()
{
if (mActive) {
mServerPort.deallocate();
mClientPort.destroy();
mActive = false;
}
}
void Client::cancel()
{
struct {
mach_msg_header_t Head;
NDR_record_t NDR;
kern_return_t result;
OSStatus status;
} request;
request.Head.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, 0);
request.Head.msgh_remote_port = mClientPort;
request.Head.msgh_local_port = MACH_PORT_NULL;
request.Head.msgh_id = cancelMessagePseudoID;
request.NDR = NDR_record;
request.result = KERN_SUCCESS;
request.status = noErr;
MachPlusPlus::check(mach_msg_overwrite(&request.Head, MACH_SEND_MSG|MACH_SEND_TIMEOUT,
sizeof(request), 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL, (mach_msg_header_t *) NULL, 0));
}
void Client::establishServer(const char *name)
{
if (mUsePBS)
locateDesktop();
else
pbsBootstrap = mClientBootstrap;
if (desktopUid != getuid() && getuid() != 0)
CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION);
Bootstrap bootstrap(pbsBootstrap);
if (mServerPort = bootstrap.lookupOptional(name))
return;
#if defined(AGENTNAME) && defined(AGENTPATH)
StBootstrap bootSaver(pbsBootstrap);
switch (pid_t pid = fork()) {
case 0: {
unsetenv("USER");
unsetenv("LOGNAME");
unsetenv("HOME");
debug("SAclnt", "setgid(%d)", desktopGid);
setgid(desktopGid); debug("SAclnt", "setuid(%d)", desktopUid);
setuid(desktopUid);
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);
debug("SAclnt", "execl(%s)", agentExecutable);
execl(agentExecutable, agentExecutable, NULL);
debug("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;
debug("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 = bootstrap.lookupOptional(name))
break;
int status;
switch (pid_t rc = waitpid(pid, &status, WNOHANG)) {
case 0: continue;
case -1: switch (errno) {
case EINTR:
case EAGAIN: continue;
case ECHILD: debug("SAclnt", "child is dead (reaped elsewhere)");
CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION);
default:
debug("SAclnt", "waitpid failed: errno=%d", errno);
UnixError::throwMe();
}
default:
assert(rc == pid);
debug("SAclnt", "child died without claiming the SecurityAgent port");
CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION);
}
}
if (mServerPort == 0) { debug("SAclnt", "Autolaunch failed");
CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION);
}
debug("SAclnt", "SecurityAgent located");
return;
}
}
#endif
debug("SAclnt", "Cannot contact SecurityAgent");
CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION); }
void Client::finishStagedQuery()
{
if (stage == mainStage)
CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR); #if defined(NOSA)
if (getenv("NOSA")) {
printf(" [query done]\n");
stage = mainStage;
return;
}
#endif
check(secagent_client_finishStagedQuery(mStagePort, mClientPort, &status));
unstage();
terminate();
}
void Client::cancelStagedQuery(Reason reason)
{
if (stage == mainStage)
CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR); #if defined(NOSA)
if (getenv("NOSA")) {
printf(" [query canceled; reason=%d]\n", reason);
stage = mainStage;
return;
}
#endif
check(secagent_client_cancelStagedQuery(mStagePort, mClientPort, &status, reason));
unstage();
terminate();
}
void Client::queryUnlockDatabase(const OSXCode *requestor, pid_t requestPid,
const char *database, char passphrase[maxPassphraseLength])
{
Requestor req(requestor);
#if defined(NOSA)
if (getenv("NOSA")) {
getNoSA(passphrase, maxPassphraseLength, "Unlock %s [<CR> to cancel]: ", database);
stage = unlockStage;
return;
}
#endif
activate();
check(secagent_client_unlockDatabase(mServerPort, mClientPort,
&status, req, requestPid, database, &mStagePort.port(), passphrase));
stage = unlockStage;
}
void Client::retryUnlockDatabase(Reason reason, char passphrase[maxPassphraseLength])
{
if (stage != unlockStage)
CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR); #if defined(NOSA)
if (getenv("NOSA")) {
getNoSA(passphrase, maxPassphraseLength, "Retry unlock [<CR> to cancel]: ");
return;
}
#endif
check(secagent_client_retryUnlockDatabase(mStagePort, mClientPort,
&status, reason, passphrase));
}
void Client::queryNewPassphrase(const OSXCode *requestor, pid_t requestPid,
const char *database, Reason reason, char passphrase[maxPassphraseLength])
{
Requestor req(requestor);
#if defined(NOSA)
if (getenv("NOSA")) {
getNoSA(passphrase, maxPassphraseLength,
"New passphrase for %s (reason %d) [<CR> to cancel]: ",
(database ? database : "[NULL database]"), reason);
stage = newPassphraseStage;
return;
}
#endif
activate();
check(secagent_client_queryNewPassphrase(mServerPort, mClientPort,
&status, req, requestPid, database, reason,
&mStagePort.port(), passphrase));
stage = newPassphraseStage;
}
void Client::retryNewPassphrase(Reason reason, char passphrase[maxPassphraseLength])
{
if (stage != newPassphraseStage)
CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR); #if defined(NOSA)
if (getenv("NOSA")) {
getNoSA(passphrase, maxPassphraseLength,
"retry new passphrase (reason %d) [<CR> to cancel]: ", reason);
return;
}
#endif
check(secagent_client_retryNewPassphrase(mStagePort, mClientPort,
&status, reason, passphrase));
}
void Client::queryKeychainAccess(const OSXCode *requestor, pid_t requestPid,
const char *database, const char *itemName, AclAuthorization action,
bool needPassphrase, KeychainChoice &choice)
{
Requestor req(requestor);
#if defined(NOSA)
if (getenv("NOSA")) {
char answer[maxPassphraseLength+10];
getNoSA(answer, sizeof(answer), "Allow [someone] to do %d on %s in %s? [yn][g]%s ",
int(action), (itemName ? itemName : "[NULL item]"),
(database ? database : "[NULL database]"),
needPassphrase ? ":passphrase" : "");
if (needPassphrase && !strchr(answer, ':')) {
memmove(answer+2, answer, strlen(answer)+1);
memcpy(answer, "y:", 2);
}
choice.allowAccess = answer[0] == 'y';
choice.continueGrantingToCaller = answer[1] == 'g';
if (const char *colon = strchr(answer, ':'))
strncpy(choice.passphrase, colon+1, maxPassphraseLength);
else
choice.passphrase[0] = '\0';
return;
}
#endif
activate();
check(secagent_client_queryKeychainAccess(mServerPort, mClientPort,
&status, req, requestPid, (database ? database : ""), itemName, action,
needPassphrase, &choice));
terminate();
}
void Client::queryOldGenericPassphrase(const OSXCode *requestor, pid_t requestPid,
const char *prompt,
KeychainBox &addToKeychain, char passphrase[maxPassphraseLength])
{
Requestor req(requestor);
#if defined(NOSA)
if (getenv("NOSA")) {
getNoSA(passphrase, maxPassphraseLength,
"Old passphrase (\"%s\") [<CR> to cancel]: ", prompt);
stage = oldGenericPassphraseStage;
return;
}
#endif
activate();
MigBoolean addBox = addToKeychain.setting;
check(secagent_client_queryOldGenericPassphrase(mServerPort, mClientPort,
&status, req, requestPid, prompt, &mStagePort.port(),
addToKeychain.show, &addBox, passphrase));
addToKeychain.setting = addBox;
stage = oldGenericPassphraseStage;
}
void Client::retryOldGenericPassphrase(Reason reason,
bool &addToKeychain, char passphrase[maxPassphraseLength])
{
if (stage != oldGenericPassphraseStage)
CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR); #if defined(NOSA)
if (getenv("NOSA")) {
getNoSA(passphrase, maxPassphraseLength,
"Retry old passphrase [<CR> to cancel]: ");
return;
}
#endif
MigBoolean addBox;
check(secagent_client_retryOldGenericPassphrase(mStagePort, mClientPort,
&status, reason, &addBox, passphrase));
addToKeychain = addBox;
}
void Client::queryNewGenericPassphrase(const OSXCode *requestor, pid_t requestPid,
const char *prompt, Reason reason,
KeychainBox &addToKeychain, char passphrase[maxPassphraseLength])
{
Requestor req(requestor);
#if defined(NOSA)
if (getenv("NOSA")) {
getNoSA(passphrase, maxPassphraseLength,
"New passphrase (\"%s\") (reason %d) [<CR> to cancel]: ",
prompt, reason);
stage = newGenericPassphraseStage;
return;
}
#endif
activate();
MigBoolean addBox = addToKeychain.setting;
check(secagent_client_queryNewGenericPassphrase(mServerPort, mClientPort,
&status, req, requestPid, prompt, reason,
&mStagePort.port(), addToKeychain.show, &addBox, passphrase));
addToKeychain.setting = addBox;
stage = newGenericPassphraseStage;
}
void Client::retryNewGenericPassphrase(Reason reason,
bool &addToKeychain, char passphrase[maxPassphraseLength])
{
if (stage != newGenericPassphraseStage)
CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR); #if defined(NOSA)
if (getenv("NOSA")) {
getNoSA(passphrase, maxPassphraseLength,
"retry new passphrase (reason %d) [<CR> to cancel]: ", reason);
return;
}
#endif
MigBoolean addBox;
check(secagent_client_retryNewGenericPassphrase(mStagePort, mClientPort,
&status, reason, &addBox, passphrase));
addToKeychain = addBox;
}
bool Client::authorizationAuthenticate(const OSXCode *requestor, pid_t requestPid,
const char *neededGroup, const char *candidateUser,
char user[maxUsernameLength], char passphrase[maxPassphraseLength])
{
Requestor req(requestor);
#if defined(NOSA)
if (getenv("NOSA")) {
getNoSA(user, maxUsernameLength,
"User to authenticate for group %s (try \"%s\" [\"-\" to deny]): ",
neededGroup, (candidateUser ? candidateUser : "[NULL]"));
if (strcmp(user, "-"))
getNoSA(passphrase, maxPassphraseLength,
"Passphrase for user %s: ", user);
stage = authorizeStage;
return strcmp(user, "-");
}
#endif
activate();
check(secagent_client_authorizationAuthenticate(mServerPort, mClientPort,
&status, req, requestPid, neededGroup, candidateUser, &mStagePort.port(), user, passphrase));
stage = authorizeStage;
return status == noErr;
}
bool Client::retryAuthorizationAuthenticate(Reason reason, char user[maxUsernameLength],
char passphrase[maxPassphraseLength])
{
if (stage != authorizeStage)
CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR); #if defined(NOSA)
if (getenv("NOSA")) {
getNoSA(user, maxUsernameLength,
"Retry authenticate (reason=%d) ([\"-\" to deny again]): ", reason);
if (strcmp(user, "-"))
getNoSA(passphrase, maxPassphraseLength,
"Passphrase for user %s: ", user);
return strcmp(user, "-");
}
#endif
check(secagent_client_retryAuthorizationAuthenticate(mStagePort, mClientPort,
&status, reason, user, passphrase));
return status == noErr;
}
bool Client::invokeMechanism(const string &inPluginId, const string &inMechanismId, const AuthorizationValueVector *inArguments, const AuthorizationItemSet *inHints, const AuthorizationItemSet *inContext, AuthorizationResult *outResult, AuthorizationItemSet *&outHintsPtr, AuthorizationItemSet *&outContextPtr)
{
Copier<AuthorizationValueVector> inArgumentVector(inArguments);
Copier<AuthorizationItemSet> inHintsSet(inHints);
Copier<AuthorizationItemSet> inContextSet(inContext);
COPY_OUT_DECL(AuthorizationItemSet, outHintsSet);
COPY_OUT_DECL(AuthorizationItemSet, outContextSet);
activate();
check(secagent_client_invokeMechanism(mServerPort, mClientPort,
&status, &mStagePort.port(),
inPluginId.c_str(),
inMechanismId.c_str(),
COPY(inArgumentVector),
COPY(inHintsSet),
COPY(inContextSet),
outResult,
COPY_OUT(outHintsSet),
COPY_OUT(outContextSet)));
if (status != errAuthorizationDenied)
{
relocate(outHintsSet, outHintsSetBase);
Copier<AuthorizationItemSet> copyHints(outHintsSet);
outHintsPtr = copyHints.keep();
relocate(outContextSet, outContextSetBase);
Copier<AuthorizationItemSet> copyContext(outContextSet);
outContextPtr = copyContext.keep();
}
return (status == noErr);
}
void Client::terminateAgent()
{
if (mUsePBS)
locateDesktop();
if (desktopUid != getuid() && getuid() != 0)
CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION);
bool agentRunning = false;
if (mUsePBS)
{
Bootstrap bootstrap(pbsBootstrap);
if (mServerPort = bootstrap.lookupOptional("SecurityAgent"))
agentRunning = true;
}
else
{
if (mServerPort = mClientBootstrap.lookupOptional("SecurityAgent"))
agentRunning = true;
}
if (agentRunning)
{
activate();
check(secagent_client_terminate(mServerPort, mClientPort));
}
}
#include <sys/types.h>
#include <grp.h>
void Client::setClientGroupID(const char *grpName)
{
struct group *grent = getgrnam(grpName ? grpName : "nobody");
desktopGid = grent ? grent->gr_gid : -2;
}
#include <sys/sysctl.h>
#include <mach/mach_error.h>
void Client::locateDesktop()
{
int mib[3] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL};
size_t bufSize;
struct kinfo_proc *procBuf;
if (sysctl(mib, 3, NULL, &bufSize, NULL, 0) < 0) {
perror("sysctl");
abort();
}
procBuf = (struct kinfo_proc *)malloc(bufSize); if (sysctl(mib, 3, procBuf, &bufSize, NULL, 0))
CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR);
int count = bufSize / sizeof(struct kinfo_proc);
struct kinfo_proc *pbsProc = NULL;
for (struct kinfo_proc *proc = procBuf; proc < procBuf + count; proc++) {
if (!strncmp(proc->kp_proc.p_comm, "pbs", MAXCOMLEN)) {
pbsProc = proc;
break;
}
}
if (!pbsProc) { debug("SAclnt", "No pasteboard server - no user logged in");
CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION);
}
desktopUid = pbsProc->kp_eproc.e_ucred.cr_uid;
desktopGid = pbsProc->kp_eproc.e_ucred.cr_gid;
pid_t pbsPid = pbsProc->kp_proc.p_pid;
debug("SAclnt", "Desktop has uid %d", desktopUid);
free(procBuf);
kern_return_t result;
mach_port_t pbsTaskPort;
result = task_for_pid(mach_task_self(), pbsPid, &pbsTaskPort);
if (result)
{
mach_error("task_for_pid(pbs)", result);
CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION);
}
result = task_get_bootstrap_port(pbsTaskPort, &pbsBootstrap);
if (result)
{
mach_error("task_get_bootstrap_port(pbs)", result);
CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION);
}
}
} }