/*
 * This program is run by a detached process as part of scheme to provide
 * the same functionality as DECnet using mailboxes instead of actual
 * DECnet links.
 *
 *
 *            +----------------+     +----------------+
 *            | Daemon process |---->: response MBX   :
 *            | (this program) |     + - - - - - - - -+
 *            +- - - - - - - - +     | client process |
 *         +->:  Request MBX   :<----| (web server)   |
 *         |  +----------------+     +----------------+
 *         |             \                    |
 *         |              \                   |
 *         |           +----------------+     | (bi-directional)
 *         +-----------| server process |     |
 *                     | (cgi script)   |     |
 *                     + - - - - - - - -+     |
 *                     : link MBX       :<>---+
 *                     +----------------+
 *
 * Each of the three process involved has a mailbox that it creates
 * and is responsible for.
 *
 * Daemon process/request MBX:
 *   The daemon process reads messages from the request MBX and takes the
 *   following actions:
 *	msg type:
 *        'Hello'  register the sending process PID as a client and
 *		   assign channel to its response MBX
 *
 *	  'Connect' Look for available server or create one, send
 *		    the resulting server's PID and link MBX unit number to
 *		    requesting client's response MBX.  Created servers
 *		    have their termination mailbox set to REQUEST MBX.
 *		    Client will then send connect message to server's
 *		    link MBX.
 *
 *	  'Accept' Mark sender as available to service a connect request.
 *		   (if sender is currently connected to a client, send
 *		    the client a disonnect message).
 *
 *	  'Disconnect' Wait x seconds for client to finish and then forceex.
 *
 *	  'DELPROC' Remove sender from process list and send connected client
 *		    a disconnect message.  Clients that receive disconnect
 *		    should cancel pending I/O to link MBX.
 *
 * Server process/link MBX:
 *   The server process executes a DCL command procedure that is an analogue
 *   to sys$system:netserver.com.  This procedure has a loop that
 *   the runs a program (analogue to netserver.exe) that creates the link MBX
 *   and sends an accept message to the request MBX and waits for a client
 *   to send a connect message.
 *
 *   Note: The server processes are created via SYS$CREPRC() and do not
 *   inherit the ACP process's DCL symbols or process logical names.  In
 *   particular watch for use of process logical names in the RMS default.
 *
 * client process/response MBX:
 *   This mailbox receives status messages to pending requests made by the
 *   client.  The client receives a confirm message when a connection has
 *   been received.
 *
 * Usage:
 *    $ MBXNET_DAEMON [client-proc [p1] [p2] [p3]...]
 *    $ deck
 *    .
 *    .   (parameters file, see below)
 *    .
 *    $ eod
 *
 * The program reads parameter settings from standard input, each line
 * of input may be a comment (first non-blank character a '!' or '#'), or
 * an assignment statement of the form: keyword=value.  The following
 * table describes the parameters settable by the parameters file:
 *
 *   keyword        default value              Description
 *    limit         "4"			Limit on number of server processes
 *					to create.  Currently unimplemented
 *					
 *    timeout       "10"			Length of time non-permanent servers
 *					can remain idle before being killed.
 *					Units are in number of 6-second 'ticks'.
 *					
 *    detach        "0"			If non-zero, processes are created as
 *					detached processes, otherwise processes
 *					are created as sub-processes.
 *
 *    username      ""			Username under which created processes
 *					are to run, requires detach be non-zero.
 *
 *    logfile       "mbxnet_server.log"	Output filename for server processes.
 *
 *    server        "mbxnet_server.com"	Input filename for server processes.
 *					(DCL command procedure).
 *
 *    command       ""			DCL command to be executed by
 *					client process (runs web server).
 *
 *    command_out   "mbxnet_command.out" Output file for client process.
 *
 *   Author:	David Jones
 *   Date:	14-JAN-1998		Rework.
 *   
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <descrip.h>
#include <ctype.h>
#include <iodef.h>
#include <impdef.h>
#ifndef IO$M_READERCHECK
#define IO$M_READERCHECK 0x100
#endif
#include <ssdef.h>
#include <dvidef.h>
#ifdef __DECC
#include <cmbdef.h>
#else
#ifndef CMB$M_READONLY
#define CMB$M_READONLY 0x1
#define CMB$M_WRITEONLY 0x2
#endif
#endif
#include <jpidef.h>
#include <lckdef.h>
#include <prcdef.h>

#include "mbxnet.h"

int SYS$QIOW(), SYS$ASSIGN(), SYS$DASSGN(), SYS$CREMBX(), SYS$DELMBX();
int LIB$GETDVI(), LIB$SPAWN(), SYS$CREPRC(), SYS$DELPRC(), SYS$EXIT();
int SYS$QIO(), SYS$WFLOR(), SYS$SETIMR(), SYS$SETEF(), SYS$CLREF();
int SYS$PERSONA_CREATE(), SYS$PERSONA_ASSUME(), SYS$DCLEXH();
int LIB$GETJPI(), SYS$SETPRV();

static short request_mbx;
static int request_unit;
static $DESCRIPTOR(request_mbx_name, "WWW_MBXNET_REQUEST");
static $DESCRIPTOR(loginout, "SYS$SYSTEM:LOGINOUT.EXE");
static $DESCRIPTOR(comfile, "MBXNET_SYSTEM:MBXNET_SERVER.COM");
static $DESCRIPTOR(outfile, "SYS$LOGIN:MBXNET_SERVER.LOG");
static char expanded_outfile[300];
static int current_tick = 0;
static int tick_pending;
static long command_pid;

struct pcb {
    struct pcb *flink, *blink;
    struct pcb *partner;		/* pcb of partner process or null */
    long pid;			/* Process ID */
    long flags;				/* options for client */
    /*
     * The following fields are only valid for server process PCBs.
     */
    int pending;		/* number of connect requests pending */
    int create_tick;		/* timestamp of when server created. */
    int expire_tick;		/* timestamp of when server dies if idle */
    /*
     * unit number of response (client) or link (server) mailbox.
     */
    short unit;
    /*
     * The chan field is only valid for client PCB and is the channel
     * assigned to MBA'unit'.
     */
    short chan;
};
typedef struct pcb *pcbptr;

struct io_status_block { unsigned short status, length; long pid; };

static pcbptr free_pcb, client_pcb, server_pcb;

static void rundown_server ( netmbx_req_message *msg, 
		struct io_status_block *iosb );
static void register_client ( netmbx_req_message *msg, 
		struct io_status_block *iosb );
static void register_server ( netmbx_req_message *msg, 
		struct io_status_block *iosb );
static void connect_client ( netmbx_req_message *msg, 
		struct io_status_block *iosb );
static void disconnect_server ( netmbx_req_message *msg, 
		struct io_status_block *iosb );
static void do_housekeeping();
static struct server_parameters {
    int timeout;		/* Max idle time for servers, 6 sec. ticks */
    int limit;			/* Max number of servers (unimplemented) */
    int permanent;		/* Number of servers to keep ready */
    int detach;			/* Create detached processes if true */
    char *username;		/* Run daemon as this user */
    char *server;		/* Command file for server processes */
    char *logfile;		/* Out file for server processes */
    char *command;		/* client program to run (HTTP server) */
    char *command_out;		/* Main log file for client process */
} param;

/*****************************************************************************/
static int init_server_parameters (int argc, char **argv )
{
    int i, state;
    char line[2048], *key, *value;
    FILE *in;
    /*
     * Initialize parameters to default values.
     */
    param.timeout = 10;
    param.limit = 4;
    param.permanent = 1;
    param.detach = 0;
    param.username = (char *) 0;
    param.server = (char *) 0;
    param.logfile = (char *) 0;
    param.command = (char *) 0;
    param.command_out = (char *) 0;
    /*
     * Read standard input.
     */
    in = fopen ( "SYS$INPUT", "r" );
    if ( !in ) return 0;

    while ( fgets ( line, sizeof(line), in ) ) {
	/*
	 * Parse line into key/value pairs.
	 */
	key = value = "";
	for ( i = state = 0; line[i]; i++ ) switch ( state )  {
	    case 0:		/* hunt for non-space */
		if ( isspace ( line[i] ) ) break;
		state = 1;
		key = &line[i]; 
		if ( line[i] == '#' || line[i] == '!' ) state = 2;
		line[i] = toupper ( line[i] );
		break;
	    case 1:
		if ( isspace ( line[i] ) ) {
		    state = 3;
		    line[i] = '\0';
		} else if ( line[i] == '=' ) {
		    state = 4;
		    line[i] = '\0';
		}
		line[i] = toupper ( line[i] );
		break;
	    case 2:   /* comment */
	    case 5:   /* syntax error */
		break;
	    case 3:
		if ( isspace ( line[i] ) ) break;
		state = 4;
		if ( line[i] != '=' ) state = 5;
		break;
	    case 4:
		if ( isspace ( line[i] ) ) break;
		state = 6;
		value = &line[i];
		break;
	    case 6:
		if ( line[i] == '\n' ) {
		    line[i] = '\0';
		}
		break;
	}
	if ( state != 6 ) {
	    printf("Invalid input line: '%s', ignored\n", line );
	    continue; /* comment or error */
	}
	/*
	 * Interpret keyword.
	 */
	if ( strcmp ( key, "LIMIT" ) == 0 ) {
	    param.limit = atoi ( value );
	} else if ( strcmp ( key, "TIMEOUT" ) == 0 ) {
	    param.timeout = atoi ( value );
	} else if ( strcmp ( key, "DETACH" ) == 0 ) {
	    param.detach = atoi ( value );
	} else if ( strcmp ( key, "USERNAME" ) == 0 ) {
	    param.username = malloc ( strlen(value) + 1 );
	    strcpy ( param.username, value );
	} else if ( strcmp ( key, "SERVER" ) == 0 ) {
	    param.server = malloc ( strlen(value) + 1 );
	    strcpy ( param.server, value );
	} else if ( strcmp ( key, "LOGFILE" ) == 0 ) {
	    param.logfile = malloc ( strlen(value) + 1 );
	    strcpy ( param.logfile, value );
	} else if ( strcmp ( key, "COMMAND" ) == 0 ) {
	    param.command = malloc ( strlen(value) + 1 );
	    strcpy ( param.command, value );
	} else if ( strcmp ( key, "COMMAND_OUT" ) == 0 ) {
	    param.command_out = malloc ( strlen(value) + 1 );
	    strcpy ( param.command_out, value );
	} else {
	    printf ( "Invalid keyword: '%s', ignored\n", key );
	}
	printf("Parameter '%s' has value '%s'\n", key, value );
    }
    fclose ( in );
    /*
     * If arguments are present on command line, override param.command.
     */
    if ( argc > 1 ) {
	int length;
	if ( param.command ) free ( param.command );
	for ( length = i = 1; i < argc; i++ ) length += strlen(argv[i])+1;
	param.command = malloc ( length+1 );
	param.command[0] = '\0';
	for ( i = 1; i < argc; i++ ) {
	    strcat ( param.command, " " );
	    strcat ( param.command, argv[i] );
	}
	param.command[0] = '@';
	printf("forced command line: '%s'\n", param.command );
    }
    return 1;
}
/*****************************************************************************/
static void tick_ast(int id)
{
    long int tick_interval[2] = { -60000000, -1 };	/* 6 seconds */
    int status;

    SYS$SETEF(17);
    tick_pending = 1;
    status = SYS$SETIMR ( 0, tick_interval, tick_ast, id, 0 );
    if ( (status&1) == 0 ) exit ( status );
}
/*****************************************************************************/
/* Assign channel writeonly to mailbox unit named in message.
 */
static int open_reply_mbx ( netmbx_req_message *msg,
	struct io_status_block *iosb, short *chan )
{
    int status, flags;
    char mbxnam[32];
    struct { long length; char *string; } mbxnam_dx;
    /*
     * contruct mailbox name from unit in message.
     */
    sprintf(mbxnam,"_MBA%d", (unsigned) msg->gen.unit );
    mbxnam_dx.length = strlen ( mbxnam );
    mbxnam_dx.string = mbxnam;
    flags = CMB$M_WRITEONLY;
    status = SYS$ASSIGN ( &mbxnam_dx, chan, 0, 0, flags );
    return status;
}
/*****************************************************************************/
static int reply_to_client ( pcbptr client, netmbx_req_message *msg, int mlen )
{
    struct io_status_block iosb;
    int status;

    status = SYS$QIOW ( 0, client->chan, IO$_WRITEVBLK | IO$M_READERCHECK,
	&iosb, 0, 0, msg, mlen, 0, 0, 0, 0 );
#ifdef DEBUG
printf("/acp/ reply_to_client qio to %x status: %d %d, type: %d\n", client->pid, 
status, iosb.status, msg->gen.type );
#endif
    if ( (status&1) == 1 ) status = iosb.status;
    if ( (status&1) == 0 ) {
	/* Error in write */
	printf("/acp/ reply_to_client failure: %d, msg.type: %d\n", status, msg->gen.type );
    } else if ( iosb.pid != client->pid ) {
	/*
	 * Consistecy check failed, message read by wrong guy.
	 */
	printf("/acp/ reply_to_client consitentcy failure: %d, msg.type: %d\n", status, 
		msg->gen.type );
    }
    return status;
}
/*****************************************************************************/
pcbptr sending_client ( struct io_status_block *iosb )
{
    pcbptr cur;
    for ( cur = client_pcb; cur; cur = cur->flink ) {
	if ( cur->pid == iosb->pid ) break;
    }
    return cur;
}
pcbptr sending_server ( struct io_status_block *iosb )
{
    pcbptr cur;
    for ( cur = server_pcb; cur; cur = cur->flink ) {
	if ( cur->pid == iosb->pid ) break;
    }
    return cur;
}
/*****************************************************************************/
/* Create deadman lock.
 */
static int deadman_lock ( master )
{
    int SYS$ENQW(), SYS$ENQ(), status;
    long lksb[4], code, pid, result;
    char lock_name[32];
    $DESCRIPTOR(lock_name_dx,"");
    /*
     * Get process ID and parent ID and generate lock name.
     */
    code = JPI$_OWNER;
    pid = 0;
    status = LIB$GETJPI ( &code, &pid, 0, &result, 0, 0 );
    if ( (status&1) == 0 ) return status;
    sprintf ( lock_name, "MBXNET_DEADMAN_%0x", master ? pid : result );
    lock_name_dx.dsc$w_length = strlen ( lock_name );
    lock_name_dx.dsc$a_pointer = lock_name;
    /*
     * Queue lock.
     */
    if ( master ) {
        status = SYS$ENQW ( 0, LCK$K_EXMODE, &lksb, 0, &lock_name_dx,
		0, 0, 0, 0, 0, 0 );
	if ( (status&1) == 1 ) status = lksb[0];
    } else {
        status = SYS$ENQ ( 0, LCK$K_EXMODE, &lksb, 0, &lock_name_dx,
	    0, SYS$EXIT, 20, 0, 0, 0 );
    }
    return status;
}
/*****************************************************************************/
int persona_reset ( int *reason )
{
    long original_persona;
    original_persona = 1;
    return SYS$PERSONA_ASSUME ( &original_persona, IMP$M_ASSUME_SECURITY );
}
/*****************************************************************************/
static int spawn_command( char *command, char *outfile )
{
    /*
     * Create mailbox that we send command over and get device name.
     */
    int mbx, code, namlen, status;
    static char nambuf[64];
    static $DESCRIPTOR(mailbox,nambuf);
    static $DESCRIPTOR(outfile_dx, "_NL:");
    struct io_status_block iosb;

    mbx = namlen = 0;
    status = SYS$CREMBX ( 0, &mbx, 256, 280, 0x0ff00, 0, 0, 0 );
    if ( (status&1) == 0 ) return status;
    code = DVI$_DEVNAM;
    status = LIB$GETDVI ( &code, &mbx, 0, 0, &mailbox, &namlen );
    if ( (status&1) == 0 ) return status;
    mailbox.dsc$w_length = namlen;
    nambuf[namlen] = '\0';
    if ( outfile ) {
	outfile_dx.dsc$a_pointer = outfile;
	outfile_dx.dsc$w_length = strlen ( outfile_dx.dsc$a_pointer );
    }
    /*
     * Login the process with mailbox as stdout.
     */
    status = SYS$CREPRC ( &command_pid, &loginout, &mailbox, &outfile_dx, 
	&outfile_dx,
	0, 0, 0, 4, 0, request_unit, param.detach ? PRC$M_DETACH : 0 );
    printf("status of process create: %d pid: %x\n", status, command_pid );
    /*
     * Send the command and follow with a logout command
     */
    status = SYS$QIOW ( 23, mbx, IO$_WRITEVBLK|IO$M_NOW, &iosb, 0, 0,
	command, strlen(command), 0, 0, 0, 0 );
printf("Status of command write: %d, %d\n", status, iosb.status );
    if ( (status&1) == 1 ) status = iosb.status;
    if ( (status&1) == 0 ) return status;

    status = SYS$QIOW ( 23, mbx, IO$_WRITEVBLK|IO$M_NOW, &iosb, 0, 0,
	"logout", 6, 0, 0, 0, 0 );
    if ( (status&1) == 1 ) status = iosb.status;

    return status;
}
/*****************************************************************************/
/* Main program.
 *
 *  Command line arguments:
 *	(none)
 */
int main ( int argc, char **argv )
{
    int status, code;
    long persona, image_priv[2];
    struct io_status_block iosb; 
    netmbx_req_message message;
    $DESCRIPTOR(cmd_line_dx,"");
    char *envval, cmd_line[256];
    /*
     * read parameters.
     */
    init_server_parameters(argc, argv);
    /*
     * Become user specified in username parameter, restore the image
     * privileges.
     */
    code = JPI$_IMAGPRIV;
    status = LIB$GETJPI ( &code, 0, 0, image_priv, 0, 0 );
    if ( (status&1) == 0 ) return status;
    if ( param.username && param.detach ) {
	struct dsc$descriptor username_dx;
	int flags, sys$hiber();
	static struct exit_block { struct exit_block *link;
	    int (*handler)(int *reason);
	    int argc, *reason_addr, reason; 
	} blk;
	flags = IMP$M_ASSUME_SECURITY;
	username_dx.dsc$a_pointer = param.username;
	username_dx.dsc$w_length = strlen ( param.username );
	status = SYS$PERSONA_CREATE ( &persona, &username_dx, 0 );
	if ( (status&1) == 1 ) status = SYS$PERSONA_ASSUME ( &persona, flags );
	printf("Assumed persona %s, status: %d\n", param.username, status );
	if ( (status&1) == 0 ) while ( sys$hiber() );
	if ( (status&1) == 0 ) exit ( status );
	status = SYS$SETPRV ( 1, image_priv, 0, 0 );
	/*
	 * Set up exit handler to revert back to original persona.
	 */
	blk.handler = &persona_reset;
	blk.argc = 1;
	blk.reason_addr = &blk.reason;
	status = SYS$DCLEXH ( &blk );
    } else if ( param.username ) {
	printf ("Cannot assume alternate username if not using detach mode\n");
    }
    /*
     * Setup lock to force mbxnet_server program to abort.
     */
    status = deadman_lock ( 1 );
    if ( (status&1) == 0 ) return status;
    /*
     * replace log file name if environment variable specified.
     */
    if ( param.logfile ) {
	outfile.dsc$a_pointer = param.logfile;
	outfile.dsc$w_length = strlen(outfile.dsc$a_pointer);
    }
    if ( param.server ) {
	comfile.dsc$a_pointer = param.server;
	comfile.dsc$w_length = strlen ( comfile.dsc$a_pointer );
    }
    /*
     * Create mailbox with system-wide or job-wide name, get our unit number.
     */
    status = SYS$CREMBX ( param.detach ? 1 : 0, &request_mbx, sizeof(message),
	sizeof(message)*3, 0x0ff00, 0, &request_mbx_name, CMB$M_READONLY );
    if ( (status&1) == 0 ) exit ( status );
    if ( param.detach ) {
	/* 
	 * Make system-wide mailbox go away when no more accessors 
	 */
        status = SYS$DELMBX ( request_mbx );
        if ( (status&1) == 0 ) exit ( status );
    }
    code = DVI$_UNIT;
    request_unit = 0;
    status = LIB$GETDVI ( &code, &request_mbx, 0, &request_unit, 0, 0 );
    if ( (status&1) == 0 ) exit ( status );
#ifdef DEBUG
    printf("/acp/ Mailbox chan is %d, unit is %d\n", request_mbx, request_unit );
#endif
    /*
     * Spawn process to do subsidary operations.
     */
    if ( param.command ) {
	status = spawn_command ( param.command, param.command_out );
#ifdef DEBUG
	printf("/acp/ status of spawn_command: %d\n", status );
#endif
	if ( (status&1) == 0 ) {
	    printf("/acp/ Error spawning subsidary process: %d\n", status );
	    exit ( status );
	}
    }
    /*
     * Inialize process list.
     */
    free_pcb = client_pcb = server_pcb = (pcbptr) 0;
    /*
     * Read messages.
     */
    tick_ast ( 1 );			/* get timer rolling */
    for ( iosb.status = 2312; ; ) {
	SYS$WFLOR ( 16, 0x00030000 );
	if ( tick_pending ) {
	    current_tick++;
	    tick_pending = 0;
	    SYS$CLREF ( 17 );
	    do_housekeeping();
	}
	if ( iosb.status == 0 ) continue;	/* i/o still pending */
#ifdef DEBUG
	printf("/acp/ message received, sts: %d, len: %d, pid: %x code: %d\n", 
		iosb.status, iosb.length, iosb.pid, message.gen.type );
#endif
	if ( (iosb.status&1) == 1 ) switch ( message.gen.type ) {

	    case MSG$_DELPROC:
		/*
		 * server process died.
		 */
#ifdef DEBUG
		printf("/acp/ server process %x(%x) died, status: %x\n",
		     message.delproc.pid, iosb.pid, message.delproc.finalsts );
#endif
		iosb.pid = message.delproc.pid;
		rundown_server ( &message, &iosb );
		break;

	    case MSG$_NODEACC:
		/*
		 * register PID as client (can make requests) and
		 * assign channel to it's response mailbox.
		 */
		register_client ( &message, &iosb );
		break;

	    case MSG$_CONNECT:
		/*
		 * Client wishes connection.
		 */
		connect_client ( &message, &iosb );
		break;

	     case MSG$_CONFIRM:
		/*
		 * Server is ready for next connection.
		 */
		register_server ( &message, &iosb );
		break;

	    case MSG$_DISCON:
		/*
		 * Client breaking connection, forcex server
		 */
		disconnect_server ( &message, &iosb );
		break;

	}
        status = SYS$QIO ( 16, request_mbx, IO$_READVBLK, &iosb, 0, 0,
		&message, sizeof(message), 0, 0, 0, 0 );
	if ( (status&1) == 0 ) break;	/* QIO problem */
    }
    return status;
}
/*****************************************************************************/
static void rundown_server ( netmbx_req_message *msg, 
		struct io_status_block *iosb ) 
{
    pcbptr server, client;
    netmbx_req_message message;
    int status;

    server = sending_server ( iosb );
#ifdef DEBUG
    printf("/acp/ rundown request, sender: %x(%x), partner: %x\n",  iosb->pid,
	server ? server->pid : 0, server ? server->partner : 0 );
#endif
    if ( ! server ) {
	/*
	 * See if deleted process was the command process.
	 */
	if ( iosb->pid == command_pid ) exit ( 44 );
	return;
    }
    /*
     * Find client that matches partner.
     */
    if ( server->partner ) {
	client = server->partner;
	if ( client->pid ) {
	    /*
	     * Tell client that server died.
	     */
	    message.gen.type = MSG$_DISCON;
	    message.gen.unit = server->unit;
	    message.gen.pid = server->pid;
	    status = reply_to_client ( client, &message, 12 );
	    return;
	}
    }
    /*
     * Remove from server list and place on free list.
     */
    if ( !server->blink ) server_pcb = server->flink;
    else server->blink->flink = server->flink;
    if ( server->flink ) server->flink->blink = server->blink;
    server->expire_tick = current_tick + param.timeout;

    server->flink = free_pcb;
    free_pcb = server;
}
/*****************************************************************************/
/* Received NODEACC message from a client process, add it to list. */
static void register_client ( netmbx_req_message *msg, 
		struct io_status_block *iosb ) 
{
    pcbptr client, server;
    int status;
    short reply_chan;
    char *lparen, *rparen;

    if ( iosb->length < 8 ) return;		/* invalid message */
    client = sending_client ( iosb );
#ifdef DEBUG
    printf("/acp/ registering client %x, stale: %d\n", iosb->pid, client );
#endif
    if ( client ) {
	/*
	 * Assume stale information (client app restarted).
	 */
	SYS$DASSGN ( client->chan );
	/*
         * Kill servers bound to client.
         */
	for ( server = server_pcb; server; server = server->flink ) {
	    if ( server->partner == client && server->pid ) {
		server->partner = (pcbptr) 0;
		status = SYS$DELPRC ( &server->pid, 0 );
		printf("/acp/ killed bound server %x, status: %d\n", 
			server->pid, status );
	    }
	}
    } else {
	/*
	 * Allocate new pcb.
	 */
	if ( free_pcb ) {
	    client = free_pcb;
	    free_pcb = client->flink;
        } else {
	    client = (pcbptr) malloc ( sizeof(struct pcb) );
        }
	/*
	 * Add to chain.
	 */
	client->blink = (pcbptr) 0;
	client->flink = client_pcb;
	if ( client_pcb ) client_pcb->flink = client;
	client_pcb = client;
	client->chan = 0;
	client->pid = iosb->pid;
    }
    /*
     * Initialize control block.
     */
    client->partner = 0;
    client->unit = msg->gen.unit;
    client->pending = 0;
    client->flags = 0;
    /*
     * Check for flags value in 'info' field.
     */
    msg->gen.info[iosb->length-8] = '\0';
    lparen = strchr ( msg->gen.info, '(' );
    rparen = strchr ( msg->gen.info, ')' );
    if ( lparen ) if ( lparen < rparen ) {
	*rparen = '\0';
	client->flags = atoi ( lparen+1 );
    }
    /*
     * Assign channel to client's mailbox.
     */
    status = open_reply_mbx ( msg, iosb, &client->chan );
    if ( (status&1) ==  0 ) {
	/*
	 * Open failed, deallocate.
	 */
	client_pcb = client->flink;
	if ( client_pcb ) client_pcb->blink = (pcbptr) 0;
	client->flink = free_pcb;
	free_pcb = client;
    }
}
/*****************************************************************************/
static void register_server ( netmbx_req_message *msg, 
		struct io_status_block *iosb ) 
{
    pcbptr server, client;
    int status, unit;
    netmbx_req_message message;

    server = sending_server ( iosb );
    if ( !server ) {
	/* Shouldn't happen, ignore the message */
	printf("/acp/ Unknown server confirm\n");
	return;
    }
    /*
     * Find a client matching the server's partner pid
     */
    client = server->partner;
    if ( client ) {
	    if ( !client->pending ) {
		/*
		 * If client not waiting, mark server as available.
		 */
		server->partner = (pcbptr) 0;
		server->expire_tick = current_tick + param.timeout;
		/*
		 * Send disconnect message to client to force connection rundown.
		 */
		if ( (client->flags&1) == 1 ) {
	            message.gen.type = MSG$_DISCON;
	            message.gen.unit = server->unit;
	            message.gen.pid = server->pid;
	            status = reply_to_client ( client, &message, 12 );
		}
	     } else {
	         /* 
		  * Send confirm to client mailbox
		  */
	         --client->pending;
	        msg->gen.pid = server->pid;
	        status = reply_to_client ( client, msg, 12 );
	    }
    }
    server->unit = msg->gen.unit;
}
/*****************************************************************************/
static void connect_client ( netmbx_req_message *msg, 
		struct io_status_block *iosb ) 
{
    pcbptr client, server;
    int status, i;

    if ( iosb->length < 8 ) return;	/* runt packet */
    client = sending_client ( iosb );
#ifdef DEBUG
    for ( i = 0; i < iosb->length-8; i++ )
	if ( msg->gen.info[i] < ' ' ) msg->gen.info[i] = '.';
    msg->gen.info[iosb->length-8] = '\0';
    printf("/acp/ beginning connect, sending client id: %x, info: '%s'\n", 
	client->pid, msg->gen.info );
#endif
    if ( !client ) return;	/* unregistered client */
    /*
     * Scan server list for free server.
     */
    for ( server = server_pcb; server; server = server->flink ) {
	if ( !server->partner ) {
	    /*
	     * free server, send server's link ID back to client.
	     */
#ifdef DEBUG
	    printf("/acp/ reusing server %x, unit %d\n", server->pid, server->unit );
#endif
	    server->partner = client;
	    server->pending = 0;
	    msg->gen.type = MSG$_CONFIRM;
	    msg->gen.unit = server->unit;
	    msg->gen.pid = server->pid;
	    status = reply_to_client ( server->partner, msg, 12 );
	    return;
	}
    }
    /*
     * if no match, create new server process.
     */
    if ( free_pcb ) {
	server = free_pcb;
	free_pcb = server->flink;
    } else {
	server = (pcbptr) malloc ( sizeof(struct pcb) );
    }
    server->blink = (pcbptr) 0;
    server->flink = server_pcb;
    server->pid = 0;
    server->partner = client;
    server->pending = 1;
    server->create_tick = current_tick;
    server->expire_tick = current_tick + param.timeout;
    server->unit = 0;
    status = SYS$CREPRC ( &server->pid, &loginout, &comfile, &outfile, 0,
	0, 0, 0, 4, 0, request_unit, param.detach ? PRC$M_DETACH : 0 );
    if ( (status&1) == 1 ) {
	if ( server_pcb ) server_pcb->blink = server;
	server_pcb = server;
	server->partner = client;
	client->pending++;
    } else {
	/*
	 * Create failed, put block on free list and notify client.
	 */
        printf("/acp/ Status of creprc: %d, pid: %x\n",status, server->pid );
	server->flink = free_pcb;
	free_pcb = server;

	msg->gen.type = MSG$_DISCON;
	msg->gen.pid = 0;
	status = reply_to_client ( client, msg, 12 );
    }
}
/*****************************************************************************/
static void disconnect_server ( netmbx_req_message *msg, 
		struct io_status_block *iosb ) 
{
   pcbptr server, client;
   int status;

   server = sending_server ( iosb );
   if ( !server ) return;

   if ( server->partner ) {
	/*
	 * Abort pending connection.
	 */
	msg->gen.type = MSG$_DISCON;
	msg->gen.unit = server->unit;
	msg->gen.pid = iosb->pid;

	for ( client = client_pcb; client; client = client->flink ) {
	    status = reply_to_client ( client, msg, 12 );
#ifdef DEBUG
	    printf("/acp/ notified client of process deletion: %d\n", status );
#endif
	}
   }
    /*
     * Remove from server pcb list.
     */
    if ( server->flink ) {
	server->flink->blink = server->blink;
	if ( !server->blink ) server_pcb = server->flink;
    }
    if ( server->blink ) {
	server->blink->flink = server->flink;
    }

    server->flink = free_pcb;
    free_pcb = server;
}
/********************************************************************/
/* Do periodic housekeeping tasks.
 */
void do_housekeeping()
{
    int status;
    pcbptr server;
    /*
     * Scan server list for expired servers.
     */
    for ( server = server_pcb; server; server = server->flink ) {
	if ( !server->partner && (current_tick > server->expire_tick) ) {
	    /*
	     * free server, send server's link ID back to client.
	     */
	    status = SYS$DELPRC ( &server->pid, 0 );
#ifdef DEBUG
	    printf("/acp/ killed server %x, status: %d, unit %d\n", 
		server->pid, status, server->unit );
#endif
	}
    }
    return;
}
