]> git.saurik.com Git - apple/security.git/blobdiff - securityd/src/server.cpp
Security-57031.1.35.tar.gz
[apple/security.git] / securityd / src / server.cpp
diff --git a/securityd/src/server.cpp b/securityd/src/server.cpp
new file mode 100644 (file)
index 0000000..c7f0e26
--- /dev/null
@@ -0,0 +1,567 @@
+/*
+ * Copyright (c) 2000-2010,2013 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 <securityd_client/ucsp.h>     // MIG ucsp service
+#include "self.h"                                      // MIG self service
+#include <security_utilities/logging.h>
+#include <security_cdsa_client/mdsclient.h>
+#include "server.h"
+#include "session.h"
+#include "acls.h"
+#include "notifications.h"
+#include "child.h"
+#include <mach/mach_error.h>
+#include <security_utilities/ccaudit.h>
+#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<Mutex> _(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<Connection> &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<Key> Server::key(KeyHandle key)
+{
+       return U32HandleObject::findRef<Key>(key, CSSMERR_CSP_INVALID_KEY_REFERENCE);
+}
+
+RefPointer<Database> Server::database(DbHandle db)
+{
+       return find<Database>(db, CSSMERR_DL_INVALID_DB_HANDLE);
+}
+
+RefPointer<KeychainDatabase> Server::keychain(DbHandle db)
+{
+       return find<KeychainDatabase>(db, CSSMERR_DL_INVALID_DB_HANDLE);
+}
+
+RefPointer<Database> 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<AclSource>(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<Mutex> _(*this);
+       RefPointer<Process> &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<Mutex> _(*this);
+       PortMap<Connection>::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<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()) {
+               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()) {
+               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());
+}
+
+
+//
+// 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<PowerWatcher *>::const_iterator it = mPowerClients.begin(); it != mPowerClients.end(); it++)
+               (*it)->systemWillSleep();
+}
+
+void Server::SleepWatcher::systemIsWaking()
+{
+       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());
+       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<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 (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());
+       }
+}
+
+
+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<CFURLRef> 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);
+}
+
+bool Server::inDarkWake()
+{
+    return IOPMIsADarkWake(IOPMConnectionGetSystemCapabilities());
+}
+
+//
+// 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<Mutex> _(*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<Mutex>(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();
+       }
+}