mirror of
https://github.com/macports/pallet.git
synced 2026-07-12 18:18:47 -07:00
git-svn-id: https://svn.macports.org/repository/macports/contrib/MacPorts_Framework@69795 d073be05-634f-4543-b044-5fe20cf6d1d6
1522 lines
55 KiB
Objective-C
1522 lines
55 KiB
Objective-C
//
|
|
// MPNotifications+IPCAdditions.m
|
|
// MacPorts.Framework
|
|
//
|
|
// Created by George Armah on 8/24/08.
|
|
// Copyright 2008 Lafayette College. All rights reserved.
|
|
//
|
|
|
|
|
|
|
|
#pragma mark MPNotifications Server Code
|
|
//Things will get ugly ... before they can get beautiful ...
|
|
/*
|
|
File: Server.c
|
|
|
|
Contains: Server showing integration of CFSockets and UNIX domain sockets.
|
|
|
|
Written by: DTS
|
|
|
|
Copyright: Copyright (c) 2005 by Apple Computer, Inc., All Rights Reserved.
|
|
|
|
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc.
|
|
("Apple") in consideration of your agreement to the following terms, and your
|
|
use, installation, modification or redistribution of this Apple software
|
|
constitutes acceptance of these terms. If you do not agree with these terms,
|
|
please do not use, install, modify or redistribute this Apple software.
|
|
|
|
In consideration of your agreement to abide by the following terms, and subject
|
|
to these terms, Apple grants you a personal, non-exclusive license, under Apple's
|
|
copyrights in this original Apple software (the "Apple Software"), to use,
|
|
reproduce, modify and redistribute the Apple Software, with or without
|
|
modifications, in source and/or binary forms; provided that if you redistribute
|
|
the Apple Software in its entirety and without modifications, you must retain
|
|
this notice and the following text and disclaimers in all such redistributions of
|
|
the Apple Software. Neither the name, trademarks, service marks or logos of
|
|
Apple Computer, Inc. may be used to endorse or promote products derived from the
|
|
Apple Software without specific prior written permission from Apple. Except as
|
|
expressly stated in this notice, no other rights or licenses, express or implied,
|
|
are granted by Apple herein, including but not limited to any patent rights that
|
|
may be infringed by your derivative works or by other works in which the Apple
|
|
Software may be incorporated.
|
|
|
|
The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO
|
|
WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
|
|
WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
|
|
COMBINATION WITH YOUR PRODUCTS.
|
|
|
|
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
|
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
|
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
|
|
OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT
|
|
(INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN
|
|
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
Change History (most recent first):
|
|
|
|
$Log: Server.c,v $
|
|
Revision 1.2 2005/05/18 13:36:39
|
|
Fixed various documentation/comment changes.
|
|
|
|
Revision 1.1 2005/05/17 12:19:32
|
|
First checked in.
|
|
|
|
|
|
*/
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
|
|
// System interfaces
|
|
|
|
#include <stdlib.h>
|
|
#include <assert.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/un.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/param.h>
|
|
|
|
|
|
#include "MPHelperNotificationsCommon.h"
|
|
#include "MPHelperNotificationsProtocol.h"
|
|
#import "MPNotifications+IPCAdditions.h"
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
#pragma mark ***** Client State Management
|
|
|
|
// The server maintains a ClientState structure to track the state of
|
|
// each client. This state divides neatly into three groups.
|
|
//
|
|
// o socket -- fSockFD, fSockCF, and fRunLoopSource all represent different
|
|
// aspects of the UNIX domain socket that we're using to talk to the client.
|
|
//
|
|
// o incoming -- fBufferedData is a buffer containing any incomplete packets that
|
|
// we've received from the client.
|
|
//
|
|
// o outgoing -- fPendingSends and fPendingSendOffset control the packets that
|
|
// are waiting to be sent to the client. This list might get long if the
|
|
// client stops listening to us. Packets will first back up in the UNIX
|
|
// domain socket's socket buffer. Once that fills up we won't be able to
|
|
// write to the socket anymore. We respond to that by buffering the
|
|
// packets on the fPendingSends list. We also tell CFSocket to let us know
|
|
// (with a kCFSocketWriteCallBack event) if space becomes available.
|
|
//
|
|
// At this point one of two things will happen. Either we'll buffer too
|
|
// many packets for the client (kClientMaximumPendingSends) in which case
|
|
// we'll kill the client. Or the client will start receiving packets again,
|
|
// which will start to empty the socket buffer. CFSocket will tells about
|
|
// this by sending us a kCFSocketWriteCallBack event, and we'll start
|
|
// pulling packets off the fPendingSends list and writing them to the socket.
|
|
|
|
enum {
|
|
kClientStateMagic = 'LSCM' // for Local Server Client Magic
|
|
};
|
|
|
|
struct ClientState {
|
|
OSType fMagic; // kClientStateMagic
|
|
int fSockFD; // UNIX domain socket to client
|
|
CFSocketRef fSockCF; // CFSocket wrapper for the above
|
|
CFRunLoopSourceRef fRunLoopSource; // runloop source for the above
|
|
CFMutableDataRef fBufferedData; // buffers data for incomplete incoming packets
|
|
CFMutableArrayRef fPendingSends; // list of packets waiting to be sent
|
|
size_t fPendingSendOffset; // offset of next byte to send in first packet on list
|
|
Boolean fListening; // true if this client is a listener
|
|
};
|
|
typedef struct ClientState ClientState;
|
|
|
|
// To prevent a deaf client from sucking down all of our memory, we limit the
|
|
// number of packets that we'll buffer for a given client. If the length of
|
|
// fPendingSends exceeds kClientMaximumPendingSends, we'll kill the client rather
|
|
// than queue more data.
|
|
|
|
enum {
|
|
kClientMaximumPendingSends = 100
|
|
};
|
|
|
|
// gClients is a set of all clients we know about.
|
|
|
|
static CFMutableSetRef gClients = NULL; // of (ClientState *)
|
|
|
|
#pragma mark Misc
|
|
|
|
static Boolean ClientCheckPacketSize(ClientState *client, const PacketHeader *packet, size_t requiredSize)
|
|
// Checks that a packet that has arrived from a client is of the
|
|
// appropriate size. Returns false, and prints a message, if it isn't.
|
|
{
|
|
Boolean result;
|
|
|
|
assert(client != NULL);
|
|
assert(packet != NULL);
|
|
assert(requiredSize >= sizeof(PacketHeader));
|
|
|
|
result = true;
|
|
if (packet->fSize != requiredSize) {
|
|
fprintf(
|
|
stderr,
|
|
"ClientCheckPacketSize: Client %p sent us a '%.4s' of the wrong size (got %" PRIu32 ", wanted %zu).\n",
|
|
client,
|
|
(char *) &packet->fType,
|
|
packet->fSize,
|
|
requiredSize
|
|
);
|
|
result = false;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static Boolean ClientCheckPacketID(ClientState *client, const PacketHeader *packet, int32_t requiredID)
|
|
// Checks that a packet that has arrived from a client has the
|
|
// correct ID. Returns false, and prints a message, if it doesn't.
|
|
{
|
|
Boolean result;
|
|
|
|
assert(client != NULL);
|
|
assert(packet != NULL);
|
|
|
|
result = true;
|
|
if (packet->fID != requiredID) {
|
|
fprintf(
|
|
stderr,
|
|
"ClientCheckPacketID: Client %p sent us a '%.4s' with the wrong ID (got %" PRId32 ", wanted %" PRId32 ").\n",
|
|
client,
|
|
(char *) &packet->fType,
|
|
packet->fID,
|
|
requiredID
|
|
);
|
|
result = false;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Forward declarations
|
|
|
|
static void ClientGotSpace(ClientState *client);
|
|
static void ClientGotData(ClientState *client, const void *data);
|
|
|
|
static void ClientEvent(
|
|
CFSocketRef s,
|
|
CFSocketCallBackType type,
|
|
CFDataRef address,
|
|
const void * data,
|
|
void * info
|
|
)
|
|
// This is the CFSocket event callback for client sockets. For a description
|
|
// of the parameters, see the CFSocket documentation.
|
|
//
|
|
// This routine responds to two events, kCFSocketDataCallBack and
|
|
// kCFSocketWriteCallBack, dispatching them to ClientGotData and
|
|
// ClientGotSpace, respectively.
|
|
{
|
|
#pragma unused(address)
|
|
ClientState * client;
|
|
|
|
assert(s != NULL);
|
|
|
|
client = (ClientState *) info;
|
|
assert(client != NULL);
|
|
assert(client->fMagic == kClientStateMagic);
|
|
|
|
switch (type) {
|
|
case kCFSocketDataCallBack:
|
|
ClientGotData(client, data);
|
|
break;
|
|
case kCFSocketWriteCallBack:
|
|
ClientGotSpace(client);
|
|
break;
|
|
default:
|
|
assert(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
#pragma mark Create/Destroy
|
|
|
|
static void ClientDestroy(ClientState *client);
|
|
|
|
static int ClientInitialise(void)
|
|
// Initialises the client management layer, which simply involves
|
|
// creating an empty gClients set.
|
|
{
|
|
int err;
|
|
|
|
err = 0;
|
|
gClients = CFSetCreateMutable(NULL, 0, NULL);
|
|
if (gClients == NULL) {
|
|
err = ENOMEM;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
static void ClientTerminate(void)
|
|
// Shuts down the client management layer. This involves destroying
|
|
// any remaining clients and disposing of gClients.
|
|
{
|
|
CFIndex clientCount;
|
|
CFIndex clientIndex;
|
|
ClientState ** allClients;
|
|
|
|
if (gClients != NULL) {
|
|
// Can't use CFSetApplyFunction because the ClientDestroy modifies
|
|
// the gClients set.
|
|
|
|
clientCount = CFSetGetCount(gClients);
|
|
|
|
allClients = calloc(clientCount, sizeof(ClientState *));
|
|
if (allClients == NULL) {
|
|
fprintf(stderr, "CFLocalServer: Could not clean up clients because we couldn't allocate memory.\n");
|
|
} else {
|
|
CFSetGetValues(gClients, (const void **) allClients);
|
|
|
|
for (clientIndex = 0; clientIndex < clientCount; clientIndex++) {
|
|
fprintf(stderr, "CFLocalServer: Client %p killed because we're quitting.\n", allClients[clientIndex]);
|
|
|
|
ClientDestroy( allClients[clientIndex] );
|
|
}
|
|
}
|
|
|
|
free(allClients);
|
|
|
|
CFRelease(gClients);
|
|
gClients = NULL;
|
|
}
|
|
}
|
|
|
|
static int ClientCreate(int clientSockFD, ClientState **clientPtr)
|
|
// Creates a new client that communicates over clientSockFD.
|
|
// If clientPtr is not NULL, it returns a pointer to the
|
|
// client state record in *clientPtr.
|
|
//
|
|
// clientSockFD must be a valid file descriptor referencing a
|
|
// socket that's connected to the client
|
|
// On input, if clientPtr is not NULL, *clientPtr must be NULL
|
|
// Returns an errno-style error code
|
|
// On success, if clientPtr is not NULL, *clientPtr will not be NULL
|
|
// On success, clientSockFD is owned by the new client; the caller
|
|
// need not close it
|
|
// On error, if clientPtr is not NULL, *clientPtr will be NULL
|
|
// On error, clientSockFD will have been closed.
|
|
//
|
|
// IMPORTANT:
|
|
// Regardless of whether this routine succeeds or fails, it assumes
|
|
// responsibility for clientSockFD. The caller is never required to
|
|
// close it.
|
|
{
|
|
int err;
|
|
int junk;
|
|
ClientState * client;
|
|
|
|
assert( (clientPtr == NULL) || (*clientPtr == NULL) );
|
|
|
|
assert(gClients != NULL);
|
|
|
|
// Create the client state record.
|
|
|
|
err = 0;
|
|
client = (ClientState *) calloc(1, sizeof(*client));
|
|
if (client == NULL) {
|
|
err = ENOMEM;
|
|
}
|
|
|
|
// Fill in the easy fields. This also prepares us for the clean up
|
|
// on failure.
|
|
|
|
if (err == 0) {
|
|
client->fMagic = kClientStateMagic;
|
|
|
|
// For clean up to work properly, we must make sure that, if
|
|
// the connection record is allocated successfully, we always
|
|
// set fSockFD to the incoming clientSockFD.
|
|
|
|
client->fSockFD = clientSockFD;
|
|
|
|
client->fBufferedData = CFDataCreateMutable(NULL, 0);
|
|
client->fPendingSends = CFArrayCreateMutable(NULL, 0, NULL);
|
|
|
|
if ( (client->fBufferedData == NULL) || (client->fPendingSends == NULL) ) {
|
|
err = ENOMEM;
|
|
}
|
|
}
|
|
|
|
// Make the socket non-blocking. We need to do this because
|
|
// otherwise ClientSendPending can get stuck in a write.
|
|
|
|
if (err == 0) {
|
|
err = MoreUNIXSetNonBlocking(client->fSockFD);
|
|
}
|
|
|
|
// Wrap the socket in a CFSocket, and create and install the run loop source.
|
|
|
|
if (err == 0) {
|
|
CFSocketContext context;
|
|
|
|
memset(&context, 0, sizeof(context));
|
|
context.info = client;
|
|
|
|
client->fSockCF = CFSocketCreateWithNative(
|
|
NULL,
|
|
(CFSocketNativeHandle) client->fSockFD,
|
|
kCFSocketDataCallBack + kCFSocketWriteCallBack,
|
|
ClientEvent,
|
|
&context
|
|
);
|
|
if (client->fSockCF == NULL) {
|
|
err = EINVAL;
|
|
}
|
|
}
|
|
|
|
if (err == 0) {
|
|
client->fRunLoopSource = CFSocketCreateRunLoopSource(NULL, client->fSockCF, 0);
|
|
if (client->fRunLoopSource == NULL) {
|
|
err = EINVAL;
|
|
}
|
|
}
|
|
if (err == 0) {
|
|
CFRunLoopAddSource( CFRunLoopGetCurrent(), client->fRunLoopSource, kCFRunLoopDefaultMode);
|
|
|
|
assert( ! CFSetContainsValue(gClients, client) );
|
|
|
|
// It's all good. Record that this client exists.
|
|
|
|
CFSetAddValue(gClients, client);
|
|
}
|
|
|
|
// Clean up.
|
|
|
|
if (err != 0) {
|
|
fprintf(stderr, "ClientCreate: Error %d creating client.\n", err);
|
|
|
|
// If client is NULL, we couldn't allocate a client record, therefore
|
|
// we had nowhere to record clientSockFD, therefore ClientDestroy won't
|
|
// clean it up. Thus, we have to do it ourselves.
|
|
|
|
if (client == NULL) {
|
|
junk = close(clientSockFD);
|
|
assert(junk == 0);
|
|
} else {
|
|
ClientDestroy(client);
|
|
}
|
|
client = NULL;
|
|
}
|
|
if (clientPtr != NULL) {
|
|
*clientPtr = client;
|
|
}
|
|
|
|
assert( (clientPtr == NULL) || ((err == 0) == (*clientPtr != NULL)) );
|
|
|
|
return err;
|
|
}
|
|
|
|
static void ClientDestroy(ClientState *client)
|
|
// Destroys a client. This is called in a number of different circumstances,
|
|
// but these basically boil down to:
|
|
//
|
|
// a) if ClientCreate fails, it's called to destroy the partially-created client,
|
|
// b) if some sort of communications error happens, it's called to destroy the
|
|
// client,
|
|
// c) if the client sends us a goodbye packet, this is called to destroy the client, and
|
|
// d) on quit, all clients are destroyed.
|
|
{
|
|
int junk;
|
|
|
|
assert(client != NULL);
|
|
assert(client->fMagic == kClientStateMagic);
|
|
|
|
// This following assert is NOT true. If the client dies before it
|
|
// gets fully started (that is, we get an error halfway through
|
|
// ClientCreate), ClientDestroy is called to tidy up the mess but
|
|
// the client hasn't been added into gClients yet.
|
|
|
|
// assert( CFSetContainsValue(gClients, client) );
|
|
|
|
// Remove the client our record of existant clients.
|
|
|
|
CFSetRemoveValue(gClients, client);
|
|
|
|
// Clean up the runloop source and CFSocket.
|
|
|
|
if (client->fRunLoopSource != NULL) {
|
|
CFRunLoopSourceInvalidate(client->fRunLoopSource);
|
|
|
|
CFRelease(client->fRunLoopSource);
|
|
}
|
|
if (client->fSockCF != NULL) {
|
|
CFSocketInvalidate(client->fSockCF);
|
|
|
|
CFRelease(client->fSockCF);
|
|
}
|
|
|
|
// Close the socket itself, but only if we don't have a corresponding
|
|
// CFSocket; if a CFSocket was created, it takes over responsibility
|
|
// for closing the socket.
|
|
|
|
if ( (client->fSockFD != -1) && (client->fSockCF == NULL) ) {
|
|
junk = close(client->fSockFD);
|
|
assert(junk == 0);
|
|
}
|
|
|
|
// Free any packets waiting to go out to this client.
|
|
|
|
if (client->fPendingSends != NULL) {
|
|
CFIndex index;
|
|
CFIndex count;
|
|
|
|
count = CFArrayGetCount(client->fPendingSends);
|
|
for (index = 0; index < count; index++) {
|
|
free( (void *) CFArrayGetValueAtIndex(client->fPendingSends, index) );
|
|
}
|
|
CFRelease(client->fPendingSends);
|
|
}
|
|
|
|
// Free any buffered data from this client.
|
|
|
|
if (client->fBufferedData != NULL) {
|
|
CFRelease(client->fBufferedData);
|
|
}
|
|
|
|
// Free the client state record itself.
|
|
|
|
client->fMagic = 'FRE!';
|
|
free(client);
|
|
}
|
|
|
|
#pragma mark Send
|
|
|
|
static int ClientSendPending(ClientState *client)
|
|
// This routine attempts to send any packets that are queued in the fSendPending
|
|
// array. It is somewhat complex. There are three possible final results.
|
|
//
|
|
// o It successfully sends all packets in the queue. In this case it returns 0.
|
|
//
|
|
// o The write side of the socket is full (flow controlled). In this case the
|
|
// function enables the socket write callback (kCFSocketWriteCallBack, using
|
|
// CFSocketEnableCallBacks) and returns 0. When socket buffer empties a little,
|
|
// CFSocket will send us the kCFSocketWriteCallBack event and we'll resume sending.
|
|
//
|
|
// o It fails for some other reasons (for example, the other end of the socket has
|
|
// been closed, causing an EPIPE). In this case it returns an errno-style error
|
|
// indicating the failure. The caller typically responds by destroying the client.
|
|
//
|
|
// This whole process is further complicated by the possibilty that the socket
|
|
// buffer might have enough space for half a packet. In this case you'll get a
|
|
// short write, that is, write will return a positive number less than its nbytes
|
|
// parameter. To handle this case we record the offset into the packet of the first
|
|
// byte of unwritten data. When we go to send a packet, we always send from there.
|
|
// When write accepts some data, we bump the offset by that amount. If that
|
|
// completes the send of the packet, we start on next packet, resetting the offset
|
|
// back to 0.
|
|
{
|
|
int err;
|
|
Boolean done;
|
|
const PacketHeader * thisPacket;
|
|
ssize_t bytesWritten;
|
|
|
|
err = 0;
|
|
|
|
// Keep going until we've sent all pending packets for this client.
|
|
|
|
while ( (err == 0) && (CFArrayGetCount(client->fPendingSends) != 0) ) {
|
|
thisPacket = (const PacketHeader *) CFArrayGetValueAtIndex(client->fPendingSends, 0);
|
|
|
|
// Try to send this packet by writing it to the socket.
|
|
|
|
done = false;
|
|
do {
|
|
bytesWritten = write(
|
|
client->fSockFD,
|
|
((char *) thisPacket) + client->fPendingSendOffset,
|
|
thisPacket->fSize - client->fPendingSendOffset
|
|
);
|
|
|
|
if (bytesWritten > 0) {
|
|
// We're written some bytes. Adjust fPendingSendOffset by
|
|
// that amount and see if that completes the packet.
|
|
|
|
client->fPendingSendOffset += bytesWritten;
|
|
|
|
if (client->fPendingSendOffset == thisPacket->fSize) {
|
|
// Packet complete. Delete it from the head of the
|
|
// send list, reset offset back to 0, and let's go
|
|
// deal with the next packet.
|
|
|
|
CFArrayRemoveValueAtIndex(client->fPendingSends, 0);
|
|
free( (void *) thisPacket);
|
|
client->fPendingSendOffset = 0;
|
|
done = true;
|
|
} else {
|
|
// Packet still not fully sent. The send offset has already
|
|
// been updated, so we just loop to try again.
|
|
}
|
|
} else if (bytesWritten == -1) {
|
|
// We got some sort of error.
|
|
|
|
err = errno;
|
|
switch (err) {
|
|
case EINTR:
|
|
// Interrupted. Do nothing, so we loop and retry this
|
|
// send immediately.
|
|
|
|
err = 0;
|
|
break;
|
|
case EAGAIN:
|
|
// Flow controlled. Break out of the loop with an EAGAIN
|
|
// error; we try again when space becomes available.
|
|
|
|
fprintf(stderr, "ClientSendPending: Client %p write-side flow control.\n", client);
|
|
|
|
// Tell the CFSocket that we now /really/ need to be told about
|
|
// write space becoming available. Without this we'll never
|
|
// recover if the client stops accepting messages temporarily
|
|
// (which causes the socket buffer to fill up and us to get an
|
|
// EAGAIN) and then starts accepting messages again. At that
|
|
// point the client will drain the socket buffer, but we'll never
|
|
// hear about it because CFSocket doesn't know that we care
|
|
// about write space. With this call CFSocket knows that we
|
|
// care, and will send us an kCFSocketWriteCallBack event if
|
|
// space becomes available in the socket buffer.
|
|
|
|
CFSocketEnableCallBacks(client->fSockCF, kCFSocketWriteCallBack);
|
|
break;
|
|
default:
|
|
// Errored. Our response is typically draconian:
|
|
// we return the error to our caller, which then kills the client
|
|
// completely.
|
|
|
|
fprintf(stderr, "ClientSendPending: Client %p killed because of send error (%d).\n", client, err);
|
|
break;
|
|
}
|
|
} else {
|
|
assert(false);
|
|
}
|
|
} while ( (err == 0) && ! done );
|
|
}
|
|
|
|
// As far as the caller is concerned, write-side flow control is not an error.
|
|
|
|
if (err == EAGAIN) {
|
|
err = 0;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static Boolean ClientSend(ClientState *client, const PacketHeader *packet)
|
|
// Called in various places to send a packet to a client.
|
|
// This adds it to the send queue and then calls ClientSendPending
|
|
// to attempt a send.
|
|
{
|
|
Boolean result;
|
|
PacketHeader * copiedPacket;
|
|
|
|
assert(client != NULL);
|
|
assert(packet != NULL);
|
|
assert(packet->fSize >= sizeof(PacketHeader));
|
|
|
|
// If we've buffered kClientMaximumPendingSends already, the client is
|
|
// just not reading them. To avoid us consuming all of our memory buffering
|
|
// packets for a deaf client, we just kill the client.
|
|
|
|
result = true;
|
|
if ( CFArrayGetCount(client->fPendingSends) >= kClientMaximumPendingSends ) {
|
|
fprintf(stderr, "ClientSend: Client %p killed because of too many outstanding sends.\n", client);
|
|
|
|
result = false;
|
|
}
|
|
|
|
// Copy the packet data, append that copy to the send queue, and then
|
|
// give it a kick.
|
|
//
|
|
// The memory allocated here will be freed when the packet is succesfully
|
|
// sent (SendPending), or the client is destroy.
|
|
|
|
if (result) {
|
|
copiedPacket = (PacketHeader *) malloc(packet->fSize);
|
|
result = (copiedPacket != NULL);
|
|
}
|
|
if (result) {
|
|
memcpy(copiedPacket, packet, packet->fSize);
|
|
|
|
CFArrayAppendValue(client->fPendingSends, copiedPacket);
|
|
|
|
result = ( ClientSendPending(client) == 0 );
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static Boolean ClientSendReply(ClientState *client, const PacketHeader *request, int errNum)
|
|
// Send an RPC reply packet to the client. You must supply request
|
|
// because it forms the basis of many of the fields in the reply.
|
|
// You also have to supply errNum, which is an errno-style error
|
|
// indicating the fate of the request.
|
|
{
|
|
PacketReply response;
|
|
|
|
assert(client != NULL);
|
|
assert(request != NULL);
|
|
|
|
InitPacketHeader(&response.fHeader, kPacketTypeReply, sizeof(response), false);
|
|
// Copy the ID from the request packet, overriding the fID set by InitPacketHeader.
|
|
response.fHeader.fID = request->fID;
|
|
response.fErr = errNum;
|
|
|
|
return ClientSend(client, &response.fHeader);
|
|
}
|
|
|
|
static void ClientGotSpace(ClientState *client)
|
|
// This routine is called by ClientEvent when it receives the kCFSocketWriteCallBack
|
|
// event, indicating that there is space to write in the client's socket buffer.
|
|
// It calls ClientSendPending to process any packets that are waiting to be sent.
|
|
// In most cases this does nothing because the client send queue is empty. However,
|
|
// if the client goes deaf, so the socket buffer becomes write-side flow controlled,
|
|
// packets can back up in the send queue. When the client starts receiving packets
|
|
// again, space becomes available in the socket buffer and CFSocket sends us the
|
|
// kCFSocketWriteCallBack. We respond to that by resuming our sends.
|
|
{
|
|
int err;
|
|
|
|
assert(client != NULL);
|
|
|
|
fprintf(stderr, "ClientGotSpace: Client %p lifted write-side flow control.\n", client);
|
|
|
|
err = ClientSendPending(client);
|
|
|
|
// If the sending failed for any reason (except flow control, for which
|
|
// ClientSendPending mutates the EAGAIN status to a 0) we kill the client.
|
|
|
|
if (err != 0) {
|
|
ClientDestroy(client);
|
|
}
|
|
}
|
|
|
|
#pragma mark Receive
|
|
|
|
// The receive engine is based around ClientGotData, which is the routine that gets
|
|
// called when new data arrives, and a variety of packet handlers for processing
|
|
// specific types of packets and that all have the same form.
|
|
//
|
|
// A packet handle routine takes two parameters, the client and the packet, neither
|
|
// of which can be NULL, and does the work to process that packet. This typically
|
|
// involves checking that the packet is valid, doing the job requested by the packet,
|
|
// and then, if the packet is for an RPC, sending the reply.
|
|
//
|
|
// If the packet handler returns false, the caller (ClientGotData) assumes that
|
|
// something was seriously wrong with the packet and kills the connection to the
|
|
// client. A packet handler typically does this if the packet itself is malformed;
|
|
// if the job requested by the packet can't be done (for example, there might not
|
|
// be enough memory), the packet handler wouldn't return false but would, instead,
|
|
// send an error status back to the client in the RPC reply.
|
|
|
|
static Boolean ClientGoodbye(ClientState *client, PacketGoodbye *packet)
|
|
// A packet handler for the Goodbye packet. See the large comment above for
|
|
// a discussion of the general form of a packet handler.
|
|
//
|
|
// A Goodbye packet is sent by the client to indicate to us that it's closing
|
|
// its end of the connection.
|
|
{
|
|
Boolean result;
|
|
|
|
assert(client != NULL);
|
|
assert(packet != NULL);
|
|
|
|
result = ClientCheckPacketSize(client, &packet->fHeader, sizeof(PacketGoodbye));
|
|
if ( result ) {
|
|
result = ClientCheckPacketID(client, &packet->fHeader, kPacketIDNone);
|
|
}
|
|
|
|
if (result) {
|
|
// During reliability print all of the goodbyes proved to be too verbose,
|
|
// so I've disabled it for now.
|
|
|
|
if (false) {
|
|
fprintf(stderr, "%p: Goodbye (%.*s).\n", client, (int) sizeof(packet->fMessage), packet->fMessage);
|
|
}
|
|
|
|
// Unlike most packet handlers, we return false on success. This is because
|
|
// the Goodbye packet tells us that the client has gone away, and thus we
|
|
// need to kill the client. It turns out that returning false does the job
|
|
// without us having to write any special code.
|
|
|
|
result = false;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static Boolean ClientNOP(ClientState *client, PacketNOP *packet)
|
|
// A packet handler for the NOP packet. See the large comment above for
|
|
// a discussion of the general form of a packet handler.
|
|
//
|
|
// A NOP RPC does nothing; it's used to test client/server connection.
|
|
{
|
|
Boolean result;
|
|
|
|
result = ClientCheckPacketSize(client, &packet->fHeader, sizeof(PacketNOP));
|
|
|
|
if (result) {
|
|
fprintf(stderr, "%p: NOP\n", client);
|
|
|
|
result = ClientSendReply(client, &packet->fHeader, 0);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static Boolean ClientWhisper(ClientState *client, PacketWhisper *packet)
|
|
// A packet handler for the Whisper packet. See the large comment above for
|
|
// a discussion of the general form of a packet handler.
|
|
//
|
|
// A Whisper RPC causes the server to print the associated message.
|
|
{
|
|
Boolean result;
|
|
|
|
result = ClientCheckPacketSize(client, &packet->fHeader, sizeof(PacketWhisper));
|
|
if (result) {
|
|
fprintf(stderr, "%p: Whisper \"%.*s\"\n", client, (int) sizeof(packet->fMessage), packet->fMessage);
|
|
|
|
result = ClientSendReply(client, &packet->fHeader, 0);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static Boolean ClientShout(ClientState *client, PacketShout *packet)
|
|
// A packet handler for the Shout packet. See the large comment above for
|
|
// a discussion of the general form of a packet handler.
|
|
//
|
|
// A Shout packet causes the server to echo the message (in the form
|
|
// of a Shout packet) to every client that has registered as a listener.
|
|
{
|
|
Boolean result;
|
|
Boolean sendResult;
|
|
|
|
result = ClientCheckPacketSize(client, &packet->fHeader, sizeof(PacketShout));
|
|
if (result) {
|
|
result = ClientCheckPacketID(client, &packet->fHeader, kPacketIDNone);
|
|
//NSLog(@"Packetsize is OK");
|
|
}
|
|
else
|
|
NSLog(@"Packetsize is NOT OK");
|
|
|
|
// The Shout packet is good. Let's echo it to each listener.
|
|
|
|
if (result) {
|
|
//NSLog(@"PacketID is ok");
|
|
|
|
|
|
ClientState ** allClients;
|
|
CFIndex clientCount;
|
|
CFIndex clientIndex;
|
|
|
|
//fprintf(stderr, "%p: Shout \"%.*s\"\n", client, (int) sizeof(packet->fMessage), packet->fMessage);
|
|
|
|
NSString * shout = [NSString stringWithCString:packet->fMessage encoding:NSUTF8StringEncoding];
|
|
//NSLog(@"CLIENT SHOUT BEING CALLED (YAAAY!!) : %@" , shout);
|
|
[[MPNotifications sharedListener] sendIPCNotification:shout];
|
|
// [[NSNotificationCenter defaultCenter] postNotificationName:MPINFO
|
|
// object:nil
|
|
// userInfo:[NSDictionary dictionaryWithObjectsAndKeys:shout, MPMESSAGE, nil]];
|
|
|
|
|
|
// We make a snapshot of the client list because clients might disappear
|
|
// as we talk to them. That is, the act of talking to the client might
|
|
// cause us to notice that the client is dead.
|
|
|
|
clientCount = CFSetGetCount(gClients);
|
|
|
|
allClients = calloc(clientCount, sizeof(ClientState *));
|
|
if (allClients == NULL) {
|
|
fprintf(stderr, "ClientShout: Shout from %p failed because we couldn't allocate memory.\n", client);
|
|
} else {
|
|
CFSetGetValues(gClients, (const void **) allClients);
|
|
|
|
// Iterate through the array of clients, sending the Shout packet to each.
|
|
|
|
for (clientIndex = 0; clientIndex < clientCount; clientIndex++) {
|
|
ClientState * thisClient;
|
|
|
|
thisClient = allClients[clientIndex];
|
|
if (thisClient->fListening) {
|
|
sendResult = ClientSend(thisClient, &packet->fHeader);
|
|
|
|
// Fun fun fun. If we're sending to ourselves, we return the result
|
|
// of the send as our function result, which, if there's a failure,
|
|
// will trigger ClientGotData to clean us up. OTOH, if we're sending
|
|
// to another client, we're responsible for cleaning up if there's
|
|
// a failure.
|
|
|
|
if (thisClient == client) {
|
|
result = sendResult;
|
|
} else {
|
|
if ( ! sendResult ) {
|
|
fprintf(stderr, "ClientShout: Shout from %p to %p failed.\n", client, thisClient);
|
|
|
|
ClientDestroy(thisClient);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
free(allClients);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static Boolean ClientListen(ClientState *client, PacketListen *packet)
|
|
// A packet handler for the Listen packet. See the large comment above for
|
|
// a discussion of the general form of a packet handler.
|
|
//
|
|
// A Listen RPC tells the server that the client wants to hear about shouted
|
|
// messages.
|
|
{
|
|
Boolean result;
|
|
|
|
result = ClientCheckPacketSize(client, &packet->fHeader, sizeof(PacketListen));
|
|
if (result) {
|
|
if (client->fListening) {
|
|
fprintf(stderr, "ClientListen: Redundant Listen from %p.\n", client);
|
|
} else {
|
|
fprintf(stderr, "%p: Listen\n", client);
|
|
}
|
|
client->fListening = true;
|
|
|
|
result = ClientSendReply(client, &packet->fHeader, 0);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
static Boolean ClientQuit(ClientState *client, PacketQuit *packet)
|
|
// A packet handler for the Quit packet. See the large comment above for
|
|
// a discussion of the general form of a packet handler.
|
|
//
|
|
// A Quit RPC causes the server to quit.
|
|
{
|
|
Boolean result;
|
|
|
|
result = ClientCheckPacketSize(client, &packet->fHeader, sizeof(PacketQuit));
|
|
if (result) {
|
|
fprintf(stderr, "%p: Quit\n", client);
|
|
|
|
// Stop the main event loop.
|
|
|
|
CFRunLoopStop( CFRunLoopGetCurrent() );
|
|
|
|
// This reply should go out immediately. If the client, for some reason,
|
|
// is flow controlled, it may not see the response. But really, that's
|
|
// the client's fault (-:
|
|
|
|
result = ClientSendReply(client, &packet->fHeader, 0);
|
|
}
|
|
|
|
if (result) {
|
|
//This is a shared variable, the server thread uses this to keep the while
|
|
//loop running. For now i'm not synchronizing variable access since the
|
|
//worst that could happen is that the server is late in reading a chaged
|
|
//value update
|
|
clientHasQuit = 1;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static void ClientGotData(ClientState *client, const void *data)
|
|
// This routine is called by ClientEvent when it receives the kCFSocketDataCallBack
|
|
// event, indicating that CFSocket has read data from the socket. The routine
|
|
// appends the data to the receive buffer (fBufferedData) and then looks through
|
|
// the receive buffer for complete packets. For each complete packet it finds,
|
|
// it calls the packet handler (the various routines above) to process the packet
|
|
// and then it deletes the packet from the front of the receive buffer.
|
|
//
|
|
// data is the data read for us by CFSocket. It's actually a CFDataRef
|
|
// but, because we're being called from a generic CFSocket event handler,
|
|
// it's of type (const void *). We have to do the cast here.
|
|
{
|
|
CFDataRef newData;
|
|
|
|
assert(client != NULL);
|
|
|
|
newData = (CFDataRef) data;
|
|
assert(newData != NULL);
|
|
assert( CFGetTypeID(newData) == CFDataGetTypeID() );
|
|
|
|
if ( CFDataGetLength(newData) == 0 ) {
|
|
// A zero length data indicates the end of the data stream; the client is dead
|
|
// so we just go and remove our record of it.
|
|
|
|
fprintf(stderr, "ClientGotData: Client %p died unexpectedly.\n", client);
|
|
|
|
ClientDestroy(client);
|
|
} else {
|
|
|
|
// Append the new data to whatever data we have already buffered
|
|
// (most likely nothing).
|
|
|
|
CFDataAppendBytes(client->fBufferedData, CFDataGetBytePtr(newData), CFDataGetLength(newData));
|
|
|
|
// Process packets until we run out of complete ones.
|
|
|
|
do {
|
|
PacketHeader * thisPacket;
|
|
Boolean success;
|
|
|
|
if ( CFDataGetLength(client->fBufferedData) < sizeof(PacketHeader) ) {
|
|
// Not enough data for the packet header; we're done.
|
|
break;
|
|
}
|
|
|
|
thisPacket = (PacketHeader *) CFDataGetBytePtr(client->fBufferedData);
|
|
|
|
if ( thisPacket->fMagic != kPacketMagic ) {
|
|
fprintf(stderr, "ClientGotData: Client %p sent us a packet with bad magic (%.4s).\n", client, (char *) &thisPacket->fMagic);
|
|
|
|
ClientDestroy(client);
|
|
break;
|
|
}
|
|
|
|
if (thisPacket->fSize > kPacketMaximumSize) {
|
|
fprintf(stderr, "ClientGotData: Client %p sent us a packet that's just too big (%" PRIu32 ").\n", client, thisPacket->fSize);
|
|
|
|
ClientDestroy(client);
|
|
break;
|
|
}
|
|
|
|
if ( CFDataGetLength(client->fBufferedData) < thisPacket->fSize ) {
|
|
// Not enough data for the packet body; we're done.
|
|
break;
|
|
}
|
|
|
|
// Dispatch to the appropriate packet handler.
|
|
|
|
switch (thisPacket->fType) {
|
|
case kPacketTypeGoodbye:
|
|
success = ClientGoodbye(client, (PacketGoodbye *) thisPacket);
|
|
break;
|
|
case kPacketTypeNOP:
|
|
success = ClientNOP(client, (PacketNOP *) thisPacket);
|
|
break;
|
|
case kPacketTypeWhisper:
|
|
success = ClientWhisper(client, (PacketWhisper *) thisPacket);
|
|
break;
|
|
case kPacketTypeShout:
|
|
success = ClientShout(client, (PacketShout *) thisPacket);
|
|
break;
|
|
case kPacketTypeListen:
|
|
success = ClientListen(client, (PacketListen *) thisPacket);
|
|
break;
|
|
case kPacketTypeQuit:
|
|
success = ClientQuit(client, (PacketQuit *) thisPacket);
|
|
break;
|
|
default:
|
|
fprintf(stderr, "ClientGotData: Client %p sent us a packet with an unexpected type (%.4s).\n", client, (char *) &thisPacket->fType);
|
|
|
|
success = false;
|
|
break;
|
|
}
|
|
if ( ! success ) {
|
|
ClientDestroy(client);
|
|
break;
|
|
}
|
|
|
|
// Delete this packet from the front of our packet buffer.
|
|
|
|
CFDataDeleteBytes(client->fBufferedData, CFRangeMake(0, thisPacket->fSize));
|
|
|
|
} while (true);
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
#pragma mark ***** Debug Infrastructure
|
|
|
|
static void ClientPrintInfo(const void *value, void *context)
|
|
// Called by PrintServerState to print the state of a particular
|
|
// client. Actually passed as a callback to CFSetApplyFunction,
|
|
// which is why the value parameter is a (const void *) rather
|
|
// than a (ClientState *). context is not used in this... context (-;
|
|
{
|
|
#pragma unused(context)
|
|
ClientState * client;
|
|
static const char * kBoolToStr[2] = { "false", "true" };
|
|
|
|
client = (ClientState *) value;
|
|
assert(client != NULL);
|
|
assert(client->fMagic == kClientStateMagic);
|
|
|
|
fprintf(stderr, " Client %p:\n", client);
|
|
fprintf(stderr, " fSockFD = %d\n", client->fSockFD);
|
|
fprintf(stderr, " fSockCF = %p\n", client->fSockCF);
|
|
fprintf(stderr, " fRunLoopSource = %p\n", client->fRunLoopSource);
|
|
fprintf(stderr, " fBufferedData = %p (count: %ld)\n", client->fBufferedData, CFDataGetLength(client->fBufferedData));
|
|
fprintf(stderr, " fPendingSends = %p (count: %ld)\n", client->fPendingSends, CFArrayGetCount(client->fPendingSends));
|
|
fprintf(stderr, " fPendingSendOffset = %zd\n", client->fPendingSendOffset);
|
|
fprintf(stderr, " fListening = %s\n", kBoolToStr[client->fListening]);
|
|
}
|
|
|
|
static void PrintServerState(void)
|
|
// Called in response to a SIGINFO. This prints a bunch of state
|
|
// information about the server. Note that it is not called from a
|
|
// signal handler directly, rather from SignalRunLoopCallback which
|
|
// is a runloop callback. So we can do all sorts of things that
|
|
// aren't safe in a true signal handler.
|
|
{
|
|
fprintf(stderr, "CFLocalServer State\n");
|
|
fprintf(stderr, "-------------------\n");
|
|
if ( CFSetGetCount(gClients) == 0) {
|
|
fprintf(stderr, "Clients: none\n");
|
|
} else {
|
|
fprintf(stderr, "Clients:\n");
|
|
CFSetApplyFunction(gClients, ClientPrintInfo, NULL);
|
|
}
|
|
fprintf(stderr, "\n");
|
|
|
|
DebugPrintDescriptorTable();
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
#pragma mark ***** Server Framework
|
|
|
|
static void ListeningSocketAcceptCallback(
|
|
CFSocketRef s,
|
|
CFSocketCallBackType type,
|
|
CFDataRef address,
|
|
const void * data,
|
|
void * info
|
|
)
|
|
// This is the CFSocket event callback for the listening socket. For a
|
|
// description of the parameters, see the CFSocket documentation.
|
|
//
|
|
// CFSocket calls this routine when it has accepted a new connection on
|
|
// the socket. in this case data is a pointer to the newly created
|
|
// file descriptor that describes the new connection. This routine
|
|
// responds by calling into the client layer to create a new client.
|
|
{
|
|
#pragma unused(s)
|
|
#pragma unused(address)
|
|
#pragma unused(info)
|
|
|
|
assert(type == kCFSocketAcceptCallBack);
|
|
assert( (int *) data != NULL );
|
|
assert( (*(int *) data) != -1 );
|
|
|
|
(void) ClientCreate( (*(int *) data), NULL );
|
|
|
|
// If ClientCreate fails, it cleans up after itself, including
|
|
// closing the newly created client socket. It's even printed
|
|
// a happy message (well, an unhappy message). So we do nothing
|
|
// on failure.
|
|
}
|
|
|
|
static void SignalRunLoopCallback(const siginfo_t *sigInfo, void *refCon)
|
|
// This routine is called in response to a signal (SIGINT
|
|
// or SIGINFO). It is not, however, a signal handler. Rather,
|
|
// we orchestrate to have it called from the runloop (via
|
|
// the magic of InstallSignalToSocket). It's purpose
|
|
// is to a) stop the server when the user types ^C (SIGINT), or
|
|
// b) print some information about the server (SIGINFO).
|
|
{
|
|
#pragma unused(sigInfo)
|
|
#pragma unused(refCon)
|
|
|
|
switch (sigInfo->si_signo) {
|
|
case SIGINFO:
|
|
// Respond to SIGINFO by printing some information about the server.
|
|
|
|
PrintServerState();
|
|
break;
|
|
case SIGINT:
|
|
// Respond to SIGINT by stopping the server.
|
|
|
|
// Stop the runloop. Note that we can get a reference to the runloop by
|
|
// calling CFRunLoopGetCurrent because this is called from the runloop.
|
|
|
|
CFRunLoopStop( CFRunLoopGetCurrent() );
|
|
|
|
// Print a bonus newline to ensure that the next command prompt isn't
|
|
// printed on the same line as the echoed ^C.
|
|
|
|
fprintf(stderr, "\n");
|
|
break;
|
|
default:
|
|
assert(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int SafeBindUnixDomainSocket(int sockFD, const char *socketPath)
|
|
// This routine is called to safely bind the UNIX domain socket
|
|
// specified by sockFD to the path specificed by socketPath. To avoid
|
|
// security problems, socketPath must point it to a sticky directory
|
|
// (such as "/var/tmp"). This allows us to create the socket with
|
|
// very specific permissions, without us having to worry about a malicious
|
|
// process switching stuff out from underneath us.
|
|
//
|
|
// For this test program, socketpath is "/var/tmp/com.apple.dts.CFLocalServer/Socket".
|
|
// The code calculates parentPath as ""/var/tmp/com.apple.dts.CFLocalServer"
|
|
// and grandParentPath as "/var/tmp". Each ancestor has certain key attributes.
|
|
//
|
|
// o grandParentPath must a sticky directory. Because it's sticky, we
|
|
// can create a directory within it and know that either a) we created
|
|
// the directory, and no one else can mess with it because it's sticky,
|
|
// or b) the directory exists, in which case we can check it's owner
|
|
// and permissions and, if they are set correctly, know that no one else
|
|
// can mess with it.
|
|
//
|
|
// o When we create the parentPath directory within grandParentPath, we set its
|
|
// permissions to make it readable by everyone (so everyone can connect to our
|
|
// server) but writeable only by us (so that only we can create the listening
|
|
// socket). Because parentPath is set this way, we know that no one else
|
|
// can modify it to produce a security problem.
|
|
//
|
|
// IMPORTANT:
|
|
// This routine is designed to protect against external attack, not against
|
|
// being called incorrectly. It only does minimal checking of socketPath.
|
|
// For example, if one of the components of socketPath was "..", the security
|
|
// checking done by this routine might be invalid. Do not pass an untrusted
|
|
// socketPath to this routine.
|
|
{
|
|
int err;
|
|
char * parentPath;
|
|
char * grandParentPath;
|
|
char * lastSlash;
|
|
struct stat sb;
|
|
struct sockaddr_un bindReq;
|
|
static const mode_t kRequiredParentMode = S_IRWXU | (S_IRGRP | S_IXGRP) | (S_IROTH | S_IXOTH); // rwxr-xr-x
|
|
|
|
parentPath = NULL;
|
|
grandParentPath = NULL;
|
|
|
|
// sockaddr_un can only hold a very short path (it's 104 bytes long),
|
|
// so we check that limit right up front. Note the use of >= in the
|
|
// check below: we fail if socketPath is exactly 104 chars long because
|
|
// that would leave no space for the trailing null character. Looking at
|
|
// the kernel code, I don't think this is strictly necessary (in fact,
|
|
// it seems that the kernel code will handle much longer paths than sun_path,
|
|
// up to an overall sockaddr size ofSOCK_MAXADDRLEN), but I'm being
|
|
// paranoid.
|
|
|
|
err = 0;
|
|
if (strlen(socketPath) >= sizeof(bindReq.sun_path)) {
|
|
err = EINVAL;
|
|
}
|
|
|
|
// Construct parentPath and grandParent path by knocking path components
|
|
// off the end.
|
|
|
|
if (err == 0) {
|
|
parentPath = strdup(socketPath);
|
|
if (parentPath == NULL) {
|
|
err = ENOMEM;
|
|
}
|
|
}
|
|
if (err == 0) {
|
|
lastSlash = strrchr(parentPath, '/');
|
|
if (lastSlash == NULL) {
|
|
fprintf(stderr, "SafeBindUnixDomainSocket: Can't get parent for path (%s).\n", socketPath);
|
|
err = EINVAL;
|
|
} else {
|
|
*lastSlash = 0;
|
|
}
|
|
}
|
|
if (err == 0) {
|
|
grandParentPath = strdup(parentPath);
|
|
if (grandParentPath == NULL) {
|
|
err = ENOMEM;
|
|
}
|
|
}
|
|
if (err == 0) {
|
|
lastSlash = strrchr(grandParentPath, '/');
|
|
if (lastSlash == NULL) {
|
|
fprintf(stderr, "SafeBindUnixDomainSocket: Can't get grandparent for path (%s).\n", socketPath);
|
|
err = EINVAL;
|
|
} else {
|
|
*lastSlash = 0;
|
|
}
|
|
}
|
|
|
|
// Check that the parent directory is a sticky root-owned directory. If the
|
|
// grandparent directory is sticky, we know that any items in that directory
|
|
// that are owned by us can't be substituted by anyone else (that is: deleted,
|
|
// moved or renamed, and then replaced by an attacker's item).
|
|
|
|
if (err == 0) {
|
|
err = stat(grandParentPath, &sb);
|
|
err = MoreUNIXErrno(err);
|
|
}
|
|
|
|
if ( (err == 0) && ( ! (sb.st_mode & S_ISTXT) || (sb.st_uid != 0) ) ) {
|
|
fprintf(stderr, "SafeBindUnixDomainSocket: Grandparent directory (%s) is not a sticky root-owned directory.\n", grandParentPath);
|
|
err = EINVAL;
|
|
}
|
|
|
|
// Create the parent directory. Ignore an EEXIST error because of the
|
|
// next check.
|
|
|
|
if (err == 0) {
|
|
err = mkdir(parentPath, kRequiredParentMode);
|
|
err = MoreUNIXErrno(err);
|
|
|
|
if (err == EEXIST) {
|
|
err = 0;
|
|
}
|
|
}
|
|
|
|
// Check that the parent directory is a directory, is owned by us, and
|
|
// has the right mode. This ensures that no one except us can be monkeying
|
|
// with its contents. And we know that no one can substitute a /different/
|
|
// directory underneath us because its parent (grandParentPath) is sticky.
|
|
|
|
if (err == 0) {
|
|
err = stat(parentPath, &sb);
|
|
err = MoreUNIXErrno(err);
|
|
}
|
|
if ( (err == 0) && (sb.st_uid != geteuid()) ) {
|
|
fprintf(stderr, "SafeBindUnixDomainSocket: Parent (%s) is not owned by us.\n", parentPath);
|
|
err = EINVAL;
|
|
}
|
|
if ( (err == 0) && ! S_ISDIR(sb.st_mode) ) {
|
|
fprintf(stderr, "SafeBindUnixDomainSocket: Parent (%s) is not a directory.\n", parentPath);
|
|
err = EINVAL;
|
|
}
|
|
if ( (err == 0) && ( (sb.st_mode & ACCESSPERMS) != kRequiredParentMode ) ) {
|
|
fprintf(stderr, "SafeBindUnixDomainSocket: Parent (%s) has wrong permissions.\n", parentPath);
|
|
err = EINVAL;
|
|
}
|
|
|
|
// If all is well, let's bind our socket. This involves deleting any existing
|
|
// socket and recreating our own. We know we can do this without worrying
|
|
// about substitution because only we have write access to the parent directory.
|
|
|
|
if (err == 0) {
|
|
mode_t oldUmask;
|
|
|
|
// Temporarily set the umask to 0 (the default is 0022) so that the
|
|
// socket is created rwxrwxrwx. This allows any user to connect to
|
|
// our socket.
|
|
|
|
oldUmask = umask(0);
|
|
|
|
// Delete any existing socket. We delete the socket when we shut down,
|
|
// but, if we quit unexpectedly, it could've been left lying around.
|
|
|
|
(void) unlink(socketPath);
|
|
|
|
// Bind the socket, allowing other clients to connect.
|
|
|
|
bindReq.sun_len = sizeof(bindReq);
|
|
bindReq.sun_family = AF_UNIX;
|
|
strcpy(bindReq.sun_path, socketPath);
|
|
|
|
err = bind(sockFD, (struct sockaddr *) &bindReq, SUN_LEN(&bindReq));
|
|
err = MoreUNIXErrno(err);
|
|
|
|
(void) umask(oldUmask);
|
|
}
|
|
|
|
free(parentPath);
|
|
free(grandParentPath);
|
|
|
|
return err;
|
|
}
|
|
|
|
static void PrintUsage(const char *argv0)
|
|
// Print the program's usage. Given that it supports no arguments whatsoever,
|
|
// this is pretty simple.
|
|
{
|
|
const char *command;
|
|
|
|
command = strrchr(argv0, '/');
|
|
if (command == NULL) {
|
|
command = argv0;
|
|
} else {
|
|
command += 1;
|
|
}
|
|
fprintf(stderr, "usage: %s\n", command);
|
|
}
|
|
|
|
|
|
|
|
@implementation MPNotifications (IPCAdditions)
|
|
-(BOOL) terminateBackgroundThread {
|
|
return terminateBackgroundThread;
|
|
}
|
|
|
|
-(void) setTerminateBackgroundThread:(BOOL)newStatus {
|
|
terminateBackgroundThread = newStatus;
|
|
}
|
|
|
|
-(void) startIPCServerThread:(NSDictionary *)serverInfo {
|
|
NSAutoreleasePool * sPool = [[NSAutoreleasePool alloc] init];
|
|
|
|
NSLog(@"INSIDE SERVER THREAD");
|
|
|
|
[[MPNotifications sharedListener]
|
|
setPerformingTclCommand:[serverInfo objectForKey:@"currentMethod"]];
|
|
|
|
//Configure runloop
|
|
int err = 0;
|
|
int listenerFD;
|
|
CFSocketRef listenerCF;
|
|
Boolean didBind;
|
|
|
|
didBind = false;
|
|
listenerFD = -1;
|
|
listenerCF = NULL;
|
|
|
|
|
|
|
|
|
|
// Ignore SIGPIPE because it's a deeply annoying concept. If you don't ignore
|
|
// SIGPIPE when writing to a UNIX domain socket whose far side has been closed
|
|
// will trigger a SIGPIPE, whose default action is to terminate the program.
|
|
if (err == 0) {
|
|
fprintf(stderr, "CFLocalServer: Starting up (pid: %ld).\n", (long) getpid());
|
|
NSLog(@"CFLocalServer: Starting up (pid: %ld).\n", (long) getpid());
|
|
err = MoreUNIXIgnoreSIGPIPE();
|
|
}
|
|
|
|
// Set up the signal handlers we are interested in. In this case we redirect
|
|
// SIGINT and SIGINFO to our runloop. If either of these signals occurs, we
|
|
// end up executing SignalRunLoopCallback.
|
|
if (err == 0) {
|
|
if (hasInstalledSignalsToSocket == 0) {
|
|
sigset_t interestingSignals;
|
|
(void) sigemptyset(&interestingSignals);
|
|
(void) sigaddset(&interestingSignals, SIGINT);
|
|
(void) sigaddset(&interestingSignals, SIGINFO);
|
|
|
|
err = InstallSignalToSocket(
|
|
&interestingSignals,
|
|
CFRunLoopGetCurrent(),
|
|
kCFRunLoopDefaultMode,
|
|
SignalRunLoopCallback,
|
|
NULL
|
|
);
|
|
if (err == 0) {
|
|
NSLog(@"Successfuly loaded signals to socket");
|
|
hasInstalledSignalsToSocket = 1;
|
|
}
|
|
else
|
|
NSLog(@"Attempted to unsucessfully to Install signal to socket");
|
|
}
|
|
}
|
|
|
|
// Create the initial client set.
|
|
if (err == 0) {
|
|
err = ClientInitialise();
|
|
NSLog(@"Initilalizing Client");
|
|
}
|
|
|
|
// Create our listening socket, bind it, and then wrap it in a CFSocket.
|
|
if (err == 0) {
|
|
listenerFD = socket(AF_UNIX, SOCK_STREAM, 0);
|
|
err = MoreUNIXErrno(listenerFD);
|
|
NSLog(@"Creating Socket");
|
|
}
|
|
if (err == 0) {
|
|
//err = SafeBindUnixDomainSocket(listenerFD, kServerSocketPath);
|
|
//NSLog(@"Socket file path is %@", socketFilePath);
|
|
err = SafeBindUnixDomainSocket(listenerFD, [[serverInfo objectForKey:@"ipcFilePath"] cStringUsingEncoding:NSUTF8StringEncoding]);
|
|
didBind = (err == 0);
|
|
NSLog(@"Binding Socket %i", err);
|
|
}
|
|
if (err == 0) {
|
|
err = listen(listenerFD, 5);
|
|
err = MoreUNIXErrno(err);
|
|
NSLog(@"Listening Socket");
|
|
}
|
|
if (err == 0) {
|
|
listenerCF = CFSocketCreateWithNative(
|
|
NULL,
|
|
(CFSocketNativeHandle) listenerFD,
|
|
kCFSocketAcceptCallBack,
|
|
ListeningSocketAcceptCallback,
|
|
NULL);
|
|
|
|
NSLog(@"Creating Callbacks!");
|
|
if (listenerCF == NULL) {
|
|
err = EINVAL;
|
|
}
|
|
}
|
|
|
|
// Schedule the listening socket on our runloop.
|
|
NSRunLoop * currentLoop = [NSRunLoop currentRunLoop];
|
|
if (err == 0) {
|
|
CFRunLoopSourceRef rls;
|
|
|
|
rls = CFSocketCreateRunLoopSource(NULL, listenerCF, 0);
|
|
if (rls == NULL) {
|
|
err = EINVAL;
|
|
} else {
|
|
CFRunLoopAddSource( [currentLoop getCFRunLoop], rls, kCFRunLoopDefaultMode);
|
|
NSLog(@"Adding Source to current runloop");
|
|
|
|
// We no longer need this source, so we just release it.
|
|
CFRelease(rls);
|
|
}
|
|
}
|
|
|
|
|
|
double resolution = 0.1;
|
|
BOOL isRunning;
|
|
|
|
//Add input sources to my run loop
|
|
//terminateBackgroundThread is going to be set to NO before the privileged operation is called
|
|
//it will be set to YES after the privileged operation finishes execution. So I guess I need
|
|
//accessor methods?
|
|
NSThread * cThread = [NSThread currentThread];
|
|
NSLog(@"RUNNING RUN LOOP with thread %@" , [cThread threadDictionary]);
|
|
|
|
do {
|
|
NSDate * nextDate = [NSDate dateWithTimeIntervalSinceNow:resolution];
|
|
isRunning = [currentLoop runMode:NSDefaultRunLoopMode beforeDate:nextDate];
|
|
|
|
//NSLog(@"running runloop");
|
|
//might add some code here to clean up and recreate autoreleasepool
|
|
//} while (isRunning && terminateBackgroundThread == NO);
|
|
} while (isRunning || clientHasQuit == 0);
|
|
|
|
|
|
//Forcibly terminate runloop N.B. ClientQuit() which is invoked
|
|
//before this call also terminates applications main runloop ...
|
|
//check to ensure that that doesn't create problems
|
|
CFRunLoopStop([currentLoop getCFRunLoop]);
|
|
|
|
[[MPNotifications sharedListener] setPerformingTclCommand:@""];
|
|
[sPool release];
|
|
}
|
|
|
|
-(void) prepareIPCServerThread {
|
|
terminateBackgroundThread == NO;
|
|
}
|
|
|
|
-(void) stopIPCServerThread {
|
|
terminateBackgroundThread == YES;
|
|
}
|
|
|
|
-(void) sendIPCNotification:(NSString *)message {
|
|
NSArray * array = [message componentsSeparatedByString:MPSEPARATOR];
|
|
|
|
//Strip off "--->" from message
|
|
NSMutableString * msg = [NSMutableString stringWithString:[array objectAtIndex:3]];
|
|
[msg setString:[[msg componentsSeparatedByString:@"--->"] componentsJoinedByString:@""]];
|
|
|
|
// Array is of the form
|
|
// Notification type, MPCHANNEL, MPPREFIX, MPMESSAGE
|
|
// e.g. MPInfoNotification_&MP&_stdout_&MP&_None_&MP&_total size is 18781178 speedup is 57.00
|
|
NSDictionary * userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
|
|
[[MPNotifications sharedListener] performingTclCommand], MPMETHOD,
|
|
[array objectAtIndex:1], MPCHANNEL,
|
|
[array objectAtIndex:2], MPPREFIX,
|
|
msg, MPMESSAGE, nil];
|
|
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:[array objectAtIndex:0]
|
|
object:nil
|
|
userInfo:userInfo];
|
|
}
|
|
@end
|