#module NNTP_SERVER  "V5.5"

/*
**++
**  FACILITY:
**
**      NNTP_SERVER
**
**  ABSTRACT:
**
**      Implementation of NNTP using generic I/O calls. To construct an
**      NNTP server this module must be linked with the appropriate driver
**      module.
**
**      Supported drivers are: DECnet, CMU TCP/IP,  WIN TCP/IP
**
**      The server can be configured as either a single- or multi-threaded
**      server.
**
**  AUTHOR:
**
**      Geoff Huston
**
**  COPYRIGHT:
**
**      Copyright © 1988
**
**  MODIFICATION HISTORY:
**
**      V5.3    17-Jun-1988     GIH
**          Version V5.3 Release
**
**      V5.4    10-Aug-1988     Mats Sundvall, Uppsala
**          Changed SYS$SCRATCH to NEWS_MANAGER
**
**      V5.5     7-Oct-1988     GIH
**          Included support for multi-threading of the server. The acutal
**          multi-threaded driver is the responsibility of the driver level.
**          The changes involve placing the main() procedure in the driver
**          support module, and defining the server as a set of callable
**          procedures.
**
**--
**/

                            /* system definitions */
#include ctype
#include descrip
#include prvdef
#include jpidef
#include rms
#include stdio

#define BATCH_OUTPUT 1
#define BATCH_SIZE   250000
#define BATCH_OUTPUT 1
#define SCRATCHFILE     "NEWS_MANAGER:MAIL_NEWS.TMP"
#define OUTFILE         "NEWS_MANAGER:NEWS.BATCH"

                            /* set to 1 to produce DEBUG output */
#define DEBUG 1

#define CLOSE_LINK      0

#define NO_INPUT        0
#define CMD_INPUT       1
#define FILE_INPUT      2

                            /* Server name */
#define SERVER_NAME     "VAX/VMS NNTP SERVER (NEWS V6.0)"

                            /* NEWS definitions */
#include "newsdefine.h"

                            /* server commands - index list */
#define NUM_CMDS    16
#define ARTICLE     0
#define BODY        1
#define GROUP       2
#define HEAD        3
#define HELP        4
#define IHAVE       5
#define LAST        6
#define LIST        7
#define NEWGROUPS   8
#define NEWNEWS     9
#define NEXT        10
#define POST        11
#define QUIT        12
#define SLAVE       13
#define STAT        14
#define XUPDGROUP   15

                            /* server responses - index list */
#define M100  0
#define M199  1
#define M200  2
#define M201  3
#define M202  4
#define M205  5
#define M211  6
#define M215  7
#define M220  8
#define M221  9
#define M222  10
#define M223  11
#define M230  12
#define M231  13
#define M235  14
#define M240  15
#define M335  16
#define M340  17
#define M400  18
#define M411  19
#define M412  20
#define M420  21
#define M421  22
#define M422  23
#define M423  24
#define M430  25
#define M435  26
#define M436  27
#define M437  28
#define M440  29
#define M441  30
#define M500  31
#define M501  32
#define M502  33
#define M503  34
#define M280  35
#define M380  36
#define M480  37

                            /* Server command table */
char *cmds[] = {"article",
                "body",
                "group",
                "head",
                "help",
                "ihave",
                "last",
                "list",
                "newgroups",
                "newnews",
                "next",
                "post",
                "quit",
                "slave",
                "stat",
                "xupdgroup"};

                            /* server response table */
char *msg[] =        {"100 help text follows\r\n",
                      "199 debug output\r\n",
                      "200 %s server ready - posting allowed\r\n",
                      "201 %s server ready - no posting allowed\r\n",
                      "202 slave status noted\r\n",
                      "205 closing connection - goodbye!\r\n",
                      "211 %d %d %d %s group selected\r\n",
                      "215 list of newsgroups follows\r\n",
                      "220 %d %s article retrieved - head and body follow\r\n",
                      "221 %d %s article retrieved - head follows\r\n",
                      "222 %d %s article retrieved - body follows\r\n",
                      "223 %d %s article retrieved - request text separately\r\n",
                      "230 list of new articles by message-id follows\r\n",
                      "231 list of new newsgroups follows\r\n",
                      "235 article transferred ok\r\n",
                      "240 article posted ok\r\n",
                      "335 send article to be transferred.  End with <CR-LF>.<CR-LF>\r\n",
                      "340 send article to be posted. End with <CR-LF>.<CR-LF>\r\n",
                      "400 service discontinued - read timeout\r\n",
                      "411 no such news group\r\n",
                      "412 no newsgroup has been selected\r\n",
                      "420 no current article has been selected\r\n",
                      "421 no next article in this group\r\n",
                      "422 no previous article in this group\r\n",
                      "423 no such article number in this group\r\n",
                      "430 no such article found\r\n",
                      "435 article not wanted - do not send it\r\n",
                      "436 transfer failed - try again later\r\n",
                      "437 article rejected - do not try again.\r\n",
                      "440 posting not allowed\r\n",
                      "441 posting failed\r\n",
                      "500 command not recognized\r\n",
                      "501 command syntax error\r\n",
                      "502 access restriction or permission denied\r\n",
                      "503 program fault - command not performed\r\n",
                      "280 list of updates to item list follows\r\n",
                      "380 send id's to be compared. End with <CR-LF>.<CR-LF>\r\n",
                      "480 no such news group\r\n"};

struct context {
    int curr_group;             /* server context - current newsgroup number */
    int curr_item;              /* server context current news item number */
    int cfilt_date;
    unsigned short cgrp;
    } *cxt;

int status,                     /* vms sys call returned status */
    curbsize = 0,               /* added batch size */
    bsize = 0;                  /* batch size */

char itm_fname[256];            /* read filename */

struct FAB grpfab,              /* newsgroup file fab */
           itmfab;              /* newsitem file fab */

struct RAB grprab,              /* newsgroup file rab */
           itmrab;              /* newsitem file rab */

ITM newsitm;                    /* newsitem i/o buffer */

GRP newsgrp;                    /* newsgroup i/o buffer */

char ibuf[515],                 /* network channel input buffer */
     sbuf[515];                 /* network channel output buffer */
FILE *fpt = 0,
     *fpw = 0;


/*  SERVICE ROUTINES
 *
 *  open_out_file
 *
 *  open output file
 */

FILE *open_out_file()
{
    if (BATCH_OUTPUT) return((!fpt) ? (fpt = fopen(SCRATCHFILE,"w")) : fpt);
    else return((!fpw) ? (fpw = fopen(OUTFILE,"w")) : fpw);
}

/*
 *  output_file
 *
 *  If batch output is requires, append message to batch output
 */

output_file()
{
    char xbuf[512],
         lines[132];

    if (!BATCH_OUTPUT) return(fclose(fpw), fpw = 0);
    if (fpt) {
        fclose(fpt);
        if (curbsize > BATCH_SIZE) {
            close(fpw);
            curbsize = 0;
            fpw = 0;
            }
        if (bsize) {
            fpt = fopen(SCRATCHFILE,"r");
            if (!fpw) fpw = fopen(OUTFILE,"w");
            fprintf(fpw,"#! rnews %d\n",bsize);
            while (fgets(xbuf,510,fpt)) fputs(xbuf,fpw);
            fclose(fpt);
            curbsize += bsize;
            }
        while (!delete(SCRATCHFILE));
        fpt = 0;
        }
}

/*
 *  open_server_files
 *
 *  Open the item and newsgroup files for sharing
 */

open_server_files()
{
    itmfab = cc$rms_fab;
    itmfab.fab$b_bks = 4;
    itmfab.fab$b_fac = FAB$M_GET;
    itmfab.fab$l_fna = Item_file;
    itmfab.fab$b_fns = strlen(itmfab.fab$l_fna);
    itmfab.fab$w_mrs = sizeof newsitm;
    itmfab.fab$b_shr = FAB$M_SHRDEL | FAB$M_SHRGET | FAB$M_SHRPUT | FAB$M_SHRUPD;

    itmrab = cc$rms_rab;
    itmrab.rab$l_fab = &itmfab;
    itmrab.rab$b_krf = 0;
    itmrab.rab$b_ksz = 4;
    itmrab.rab$l_ubf = &newsitm;
    itmrab.rab$w_usz = sizeof newsitm;
    itmrab.rab$l_rbf = &newsitm;
    itmrab.rab$w_rsz = sizeof newsitm;

    grpfab = cc$rms_fab;
    grpfab.fab$b_fac = FAB$M_GET ;
    grpfab.fab$l_fna = Group_file;
    grpfab.fab$b_fns = strlen(grpfab.fab$l_fna);
    grpfab.fab$b_shr = FAB$M_SHRDEL | FAB$M_SHRGET | FAB$M_SHRPUT | FAB$M_SHRUPD;

    grprab = cc$rms_rab;
    grprab.rab$l_fab = &grpfab;
    grprab.rab$l_ubf = &newsgrp;
    grprab.rab$w_usz = sizeof newsgrp;
    grprab.rab$l_rbf = &newsgrp;
    grprab.rab$w_rsz = sizeof newsgrp;

    if (!((status = sys$open(&itmfab)) & 1)) return(0);
    if (!((status = sys$connect(&itmrab)) & 1)) return(0);
    if (!((status = sys$open(&grpfab)) & 1)) return(0);
    if (!((status = sys$connect(&grprab)) & 1)) return(0);
    return(1);
}

/*
 *  util_dir
 *
 *  Convert a newsgroup name to a VMS directory string
 */

char dir_result[SUBJLEN + SUBJLEN];

util_dir(input)
    char *input;
{
    char *p = dir_result,
         *in = input;

    while (*in) {
        if (isalnum(*in) || (*in == '-') || (*in == '.')) *p++ = *in++;
        else {
            *p++ = '_';
            if (*in == '_') *p++ = '_';
            else if (*in < '0') *p++ = (*in - '!') + 'A';
            else if (*in < 'A') *p++ = (*in - ':') + '0';
            else if (*in < 'a') *p++ = (*in - '[') + 'P';
            else *p++ = (*in - '{') + 'V';
            in++;
            }
        }
    *p = '\0';
    return(dir_result);
}

/*
 *  s_to_lower
 *
 *  convert a string to lower case
 */

char *s_to_lower(s)
    char *s;
{
    char *save = s;
    while (*s) {
        *s = tolower(*s);
        s++;
        }
    return(save);
}

/*
 *  file_copy
 *
 *  Use RMS to copy a file.
 */

file_copy(from,to)
char *from,
     *to;
{
    struct FAB infab,
        outfab;
    struct RAB inrab,
        outrab;

    struct XABDAT inxabdat,
        outxabdat;

    struct XABFHC inxabfhc,
        outxabfhc;

    struct XABRDT inxabrdt,
        outxabrdt;

    struct XABPRO inxabpro,
        outxabpro;

    int status;

    char rec_buff[4096];
    short rec_size = 4096;

    infab = cc$rms_fab;
    infab.fab$l_fna = from;
    infab.fab$b_fns = strlen(from);
    infab.fab$b_fac = FAB$M_BIO | FAB$M_GET ;
    infab.fab$l_xab = &inxabdat;

    inxabdat = cc$rms_xabdat;
    inxabdat.xab$l_nxt = &inxabfhc;

    inxabfhc = cc$rms_xabfhc;
    inxabfhc.xab$l_nxt = &inxabrdt;

    inxabrdt = cc$rms_xabrdt;
    inxabrdt.xab$l_nxt = &inxabpro;

    inxabpro = cc$rms_xabpro;

    inrab = cc$rms_rab;
    inrab.rab$l_fab = &infab;
    inrab.rab$l_bkt = 0;
    inrab.rab$l_ubf = rec_buff;
    inrab.rab$w_usz = rec_size;

    outfab = cc$rms_fab;
    outfab.fab$l_fna = to;
    outfab.fab$b_fns = strlen(to);
    outfab.fab$l_fop = FAB$M_CBT;
    outfab.fab$w_mrs = rec_size;
    outfab.fab$b_fac = FAB$M_BIO | FAB$M_PUT;
    outfab.fab$b_rat = FAB$M_CR;
    outfab.fab$l_xab = &outxabdat;

    outxabdat = cc$rms_xabdat;
    outxabdat.xab$l_nxt = &outxabfhc;

    outxabfhc = cc$rms_xabfhc;
    outxabfhc.xab$l_nxt = &outxabrdt;

    outxabrdt = cc$rms_xabrdt;
    outxabrdt.xab$l_nxt = &outxabpro;

    outxabpro = cc$rms_xabpro;

    outrab = cc$rms_rab;
    outrab.rab$l_fab = &outfab;
    outrab.rab$l_bkt = 0;
    outrab.rab$l_rbf = rec_buff;

    if (!((status = sys$open(&infab)) & 1)) return(status);

    outfab.fab$l_alq = infab.fab$l_alq;
    outfab.fab$w_deq = infab.fab$w_deq;
    outfab.fab$b_fsz = infab.fab$b_fsz;
    outfab.fab$l_mrn = infab.fab$l_mrn;
    outfab.fab$w_mrs = infab.fab$w_mrs;
    outfab.fab$b_org = infab.fab$b_org;
    outfab.fab$b_rat = infab.fab$b_rat;
    outfab.fab$b_rfm = infab.fab$b_rfm;

    outxabdat.xab$w_rvn = inxabdat.xab$w_rvn + 1;

    outxabfhc.xab$b_rfo = inxabfhc.xab$b_rfo;
    outxabfhc.xab$b_atr = inxabfhc.xab$b_atr;
    outxabfhc.xab$w_lrl = inxabfhc.xab$w_lrl;
    outxabfhc.xab$b_bkz = inxabfhc.xab$b_bkz;
    outxabfhc.xab$b_hsz = inxabfhc.xab$b_hsz;
    outxabfhc.xab$w_mrz = inxabfhc.xab$w_mrz;
    outxabfhc.xab$w_dxq = inxabfhc.xab$w_dxq;
    outxabfhc.xab$l_sbn = 0;

    outxabrdt.xab$w_rvn = inxabrdt.xab$w_rvn + 1;

    outxabpro.xab$w_pro = inxabpro.xab$w_pro;

    if (!((status = sys$connect(&inrab)) & 1)) return(sys$close(&infab),status);
    if (!((status = sys$create(&outfab)) & 1)) return(sys$close(&infab),status);
    if (!((status = sys$connect(&outrab)) & 1)) return(sys$close(&infab),sys$close(&outfab),status);
    do {
        status = sys$read(&inrab);

        if (status & 1) {
            outrab.rab$w_rsz = inrab.rab$w_rsz;
            status = sys$write(&outrab);
            }
        } while (status & 1);
    if (status == RMS$_EOF) status = RMS$_NORMAL;

    sys$close(&infab);
    sys$close(&outfab);
    return(status);
}

/*
 *  wild_match
 *
 *  String equality routine, including matching the '*' character in
 *  the pattern to the minimal substring in the match text
 */

wild_match(l,p)
    char *l,
         *p;
{
    if (!*l) {
        if (!*p) return(1);
        else if (*p == '*') return(wild_match(l,p+1));
        else return(0);
        }
    if (*p == '*') {
        while (!wild_match(l,p+1)) {
            l++;
            if (!*l) {
                if (!*(p+1)) return(1);
                else return(0);
                }
            }
        return(1);
        }
    return((*l == *p) && wild_match(l+1,p+1));
}

/*
 *  strip
 *
 *  Remove trailing blanks
 */

strip(p,n)
    char *p;
    int n;
{
    do {
        n--;
        } while ((n > 0) && (p[n] == ' '));
    p[n+1] = '\0';
}

/*
 *  util_cvrt
 *
 *  Convert a string into standard newsgroup format
 */

util_cvrt(result, input)
    char *result,
         *input;
{
    char *p = result,
         *in = input;
    int i;
    while ((*in) && (*in == ' ')) in++;
    strncpy(result,in,SUBJLEN);
    result[SUBJLEN - 1] = '\0';
    strip(result,strlen(result));
    while (*p) {
        if (isgraph(*p)) *p = tolower(*p);
        else *p = '_';
        p++;
        }
    i = strlen(result);
    while (i < SUBJLEN) result[i++] = '\0';
}


char *mstr[] = {"NONE","JAN","FEB","MAR","APR","MAY","JUN","JUL","AUG","SEP","OCT","NOV","DEC"};

cvt_date(d,h)
    char *d,
         *h;
{
    char c_str[30];
    int ind,
        two = 2,
        vax_date[2],
        offset[2] = {0X4BEB4000, 0X007C9567},
        adjtim[2],
        divisor = 10000000;
    $DESCRIPTOR(c_dsc,c_str);

    sprintf(c_str,"%c%c-%s-19%c%c %c%c:%c%c:%c%c.0",
        d[4],d[5],mstr[((ind = (((d[2] - '0') * 10) + (d[3] - '0'))) > 12 ? 0 : ind)],d[0],d[1],
        h[0],h[1],h[2],h[3],h[4],h[5]);

    c_dsc.dsc$w_length = strlen(c_str);
    if (!(sys$bintim(&c_dsc,vax_date) & 1)) time(&ind);
    else {
        lib$subx(vax_date, offset, adjtim, &two);
        lib$ediv(&divisor, adjtim, &ind, vax_date);
        }
    return(ind);
}

struct il {
    char id[132];
    struct il *next;
    } *itm_list = 0,
      *ngp_list = 0;

add_list(i,tp)
    char *i;
    struct il **tp;
{
    struct il *tmp;

    tmp = malloc(sizeof *tmp);
    strncpy(tmp->id,i,IDLEN);
    tmp->next = *tp;
    *tp = tmp;
}

loc_grp(g,tmp)
    char *g;
    struct il *tmp;
{
    while (tmp) {
        if (wild_match(g,tmp->id)) return(1);
        tmp = tmp->next;
        }
    return(0);
}

loc_ngrp(g,tmp)
    char *g;
    struct il *tmp;
{
    while (tmp) {
        if (*(tmp->id) == '!') {
            if (wild_match(g,(tmp->id) + 1)) return(0);
            }
        else if (wild_match(g,tmp->id)) return(1);
        tmp = tmp->next;
        }
    return(0);
}

loc_id(i)
    char *i;
{
    struct il *tmp = itm_list,
              *ptmp = 0;

    while (tmp) {
        if (!strcmp(i,tmp->id)) {
            if (ptmp) ptmp->next = tmp->next;
            else itm_list = tmp->next;
            free(tmp);
            return(1);
            }
        ptmp = tmp;
        tmp = tmp->next;
        }
    return(0);
}

/*  COMMAND PARSING
 *
 *  loc_cmd
 *
 *  Return the index of the command c
 */

loc_cmd(c)
    char *c;
{
    int i = 0;
    while (i < NUM_CMDS) {
        if (!strcmp(c,cmds[i])) return(i);
        i++;
    }
    return(i);
}

/*
 *  strip_compress_lower
 *
 *  strip off leading and trailing blanks, convert all whitespace to
 *  a single blank, and convert all alphas to lower case
 */

strip_compress_lower(s)
    char *s;
{
    char *start = s,
         *r = s;

    while (*s) {
        if (isgraph(*s)) *r++ = tolower(*s);
        else if ((r > start) && (*(r-1) != ' ')) *r++ = ' ';
        s++;
        }
    if ((r > start) && (*(r-1) == ' ')) --r;
    *r = '\0';
}

/*  NNTP ROUTINES
 *
 *  get_item
 *
 *  ARTICLE, BODY, HEAD, STAT commands
 */

int parser();

get_item(num,id,cmd_type,unit)
    int num;
    char *id;
    int cmd_type;
    int unit;
{
    char l_id[IDLEN + 2],
         *inpline;
    int i;
    FILE *fpr;

    grprab.rab$l_kbf = &(newsitm.itm_grp);
    grprab.rab$b_ksz = 2;
    grprab.rab$b_krf = 1;
    grprab.rab$l_rop = RAB$M_RRL | RAB$M_NLK;
    grprab.rab$b_rac = RAB$C_KEY;

    if (id) {
        for (i = 0; i < (IDLEN + 2); ++i) l_id[i] = '\0';
        strncpy(l_id,id,IDLEN);
        s_to_lower(l_id);

        itmrab.rab$l_kbf = l_id;
        itmrab.rab$b_ksz = IDLEN + 2;
        itmrab.rab$b_krf = 1;
        itmrab.rab$l_rop = RAB$M_KGE | RAB$M_RRL | RAB$M_NLK;
        itmrab.rab$b_rac = RAB$C_KEY;

        if ((!(sys$get(&itmrab) & 1)) || (strcmp(l_id,newsitm.itm_id)) || (!(sys$get(&grprab) & 1))) return(write_net(msg[M430],unit));
        }
    else {
        unsigned int l_key;

        if (!cxt[unit].curr_group) return(write_net(msg[M412],unit));
        if ((!num) && (!cxt[unit].curr_item)) return(write_net(msg[M420],unit));
        if (!num) num = cxt[unit].curr_item;

        l_key = (cxt[unit].curr_group << 16) + num;
        itmrab.rab$l_kbf = &l_key;
        itmrab.rab$b_ksz = 4;
        itmrab.rab$b_krf = 0;
        itmrab.rab$l_rop = RAB$M_RRL | RAB$M_NLK;
        itmrab.rab$b_rac = RAB$C_KEY;

        if ((!(sys$get(&itmrab) & 1)) || (!(sys$get(&grprab) & 1))) return(write_net(msg[M423],unit));
        cxt[unit].curr_item = newsitm.itm_num;
        }

    sprintf(itm_fname,Itm_template,util_dir(newsgrp.grp_name),newsitm.itm_num);
    if (!(fpr = fopen(itm_fname,"r"))) return(write_net(msg[M430],unit));

    sprintf(sbuf,msg[M220 + cmd_type],newsitm.itm_num,newsitm.itm_id);
    write_net(sbuf,unit);

    if (cmd_type != 3) {
        int header = 1;

        inpline = sbuf;
        while (fgets(inpline,255,fpr)) {
            if (!strcmp(inpline,".\n")) strcpy(inpline,"..\n");
            strcpy(&inpline[strlen(inpline) - 1],"\r\n");
            if (((cmd_type == 1) && header && (*inpline != '\r')) || ((cmd_type == 2) && !header) || (!cmd_type)) write_net(sbuf,unit);
            if (*inpline == '\r') header = 0;
            }
        strcpy(sbuf,".\r\n");
        write_net(sbuf,unit);
        }
    fclose(fpr);
}

/*
 *  get_group
 *
 *  GROUP command
 */

get_group(grp,unit)
    char *grp;
    int unit;
{
    char l_grp[SUBJLEN];
    int l_key;

    util_cvrt(l_grp,grp);
    grprab.rab$l_kbf = l_grp;
    grprab.rab$b_ksz = SUBJLEN;
    grprab.rab$b_krf = 0;
    grprab.rab$l_rop = RAB$M_RRL | RAB$M_NLK;
    grprab.rab$b_rac = RAB$C_KEY;

    if (!(sys$get(&grprab) & 1)) return(write_net(msg[M411],unit));

    cxt[unit].curr_group = newsgrp.grp_num;
    l_key = (cxt[unit].curr_group << 16);
    itmrab.rab$l_kbf = &l_key;
    itmrab.rab$b_ksz = 4;
    itmrab.rab$b_krf = 0;
    itmrab.rab$l_rop = RAB$M_KGE | RAB$M_RRL | RAB$M_NLK;
    itmrab.rab$b_rac = RAB$C_KEY;

    if (!(sys$get(&itmrab) & 1)) cxt[unit].curr_item = 0;
    else if (newsitm.itm_grp != newsgrp.grp_num) cxt[unit].curr_item = 0;
    else cxt[unit].curr_item = newsitm.itm_num;

    sprintf(sbuf,msg[M211],newsgrp.grp_count,(cxt[unit].curr_item ? cxt[unit].curr_item : newsgrp.grp_topnum + 1),newsgrp.grp_topnum,newsgrp.grp_name);
    write_net(sbuf,unit);
}

/*
 *  help_text
 *
 *  write out help text to the channel
 */

char *help_cmds[] = {
    "ARTICLE <message-id> Send article referenced by id\r\n",
    "ARTICLE <nnn>        Send article, number <nnn> from current newsgroup\r\n",
    "ARTICLE              Send current article\r\n",
    "BODY <message-id>    Send article body, referenced by id\r\n",
    "BODY <nnn>           Send article body, number <nnn> from current newsgroup\r\n",
    "BODY                 Send current article body\r\n",
    "HEAD <message-id>    Send article headers, referenced by id\r\n",
    "HEAD <nnn>           Send article headers, number <nnn> from current newsgroup\r\n",
    "HEAD                 Send current article headers\r\n",
    "GROUP <ggg>          Set current group to <ggg>, return status\r\n",
    "HELP                 Send this text\r\n",
    "IHAVE <message-id>   Notify server of new item and send to server\r\n",
    "LAST                 Set current item pointer to previous item\r\n",
    "LIST                 List all newsgroups held on server\r\n",
    "NEWGROUPS date time [GMT] [<distributions>]\r\n",
    "                     List of newsgroups created since <date time>\r\n",
    "NEWNEWS newsgroups date time [GMT] [<distributions>]\r\n",
    "                     List of new news items created since <date time>\r\n",
    "STAT <message-id>    Send article status, referenced by id\r\n",
    "STAT <nnn>           Send article status, number <nnn> from current newsgroup\r\n",
    "STAT                 Send current article status\r\n",
    ".\r\n",
    ""};

help_text(unit)
    int unit;
{
    int i = 0;

    write_net(msg[M100],unit);
    while (*help_cmds[i]) {
        write_net(help_cmds[i],unit);
        i++;
        }
}

/*
 *  ihave_cmd
 *
 *  news item distribution command
 */

int ihave_2();

ihave_cmd(id,unit)
    char *id;
    int unit;
{
    char l_id[IDLEN + 2];
    int i;

    for (i = 0; i < (IDLEN + 2); ++i) l_id[i] = '\0';
    strncpy(l_id,id,IDLEN);
    s_to_lower(l_id);

    itmrab.rab$l_kbf = l_id;
    itmrab.rab$b_ksz = IDLEN + 2;
    itmrab.rab$b_krf = 1;
    itmrab.rab$l_rop = RAB$M_KGE | RAB$M_RRL | RAB$M_NLK;
    itmrab.rab$b_rac = RAB$C_KEY;

    if ((!(sys$get(&itmrab) & 1)) || (strcmp(l_id,newsitm.itm_id))) {
        write_net(msg[M335],unit);
        next_call(unit,ihave_2,FILE_INPUT);
        }
    else write_net(msg[M435],unit);
}

ihave_2(unit)
    int unit;
{
    FILE *fpl = 0;
    int copystat,
        xfr_ok = 1;

    next_call(unit,parser,CMD_INPUT);
    bsize = 0;
    do {
        read_net(ibuf,512,unit);
        if (strcmp(ibuf,".\n")) {
            if (xfr_ok && (!fpl)) fpl = open_out_file();
            if ((!strcmp(ibuf,"..\n")) && (fpl)) {
                bsize += strlen(ibuf);
                fputs(ibuf + 1,fpl);
                }
            else if (fpl)  {
                bsize += strlen(ibuf);
                fputs(ibuf,fpl);
                }
            }
        } while (strcmp(ibuf,".\n"));
    bsize = bsize - 2;
    output_file();
    if (fpl) {
        fclose(fpl);
        while (!delete(SCRATCHFILE));
        }
    if (xfr_ok) write_net(msg[M235],unit);
    else write_net(msg[M436],unit);
}

/*
 *  last_cmd
 */

last_cmd(unit)
    int unit;
{
    int l_key;
    ITM savitm;

    if (!cxt[unit].curr_group) return(write_net(msg[M412],unit));
    if (!cxt[unit].curr_item) return(write_net(msg[M420],unit));

    l_key = (cxt[unit].curr_group << 16);
    itmrab.rab$l_kbf = &l_key;
    itmrab.rab$b_ksz = 4;
    itmrab.rab$b_krf = 0;
    itmrab.rab$l_rop = RAB$M_KGE | RAB$M_RRL | RAB$M_NLK;
    itmrab.rab$b_rac = RAB$C_KEY;

    if ((!((status = sys$get(&itmrab)) & 1)) || (newsitm.itm_grp != cxt[unit].curr_group) || (cxt[unit].curr_item <= newsitm.itm_num)) return(write_net(msg[M422],unit));
    else {
        itmrab.rab$b_rac = RAB$C_SEQ;
        do {
            savitm = newsitm;
            if (!((status = sys$get(&itmrab)) & 1)) break;
            if (newsitm.itm_grp != cxt[unit].curr_group) break;
            } while (cxt[unit].curr_item > newsitm.itm_num);
        cxt[unit].curr_item = savitm.itm_num;
        sprintf(sbuf,msg[M223],savitm.itm_num,savitm.itm_id);
        write_net(sbuf,unit);
        }
}

/*
 *  list_cmd
 *
 *  LIST command
 */

list_cmd(unit)
{
    int l_key,
        first_num;

    grprab.rab$b_krf = 0;
    grprab.rab$l_rop = RAB$M_RRL | RAB$M_NLK;
    grprab.rab$b_rac = RAB$C_SEQ;

    itmrab.rab$l_kbf = &l_key;
    itmrab.rab$b_ksz = 4;
    itmrab.rab$b_krf = 0;
    itmrab.rab$l_rop = RAB$M_KGE | RAB$M_RRL | RAB$M_NLK;
    itmrab.rab$b_rac = RAB$C_KEY;

    write_net(msg[M215],unit);

    sys$rewind(&grprab);
    while (sys$get(&grprab) & 1) {
        if (!newsgrp.grp_num) continue;

        l_key = (newsgrp.grp_num << 16);
        if ((sys$get(&itmrab) & 1) && (newsitm.itm_grp == newsgrp.grp_num)) first_num = newsitm.itm_num;
        else first_num = newsgrp.grp_topnum + 1;

        sprintf(sbuf,"%s %d %d %s\r\n",newsgrp.grp_name, newsgrp.grp_topnum,first_num,(newsgrp.grp_moderator ? "n" : "y"));
        write_net(sbuf,unit);
        }
    strcpy(sbuf,".\r\n");
    write_net(sbuf,unit);
}

newgroups_cmd(date,time,zone,dist,unit)
    char *date,
         *time,
         *zone,
         *dist;
    int unit;
{
    int filt_date,
        dists = 0,
        l_key,
        first_num;
    char dist_filter[80];
    struct il *tmp;

    filt_date = cvt_date(date,time);

    if ((dist) && (*dist == '<')) {
        char *cp1, *cp2;

        itm_list = 0;
        if (cp1 = strchr(dist,'>')) *cp1 = '\0';
        cp1 = dist +1;
        do {
            if (cp2 = strchr(cp1,',')) *cp2++ = '\0';
            dists++;
            strcpy(dist_filter,cp1);
            strcat(dist_filter,".*");
            add_list(dist_filter,&itm_list);
            cp1 = cp2;
            } while (cp1);
        }

    grprab.rab$b_krf = 0;
    grprab.rab$l_rop = RAB$M_RRL | RAB$M_NLK;
    grprab.rab$b_rac = RAB$C_SEQ;

    itmrab.rab$l_kbf = &l_key;
    itmrab.rab$b_ksz = 4;
    itmrab.rab$b_krf = 0;
    itmrab.rab$l_rop = RAB$M_KGE | RAB$M_RRL | RAB$M_NLK;
    itmrab.rab$b_rac = RAB$C_KEY;

    write_net(msg[M231],unit);

    sys$rewind(&grprab);
    while (sys$get(&grprab) & 1) {
        if (!newsgrp.grp_num) continue;
        if (newsgrp.grp_credate < filt_date) continue;
        if ((dists) && (!loc_grp(newsgrp.grp_name,itm_list))) continue;

        l_key = (newsgrp.grp_num << 16);
        if ((sys$get(&itmrab) & 1) && (newsitm.itm_grp == newsgrp.grp_num)) first_num = newsitm.itm_num;
        else first_num = newsgrp.grp_topnum + 1;

        sprintf(sbuf,"%s %d %d %s\r\n",newsgrp.grp_name, newsgrp.grp_topnum,first_num,(newsgrp.grp_moderator ? "n" : "y"));
        write_net(sbuf,unit);
        }
    strcpy(sbuf,".\r\n");
    write_net(sbuf,unit);
    while (itm_list) {
        tmp = itm_list->next;
        free(itm_list);
        itm_list = tmp;
        }
}

newnews_cmd(ngroups,date,time,zone,dist,unit)
    char *ngroups,
         *date,
         *time,
         *zone,
         *dist;
    int unit;
{
    int filt_date,
        dists = 0;
    char dist_filter[80],
         last_id[IDLEN],
         *cp1,
         *cp2;
    struct il *tmp;

    ngp_list = 0;
    cp1 = ngroups;
    do {
        if (cp2 = strchr(cp1,',')) *cp2++ = '\0';
        add_list(cp1,&ngp_list);
        cp1 = cp2;
        } while (cp1);

    filt_date = cvt_date(date,time);

    if ((dist) && (*dist == '<')) {
        itm_list = 0;
        if (cp1 = strchr(dist,'>')) *cp1 = '\0';
        cp1 = dist +1;
        do {
            if (cp2 = strchr(dist,',')) *cp2++ = '\0';
            dists++;
            strcpy(dist_filter,cp1);
            strcat(dist_filter,".*");
            add_list(dist_filter,&itm_list);
            cp1 = cp2;
            } while (cp1);
        }

    grprab.rab$l_kbf = &(newsitm.itm_grp);
    grprab.rab$b_ksz = 2;
    grprab.rab$b_krf = 1;
    grprab.rab$l_rop = RAB$M_RRL | RAB$M_NLK;
    grprab.rab$b_rac = RAB$C_KEY;

    itmrab.rab$b_krf = 1;
    itmrab.rab$l_rop = RAB$M_RRL | RAB$M_NLK;
    itmrab.rab$b_rac = RAB$C_SEQ;

    write_net(msg[M230],unit);

    *last_id = '\0';
    sys$rewind(&itmrab);
    while (sys$get(&itmrab) & 1) {
        if (!strcmp(newsitm.itm_id,last_id)) continue;
        if (newsitm.itm_date < filt_date) continue;
        if (!(sys$get(&grprab) & 1)) continue;
        if (!loc_ngrp(newsgrp.grp_name,ngp_list)) continue;
        if ((dists) && (!loc_grp(newsgrp.grp_name,itm_list))) continue;

        sprintf(sbuf,"%s\r\n",newsitm.itm_id);
        write_net(sbuf,unit);
        strncpy(last_id,newsitm.itm_id,IDLEN);
        }
    strcpy(sbuf,".\r\n");
    write_net(sbuf,unit);
    while (itm_list) {
        tmp = itm_list->next;
        free(itm_list);
        itm_list = tmp;
        }
    while (ngp_list) {
        tmp = ngp_list->next;
        free(ngp_list);
        ngp_list = tmp;
        }
}

/*
 *  next_cmd
 */

next_cmd(unit)
    int unit;
{
    int l_key;

    if (!cxt[unit].curr_group) return(write_net(msg[M412],unit));
    if (!cxt[unit].curr_item) return(write_net(msg[M420],unit));

    l_key = (cxt[unit].curr_group << 16) + cxt[unit].curr_item + 1;
    itmrab.rab$l_kbf = &l_key;
    itmrab.rab$b_ksz = 4;
    itmrab.rab$b_krf = 0;
    itmrab.rab$l_rop = RAB$M_KGE | RAB$M_RRL | RAB$M_NLK;
    itmrab.rab$b_rac = RAB$C_KEY;

    if ((!((status = sys$get(&itmrab)) & 1)) || (newsitm.itm_grp != cxt[unit].curr_group)) return(write_net(msg[M421],unit));
    cxt[unit].curr_item = newsitm.itm_num;
    sprintf(sbuf,msg[M223],newsitm.itm_num,newsitm.itm_id);
    write_net(sbuf,unit);
}

/*
 *  post_cmd
 */

int post_2();

post_cmd(unit)
{
    write_net(msg[M340],unit);
    next_call(unit,post_2,FILE_INPUT);
}

post_2(unit)
    int unit;
{
    FILE *fpl = 0;
    int i;

    next_call(unit,parser,CMD_INPUT);
    bsize = 0;
    do {
        read_net(ibuf,512,unit);
        if (strcmp(ibuf,".\n")) {
            if (!fpl) fpl = open_out_file();
            if (!strcmp(ibuf,"..\n"))  {
                bsize += strlen(ibuf);
                fputs(ibuf + 1,fpl);
                }
            else  {
                bsize += strlen(ibuf);
                fputs(ibuf ,fpl);
                }
            }
        } while (strcmp(ibuf,".\n"));
        bsize = bsize -2;
        output_file();
        if (fpl) {
            fclose(fpl);
            while (!delete(SCRATCHFILE));
        }
    write_net(msg[M240],unit);
}

/*
 *  quit_cmd
 */

quit_cmd(unit)
{
    write_net(msg[M205],unit);
    next_call(unit,CLOSE_LINK,NO_INPUT);
}

/*
 *  slave_cmd
 */

slave_cmd(unit)
    int unit;
{
    write_net(msg[M202],unit);
}


int xupd_2();

xupdgroup_cmd(g,date,time,zone,unit)
    char *g,
         *date,
         *time,
         *zone;
    int unit;
{
    char l_g[SUBJLEN];

    if (date) cxt[unit].cfilt_date = cvt_date(date,time);
    else cxt[unit].cfilt_date = 0;

    strncpy(l_g,g,SUBJLEN);
    grprab.rab$l_kbf = l_g;
    grprab.rab$b_ksz = SUBJLEN;
    grprab.rab$b_krf = 0;
    grprab.rab$l_rop = RAB$M_RRL | RAB$M_NLK;
    grprab.rab$b_rac = RAB$C_KEY;

    if (!(sys$get(&grprab) & 1)) return(write_net(msg[M480],unit));
    cxt[unit].cgrp = newsgrp.grp_num;
    write_net(msg[M380],unit);
    next_call(unit,xupd_2,FILE_INPUT);
}

xupd_2(unit)
    int unit;
{
    int i_key;
    struct il *tmp;

    next_call(unit,parser,CMD_INPUT);
    itm_list = 0;
    do {
        read_net(ibuf,512,unit);
        if (strcmp(ibuf,".\n")) {
            ibuf[strlen(ibuf) - 1] = '\0';
            add_list(ibuf,&itm_list);
            }
        } while (strcmp(ibuf,".\n"));
    write_net(msg[M280],unit);
    i_key = (cxt[unit].cgrp << 16);
    itmrab.rab$l_kbf = &i_key;
    itmrab.rab$b_ksz = 4;
    itmrab.rab$b_krf = 0;
    itmrab.rab$l_rop = RAB$M_KGE | RAB$M_RRL | RAB$M_NLK;
    itmrab.rab$b_rac = RAB$C_KEY;

    while ((status = sys$get(&itmrab)) & 1) {
        itmrab.rab$b_rac = RAB$C_SEQ;
        if (newsitm.itm_grp != newsgrp.grp_num) break;
        if (newsitm.itm_date < cxt[unit].cfilt_date) continue;
        if (!loc_id(newsitm.itm_id)) {
            sprintf(sbuf,"%s %d %d %s\r\n",newsitm.itm_id,newsitm.itm_date,newsitm.itm_size,newsitm.itm_title);
            write_net(sbuf,unit);
            }
        }
    while (itm_list) {
        tmp = itm_list->next;
        sprintf(sbuf,"D %s\r\n",itm_list->id);
        write_net(sbuf,unit);
        free(itm_list);
        itm_list = tmp;
        }
    strcpy(sbuf,".\r\n");
    write_net(sbuf,unit);
}

/*
 *  parser
 */

parser(unit)
    int unit;
{
    char *arg;
    int arg_int,
        i;

    next_call(unit,parser,CMD_INPUT);
    read_net(ibuf,512,unit);
    strip_compress_lower(ibuf);
    if (arg = strchr(ibuf,' ')) *arg++ = '\0';
    switch (i = loc_cmd(ibuf)) {
        case ARTICLE :
            if (!arg) get_item(0,0,0,unit);
            else if (*arg == '<') get_item(0,arg,0,unit);
            else if (sscanf(arg,"%d",&arg_int) == 1) get_item(arg_int,0,0,unit);
            else write_net(msg[M501],unit);
            break;
        case BODY :
            if (!arg) get_item(0,0,2,unit);
            else if (*arg == '<') get_item(0,arg,2,unit);
            else if (sscanf(arg,"%d",&arg_int) == 1) get_item(arg_int,0,2,unit);
            else write_net(msg[M501],unit);
            break;
        case GROUP :
            if (!arg) write_net(msg[M501],unit);
            else get_group(arg,unit);
            break;
        case HEAD :
            if (!arg) get_item(0,0,1,unit);
            else if (*arg == '<') get_item(0,arg,1,unit);
            else if (sscanf(arg,"%d",&arg_int) == 1) get_item(arg_int,0,1,unit);
            else write_net(msg[M501],unit);
            break;
        case HELP :
            if (arg) write_net(msg[M501],unit);
            else help_text(unit);
            break;
        case IHAVE :
            if ((!arg) || (*arg != '<')) write_net(msg[M501],unit);
            else ihave_cmd(arg,unit);
            break;
        case LAST :
            if (arg) write_net(msg[M501],unit);
            else last_cmd(unit);
            break;
        case LIST :
            if (arg) write_net(msg[M501],unit);
            else list_cmd(unit);
            break;
        case NEWGROUPS :
            if (!arg) write_net(msg[M501],unit);
            else {
                char *date = 0,
                     *time = 0,
                     *zone = 0,
                     *dist = 0;

                date = arg;
                if (!(time = strchr(date,' '))) {
                    write_net(msg[M501],unit);
                    break;
                    }
                else *time++ = '\0';
                if (zone = strchr(time, ' ')) *zone++ = '\0';
                if ((zone) && (*zone == '<')) {
                    dist = zone;
                    zone = 0;
                    }
                else if ((zone) && (dist = strchr(zone,' '))) *dist++ = '\0';
                if ((dist) && ((*dist != '<') || strchr(dist,' '))) {
                    write_net(msg[M501],unit);
                    break;
                    }
                newgroups_cmd(date,time,zone,dist,unit);
                }
            break;
        case NEWNEWS :
            if (!arg) write_net(msg[M501],unit);
            else {
                char *ngroups,
                     *date = 0,
                     *time = 0,
                     *zone = 0,
                     *dist = 0;

                ngroups = arg;
                if (!(date = strchr(ngroups,' '))) {
                    write_net(msg[M501],unit);
                    break;
                    }
                else *date++ = '\0';
                if (!(time = strchr(date,' '))) {
                    write_net(msg[M501],unit);
                    break;
                    }
                else *time++ = '\0';
                if (zone = strchr(time, ' ')) *zone++ = '\0';
                if ((zone) && (*zone == '<')) {
                    dist = zone;
                    zone = 0;
                    }
                else if ((zone) && (dist = strchr(zone,' '))) *dist++ = '\0';
                if ((dist) && ((*dist != '<') || strchr(dist,' '))) {
                    write_net(msg[M501],unit);
                    break;
                    }
                newnews_cmd(ngroups,date,time,zone,dist,unit);
                }
            break;
        case NEXT :
            if (arg) write_net(msg[M501],unit);
            else next_cmd(unit);
            break;
        case POST :
            if (arg) write_net(msg[M501],unit);
            else post_cmd(unit);
            break;
        case QUIT :
            if (arg) write_net(msg[M501],unit);
            else quit_cmd(unit);
            break;
        case SLAVE :
            if (arg) write_net(msg[M501],unit);
            else slave_cmd(unit);
            break;
        case STAT :
            if (!arg) get_item(0,0,3,unit);
            else if (*arg == '<') get_item(0,arg,3,unit);
            else if (sscanf(arg,"%d",&arg_int) == 1) get_item(arg_int,0,3,unit);
            else write_net(msg[M501],unit);
            break;
        case XUPDGROUP :
            if (!arg) write_net(msg[M501],unit);
            else {
                char *ngroups,
                     *date = 0,
                     *time = 0,
                     *zone = 0;

                ngroups = arg;
                if (!(date = strchr(ngroups,' '))) {
                    xupdgroup_cmd(ngroups,0,0,0);
                    break;
                    }
                else *date++ = '\0';
                if (!(time = strchr(date,' '))) {
                    write_net(msg[M501],unit);
                    break;
                    }
                else *time++ = '\0';
                if (zone = strchr(time, ' ')) *zone++ = '\0';
                xupdgroup_cmd(ngroups,date,time,zone,unit);
                }
            break;
        default :
            write_net(msg[M500],unit);
            break;
        }
}

server_init(max_units)
    int max_units;
{
    cxt = malloc((max_units + 1) * (sizeof *cxt));
    return(open_server_files());
}

server_init_unit(unit)
{
    cxt[unit].curr_group = 0;
    cxt[unit].curr_item = 0;
    sprintf(sbuf,msg[M200],SERVER_NAME);
    write_net(sbuf,unit);
    next_call(unit,parser,CMD_INPUT);
}

server_shut()
{
    sys$close(&grpfab);
    sys$close(&itmfab);
    if (fpt) {
        fclose(fpt);
        while (!delete(SCRATCHFILE));
        }
    if (fpw) fclose(fpw);
}
