/*
**++
**  FACILITY:
**      NNTP_DECM
**
**  ABSTRACT:
**      This module contains the QIO interface to DECnet for the NNTP
**      server module. This differs from the NNTP_DEC code by allowing
**      multiple NNTP connections to be served from a single network
**      server process. The code is based on the SYS$EXAMPLES:DB_SERVER.C
**      code written by Digital.
**
**  AUTHOR:
**      Geoff Huston
**
**  MODIFICATION HISTORY:
**      V5.5     7-Oct-1988     GIH
**          Initial multi-threaded server inplementation. This version has NOT
**          been exhaustively tested - and bugs may still remain with parts of
**          this code.
**      V6.1b7   9-Aug-1993  Charles Bailey  bailey@genetics.upenn.edu
**        - added close_net() as part of revising News memory management
**	V6.1b9	17-Aug-1994     Mark Martinec   mark.martinec@ijs.si
**	  - code cleanup to make it compile under gcc 2.6.0 with full
**	    warnings reporting turned on - with no or very few harmless warnings
**	V6.1b9	17-Sep-1994     Mark Martinec   mark.martinec@ijs.si
**	  - code cleanup to preserve the read-only nature of string literals
**	    (strategically placed 'const' attribute to string parameters)
**--
**/

#ifdef vaxc
#module NNTP_DECM "V6.1"
#endif

#define _NNTP_SERVER_DRIVER_C
#define _NNTP_DECM_C
#define module_name "NNTP_DECM"

#include "nntpinclude.h"

#if NAKED_INCLUDES
#include msgdef
#include nfbdef
#else
#include <msgdef.h>
#endif

#ifdef __DECC
#define NFB$C_DECLNAME 21  /* DECC doesn't provide nfbdef.h */
#else
#include <nfbdef.h>
#endif

#ifndef EXIT_ON_TIMEOUT
#define EXIT_ON_TIMEOUT	0
#endif

#define TEMP_MBX 0
#define MAX_MSG         128
#define MAX_NCB         110
#define BUF_QUO         128
                                        /* AST delivery types */
#define NET_RD          1
#define NET_WRT         2
#define NET_CMD         3
                                        /* processing queue types */
#define FREE_QUE        0
#define LIVE_QUE        1
#define ATTN_QUE        2
#define CMD_QUE         3

#define MAX_BUFFS       100
#define MAX_LINKS       32              /* Must be <= 32 for ffs/ffc to work */
#define SERVER_REQ      36

                                        /* read terminator types */
#define NO_INPUT        0
#define CMD_INPUT       1
#define FILE_INPUT      2

#define IDLE                0
#define READ_IN_PROGRESS    1
#define WRITE_IN_PROGRESS   2
#define LINK_ERROR          3

#define chk_ret(v)      if (!((status = (v)) & 1)) return(status)

#ifdef __DECC
#pragma member_alignment save
#pragma nomember_alignment  /* no member alignment - these are VMS structures */
#endif

static $DESCRIPTOR_CONST(server_idle,"0 00:15:00.00");        /* 15 minutes */
static $DESCRIPTOR_CONST(read_idle,"0 00:10:00.00");          /* 10 minutes */
static $DESCRIPTOR_CONST(netcmd_mbx,"NETCMD_MBX");
static $DESCRIPTOR_CONST(net_device,"_NET:");
static $DESCRIPTOR_CONST(object_name,"NNTP");

struct dsc$descriptor_s nfb_desc;
struct dsc$descriptor_s ncb_desc;

int dvi_unit;

struct {
        short int buff_len;
        short int code;
        int *ret_info;
        int ret_len;
        int terminator;
        } getdvi_itm = { 4, DVI$_UNIT, &dvi_unit, 0, 0 };

struct {
        char func;
        int terminator;
        } nfb = { NFB$C_DECLNAME, 0 };

struct mbx_blk {
        short int msg;                          /* 2 */
        short int unit;                         /* 2 */
        char name_info[MAX_NCB];                /* 110 */
        };                                      /* 114 */

struct ast_blk {
        char type;
        short int ndx;
        char unused;
        };

struct io_stat_blk {
        short int status ;
        short int msg_len ;
        int unused;
        } iosb;

struct quehdr {
        struct quehdr * flink;
        struct quehdr * blink;
        } *que_hdr;

struct wb {
        char *txt;
        struct wb *wnext;
        };

struct lct_blk {
        short int unit;
        short int channel;
        struct buff_blk *cur_buff;
        int state;
        struct wb *writenext, *writetail;
        struct wb *readnext, *readtail;
        void (*next_function)(int);
        int status;
        char active;
        int read_type;
        const char *stop_read_text;
        char rem_node[10];
        } lct[MAX_LINKS];

#ifdef __DECC
#pragma member_alignment
#endif
                                        /* this is a quadword multiple size */
struct buff_blk {
        struct buff_blk * flink;                        /* 4 */
        struct buff_blk * blink;                        /* 4 */
        union {
            int astid;                                  /* 4 */
            struct ast_blk ast;                         /* 4 */
            } cmplt_stat;                       /* 4 */
        struct io_stat_blk iosb;                /* 8 */
        union {
            char data[512];                             /* 512 */
            struct mbx_blk mbx_msg;                     /* 114 */
            } msg_area;                         /* 512 */
        int valid;                              /* 4 */
        };

struct buff_blk *cur_buff,*buffers,*netcmd_buff;

#ifdef __DECC
#pragma member_alignment restore
#endif

#ifndef LIB$_QUEWASEMP
#if NOGLOBALREF
extern int LIB$_QUEWASEMP;
#else
globalvalue int LIB$_QUEWASEMP;
#endif
#endif

#ifndef LIB$_NOTFOU
#if NOGLOBALREF
extern int LIB$_NOTFOU;
#else
globalvalue int LIB$_NOTFOU;
#endif
#endif

#ifndef DEBUG
#define DEBUG 0
#endif

#if DEBUG
FILE *fpd;
#endif

int status,
    net_shut = 0,
    rtry = 10,
    startup = 1,
    start_pos = 0,
    gindex;

unsigned char mask_siz = 32;
unsigned long lct_alloc_mask = 0;

unsigned int idle_server[2],
             idle_read[2];

short int mbx_msg_len = MAX_MSG,
          mbx_chan,
          netdcl_chan;

char ncb[MAX_NCB];


int check_attn_queue __ARGS((void));
int check_cmd_queue __ARGS((void));
int check_live_queue __ARGS((void));
int declare_network_object __ARGS((void));
int establish_link __ARGS((void));
int init __ARGS((void));
int init_vars __ARGS((void));
int insque_buffer __ARGS((char, struct buff_blk *));
int remque_buffer __ARGS((char, struct buff_blk **));
int issue_netcmd_read __ARGS((void));
int read_completed __ARGS((int));
int write_completed __ARGS((int));
int resume_server __ARGS((int));
int shutdown_link __ARGS((void));
void timeout_check __ARGS((void));
int link_failure(short int);
int netcmd __ARGS((void));
int not_used __ARGS((void));
void read_timeout __ARGS((int));
int ast_routine __ARGS((struct ast_blk));

extern int lib$ffc __ARGS((const int *, const unsigned char *, const unsigned long *, int *));
extern int lib$insqti __ARGS((struct buff_blk *, struct quehdr *, int *));
extern int lib$remqhi __ARGS((struct quehdr *, struct buff_blk **, int *));

/*
 *  main
 *
 *  server entry point
 */

int main(void)
{
    int lq_status,
        aq_status,
        cq_status;


#if DEBUG
    fpd = fopen("DMSRV.LOG","w");
    fprintf(fpd,"DMSRV\n");
#endif

    init();
    for (;;) {
        lq_status = check_live_queue();
        aq_status = check_attn_queue();
        cq_status = check_cmd_queue();
        if ((lq_status == LIB$_QUEWASEMP) && (aq_status == lq_status) &&
                (cq_status == lq_status)) {
            if (!(lct_alloc_mask || startup)) timeout_check();
            else sys$hiber();
            }
        }
}

/*
 *  Server idle timeout check
 */

void server_timeout(void)
{
#if EXIT_ON_TIMEOUT
  exit(SS$_NORMAL);
#else
  return;
#endif
}

extern int news_lock_alarm;

void timeout_check()
{

#if DEBUG
    fprintf(fpd,"Timeout check - no units active\n");
#endif

  if (news_lock_alarm) exit(1);    
  sys$setimr(0,idle_server,server_timeout,SERVER_REQ);
  sys$hiber();
  sys$cantim(SERVER_REQ,0);
}

int init()
{
  chk_ret(server_init(MAX_LINKS));
  chk_ret(init_vars());
  chk_ret(declare_network_object());
  return(status = issue_netcmd_read());
}

int init_vars()
{
    int i;
    char *qvb;    /* temporary pointer used to quad align (struct quehdr *)   */
    char *qbb;    /* temporary pointer used to quad align (struct buff_blk *) */

    status = SS$_NORMAL;

    qvb = (char *) news_malloc((sizeof *que_hdr) * 5);
    while (((unsigned int) qvb) & 7) ++qvb;    /* quad align it ! */
    que_hdr = (struct quehdr *) qvb;

    que_hdr[0].flink = que_hdr[0].blink = 0;
    que_hdr[1].flink = que_hdr[1].blink = 0;

    qbb = (char *) news_malloc((sizeof *buffers) * (MAX_BUFFS + 1));
    while (((unsigned int) qbb) & 7) ++qbb;    /* quad align it ! */
    buffers = (struct buff_blk *) qbb;

    nfb_desc.dsc$w_length = sizeof( nfb );
    nfb_desc.dsc$a_pointer = (char *) &nfb;
    nfb_desc.dsc$b_dtype = DSC$K_DTYPE_T;
    nfb_desc.dsc$b_class = DSC$K_CLASS_S;

    ncb_desc.dsc$w_length = sizeof(ncb);
    ncb_desc.dsc$a_pointer = (char *) &ncb;

    sys$bintim(&server_idle,idle_server);
    sys$bintim(&read_idle,idle_read);
    for (i = 0; i < MAX_BUFFS-1 ; i++) {
        buffers[i].valid = 0;
        chk_ret(lib$insqti(&buffers[i],&que_hdr[FREE_QUE],&rtry));
        }
    return(status = remque_buffer(FREE_QUE, &netcmd_buff));
}

int declare_network_object()
{
    chk_ret(sys$crembx(TEMP_MBX,&mbx_chan,MAX_MSG,BUF_QUO,0,0,&netcmd_mbx));
    chk_ret(sys$assign(&net_device,&netdcl_chan,0,&netcmd_mbx));
    chk_ret(sys$qiow(0,netdcl_chan,IO$_ACPCONTROL,&iosb,0,0,
        &nfb_desc,&object_name,0,0,0,0));
    return(status = iosb.status);
}

/*  Find a link control table entry based on the unit.                     */

int find_lct(unit,pindex)
    short int unit;
    int *pindex;
{
    int i;

    for (i = 0; i < MAX_LINKS; i++) {
        if (lct[i].unit == unit) {
            *pindex = i-1;
            return(SS$_NORMAL);
            }
        }
    return(status = LIB$_NOTFOU);
}

/*  Release a link control table (lct) entry.  Clear the lct entry and    *
 *  clear the corresponding bit in the allocation mask.  The mask is used *
 *  to scan the entire table in one instruction.                          */

int release_lct(index)
    int index;
{
    lct_alloc_mask ^= ( 1 << index );
    lct[index].unit = 0;
    lct[index].channel = 0;
    lct[index].cur_buff = NULL;
    return(SS$_NORMAL);
}

/*  Allocate the first available link control table (lct) entry.  Then     *
 *  set the corresponding bit in the allocation mask.                      */

int allocate_lct(pindex)
    int *pindex;
{
    chk_ret(lib$ffc(&start_pos,&mask_siz,&lct_alloc_mask,pindex));
    lct_alloc_mask |= ( 1 << *pindex );
    return(status);
}

int check_fatal_io(io_stat)
    short int io_stat;
{
    if ((io_stat != SS$_BUFFEROVF) && (io_stat != SS$_FILNOTACC)
        && (io_stat != SS$_INSFMEM)) io_stat = SS$_NORMAL;
    return(io_stat);
}

int check_fatal_accept(io_stat)
    short int io_stat;
{
    if ((io_stat != SS$_DEVALLOC) && (io_stat != SS$_INSFMEM)
        && (io_stat != SS$_IVDEVNAM)) io_stat = SS$_NORMAL;
    return(io_stat);
}


/* ----------------- READ/WRITE QIO CALLS ------------------------------- */

/*  Issue a read on the network command mailbox.  The astid will inform    *
 *  the AST routine which buffer completed. This procedure is called at    *
 *  AST and Non-AST levels - the Non-AST call is startup only.             */

int issue_netcmd_read()
{
    struct buff_blk *netcb;

    netcb = (struct buff_blk *) netcmd_buff;
    netcb->cmplt_stat.astid = NET_CMD;
    return(sys$qio(0,mbx_chan,IO$_READVBLK,&netcb->iosb,&ast_routine,NET_CMD,
        &netcb->msg_area.mbx_msg,mbx_msg_len,0,0,0,0));
}

/*  Issue an asynchronous read on a logical link.  The astid will inform   *
 *  the AST routine which buffer completed.                                */

int issue_link_read(index)
    int index;
{
    struct buff_blk *bp;

    lct[index].state = READ_IN_PROGRESS;
    bp = cur_buff;
    bp->cmplt_stat.ast.ndx = index;
    bp->cmplt_stat.ast.type = NET_RD;
    sys$setimr(0,idle_read,read_timeout,index);
    return(sys$qio(0,lct[index].channel,IO$_READVBLK,&bp->iosb,&ast_routine,
        bp->cmplt_stat.astid,bp->msg_area.data,sizeof(bp->msg_area.data),
        0,0,0,0));
}

/*  Issue an asynchronous write on a logical link.  The astid will inform  *
 *  the AST routine which buffer completed.                                */

int issue_link_write(index)
    int index;
{
    struct buff_blk *bufp;
    struct wb *tmp;

    bufp = lct[index].cur_buff;
    bufp->cmplt_stat.ast.ndx = index;
    bufp->cmplt_stat.ast.type = NET_WRT;
    tmp = lct[index].writenext;
    strcpy(bufp->msg_area.data,tmp->txt);
    if (tmp->wnext) lct[index].writenext = tmp->wnext;
    else {
        lct[index].writenext = 0;
        lct[index].writetail = 0;
        }
    lct[index].state = WRITE_IN_PROGRESS;
    news_free(tmp->txt);
    news_free(tmp);

#if DEBUG
    fprintf(fpd,"Issue_write (%d) %s",index,bufp->msg_area.data);
#endif


    return(sys$qio(0,lct[index].channel,IO$_WRITEVBLK,&bufp->iosb,&ast_routine,
        bufp->cmplt_stat.astid,bufp->msg_area.data,
        strlen(bufp->msg_area.data),0,0,0,0));
}

/* ----------------- ATTN QUEUE PROCESSING ------------------------- */

int check_attn_queue()
{
    int status;

    while ((status = remque_buffer(ATTN_QUE, &cur_buff)) & 1)
        netcmd();
    return(status);
}

int netcmd()
{
    short int unit;
    struct buff_blk *bufp;

    bufp = cur_buff;
    chk_ret(bufp->iosb.status);
    unit = bufp->msg_area.mbx_msg.unit;
    switch (bufp->msg_area.mbx_msg.msg) {
        case MSG$_ABORT     : status = link_failure(unit); break;
        case MSG$_CONFIRM   : status = not_used();         break;
        case MSG$_CONNECT   : status = establish_link();   break;
        case MSG$_DISCON    : status = link_failure(unit); break;
        case MSG$_EXIT      : status = link_failure(unit); break;
        case MSG$_INTMSG    : status = not_used();         break;
        case MSG$_PATHLOST  : status = link_failure(unit); break;
        case MSG$_PROTOCOL  : status = link_failure(unit); break;
        case MSG$_REJECT    : status = not_used();         break;
        case MSG$_THIRDPARTY: status = link_failure(unit); break;
        case MSG$_TIMEOUT   : status = link_failure(unit); break;
        case MSG$_NETSHUT   : status = shutdown_link();    break;
        default             : status = SS$_BADPARAM;       break;
        }
    return(status);
}

/* To establish a logical link, the incoming request must be accepted.     *
 * Five steps are required to accept a request:                            *
 *   1.  Copy the information portion of the NCB into a buffer.            *
 *   2.  Allocate a link control table entry.                              *
 *   3.  Assign a channel to the _NET device and associate the network     *
 *       command mailbox with the channel.                                 *
 *   4.  Get the unit number of the _NET channel to identify the network   *
 *       command messages.                                                 *
 *   5.  Issue to the _NET channel a QIO with the IO$_ACCESS function code *
 *       and the P2 parameter set to the address of the copied NCB         *
 *       information.                                                      */

int establish_link()
{
    int start,
        info_cnt,
        i,
        unit;
    struct buff_blk *bufp;

    bufp = cur_buff;
    i = bufp->msg_area.mbx_msg.name_info[0] + 1;
    info_cnt = bufp->msg_area.mbx_msg.name_info[i];
    start = i + 1;

    for (i = 0; i < info_cnt; i++)
        ncb[i] = bufp->msg_area.mbx_msg.name_info[start+i];
    ncb_desc.dsc$w_length = info_cnt;

    if ((status = allocate_lct(&gindex)) & 1) {
        chk_ret(sys$assign(&net_device,&lct[gindex].channel,0,&netcmd_mbx));
        getdvi_itm.ret_info = &unit;
        chk_ret(sys$getdviw(0,lct[gindex].channel,0,&getdvi_itm,&iosb,0,0,0));
        chk_ret(iosb.status);
        lct[gindex].unit = unit;
        chk_ret(sys$qiow(0,lct[gindex].channel,IO$_ACCESS,&iosb,0,0,
            0,&ncb_desc,0,0,0,0));
        if (iosb.status & 1) {
            strcpy(lct[gindex].rem_node,"DECNET");   /* temporary! */
            lct[gindex].cur_buff = cur_buff;
            lct[gindex].cur_buff->valid = 1;
            lct[gindex].state = IDLE;
            startup = 0;

#if DEBUG
    fprintf(fpd,"New link - unit %d\n",gindex);
#endif

            server_init_unit(gindex);
            return(1);
            }
        chk_ret(check_fatal_accept(iosb.status));
        chk_ret(link_failure(lct[gindex].unit));
        }
    chk_ret(sys$qiow(0,netdcl_chan,IO$_ACCESS | IO$M_ABORT,&iosb,0,0,
        0,&ncb_desc,0,0,0,0));
    return(status = insque_buffer(FREE_QUE, cur_buff));
}

int link_failure (short int unit)
{
    if ((status = find_lct(unit, &gindex)) & 1) {

#if DEBUG
    fprintf(fpd,"Link failure for unit %d\n",gindex);
#endif

        if (sys$dassgn(lct[gindex].channel) & 1) {
            if (lct[gindex].cur_buff) lct[gindex].cur_buff->valid = 0;
            release_lct(gindex);
            }
        }
    status = insque_buffer(FREE_QUE, cur_buff);
    return(status);
}

int shutdown_link()
{
    exit(1);
    return(0);  /* just in case */
}

int not_used()
{
    return(insque_buffer(FREE_QUE, cur_buff));
}

/* ----------------- CMD QUEUE PROCESSING ---------------------------------*/

int check_cmd_queue()
{
    struct buff_blk *bp;
    int index;
    int status;

    while ((status = remque_buffer(CMD_QUE, &cur_buff)) & 1) {
        bp = cur_buff;
        index = bp->cmplt_stat.ast.ndx;
        if (bp->valid) resume_server(index);
        else insque_buffer(FREE_QUE,cur_buff);
        }
    return(status);
}

int resume_server(index)
    int index;
{
    if ((lct[index].state == IDLE) && (lct[index].next_function)) {
        lct[index].active = 1;
        (*lct[index].next_function)(index);
        lct[index].active = 0;
        if (lct[index].state == IDLE) {
            if (lct[index].next_function) return(issue_link_read(index));
            }
        else if (lct[index].state != LINK_ERROR) return(1);
        }

#if DEBUG
    fprintf(fpd,"Unit %d - signing off\n",index);
#endif

    log_to_file(index);
    sys$dassgn(lct[index].channel);
    if (lct[index].cur_buff) {
        lct[index].cur_buff->valid =0;
        insque_buffer(FREE_QUE, lct[index].cur_buff);
        }
    release_lct(index);
    return(1);
}

/* ----------------- LIVE QUEUE PROCESSING --------------------------------*/

/* This module removes the QIO completion entries from the LIVE QUEUE.     *
 * These QIOS denote completion of a read or write call on a logical link, *
 * and the appropriate handler is invoked for each IO type.                */

int check_live_queue()
{
    struct buff_blk *bufp;
    int index;
    int status;

    while ((status = remque_buffer(LIVE_QUE, &cur_buff)) & 1) {
        bufp = cur_buff;
        if (!bufp->valid) insque_buffer(FREE_QUE,cur_buff);
        else {
            index = bufp->cmplt_stat.ast.ndx;
            switch (bufp->cmplt_stat.ast.type) {
                case NET_RD  : read_completed(index); break;
                case NET_WRT : write_completed(index); break;
                }
            }
        }
    return(status);
}

/* read completed -
 *  If there is more to read then requeue a read request, otherwise
 *  queue the buffer on the CMD_QUE.
 */

int read_completed(index)
    int index;
{
    struct buff_blk *bp;
    struct wb *tmp;
    int i;
    char *t;

    bp = lct[index].cur_buff;
    if ((lct[index].status = bp->iosb.status) & 1) {
        i = bp->iosb.msg_len;
        t = bp->msg_area.data;
        if ((t[i-2] == '\r') && (t[i-1] == '\n')) {
            t[i-2] = '\n';
            t[--i] = '\0';
            }
        tmp = (struct wb *) news_malloc(sizeof *tmp);
        tmp->wnext = 0;
        strncpy((tmp->txt = (char *) news_malloc(i+1)), t,i);
        tmp->txt[i] = '\0';

#if DEBUG
    fprintf(fpd,"read_complete (%d) %s",index,tmp->txt);
#endif

        if (lct[index].readtail) lct[index].readtail->wnext = tmp;
        if (!lct[index].readnext) lct[index].readnext = tmp;
        lct[index].readtail = tmp;
        if ((lct[index].read_type == FILE_INPUT) && (strcmp(tmp->txt,".\n")))
            return(issue_link_read(index));
        lct[index].state = IDLE;
        }
    else lct[index].state = LINK_ERROR;
    insque_buffer(CMD_QUE,bp);
    return(1);
}

/* write completed -
 *  If there is more to write then requeue a write request, otherwise
 *  set the state to idle.
 */

int write_completed(index)
    int index;
{
    struct buff_blk *bp;
    struct wb *tmp;

    bp = lct[index].cur_buff;
    if ((lct[index].status = bp->iosb.status) & 1) {
        if (lct[index].writenext) return(issue_link_write(index));
        lct[index].state = IDLE;
        }
    else {
        while ( (tmp = lct[index].writenext) ) {
            if (tmp->wnext) lct[index].writenext = tmp->wnext;
            else {
                lct[index].writenext = 0;
                lct[index].writetail = 0;
                }
            news_free(tmp->txt);
            news_free(tmp);
            }
        lct[index].state = LINK_ERROR;
        }
    if (!lct[index].active) {
        if ((lct[index].state == IDLE) && (lct[index].next_function))
            return(issue_link_read(index));
        else insque_buffer(CMD_QUE,bp);
        }
    return(1);
}

/*--------------- QUEUE HANDLING CODE (both AST and Non-AST level) --------*/

/*  Insert/Remove the buffer into the specified queue.                     *
 *  Relative queues are used because of the interlock supplied in the RTL  *
 *  routine.                                                               */

int insque_buffer(char queue, struct buff_blk *cur_buff)
{
    return(lib$insqti(cur_buff,&que_hdr[queue],&rtry));
}

int remque_buffer(char queue, struct buff_blk **cur_buff)
{
    return(lib$remqhi(&que_hdr[queue],cur_buff,&rtry));
}

/*--------------- AST HANDLER CODE ----------------------------------------*/

/* AST routine.  This module handles the completion ASTs of all the        *
 * asynchronous QIOs issued by the main process.  The ast_parameter passed *
 * to this module governs the processing.  The ast_parameter is actually   *
 * split into three parts:                                                 *
 *   ast_type   The type of QIO completion (NET_RD, NET_WRT, NET_CMD).     *
 *   index      The corresponding link control table entry (only valid for *
 *              NET_RD and NET_WRT).                                       *
 *   unused     Free space for later use.                                  *
 * Buffers are inserted into the LIVE_QUE or ATTN_QUE for processing in    *
 * the main module.  NET_CMD QIOs are reissued immediately to accomodate   *
 * multiple network requests.                                              */

int ast_routine(ast_param)
    struct ast_blk ast_param;
{
    switch (ast_param.type) {
        case NET_RD  :
            sys$cantim(ast_param.ndx,0);
        case NET_WRT :
            status = insque_buffer(LIVE_QUE,lct[ast_param.ndx].cur_buff );
            break;
        case NET_CMD :
            if (((status = insque_buffer(ATTN_QUE,netcmd_buff)) & 1)
                  && ((status = remque_buffer(FREE_QUE,&netcmd_buff)) & 1))
                status = issue_netcmd_read();
            break;
        default      :
            break;
        }
    if ((!(status & 1)) || (!((status = sys$wake(0,0)) & 1)))
        exit(status);
    return(1);
}

void read_timeout(index)
    int index;
{
#if DEBUG
    fprintf(fpd,"Unit %d - read timed out\n",index);
#endif
}

/*---------------- SERVER INTERFACE ROUTINES ------------------------------*/

int read_net(buffer,size,index)
    char *buffer;
    int size;
    int index;
{
    char *rtn_txt;
    struct wb *tmp;

    check_attn_queue();
    check_live_queue();

    if (!lct[index].readnext) strcpy(buffer,lct[index].stop_read_text);
    else {
        rtn_txt = lct[index].readnext->txt;
        strcpy(buffer,rtn_txt);
        tmp = lct[index].readnext;
        lct[index].readnext = tmp->wnext;
        if (!lct[index].readnext) lct[index].readtail = 0;
        news_free(tmp->txt);
        news_free(tmp);
        }
    return(strlen(buffer));
}

void write_net(s,index)
    const char *s;
    int index;
{
    struct wb *tmp;

    check_attn_queue();
    check_live_queue();
    if (lct[index].state == LINK_ERROR) return;
    tmp = (struct wb *) news_malloc(sizeof *tmp);
    tmp->wnext = 0;
    strcpy(tmp->txt = (char *) news_malloc(strlen(s)+1),s);
    if (lct[index].writetail) lct[index].writetail->wnext = tmp;
    if (!lct[index].writenext) lct[index].writenext = tmp;
    lct[index].writetail = tmp;
    if (lct[index].state == IDLE) issue_link_write(index);
}

/*
 * close_net - close all active links.  Called by mem_fail if memory allocation
 * request fails.
 */

void close_net()
{
  int i;
  unsigned long tmpmask = lct_alloc_mask;

  for (i = 0; i <= MAX_LINKS; i++) {
    if (tmpmask & 1) link_failure(i);
    tmpmask = tmpmask >> 1;
    }
}

/*
 *  next_call
 *
 *  Set the context block to reference the next state of the server for this
 *  connection.
 */

void next_call(index,func,type)
    int index;
    void (*func)(int);
    int type;
{
    lct[index].next_function = func;
    lct[index].read_type = type;
    if (type == FILE_INPUT) lct[index].stop_read_text = ".\n";
    else lct[index].stop_read_text = "QUIT\n";
}

/*
 *  getremhost
 *
 *  Return the remote host name and remote username
 */

void getremhost(remhost,remuser,unit)
    char *remhost, *remuser;
    int unit;    
{
    if (remhost) strcpy(remhost,"DECnet");
    if (remuser) strcpy(remuser,"nntp");
}
