/*
- * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved.
+ * Copyright (c) 2000-2004,2009 Apple Inc. All Rights Reserved.
*
* @APPLE_LICENSE_HEADER_START@
*
#include "child.h"
#include <mach/mach_error.h>
#include <security_utilities/ccaudit.h>
+#include "pcscmonitor.h"
#include "agentquery.h"
{
}
+
//
// Construct the server object
//
Server::Server(Authority &authority, CodeSignatures &signatures, const char *bootstrapName)
: MachServer(bootstrapName),
mBootstrapName(bootstrapName),
- mShutdown(shutdownImmediately),
mCSPModule(gGuidAppleCSP, mCssm), mCSP(mCSPModule),
mAuthority(authority),
mCodeSignatures(signatures),
- mAudit(geteuid(), getpid())
+ mVerbosity(0),
+ mWaitForClients(true), mShuttingDown(false)
{
// make me eternal (in the object mesh)
ref();
- mAudit.registerSession();
-
// engage the subsidiary port handler for sleep notifications
add(sleepWatcher);
}
// by calling Server::connection() [no argument] until it is released by
// calling Connection::endWork().
//
-Connection &Server::connection(mach_port_t port)
+Connection &Server::connection(mach_port_t port, audit_token_t &auditToken)
{
Server &server = active();
StLock<Mutex> _(server);
Connection *conn = server.mConnections.get(port, CSSM_ERRCODE_INVALID_CONTEXT_HANDLE);
+ conn->process().checkSession(auditToken);
active().mCurrentConnection() = conn;
- conn->beginWork();
+ conn->beginWork(auditToken);
return *conn;
}
RefPointer<Key> Server::key(KeyHandle key)
{
- return HandleObject::findRef<Key>(key, CSSMERR_CSP_INVALID_KEY_REFERENCE);
+ return U32HandleObject::findRef<Key>(key, CSSMERR_CSP_INVALID_KEY_REFERENCE);
}
RefPointer<Database> Server::database(DbHandle db)
//
// Locate an ACL bearer (database or key) by handle
+// The handle might be used across IPC, so we clamp it accordingly
//
-AclSource &Server::aclBearer(AclKind kind, CSSM_HANDLE handle)
+AclSource &Server::aclBearer(AclKind kind, U32HandleObject::Handle handle)
{
- AclSource &bearer = HandleObject::find<AclSource>(handle, CSSMERR_CSSM_INVALID_ADDIN_HANDLE);
+ AclSource &bearer = U32HandleObject::find<AclSource>(handle, CSSMERR_CSSM_INVALID_ADDIN_HANDLE);
if (kind != bearer.acl().aclKind())
CssmError::throwMe(CSSMERR_CSSM_INVALID_HANDLE_USAGE);
return bearer;
boolean_t self_server(mach_msg_header_t *, mach_msg_header_t *);
-#if !defined(NDEBUG)
-
-struct IPCName { const char *name; int ipc; };
-static IPCName ucspNames[] = { subsystem_to_name_map_ucsp }; // generated by MIG
-static IPCName selfNames[] = { subsystem_to_name_map_self }; // generated by MIG
-
-#endif //NDEBUG
-
boolean_t Server::handle(mach_msg_header_t *in, mach_msg_header_t *out)
{
-#if !defined(NDEBUG)
- const int id = in->msgh_id;
- const int ucspBase = ucspNames[0].ipc;
- const int selfBase = selfNames[0].ipc;
- const char *name =
- (id >= ucspBase && id < ucspBase + ucsp_MSG_COUNT) ? ucspNames[id - ucspBase].name :
- (id >= selfBase && id < selfBase + self_MSG_COUNT) ? selfNames[id - selfBase].name :
- "OUT OF BOUNDS";
- secdebug("SSreq", "begin %s (%d)", name, in->msgh_id);
-#endif //NDEBUG
-
- boolean_t result = ucsp_server(in, out) || self_server(in, out);
- IFDEBUG(secdebug("SSreq", "end %s (%d)", name, in->msgh_id));
-
- return result;
+ return ucsp_server(in, out) || self_server(in, out);
}
// Everything at and below that level is constructed. This is straight-forward except
// in the case of session re-initialization (see below).
//
-void Server::setupConnection(ConnectLevel type, Port servicePort, Port replyPort, Port taskPort,
- const audit_token_t &auditToken, const ClientSetupInfo *info, const char *identity)
+void Server::setupConnection(ConnectLevel type, Port replyPort, Port taskPort,
+ const audit_token_t &auditToken, const ClientSetupInfo *info)
{
+ AuditToken audit(auditToken);
+
// first, make or find the process based on task port
StLock<Mutex> _(*this);
RefPointer<Process> &proc = mProcesses[taskPort];
- if (type == connectNewSession && proc) {
- // The client has talked to us before and now wants to create a new session.
- proc->changeSession(servicePort);
- }
+ if (proc && proc->session().sessionId() != audit.sessionId())
+ proc->changeSession(audit.sessionId());
if (proc && type == connectNewProcess) {
// the client has amnesia - reset it
- assert(info && identity);
- proc->reset(servicePort, taskPort, info, identity, AuditToken(auditToken));
- proc->changeSession(servicePort);
+ assert(info);
+ proc->reset(taskPort, info, audit);
+ proc->changeSession(audit.sessionId());
}
if (!proc) {
if (type == connectNewThread) // client error (or attack)
CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR);
- assert(info && identity);
- proc = new Process(servicePort, taskPort, info, identity, AuditToken(auditToken));
+ assert(info);
+ proc = new Process(taskPort, info, audit);
notifyIfDead(taskPort);
mPids[proc->pid()] = proc;
}
//
void Server::notifyDeadName(Port port)
{
- StLock<Mutex> _(*this);
+ // We need the lock to get a proper iterator on mConnections or mProcesses,
+ // but must release it before we call abort or kill, as these might take
+ // unbounded time, including calls out to token daemons etc.
+
+ StLock<Mutex> serverLock(*this);
secdebug("SSports", "port %d is dead", port.port());
// is it a connection?
PortMap<Connection>::iterator conIt = mConnections.find(port);
if (conIt != mConnections.end()) {
- conIt->second->abort();
+ SECURITYD_PORTS_DEAD_CONNECTION(port);
+ RefPointer<Connection> con = conIt->second;
mConnections.erase(conIt);
+ serverLock.unlock();
+ con->abort();
return;
}
// is it a process?
PortMap<Process>::iterator procIt = mProcesses.find(port);
if (procIt != mProcesses.end()) {
- Process *proc = procIt->second;
- proc->kill();
+ SECURITYD_PORTS_DEAD_PROCESS(port);
+ RefPointer<Process> proc = procIt->second;
mPids.erase(proc->pid());
mProcesses.erase(procIt);
+ serverLock.unlock();
+ // The kill may take some time; make sure there is a spare thread around
+ // to prevent deadlocks
+ StLock<MachServer, &Server::busy, &Server::idle> _(*this);
+ proc->kill();
return;
}
// well, what IS IT?!
+ SECURITYD_PORTS_DEAD_ORPHAN(port);
secdebug("server", "spurious dead port notification for port %d", port.port());
}
//
void Server::notifyNoSenders(Port port, mach_port_mscount_t)
{
- secdebug("SSports", "port %d no senders", port.port());
- Session::destroy(port);
+ SECURITYD_PORTS_DEAD_SESSION(port);
}
mach_port_t taskPort, int sig)
{
try {
+ SECURITYD_SIGNAL_HANDLED(sig);
if (taskPort != mach_task_self()) {
Syslog::error("handleSignal: received from someone other than myself");
- secdebug("SS", "unauthorized handleSignal");
return KERN_SUCCESS;
}
- secdebug("SS", "dispatching indirect signal %d", sig);
switch (sig) {
case SIGCHLD:
ServerChild::checkChildren();
break;
case SIGINT:
- secdebug("SS", "SIGINT received: terminating immediately");
+ SECURITYD_SHUTDOWN_NOW();
Syslog::notice("securityd terminated due to SIGINT");
- exit(0);
+ _exit(0);
case SIGTERM:
- if (Server::active().beginShutdown()) {
- Syslog::notice("securityd shutting down; lingering for remaining clients");
- } else {
- secdebug("SS", "SIGTERM received: terminating immediately");
- Syslog::notice("securityd terminated due to SIGTERM");
- exit(0);
- }
+ Server::active().beginShutdown();
break;
case SIGPIPE:
- secdebug("SS", "SIGPIPE received: ignoring");
- Syslog::notice("securityd ignoring SIGPIPE received");
+ fprintf(stderr, "securityd ignoring SIGPIPE received");
break;
#if defined(DEBUGDUMP)
NodeCore::dumpAll();
break;
#endif //DEBUGDUMP
+
+ case SIGUSR2:
+ {
+ extern PCSCMonitor *gPCSC;
+ gPCSC->startSoftTokens();
+ break;
+ }
+
default:
assert(false);
}
}
+kern_return_t self_server_handleSession(mach_port_t sport,
+ mach_port_t taskPort, uint32_t event, uint64_t ident)
+{
+ try {
+ if (taskPort != mach_task_self()) {
+ Syslog::error("handleSession: received from someone other than myself");
+ return KERN_SUCCESS;
+ }
+ if (event == AUE_SESSION_CLOSE)
+ Session::destroy(ident);
+ } catch(...) {
+ secdebug("SS", "exception handling a signal (ignored)");
+ }
+ mach_port_deallocate(mach_task_self(), taskPort);
+ return KERN_SUCCESS;
+}
+
+
//
// Notifier for system sleep events
//
void Server::SleepWatcher::systemWillSleep()
{
- secdebug("SS", "sleep notification received");
+ SECURITYD_POWER_SLEEP();
Session::processSystemSleep();
- secdebug("server", "distributing sleep event to %ld clients", mPowerClients.size());
for (set<PowerWatcher *>::const_iterator it = mPowerClients.begin(); it != mPowerClients.end(); it++)
(*it)->systemWillSleep();
}
void Server::SleepWatcher::systemIsWaking()
{
- secdebug("server", "distributing wakeup event to %ld clients", mPowerClients.size());
+ SECURITYD_POWER_WAKE();
for (set<PowerWatcher *>::const_iterator it = mPowerClients.begin(); it != mPowerClients.end(); it++)
(*it)->systemIsWaking();
}
+void Server::SleepWatcher::systemWillPowerOn()
+{
+ SECURITYD_POWER_ON();
+ Server::active().longTermActivity();
+ for (set<PowerWatcher *>::const_iterator it = mPowerClients.begin(); it != mPowerClients.end(); it++)
+ (*it)->systemWillPowerOn();
+}
+
void Server::SleepWatcher::add(PowerWatcher *client)
{
assert(mPowerClients.find(client) == mPowerClients.end());
//
void Server::waitForClients(bool waiting)
{
- if (mShutdown == shuttingDown) // too late to change your mind now
- return;
- if (waiting)
- mShutdown = shutdownDelayed;
- else
- mShutdown = shutdownImmediately;
+ mWaitForClients = waiting;
}
//
-// Shutdown processing
+// Begin shutdown processing.
+// We relinquish our primary state authority. From now on, we'll be
+// kept alive (only) by our current clients.
//
-bool Server::beginShutdown()
-{
- if (mShutdown != shutdownDelayed)
- return false;
+static FILE *reportFile;
- secdebug("server", "beginning shutdown with %d client(s)", int(mProcesses.size()));
- mShutdown = shuttingDown;
-
-#if defined(SHUTDOWN_SNITCH)
- struct Snitch : public MachServer::Timer {
- void action() { Server::active().shutdownSnitch(); }
- };
- setTimer(new Snitch, Time::Interval(29)); // right before we get SIGKILLed
-#endif
-
- return true;
+void Server::beginShutdown()
+{
+ StLock<Mutex> _(*this);
+ if (!mWaitForClients) {
+ SECURITYD_SHUTDOWN_NOW();
+ _exit(0);
+ } else {
+ if (!mShuttingDown) {
+ mShuttingDown = true;
+ Session::invalidateAuthHosts();
+ SECURITYD_SHUTDOWN_BEGIN();
+ if (verbosity() >= 2) {
+ reportFile = fopen("/var/log/securityd-shutdown.log", "w");
+ shutdownSnitch();
+ }
+ }
+ }
}
+//
+// During shutdown, we report residual clients to dtrace, and allow a state dump
+// for debugging.
+// We don't bother locking for the shuttingDown() check; it's a latching boolean
+// and we'll be good enough without a lock.
+//
void Server::eventDone()
{
- if (mShutdown == shuttingDown) {
- if (mProcesses.empty()) {
- secdebug("SS", "out of clients - shutdown complete");
- Syslog::notice("securityd has finished serving its clients - terminating now");
- exit(0);
- } else {
- secdebug("SS", "shutdown in progress - %d process(es) left", int(mProcesses.size()));
- IFDUMPING("shutdown", NodeCore::dumpAll());
+ if (this->shuttingDown()) {
+ StLock<Mutex> lazy(*this, false); // lazy lock acquisition
+ if (SECURITYD_SHUTDOWN_COUNT_ENABLED()) {
+ lazy.lock();
+ SECURITYD_SHUTDOWN_COUNT(mProcesses.size(), VProc::Transaction::debugCount());
}
+ if (verbosity() >= 2) {
+ lazy.lock();
+ shutdownSnitch();
+ }
+ IFDUMPING("shutdown", NodeCore::dumpAll());
}
}
-#if defined(SHUTDOWN_SNITCH)
void Server::shutdownSnitch()
{
- Syslog::notice("29 seconds after shutdown began, securityd still has %d clients:", int(mPids.size()));
+ time_t now;
+ time(&now);
+ fprintf(reportFile, "%.24s %d residual clients:\n", ctime(&now), int(mPids.size()));
for (PidMap::const_iterator it = mPids.begin(); it != mPids.end(); ++it)
if (SecCodeRef clientCode = it->second->processCode()) {
CFRef<CFURLRef> path;
- SecCodeCopyPath(clientCode, kSecCSDefaultFlags, &path.aref());
+ OSStatus rc = SecCodeCopyPath(clientCode, kSecCSDefaultFlags, &path.aref());
if (path)
- Syslog::notice(" %s (%d)", cfString(path).c_str(), it->first);
+ fprintf(reportFile, " %s (%d)\n", cfString(path).c_str(), it->first);
else
- Syslog::notice(" pid=%d", it->first);
+ fprintf(reportFile, "pid=%d (error %d)\n", it->first, int32_t(rc));
}
+ fprintf(reportFile, "\n");
+ fflush(reportFile);
}
-#endif //SHUTDOWN_SNITCH
-
//
// Initialize the CSSM/MDS subsystem.
// system MDS here, and CSSM is pretty much always needed, so this is called
// early during program startup. Do note that the server may not (yet) be running.
//
-void Server::loadCssm()
+void Server::loadCssm(bool mdsIsInstalled)
{
if (!mCssm->isActive()) {
StLock<Mutex> _(*this);
+ VProc::Transaction xact;
if (!mCssm->isActive()) {
- secdebug("SS", "Installing MDS");
- IFDEBUG(if (geteuid() == 0))
+ if (!mdsIsInstalled) { // non-system securityd instance should not reinitialize MDS
+ secdebug("SS", "Installing MDS");
+ IFDEBUG(if (geteuid() == 0))
MDSClient::mds().install();
+ }
secdebug("SS", "CSSM initializing");
mCssm->init();
mCSP->attach();