/* * Copyright (c) 2000-2004,2009 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ // // server - securityd main server object // #include // MIG ucsp service #include "self.h" // MIG self service #include #include #include "server.h" #include "session.h" #include "acls.h" #include "notifications.h" #include "child.h" #include #include #include "pcscmonitor.h" #include "agentquery.h" using namespace MachPlusPlus; // // Construct an Authority // Authority::Authority(const char *configFile) : Authorization::Engine(configFile) { } Authority::~Authority() { } // // Construct the server object // Server::Server(Authority &authority, CodeSignatures &signatures, const char *bootstrapName) : MachServer(bootstrapName), mBootstrapName(bootstrapName), mCSPModule(gGuidAppleCSP, mCssm), mCSP(mCSPModule), mAuthority(authority), mCodeSignatures(signatures), mVerbosity(0), mWaitForClients(true), mShuttingDown(false) { // make me eternal (in the object mesh) ref(); // engage the subsidiary port handler for sleep notifications add(sleepWatcher); } // // Clean up the server object // Server::~Server() { //@@@ more later } // // Locate a connection by reply port and make it the current connection // of this thread. The connection will be marked busy, and can be accessed // by calling Server::connection() [no argument] until it is released by // calling Connection::endWork(). // Connection &Server::connection(mach_port_t port, audit_token_t &auditToken) { Server &server = active(); StLock _(server); Connection *conn = server.mConnections.get(port, CSSM_ERRCODE_INVALID_CONTEXT_HANDLE); conn->process().checkSession(auditToken); active().mCurrentConnection() = conn; conn->beginWork(auditToken); return *conn; } Connection &Server::connection(bool tolerant) { Connection *conn = active().mCurrentConnection(); assert(conn); // have to have one if (!tolerant) conn->checkWork(); return *conn; } void Server::requestComplete(CSSM_RETURN &rcode) { // note: there may not be an active connection if connection setup failed if (RefPointer &conn = active().mCurrentConnection()) { conn->endWork(rcode); conn = NULL; } IFDUMPING("state", NodeCore::dumpAll()); } // // Shorthand for "current" process and session. // This is the process and session for the current connection. // Process &Server::process() { return connection().process(); } Session &Server::session() { return connection().process().session(); } RefPointer Server::key(KeyHandle key) { return U32HandleObject::findRef(key, CSSMERR_CSP_INVALID_KEY_REFERENCE); } RefPointer Server::database(DbHandle db) { return find(db, CSSMERR_DL_INVALID_DB_HANDLE); } RefPointer Server::keychain(DbHandle db) { return find(db, CSSMERR_DL_INVALID_DB_HANDLE); } RefPointer Server::optionalDatabase(DbHandle db, bool persistent) { if (persistent && db != noDb) return database(db); else return &process().localStore(); } // // 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, U32HandleObject::Handle handle) { AclSource &bearer = U32HandleObject::find(handle, CSSMERR_CSSM_INVALID_ADDIN_HANDLE); if (kind != bearer.acl().aclKind()) CssmError::throwMe(CSSMERR_CSSM_INVALID_HANDLE_USAGE); return bearer; } // // Run the server. This will not return until the server is forced to exit. // void Server::run() { MachServer::run(0x10000, MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0) | MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT)); } // // Handle thread overflow. MachServer will call this if it has hit its thread // limit and yet still needs another thread. // void Server::threadLimitReached(UInt32 limit) { Syslog::notice("securityd has reached its thread limit (%ld) - service deadlock is possible", limit); } // // The primary server run-loop function. // Invokes the MIG-generated main dispatch function (ucsp_server), as well // as the self-send dispatch (self_server). // For debug builds, look up request names in a MIG-generated table // for better debug-log messages. // boolean_t ucsp_server(mach_msg_header_t *, mach_msg_header_t *); boolean_t self_server(mach_msg_header_t *, mach_msg_header_t *); boolean_t Server::handle(mach_msg_header_t *in, mach_msg_header_t *out) { return ucsp_server(in, out) || self_server(in, out); } // // Set up a new Connection. This establishes the environment (process et al) as needed // and registers a properly initialized Connection object to run with. // Type indicates how "deep" we need to initialize (new session, process, or connection). // 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 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 _(*this); RefPointer &proc = mProcesses[taskPort]; if (proc && proc->session().sessionId() != audit.sessionId()) proc->changeSession(audit.sessionId()); if (proc && type == connectNewProcess) { // the client has amnesia - reset it 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); proc = new Process(taskPort, info, audit); notifyIfDead(taskPort); mPids[proc->pid()] = proc; } // now, establish a connection and register it in the server Connection *connection = new Connection(*proc, replyPort); if (mConnections.contains(replyPort)) // malicious re-entry attempt? CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR); //@@@ error code? (client error) mConnections[replyPort] = connection; notifyIfDead(replyPort); } // // Synchronously end a Connection. // This is due to a request from the client, so no thread races are possible. // In practice, this is optional since the DPN for the client thread reply port // will destroy the connection anyway when the thread dies. // void Server::endConnection(Port replyPort) { StLock _(*this); PortMap::iterator it = mConnections.find(replyPort); assert(it != mConnections.end()); it->second->terminate(); mConnections.erase(it); } // // Handling dead-port notifications. // This receives DPNs for all kinds of ports we're interested in. // void Server::notifyDeadName(Port port) { // 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 serverLock(*this); secdebug("SSports", "port %d is dead", port.port()); // is it a connection? PortMap::iterator conIt = mConnections.find(port); if (conIt != mConnections.end()) { SECURITYD_PORTS_DEAD_CONNECTION(port); RefPointer con = conIt->second; mConnections.erase(conIt); serverLock.unlock(); con->abort(); return; } // is it a process? PortMap::iterator procIt = mProcesses.find(port); if (procIt != mProcesses.end()) { SECURITYD_PORTS_DEAD_PROCESS(port); RefPointer 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 _(*this); proc->kill(); return; } // well, what IS IT?! SECURITYD_PORTS_DEAD_ORPHAN(port); secdebug("server", "spurious dead port notification for port %d", port.port()); } // // Handling no-senders notifications. // This is currently only used for (subsidiary) service ports // void Server::notifyNoSenders(Port port, mach_port_mscount_t) { SECURITYD_PORTS_DEAD_SESSION(port); } // // Handling signals. // These are sent as Mach messages from ourselves to escape the limitations of // the signal handler environment. // kern_return_t self_server_handleSignal(mach_port_t sport, 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"); return KERN_SUCCESS; } switch (sig) { case SIGCHLD: ServerChild::checkChildren(); break; case SIGINT: SECURITYD_SHUTDOWN_NOW(); Syslog::notice("securityd terminated due to SIGINT"); _exit(0); case SIGTERM: Server::active().beginShutdown(); break; case SIGPIPE: fprintf(stderr, "securityd ignoring SIGPIPE received"); break; #if defined(DEBUGDUMP) case SIGUSR1: NodeCore::dumpAll(); break; #endif //DEBUGDUMP case SIGUSR2: { extern PCSCMonitor *gPCSC; gPCSC->startSoftTokens(); break; } default: assert(false); } } catch(...) { secdebug("SS", "exception handling a signal (ignored)"); } mach_port_deallocate(mach_task_self(), taskPort); return KERN_SUCCESS; } 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() { SECURITYD_POWER_SLEEP(); Session::processSystemSleep(); for (set::const_iterator it = mPowerClients.begin(); it != mPowerClients.end(); it++) (*it)->systemWillSleep(); } void Server::SleepWatcher::systemIsWaking() { SECURITYD_POWER_WAKE(); for (set::const_iterator it = mPowerClients.begin(); it != mPowerClients.end(); it++) (*it)->systemIsWaking(); } void Server::SleepWatcher::systemWillPowerOn() { SECURITYD_POWER_ON(); Server::active().longTermActivity(); for (set::const_iterator it = mPowerClients.begin(); it != mPowerClients.end(); it++) (*it)->systemWillPowerOn(); } void Server::SleepWatcher::add(PowerWatcher *client) { assert(mPowerClients.find(client) == mPowerClients.end()); mPowerClients.insert(client); } void Server::SleepWatcher::remove(PowerWatcher *client) { assert(mPowerClients.find(client) != mPowerClients.end()); mPowerClients.erase(client); } // // Expose the process/pid map to the outside // Process *Server::findPid(pid_t pid) const { PidMap::const_iterator it = mPids.find(pid); return (it == mPids.end()) ? NULL : it->second; } // // Set delayed shutdown mode // void Server::waitForClients(bool waiting) { mWaitForClients = waiting; } // // Begin shutdown processing. // We relinquish our primary state authority. From now on, we'll be // kept alive (only) by our current clients. // static FILE *reportFile; void Server::beginShutdown() { StLock _(*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 (this->shuttingDown()) { StLock 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()); } } void Server::shutdownSnitch() { 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 path; OSStatus rc = SecCodeCopyPath(clientCode, kSecCSDefaultFlags, &path.aref()); if (path) fprintf(reportFile, " %s (%d)\n", cfString(path).c_str(), it->first); else fprintf(reportFile, "pid=%d (error %d)\n", it->first, int32_t(rc)); } fprintf(reportFile, "\n"); fflush(reportFile); } // // Initialize the CSSM/MDS subsystem. // This was once done lazily on demand. These days, we are setting up the // 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(bool mdsIsInstalled) { if (!mCssm->isActive()) { StLock _(*this); VProc::Transaction xact; if (!mCssm->isActive()) { 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(); secdebug("SS", "CSSM ready with CSP %s", mCSP->guid().toString().c_str()); } } } // // LongtermActivity/lock combo // LongtermStLock::LongtermStLock(Mutex &lck) : StLock(lck, false) // don't take the lock yet { if (lck.tryLock()) { // uncontested this->mActive = true; } else { // contested - need backup thread Server::active().longTermActivity(); this->lock(); } }