server.cpp   [plain text]


/*
 * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved.
 * 
 * The contents of this file constitute Original Code as defined in and are
 * subject to the Apple Public Source License Version 1.2 (the 'License').
 * You may not use this file except in compliance with the License. Please obtain
 * a copy of the License at http://www.apple.com/publicsource and read it before
 * using this file.
 * 
 * This 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.
 */


//
// server - the actual SecurityServer server object
//
#include "server.h"
#include "session.h"
#include "acls.h"
#include "notifications.h"
#include "ucsp.h"
#include <mach/mach_error.h>

using namespace MachPlusPlus;


//
// Construct the server object
//
Server::Server(Authority &authority, CodeSignatures &signatures, const char *bootstrapName)
  : MachServer(bootstrapName),
    mBootstrapName(bootstrapName),
    mCurrentConnection(false),
    mCSPModule(gGuidAppleCSP, mCssm), mCSP(mCSPModule),
    mAuthority(authority),
	mCodeSignatures(signatures)
{
    // 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)
{
	Server &server = active();
	StLock<Mutex> _(server.lock);
	ConnectionMap::iterator it = server.connections.find(port);
	if (it == server.connections.end()) // unknown client port -- could be a hack attempt
		CssmError::throwMe(CSSM_ERRCODE_INVALID_CONTEXT_HANDLE);
	Connection *conn = it->second;
	active().mCurrentConnection = conn;
	conn->beginWork();
	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()
{
	// note: there may not be an active connection if connection setup failed
	if (Connection *conn = active().mCurrentConnection) {
		if (conn->endWork())
			delete conn;
		active().mCurrentConnection = NULL;
	}
}


//
// Locate an ACL bearer (database or key) by handle
//
SecurityServerAcl &Server::aclBearer(AclKind kind, CSSM_HANDLE handle)
{
	SecurityServerAcl &bearer = findHandle<SecurityServerAcl>(handle);
	if (kind != bearer.kind())
		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_SENDER));
}


//
// The primary server run-loop function.
// Invokes the MIG-generated main dispatch function (ucsp_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 *);

#if defined(NDEBUG)

boolean_t Server::handle(mach_msg_header_t *in, mach_msg_header_t *out)
{
	return ucsp_server(in, out);
}

#else //NDEBUG

static const struct IPCName { const char *name; int ipc; } ipcNames[] =
    { subsystem_to_name_map_ucsp };	// macro generated by MIG, from ucsp.h

boolean_t Server::handle(mach_msg_header_t *in, mach_msg_header_t *out)
{
    const int first = ipcNames[0].ipc;
    const char *name = (in->msgh_id >= first && in->msgh_id < first + ucsp_MSG_COUNT) ?
		ipcNames[in->msgh_id - first].name : "OUT OF BOUNDS";
    secdebug("SSreq", "begin %s (%d)", name, in->msgh_id);
	boolean_t result = ucsp_server(in, out);
    secdebug("SSreq", "end %s (%d)", name, in->msgh_id);
    return result;
}

#endif //NDEBUG


//
// 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 servicePort, Port replyPort, Port taskPort,
    const security_token_t &securityToken, const ClientSetupInfo *info, const char *identity)
{
	// first, make or find the process based on task port
	StLock<Mutex> _(lock);
	Process * &proc = processes[taskPort];
	if (type == connectNewSession && proc) {
		// The client has talked to us before and now wants to create a new session.
		// We'll unmoor the old process object and cast it adrift (it will die either now
		// or later following the usual deferred-death mechanics).
		// The connection object will die (it's probably already dead) because the client
		// has destroyed its replyPort. So we don't worry about this here.
		secdebug("server", "session setup - marooning old process %p(%d) of session %p",
			proc, proc->pid(), &proc->session);
		if (proc->kill(true))
			delete proc;
		proc = NULL;
	}
	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,
			securityToken.val[0], securityToken.val[1]);
		notifyIfDead(taskPort);
	}

	// now, establish a connection and register it in the server
	Connection *connection = new Connection(*proc, replyPort);
	if (connections[replyPort])	// malicious re-entry attempt?
		CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR);	//@@@ error code? (client error)
	connections[replyPort] = connection;
	notifyIfDead(replyPort);
}


//
// Synchronously end a Connection.
// This is due to a request from the client, so no thread races are possible.
//
void Server::endConnection(Port replyPort)
{
	StLock<Mutex> _(lock);
	Connection *connection = connections[replyPort];
	assert(connection);
	connections.erase(replyPort);
	connection->terminate();
	delete connection;
}


//
// Handling dead-port notifications.
// This receives DPNs for all kinds of ports we're interested in.
//
void Server::notifyDeadName(Port port)
{
	StLock<Mutex> _(lock);
	secdebug("SSports", "port %d is dead", port.port());
    
    // is it a connection?
    ConnectionMap::iterator conIt = connections.find(port);
    if (conIt != connections.end()) {
        Connection *connection = conIt->second;
		if (connection->abort())
			delete connection;
		connections.erase(conIt);
        return;
    }
    
    // is it a process?
    ProcessMap::iterator procIt = processes.find(port);
    if (procIt != processes.end()) {
        Process *process = procIt->second;
		if (process->kill())
			delete process;
		processes.erase(procIt);
        return;
    }
    
    // is it a notification client?
    if (Listener::remove(port))
        return;
    
	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)
{
	secdebug("SSports", "port %d no senders", port.port());
	Session::eliminate(port);
}


//
// Notifier for system sleep events
//
void Server::SleepWatcher::systemWillSleep()
{
    secdebug("SS", "sleep notification received");
    Session::lockAllDatabases(true);
}


//
// Return the primary Cryptographic Service Provider.
// This will be lazily loaded when it is first requested.
//
CssmClient::CSP &Server::getCsp()
{
    if (!mCssm->isActive())
		loadCssm();
    return mCSP;
}


//
// Initialize the CSSM/MDS subsystem.
// This is thread-safe and can be done lazily.
//
static void initMds();

void Server::loadCssm()
{
	if (!mCssm->isActive()) {
		StLock<Mutex> _(lock);
		if (!mCssm->isActive()) {
			try {
				initMds();
			} catch (const CssmError &error) {
				switch (error.cssmError()) {
				case CSSMERR_DL_MDS_ERROR:
				case CSSMERR_DL_OS_ACCESS_DENIED:
					secdebug("SS", "MDS initialization failed; continuing");
					Syslog::warning("MDS initialization failed; continuing");
					break;
				default:
					throw;
				}
			}
			secdebug("SS", "CSSM initializing");
			mCssm->init();
			mCSP->attach();
			IFDEBUG(char guids[Guid::stringRepLength+1]);
			secdebug("SS", "CSSM ready with CSP %s", mCSP->guid().toString(guids));
		}
	}
}

#include <Security/mds.h>

static void initMds()
{
	secdebug("SS", "MDS initializing");
	CssmAllocatorMemoryFunctions memory(CssmAllocator::standard());
	MDS_FUNCS functions;
	MDS_HANDLE handle;
	CssmError::check(MDS_Initialize(NULL, &memory, &functions, &handle));
	CssmError::check(MDS_Install(handle));
	CssmError::check(MDS_Terminate(handle));
}