Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
Server.c
/* |
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 eskimo1 |
Fixed various documentation/comment changes. |
Revision 1.1 2005/05/17 12:19:32 eskimo1 |
First checked in. |
*/ |
///////////////////////////////////////////////////////////////// |
// System interfaces |
#include <CoreServices/CoreServices.h> |
#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> |
// Project interfaces |
#include "Protocol.h" |
#include "Common.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); |
} |
// The Shout packet is good. Let's echo it to each listener. |
if (result) { |
ClientState ** allClients; |
CFIndex clientCount; |
CFIndex clientIndex; |
fprintf(stderr, "%p: Shout \"%.*s\"\n", client, (int) sizeof(packet->fMessage), packet->fMessage); |
// 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); |
} |
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); |
} |
int main (int argc, const char * argv[]) |
// The primary entry point. |
{ |
int err; |
int junk; |
int listenerFD; |
CFSocketRef listenerCF; |
Boolean didBind; |
didBind = false; |
listenerFD = -1; |
listenerCF = NULL; |
// Check the command line arguments (there shouldn't be any). |
err = 0; |
if (argc != 1) { |
PrintUsage(argv[0]); |
err = ECANCELED; |
} |
// 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()); |
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) { |
sigset_t interestingSignals; |
(void) sigemptyset(&interestingSignals); |
(void) sigaddset(&interestingSignals, SIGINT); |
(void) sigaddset(&interestingSignals, SIGINFO); |
err = InstallSignalToSocket( |
&interestingSignals, |
CFRunLoopGetCurrent(), |
kCFRunLoopDefaultMode, |
SignalRunLoopCallback, |
NULL |
); |
} |
// Create the initial client set. |
if (err == 0) { |
err = ClientInitialise(); |
} |
// 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); |
} |
if (err == 0) { |
err = SafeBindUnixDomainSocket(listenerFD, kServerSocketPath); |
didBind = (err == 0); |
} |
if (err == 0) { |
err = listen(listenerFD, 5); |
err = MoreUNIXErrno(err); |
} |
if (err == 0) { |
listenerCF = CFSocketCreateWithNative( |
NULL, |
(CFSocketNativeHandle) listenerFD, |
kCFSocketAcceptCallBack, |
ListeningSocketAcceptCallback, |
NULL); |
if (listenerCF == NULL) { |
err = EINVAL; |
} |
} |
// Schedule the listening socket on our runloop. |
if (err == 0) { |
CFRunLoopSourceRef rls; |
rls = CFSocketCreateRunLoopSource(NULL, listenerCF, 0); |
if (rls == NULL) { |
err = EINVAL; |
} else { |
CFRunLoopAddSource( CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode); |
// We no longer need this source, so we just release it. |
CFRelease(rls); |
} |
} |
// Go go gadget server! |
if (err == 0) { |
CFRunLoopRun(); |
} |
// Clean up. |
// Close down any connected clients. |
ClientTerminate(); |
// Clean up our listenenng socket. |
if (listenerCF != NULL) { |
CFSocketInvalidate(listenerCF); |
CFRelease(listenerCF); |
} |
// CFSocket will close listenerFD when listenerCF is invalidated, so we |
// don't close it if we did the invalidate above. |
if ( (listenerFD != -1) && (listenerCF == NULL) ) { |
junk = close(listenerFD); |
assert(junk == 0); |
} |
if (didBind) { |
(void) unlink(kServerSocketPath); |
} |
if (err == 0) { |
fprintf(stderr, "CFLocalServer: Shutting down.\n"); |
} else if (err != ECANCELED) { |
fprintf(stderr, "CFLocalServer: Failed with error %d.\n", err); |
} |
return (err == 0) ? EXIT_SUCCESS : EXIT_FAILURE; |
} |
Copyright © 2005 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2005-07-26