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>
extern "C" int nanosleep(const struct timespec *rqtp, struct timespec *rmtp);
namespace Security {
namespace SecurityAgent {
using namespace Security;
using namespace MachPlusPlus;
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), mKeepAlive(false), stage(mainStage)
{
}
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)
{
locateDesktop();
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);
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,
Client::KeychainChoice &choice)
{
Requestor req(requestor);
#if defined(NOSA)
if (getenv("NOSA")) {
char answer[10];
getNoSA(answer, sizeof(answer), "Allow [someone] to do %d on %s in %s? ",
int(action), (itemName ? itemName : "[NULL item]"),
(database ? database : "[NULL database]"));
choice.allowAccess = answer[0] == 'y';
choice.continueGrantingToCaller = answer[1] == 'g';
return;
}
#endif
activate();
check(secagent_client_queryKeychainAccess(mServerPort, mClientPort,
&status, req, requestPid, (database ? database : ""), itemName, action, &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;
}
#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);
}
}
} }