/*
**++
**  FACILITY:
**      NEWSCONTROL
**
**  ABSTRACT:
**      This module interprets Control: messages.
**
**  ENTRY POINTS:
**	int parse_control_item(argv,fn,switches)
**		char *argv[], *fn; int switches;
**
**  AUTHOR:
**      Geoff Huston
**
**  COPYRIGHT:
**      Copyright © 1990,1991
**
**  VERSION
**	V6.1	23-07-91
**		Bill Fenner       wcf@ecl.psu.edu
**		> We got a few newgroup's last night, like "newgroup comp.sys.handhelds
** 		> unmoderated".  ANU/NEWS created the newsgroups _moderated_, it seems as though
**     		> it only checks for the presence of a second argument, not the text of it.
**    		Actually, it turns out that it checks for an argument with the string
**     		"moderated" anywhere in it.  Too bad "unmoderated" has "moderated" in it.
**     		[change made] to make the comparison exact, and also to log
**     		moderated/unmoderated status in the log file and in the subject of the
**     		mail message.
**	V6.1	26-Feb-1992
**		Mark Pizzolato	mark@infocomm.com
**		Allow the /ACCEPT command qualifier to override missing
**		"Approved" header lines.
**		This is determined by referencing the more general global
**		variable itm_approved (set in NEWSADD) instead of explicitly 
**		referencing the APPROVED header line.
**	V6.1	26-Jul-1992
**		Bill Fenner  wcf@ecl.psu.edu
**		Added support for descriptions in newgroup control messages
**		of the form:
**		For your newsgroups file:
**		news.group.name		Description
**		This is the format that David Lawrence uses for his control
**		messages, so all "official" USENET newsgroups will now get
**		their descriptions automatically.
**	V6.1	13-Sep-1992
**		Bill Fenner  wcf@ecl.psu.edu
**		Modified newgrp description code to skip blank lines early
**		in the loop -- somehow, the strncmp compared true on a
**		blank line, so you'd get newsgroups with descriptions of
**		"your newsgroups file:\n".  Also checked a bit more strictly
**		for the proper format.
**	V6.1b8  22-Dec-1993	mark.martinec@ijs.si
**		convert calls to RMS to use new sys_* macros for error handling 
**	V6.1b9  25-May-1994  Charles Bailey  bailey@genetics.upenn.edu
**	 - add dummy flk param to do_oitem calls
**      V6.1b9  24-Feb-1995	Bob Sloane sloane@kuhub.cc.ukans.edu
**       - added extended newsgroup names for control items:
**         control.type.hier, so that cancel, rmgroup, etc messages
**         can be put in groups like control.cancel.alt, or control.rmgroup.
**	V6.1b9   3-Mar-1995	mark.martinec@ijs.si
**	 - replace strncpy with util_idcpy to match the way it is done
**	   in NNTP_SERVER.C (is item ID size 60 or 59+'\0' characters ?)
**	V6.1b10  4-Nov-1996	mark@infocomm.com
**	 - fixed accvio bug with control checkgroups for groups that 
**	   previously didn't have a topic string set.
**	V6.1b11 26-Mar-1997	Mark Martinec   mark.martinec@ijs.si
**	 - new procedure pgp_verify
**	V6.1b11 14-Apr-1997	Mark Martinec   mark.martinec@ijs.si
**	 - add 'const' attributes to control_* routines arguments
**	 - change %s to %.<n>s in sprintf to guard against overwriting buffer
**	 - replace several calls to strcpy/strcat with safer strnncpy/strnncat
**	 - replace pattern strcpy(news_malloc(strlen(x)+1),x) with news_strdup
**	 - fix the year 2000 problem when generating the 'Date:' header line
**	 - new procedure put_pgp_message(); integrate pgp_verify into
**	   control_newgroup() and control_rmgroup()
**	V6.1b11 22-May-1997	Mark Martinec   mark.martinec@ijs.si
**	 - append_control(): assure proper commenting ($!) of very long lines
**	 - replace single flag EXECUTE_CONTROL with one for each type
**	   of control messages: EXECUTE_CONTROL_CHECKGROUPS,
**	   EXECUTE_CONTROL_NEWGROUP and EXECUTE_CONTROL_RMGROUP.
**	 - flags EXECUTE_CONTROL_RMGROUP_DELETE	and
**	   EXECUTE_CONTROL_RMGROUP_LOCAL are now used strictly as a modifier
**	   to the way groups are deleted/removed and affect the commands
**	   placed in DCL procedures as well as immediate execution.
**--
**/

#ifdef vaxc
#module NEWSCONTROL "V6.2"
#endif

#define _NEWSCONTROL_C
#define module_name "NEWSCONTROL"

#include "newsinclude.h"
#include "newsextern.h"

#if NAKED_INCLUDES
#include iodef
#include psldef
#else
#include <iodef.h>
#include <psldef.h>
#endif

#if NNTP_CLIENT_ONLY

int parse_control_item(argv,fn,switches,newsgroups)
  const char *argv[], *fn;
  int switches;
  const char *newsgroups;
{
  return(1);
}

#else

static char mail_scratch[256] = "";


/*
 *  pgp_verify
 *
 *  Given article headers and a filename with full article body,
 *  construct a temporary file with a modified version of the article
 *  according to PGP specifications for a signed text.
 *  The signature is obtained from the article header 'X-PGP-Sig:' .
 *  The constructed temporary file is submitted to PGP
 *  for signature identification and verification by calling
 *  a DCL procedure NEWS_MANAGER:PGPCALL.COM in a subprocess.
 *  The resulting status code is read back and returned as a return value.
 *  If the public key of the signer was identified (regardless of the
 *  signature being good or bad), it is copied to the caller's buffer
 *  signer_id (and '\0' appended) of the maximal size signer_id_m.
 *  The copied string length is returned in signer_id_l (if address not NULL).
 *
 *  Up to date list of public PGP keys for the main hierarcies
 *  is available at:  ftp://ftp.isc.org/pub/pgpcontrol/PGPKEYS
 *  See also:         ftp://ftp.isc.org/pub/pgpcontrol/README
 *        or:         ftp://ftp.isc.org/pub/pgpcontrol/README.html
 *
 *  Returned status:
 *     0 (OK)   signature expected; good signature and correct signer id
 *     1 (bad)  signature expected; no signature found
 *     2 (bad)  signature expected; unknown signature
 *     3 (bad)  signature expected; bad signature
 *     4 (bad)  signature expected; good signature but wrong signer for the specified hierarchy
 *    11 (OK)   no signature expected; no signature found
 *    12 (ok?)  no signature expected; unknown signature
 *    13 (bad?) no signature expected; bad signature
 *    14 (ok?)  no signature expected; good signature by un unexpected signer
 *  >200  problem not directly related to pgp analysis of signature
 */
int pgp_verify(const char *known_headers[], const char *article_fn,
               const char *newsgroup_name,
               char *signer_id, int signer_id_m, int *signer_id_l)
{
  const char *s;
  int s_l;  /* strlen(s) */
  int len;
  const char *version;    /* PGP version substring */
  const char *hdr_names;  /* comma-separated names of signed headers */
  int version_l;    /* length of version substring */
  int hdr_names_l;  /* length of hdr_names substring */
  int status;
  unsigned short mbx_chan;  /* channel number of the mailbox device */
  FILE *fpgp,*fart;
  struct mbx_iosb {unsigned short status, count;  int sender_pid;} mbx_iosb;
  int mbx_efn;
  char scratch_fn[40];
  char response_line[1024];

  if (signer_id && signer_id_m > 0) signer_id[0] = '\0';
  if (signer_id_l) *signer_id_l = 0;
/* split into three parts: version, names of signed headers, PGP signature  */
  s = known_headers[X_PGP_SIG]; s_l = strlen(s); /* possibly empty */
  len = strspn(s," \t"); s += len; s_l -= len;           /* span whitespace */
  len = strcspn(s," \t");                            /* span non-whitespace */
  version = s; version_l = len; s += len; s_l -= len;
  len = strspn(s," \t"); s += len; s_l -= len;           /* span whitespace */
  len = strcspn(s," \t");                            /* span non-whitespace */
  hdr_names = s; hdr_names_l = len; s += len; s_l -= len;
  len = strspn(s," \t"); s += len; s_l -= len;           /* span whitespace */
/* s now points to the PGP signature */
  *scratch_fn = '\0';
  if (s_l <= 0){}/* no signature, don't bother preparing article for checking */
  else {
    sprintf(scratch_fn, "SYS$SCRATCH:NEWS_PGP_%X.ASC", getpid());
    if (!(fpgp = fopen(scratch_fn,"w")))
      { _ck_open_w(fpgp,scratch_fn); return(252); }
    fputs("-----BEGIN PGP SIGNED MESSAGE-----\n\n", fpgp); _ck_put(fpgp);
    fprintf(fpgp, "X-Signed-Headers: %.*s\n",
            hdr_names_l,hdr_names); _ck_put(fpgp);
    while (hdr_names_l > 0) {
      char *c = strchr(hdr_names,',');  /* split by comma */
      int len = c ? c-hdr_names : hdr_names_l;
      int j;
      if (len > 0) {
        fprintf(fpgp, "%.*s: ", len,hdr_names); _ck_put(fpgp);
        if (find_header_name(hdr_names,len,&j,1))
          { fputs(known_headers[j], fpgp); _ck_put(fpgp); }
        fputc('\n', fpgp); _ck_put(fpgp);
        hdr_names += len; hdr_names_l -= len;
        }
      if (*hdr_names == ',') { hdr_names++; hdr_names_l--; }
      }
    fputc('\n', fpgp); _ck_put(fpgp);   /* end of headers */
    if (!(fart = fopen(article_fn,"r")))
      { _ck_open_r(fart,article_fn); return(253); }
    else {                         /* copy article body to the temporary file */
      int eol=1, skipping_headers=1, line_l;
      char line[256];     /* fit most of the lines; longer will be split-read */
      while (fgets(line,sizeof(line),fart)) {
        line_l = strlen(line);
        if (!skipping_headers) {
          if (eol && line[0] == '-') /* PGP armor for lines starting with '-' */
            { fputs("- ",fpgp); _ck_put(fpgp); }  /* prepend '- ' */
          fwrite(line,sizeof(char),line_l,fpgp); _ck_put(fpgp);
          }
        if (skipping_headers && eol && line_l == 1 && line[0] == '\n')
          skipping_headers = 0;
        eol = (line[line_l-1] == '\n');
        }
      _ck_get(fart);
      if (fclose(fart)) _ck_close(fart);
      }
    fputc('\n', fpgp); _ck_put(fpgp);
    fputs("-----BEGIN PGP SIGNATURE-----\n", fpgp); _ck_put(fpgp);
    fprintf(fpgp, "Version: %.*s\n\n", version_l, version); _ck_put(fpgp);
    while (s_l > 0) {
      len = strspn(s," \t"); s += len; s_l -= len;         /* span whitespace */
      len = strcspn(s," \t");                          /* span non-whitespace */
      fprintf(fpgp, "%.*s\n", len,s); _ck_put(fpgp);
      s += len; s_l -= len;
      }
    fputs("-----END PGP SIGNATURE-----\n", fpgp); _ck_put(fpgp);
    if (fclose(fpgp)) _ck_close(fpgp);
    }
  c$cks(sys$crembx(0,&mbx_chan,
                   sizeof(response_line), sizeof(response_line),
                   0xffff0000U, PSL$C_USER, c$dsc("PGPCALL_MBX")));
  _c$cks(lib$get_ef(&mbx_efn));
  _c$cks(sys$qio(mbx_efn,mbx_chan,IO$_READVBLK,&mbx_iosb,0,0,
                 response_line,sizeof(response_line)-1,0,0,0,0));
  { int sts;
    char pgp_cmd[256];
    sprintf(pgp_cmd, "@%.48s \"%.39s\" \"%.161s\"",
            PGPCALL_FILE, scratch_fn, newsgroup_name);
    c$cks(lib$spawn(c$dsc(pgp_cmd),c$dsc("NL:"),c$dsc("PGPCALL_MBX"),
                    0,0,0,&sts,0,0,0,0,0,0));
    sts &= 0x0fffffff; /* clear four control bits (re-enable message display) */
    if (!(sts&1) && (sts&3 != 2)) _c$cks(sts);
  }
  if (*scratch_fn)
    { if (delete(scratch_fn)) _ck_delete(scratch_fn); }
 /* read response from PGPCALL.COM passed back through a mailbox: */
 /* normally:  n key_id signer_id,  in case of procedure failure: 255 err_msg */
  { char *signer_id_tmp;
    int signer_id_tmp_l, sts_code=255;
    _c$cks(sys$synch(mbx_efn,&mbx_iosb));        /* wait for $qio to complete */
    _c$cks(mbx_iosb.status);
    _c$cks(sys$dassgn(mbx_chan)); /* close chan and discard temporary mailbox */
    _c$cks(lib$free_ef(&mbx_efn));
    response_line[mbx_iosb.count] = '\0';
    sts_code = strtol(response_line, &signer_id_tmp, 10); /* parse PGP status */
    while (*signer_id_tmp == ' ' || *signer_id_tmp == '\t') signer_id_tmp++;
    signer_id_tmp_l = strlen(signer_id_tmp);
    if (signer_id_tmp_l>0 && signer_id_tmp[signer_id_tmp_l-1] == '\n')
      signer_id_tmp_l--;  /* chop off newline */
    signer_id_tmp_l = min(signer_id_tmp_l, signer_id_m-1);
    if (signer_id && signer_id_m>0) {
      if (signer_id_tmp_l>0) memcpy(signer_id,signer_id_tmp,signer_id_tmp_l);
      signer_id[signer_id_tmp_l] = '\0';
      if (signer_id_l) *signer_id_l = signer_id_tmp_l;
      }
    return(sts_code);
    }
}

/*
 * put_pgp_message
 *
 * Write to file f one or more lines, explaining what the pgp_code signifies.
 * Each line is prefixed by a string 'prefix'.
 */
static void put_pgp_message(
  FILE *f,		 /* where to write explanation */
  const char *prefix,    /* prefix lines with this string */
  int pgp_code,          /* status code as returned by pgp_verify() */
  const char *signer_id, /* signer id as returned by pgp_verify     */
  int exec_overruled)    /* automatic execution of control message was overruled
                            due to signature verification failure   */
{
  if (!pgp_verification_available) {
    fprintf(f,"%sNOTE:\n", prefix);
    fprintf(f,"%s  PGP verification is not enabled\n",prefix);
    fprintf(f,"%s  (file %s not found or other problem activating it).\n",prefix,PGPCALL_FILE);
    fprintf(f,"%s  It is advisable to enable PGP verification of control messages\n",prefix);
    fprintf(f,"%s  to guard against forged or garbled control messages.\n",prefix);
    fprintf(f,"%s  With verification in place it is safe to enable automatic\n",prefix);
    fprintf(f,"%s  execution of \"newgroup\" and \"rmgroup\" control messages.\n",prefix);
    fprintf(f,"%s  See:  HELP ADD FILE PGP_verification\n",prefix);
    }
  else {
    switch (pgp_code) {
      case 0:
        fprintf(f,"%sGood signature, correct signatory - we can trust this control message.\n", prefix);
        break;
      case 1:
        fprintf(f,"%sPGP signature expected but no signature found!\n", prefix);
        fprintf(f,"%s(article is probably a forgery)\n", prefix);
        break;
      case 2:
        fprintf(f,"%sPGP signature expected, unknown signature found!\n", prefix);
        fprintf(f,"%s(add signer's public key to the PGP keyring)\n", prefix);
        break;
      case 3:
        fprintf(f,"%sPGP signature expected, bad signature found!\n", prefix);
        fprintf(f,"%s(article might be garbled or is a forgery)\n", prefix);
        break;
      case 4:
        fprintf(f,"%sPGP signature expected, found signature from wrong signatory!\n",prefix);
        fprintf(f,"%s(probably the pgpkeyid_* table in %s is out of date)\n", prefix,PGPCALL_FILE);
        break;
      case 11:
        fprintf(f,"%sNo PGP signature expected, no signature found.\n", prefix);
        fprintf(f,"%s(this is normal)\n", prefix);
        break;
      case 12:
        fprintf(f,"%sNo PGP signature expected, unknown signature found.\n", prefix);
        fprintf(f,"%s(probably PGP keyring and pgpkeyid_* table in %s are out date)\n", prefix,PGPCALL_FILE);
        break;
      case 13:
        fprintf(f,"%sNo PGP signature expected, bad signature found.\n", prefix);
        fprintf(f,"%s(probably message garbled and pgpkeyid_* table in %s is out date)\n", prefix,PGPCALL_FILE);
        break;
      case 14:
        fprintf(f,"%sNo PGP signature expected, good signature by un unexpected signatory.\n", prefix);
        fprintf(f,"%s(probably pgpkeyid_* table in %s is out of date)\n", prefix,PGPCALL_FILE);
        break;
      case 255:
        fprintf(f,"%sProcedure %s failed!\n", prefix,PGPCALL_FILE);
        if (*signer_id) fprintf(f,"%sError: %s\n", prefix,signer_id);
        fprintf(f,"%s(please correct the problem!)\n", prefix);
        break;
      default:
        fprintf(f,"%sUnexpected PGP status code: %d\n", prefix,pgp_code);
        if (*signer_id) fprintf(f,"%sMessage: \"%s\"\n", prefix,signer_id);
        fprintf(f,"%s(please investigate!)\n", prefix);
        break;
    }
    if (pgp_code != 255 && *signer_id)
      fprintf(f,"%sSignatory: \"%s\"\n", prefix,signer_id);
    if ((pgp_code >= 1) && (pgp_code <= 9)) {
      fprintf(f,"%sWARNING!!!\n", prefix);
      fprintf(f,"%s  This control article was expected to be PGP-signed by a known signatory.\n", prefix);
      fprintf(f,"%s  Make sure you trust this message before acting upon it!\n", prefix);
      }
    }
  fprintf(f,"%s\n", prefix);
  if (exec_overruled) {
    fprintf(f,"%sAutomatic execution of the control message overruled\n",prefix);
    fprintf(f,"%sdue to signature verification failure. Execute this script\n",prefix);
    fprintf(f,"%smanually if you believe you can trust it.\n",prefix);
    fprintf(f,"%s\n",prefix);
    }
}


/*
 *  control_version
 *
 *  Mail the News version level back to poster
 */

static int control_version(argv,fn,switches)
  const char *argv[], *fn;
  int switches;
{
  char vms_address[256];
  const char *mail_replyto;
  FILE *fpm;

  mail_replyto = argv[REPLY_TO];
  if (!mail_replyto || !*mail_replyto) mail_replyto = argv[FROM];
  if (!mail_replyto || !*mail_replyto) return(NORETURNADDRESS);

  strnncpy(vms_address,sizeof(vms_address), add_transform(mail_replyto),-1, 0);
  if (!*mail_scratch) sprintf(mail_scratch,"SYS$SCRATCH:NEWS_MAIL_%X.TMP",getpid());
  if (!(fpm = fopen(mail_scratch,"w","rfm=var","rat=cr"))) return(NOSCRATCHFILE);

  fprintf(fpm,"Response-To-Control: %s message\n",argv[CONTROL]);
  fprintf(fpm,"Responding-System: %s\n",news_node);
  fprintf(fpm,"News-Software: %s\n",NEWS_VERSION);
  fclose(fpm);
  strcpy(err_oline,"Control: version - Forward to ");
  err_oline_cat(vms_address);
  call_mail(mail_scratch,err_oline,USENET,err_oline);
  delete(mail_scratch);
  return(CTLOK);
}

/*
 *  control_senduuname
 *
 *  Mail the UUCP control file back to poster - mail error text for VMS
 */

static int control_senduuname(argv,fn,switches)
  const char *argv[], *fn;
  int switches;
{
  char vms_address[256];
  const char *mail_replyto;
  FILE *fpm;

  mail_replyto = argv[REPLY_TO];
  if (!mail_replyto || !*mail_replyto) mail_replyto = argv[FROM];
  if (!mail_replyto || !*mail_replyto) return(NORETURNADDRESS);

  strnncpy(vms_address,sizeof(vms_address), add_transform(mail_replyto),-1, 0);
  if (!*mail_scratch) sprintf(mail_scratch,"SYS$SCRATCH:NEWS_MAIL_%X.TMP",getpid());
  if (!(fpm = fopen(mail_scratch,"w","rfm=var","rat=cr"))) return(NOSCRATCHFILE);

  fprintf(fpm,"Response-To-Control: %s message\n",argv[CONTROL]);
  fprintf(fpm,"Responding-System: %s\n",news_node);
  fprintf(fpm,"News-Software: %s\n",NEWS_VERSION);
  fprintf(fpm,"This is a VAX/VMS NEWS, not configured with UUCP\n");
  fclose(fpm);
  strcpy(err_oline,"Control: senduuname - Forward to ");
  err_oline_cat(vms_address);
  call_mail(mail_scratch,err_oline,USENET,err_oline);
  delete(mail_scratch);
  return(CTLOK);
}

/*
 *  control_sendsys
 *
 *  Mail the NEWS.SYS control file back to poster
 */

static int control_sendsys(argv,fn,switches)
  const char *argv[], *fn;
  int switches;
{
  char il[512], vms_address[256];
  const char *mail_replyto;
  FILE *fpm, *fpr;

  mail_replyto = argv[REPLY_TO];
  if (!mail_replyto || !*mail_replyto) mail_replyto = argv[FROM];
  if (!mail_replyto || !*mail_replyto) return(NORETURNADDRESS);
  if (!strchr(mail_replyto,'@')) return(NORETURNADDRESS);

  strnncpy(vms_address,sizeof(vms_address), add_transform(mail_replyto),-1, 0);
  if (!*mail_scratch) sprintf(mail_scratch,"SYS$SCRATCH:NEWS_MAIL_%X.TMP",getpid());
  if (!(fpm = fopen(mail_scratch,"w","rfm=var","rat=cr"))) return(NOSCRATCHFILE);

  fprintf(fpm,"Response-To-Control: %s message\n",argv[CONTROL]);
  fprintf(fpm,"Responding-System: %s\n",news_node);
  fprintf(fpm,"News-Software: %s\n",NEWS_VERSION);
  sysprv();
  if (!(fpr = fopen("NEWS_MANAGER:NEWS.SYS","r")))
    fprintf(fpm,"No-SYS: No NEWS.SYS file defined on this node\n");
  else {
    while (fgets(il,sizeof(il)-2,fpr)) if (*il != '#') fputs(il,fpm);
    fclose(fpr);
    }
  nosysprv();
  fclose(fpm);
  strcpy(err_oline,"Control: sendsys - forward to ");
  err_oline_cat(vms_address);
  call_mail(mail_scratch,err_oline,USENET,err_oline);
  delete(mail_scratch);
  return(CTLOK);
}

/*
 * append_control
 *
 * Appends the text of a control message to the mail message sent to USENET.
 *
 * Author: William C. Fenner <wcf@ecl.psu.edu>
 */

static void append_control(from,to)
  const char *from;
  FILE *to;
{
  FILE *f;
  int terminated;
  char il[256];

  fprintf(to,"$! Control message follows:\n$!\n");
  if((f=fopen(from,"r"))==NULL) {
    fprintf(to,"$! * Could not open input file %s\n",from);
    return;
    }
  terminated = 1;
  while (fgets(il,sizeof(il),f)) {
    if (terminated) fputs("$! ",to);
    terminated = (il[strlen(il)-1] == '\n');
    fputs(il,to);
    }
  if (!terminated) fputc('\n',to);
  fclose(f);
  return;
  }

/*
 *  control_newgroup
 *
 *  Create new newsgroup control message - if auto execution enabled then
 *  new newsgroup is created, otherwise DCL is posted to USENET
 */

static int control_newgroup(argv,fn,switches)
  const char *argv[], *fn;
  int switches;
{
  char *c;
  const char *cc;
  char xbuf[512];
  char locname[GRPNAMELEN];
  char descrip[132];
  int locname_l,descrip_l;
  unsigned int g;
  int mod = 0;
  char signer_id[256];
  int pgp_code, pgp_accepted, exec_overruled;
  FILE *oldfpm, *fpm = 0;
  FILE *fpa;

  if (!itm_approved) return(NONAPPROVEDCONTROL);

  cc = argv[CONTROL] + 8;
  while (isspace(*cc)) cc++;
  if (!*cc) return(NONEWSGROUPFIELD);

  strnncpy(locname,sizeof(locname), cc,-1, 0);
  for (c=locname; isgraph(*c); c++) {}
  *c = '\0'; locname_l = c-locname; c++;

  pgp_accepted = 1; pgp_code = -1; *signer_id = '\0';
  if (pgp_verification_available) {
    pgp_code = pgp_verify(argv, fn, locname,
                          signer_id, sizeof(signer_id), 0);
    pgp_accepted = (pgp_code == 0) || (pgp_code >= 10 && pgp_code <= 19);
    }
  exec_overruled = !pgp_accepted && (switches & EXECUTE_CONTROL_NEWGROUP);

  *descrip='\0'; descrip_l = 0;
  if ((fpa = fopen(fn,"r")) != 0) {
	int flag=0;

	while (fgets(xbuf,sizeof(xbuf)-1,fpa)) {
		if (!strcmp(xbuf,"For your newsgroups file:\n")) {
			flag=1;
			continue;
		}
		if (flag) {
			char *p = xbuf;
			chop_str(p,'\n');
			if (!*p) continue;	/* skip blank lines early */
			if (!strncmp(p,locname,locname_l)) {	/* if the newsgroup name is at the beginning of the line */
				while (*p && !isspace(*p)) p++;	/* skip ng name */
				while (*p && isspace(*p)) p++;	/* skip whitespace */
				if (*p) {	/* if there's a description, use it */
					strnncpy(descrip,sizeof(descrip), p,-1, &descrip_l);
					break;
				} else
					flag=0;	/* Malformed... */
			} else {
				while (*p && isspace(*p)) p++;
				if (*p) flag=0;		/* allow blank lines */
			}
		}
	}
	fclose(fpa);
  }

  chop_str(c,' ');
  mod = (*c && (strcmp(lower_case(c),"moderated")==0)) ? NEWS_M_MAILMODERATE : 0;
  util_cvrt(locname,locname);
  if (!*mail_scratch) sprintf(mail_scratch,"SYS$SCRATCH:NEWS_MAIL_%X.TMP",getpid());
  g = ga_exact_name(locname);
  if (!g && pgp_accepted && (switches & EXECUTE_CONTROL_NEWGROUP)) {
    int sc = auto_cre_grp;

    auto_cre_grp = 1;
    do_newg(locname,0,1);
    auto_cre_grp = sc;
    if ((g = ga_exact_name(locname)) && (fpm = fopen(mail_scratch,"w","rfm=var","rat=cr"))) {
      if (pgp_code == 0)
        fprintf(fpm,"Signature verified and accepted, signed by \"%s\"\n",signer_id);
      fprintf(fpm,"Control: newgroup - created %s\n",ga[g]->grp_name);
      if (*descrip) {
        fprintf(fpm,"Control: newgroup - modified %s/TITLE=\"%s\"\n",
		ga[g]->grp_name,descrip);
        if (ga[g]->grp_topic) news_free(ga[g]->grp_topic);
        ga[g]->grp_topic = news_strdup(descrip,descrip_l);
      }
      update_newsgrp(g);
      }
    }
  if (g) {
    if (   (ga[g]->grp_flags & NEWS_M_LOCAL) 
        || ((ga[g]->grp_flags & NEWS_M_MAILMODERATE) != mod)
        || ga[g]->grp_life) {
      if (pgp_accepted && (switches & EXECUTE_CONTROL_NEWGROUP)) {
        if (ga[g]->grp_flags & NEWS_M_LOCAL) ga[g]->grp_flags &= ~NEWS_M_LOCAL;
        if (mod != (ga[g]->grp_flags & NEWS_M_MAILMODERATE)) {
          ga[g]->grp_flags &= ~NEWS_M_MAILMODERATE;
          ga[g]->grp_flags |= mod;
          }
        if (ga[g]->grp_life) ga[g]->grp_life = 0;
        update_newsgrp(g);
        }
      oldfpm = fpm;
      if (fpm || (fpm = fopen(mail_scratch,"w","rfm=var","rat=cr"))) {
        if (pgp_accepted && (switches & EXECUTE_CONTROL_NEWGROUP)) {
          if (oldfpm == NULL && pgp_code == 0)
            fprintf(fpm,"Signature verified and accepted, signed by \"%s\"\n",signer_id);
	  fprintf(fpm,"Control: newgroup - modified %s/NOLOCAL%s/HOLD\n",
                  ga[g]->grp_name,(mod ? "/MOD" : ""));		/* modified */
        } else {
          if (pgp_code != 11)
            put_pgp_message(fpm, "$! ", pgp_code, signer_id, exec_overruled);
          fprintf(fpm,"$! Generated Command file from newgroup message:\n");
          fprintf(fpm,"$! use EXTRACT/NOHEAD then execute this file\n$!\n");
          fprintf(fpm,"$ DEFINE/USER NEWSRC NL:\n");
          fprintf(fpm,"$ NEWS/NOSCREEN\n");
          if (ga[g]->grp_flags & NEWS_M_LOCAL)
	    fprintf(fpm,"Set NEWSGROUP \"%s\"/NOLOCAL\n",ga[g]->grp_name);
          if (mod)
	    fprintf(fpm,"Set NEWSGROUP \"%s\"/MOD\n\n",ga[g]->grp_name);
          else if (!mod && (ga[g]->grp_flags & NEWS_M_MAILMODERATE))
            fprintf(fpm,"Set NEWSGROUP \"%s\"/NOMOD\n",ga[g]->grp_name);
          if (ga[g]->grp_life)
            fprintf(fpm,"Set NEWSGROUP \"%s\"/HOLD\n",ga[g]->grp_name);
	  if (*descrip)
	    fprintf(fpm,"Set NEWSGROUP \"%s\"/TITLE=\"%s\"\n",
			ga[g]->grp_name,quotes(descrip));
          fprintf(fpm,"EXIT\n");
          }
        }
      }
    }
  else if (fpm || (fpm = fopen(mail_scratch,"w","rfm=var","rat=cr"))) {
    if (pgp_code != 11)
      put_pgp_message(fpm, "$! ", pgp_code, signer_id, exec_overruled);
    fprintf(fpm,"$! Generated Command file from newgroup message:\n");
    fprintf(fpm,"$! use EXTRACT/NOHEAD then execute this file\n$!\n");
    fprintf(fpm,"$ DEFINE/USER NEWSRC NL:\n");
    fprintf(fpm,"$ NEWS/NOSCREEN\n");
    fprintf(fpm,"CREATE NEWSGROUP/NOCONFIRM \"%s\"",locname);
    if (mod) fprintf(fpm,"/MOD");
    fprintf(fpm,"\n");
    if (mod) fprintf(fpm,"\n");
    if (*descrip)
      fprintf(fpm,"Set NEWSGROUP \"%s\"/TITLE=\"%s\"\n",
                  locname,quotes(descrip));
    fprintf(fpm,"EXIT\n");
    }
  else return(NOSCRATCHFILE);

  if (fpm) {
    char subjline[256];

    append_control(fn,fpm);
    fclose(fpm);
    sprintf(err_oline,"\tControl: newgroup %.200s%s - Mail to Usenet\n", locname, mod ? " moderated" : "");
    sprintf(subjline,"NEWS: newgroup %.200s%s", locname, mod ? " moderated":"");
    call_mail(mail_scratch,subjline,USENET,err_oline);
    delete(mail_scratch);
    return(CTLOK);
    }
  if (g) return(CTLOK);
  return(NEWGROUPERROR);
}

/*
 *      control_rmgroup
 *
 *      Delete a newsgroup control message - if auto execution enabled then
 *      newsgroup is deleted, otherwise DCL is posted to USENET.
 */

static int control_rmgroup(argv,fn,switches)
  const char *argv[], *fn;
  int switches;
{
  char *c;
  const char *cc;
  char locname[GRPNAMELEN];
  int g;
  char signer_id[256];
  int pgp_code, pgp_accepted, exec_overruled;
  FILE *fpm = 0;

  if (!itm_approved) return(NONAPPROVEDCONTROL);

  cc = argv[CONTROL] + 7;
  while (isspace(*cc)) cc++;
  if (!*cc) return(NONEWSGROUPFIELD);

  strnncpy(locname,sizeof(locname), cc,-1, 0);
  for (c=locname; isgraph(*c); c++) {}
  *c = '\0';
  util_cvrt(locname,locname);

  pgp_accepted = 1; pgp_code = -1; *signer_id = '\0';
  if (pgp_verification_available) {
    pgp_code = pgp_verify(argv, fn, locname,
                          signer_id, sizeof(signer_id), 0);
    pgp_accepted = (pgp_code == 0) || (pgp_code >= 10 && pgp_code <= 19);
    }
  exec_overruled = !pgp_accepted && (switches & EXECUTE_CONTROL_RMGROUP);

  if (!*mail_scratch) sprintf(mail_scratch,"SYS$SCRATCH:NEWS_MAIL_%X.TMP",getpid());

  if (!(g = ga_exact_name(locname))) return(NORMNEWSGROUP);

  if (ga[g]->grp_flags & (NEWS_M_LOCAL | NEWS_M_MAILGROUP)) return(LOCALNEWSGROUP);

  fpm = fopen(mail_scratch,"w","rfm=var","rat=cr");
  if (pgp_accepted && (switches & EXECUTE_CONTROL_RMGROUP)) {
    if (switches & EXECUTE_CONTROL_RMGROUP_DELETE) {
      delgrp_file(g);
      if (fpm && pgp_code == 0)
        fprintf(fpm,"Signature verified and accepted, signed by \"%s\"\n",signer_id);
      if (fpm) fprintf(fpm,"Control: rmgroup %s - Deleted\n",locname);
      }
    else if (switches & EXECUTE_CONTROL_RMGROUP_LOCAL) {
      ga[g]->grp_flags |= NEWS_M_LOCAL;
      update_newsgrp(g);
      if (fpm && pgp_code == 0)
        fprintf(fpm,"Signature verified and accepted, signed by \"%s\"\n",signer_id);
      if (fpm) fprintf(fpm,"Control: rmgroup %s - Set /LOCAL\n",locname);
      }
    else {
      ga[g]->grp_life = 14;
      update_newsgrp(g);
      if (fpm && pgp_code == 0)
        fprintf(fpm,"Signature verified and accepted, signed by \"%s\"\n",signer_id);
      if (fpm) fprintf(fpm,"Control: rmgroup %s - Set /HOLD=14\n",locname);
      }
    }
  else if (fpm) {
    if (pgp_code != 11)
      put_pgp_message(fpm, "$! ", pgp_code, signer_id, exec_overruled);
    fprintf(fpm,"$ ! Generated Command file from rmgroup message\n");
    fprintf(fpm,"$ ! use EXTRACT/NOHEAD then execute this file\n$ !\n");
    fprintf(fpm,"$ DEFINE/USER NEWSRC NL:\n");
    fprintf(fpm,"$ NEWS/NOSCREEN\n");
    fprintf(fpm,"! Remove NEWSGROUP %s\n",ga[g]->grp_name);
    if (switches & EXECUTE_CONTROL_RMGROUP_DELETE) {
      fprintf(fpm,"DELETE NEWSGROUP \"%s\"/NOCONFIRM\n",ga[g]->grp_name);
      fprintf(fpm,"!Or:\n");
      fprintf(fpm,"!SET NEWSGROUP \"%s\"/LOCAL\n",ga[g]->grp_name);
      fprintf(fpm,"!SET NEWSGROUP \"%s\"/HOLD=14\n",ga[g]->grp_name);
      }
    else if (switches & EXECUTE_CONTROL_RMGROUP_LOCAL) {
      fprintf(fpm,"SET NEWSGROUP \"%s\"/LOCAL\n",ga[g]->grp_name);
      fprintf(fpm,"!Or:\n");
      fprintf(fpm,"!DELETE NEWSGROUP \"%s\"/NOCONFIRM\n",ga[g]->grp_name);
      fprintf(fpm,"!SET NEWSGROUP \"%s\"/HOLD=14\n",ga[g]->grp_name);
      }
    else {
      fprintf(fpm,"SET NEWSGROUP \"%s\"/HOLD=14\n",ga[g]->grp_name);
      fprintf(fpm,"!Or:\n");
      fprintf(fpm,"!DELETE NEWSGROUP \"%s\"/NOCONFIRM\n",ga[g]->grp_name);
      fprintf(fpm,"!SET NEWSGROUP \"%s\"/LOCAL\n",ga[g]->grp_name);
      }
    fprintf(fpm,"EXIT\n");
    }
  else return(NOSCRATCHFILE);

  if (fpm) {
    char subjline[256];

    append_control(fn,fpm);
    fclose(fpm);
    sprintf(err_oline,"\tControl: rmgroup %.200s - Mail to Usenet\n",locname);
    sprintf(subjline,"NEWS: rmgroup %.200s",locname);
    call_mail(mail_scratch,subjline,USENET,err_oline);
    delete(mail_scratch);
    }
  return(CTLOK);
}

/*
 *  control_checkgroups
 *
 *  Check the local newsgroup set against the network distribution list,
 *  removing any detected inconsistencies
 */

/*
 * Domain support code - checkgroup messages generally refer to a "family" of newsgroups
 * which have a common root (or roots) in the newsgroup name hierarchy. This code supports
 * these common domains
 */

static struct domain_list {
  char *dom_name;
  struct domain_list *dom_next;
  } *dom_head = 0;

static void clear_domain()
{
  struct domain_list *tmp;

  for (tmp=dom_head; dom_head; dom_head=tmp) {
    news_free(dom_head->dom_name);
    tmp = dom_head->dom_next;
    news_free(dom_head);
    }
}

static struct domain_list *match_domain_list(n)
  char *n;
{
  struct domain_list *tmp;

  for (tmp=dom_head; tmp; tmp=tmp->dom_next) {
    if (wild_match(tmp->dom_name,n)) break;
    }
  return(tmp);
}

static void add_domain_list(s)
  char *s;
{
  struct domain_list *tmp;

  tmp = (struct domain_list *) news_malloc(sizeof *tmp);
  tmp->dom_next = dom_head;
  dom_head = tmp;
  tmp->dom_name = (char *) news_malloc(strlen(s) + 2);
  strcpy(tmp->dom_name,s);
  strcat(tmp->dom_name,".");
}

static int domain_match(lname)	char *lname;
{
  char *cmpname, *cp;
  struct domain_list *d_ent;

  cmpname = strcpy(((char *) news_malloc(strlen(lname) + 3)),lname);
  strcat(cmpname,".*");
  do {
    if ((d_ent = match_domain_list(cmpname)) != 0) {
      if ((cp = strrchr(cmpname,'*')) != 0) *cp = '\0';
      strcpy(d_ent->dom_name,cmpname);
      break;
      }
    if ((cp = strrchr(cmpname,'.')) != 0) {
      if (*(cp + 1) == '*') {
        *cp = '\0';
        if (!(cp = strrchr(cmpname,'.'))) break;
        }
      strcpy(cp,".*");
      }
    } while (cp);
  return((int) d_ent);
}

static void add_domain(lname)
  char *lname;
{
  if (!domain_match(lname)) add_domain_list(lname);
}

static int control_checkgroups(argv,fn,switches)
  const char *argv[], *fn;
  int switches;
{
  char locname[GRPNAMELEN], xbuf[512];
  int pgp_accepted, exec_overruled;
  FILE *fpr, *fpm = 0;
  GRP_PTR *ca;
  int i, g, mail_msg = 0, indx, tpc;
  int removing_explained;
  struct glist {
        struct glist *gnext;
        char *gtitle;
        int glmod;
        char glname[GRPNAMELEN];
        } *add_head = 0, *del_head = 0, *at = 0, *dt = 0, *tmp;

  if (!itm_approved) return(NONAPPROVEDCONTROL);
  if (!(fpr = fopen(fn,"r"))) return(NOACCESSINFILE);

  if (!*mail_scratch) sprintf(mail_scratch,"SYS$SCRATCH:NEWS_MAIL_%X.TMP",getpid());
  if (!(fpm = fopen(mail_scratch,"w","rfm=var","rat=cr"))) {
    fclose(fpr);
    return(NOSCRATCHFILE);
    }

/* automatic execution of checkgroups messages disabled by NEWSADD.C */
  pgp_accepted = 1;  /* not sure - Who must sign checkgroup messages? */
                     /* To which hierarchies may it apply? */
  exec_overruled = !pgp_accepted &&
                   (switches & EXECUTE_CONTROL_CHECKGROUPS);

  if (pgp_accepted && (switches & EXECUTE_CONTROL_CHECKGROUPS))
    fprintf(fpm,"Generated Log file from checkgroup message\n");
  else {
    fprintf(fpm,"$! Generated Command file from checkgroup message\n");
    fprintf(fpm,"$! use EXTRACT/NOHEAD then execute this item\n$!\n");
    fprintf(fpm,"$ DEFINE/USER NEWSRC NL:\n");
    fprintf(fpm,"$ NEWS/NOSCREEN\n");
    }

  ca = (GRP_PTR *) news_malloc((ga_size + 1) * (sizeof *ca));
  for (i = 1 ; i <= ga_size ; ++i) ca[i] = ga[i];

  while ((fgets(xbuf,sizeof(xbuf),fpr)) && (*xbuf != '\n'));  /* skip headers */
  while (fgets(xbuf,sizeof(xbuf)-2,fpr)) {                 /* newsgroup entry */
    if (*xbuf == '\n') continue;
    /*
     * checkgroup messages sometimes have imbedded comments that start with '!'
     * or blank
     */
    if ('!' == *xbuf) continue;
    if (' ' == *xbuf) continue;
    chop_str(xbuf,'\n');
    if ((indx = strcspn(xbuf," \t")) != 0) xbuf[indx] = '\0';
    util_cvrt(locname,xbuf);                             /* extract newsgroup */
    add_domain(locname);

    if ((tpc = indx) != 0) {				/* extract title */
      tpc++;
      while (isspace(xbuf[tpc])) tpc++;
      }

    if ((g = ga_exact_name(locname)) != 0
     && !(ga[g]->grp_flags & NEWS_M_MAILGROUP)) {
      ca[g] = 0;

      if (tpc && xbuf[tpc]) {                          /* update topic string */
        if ((!ga[g]->grp_topic) || strncmp(ga[g]->grp_topic,&xbuf[tpc],124)) {
          ++mail_msg;
          if (pgp_accepted && (switches & EXECUTE_CONTROL_CHECKGROUPS)) {
            if (ga[g]->grp_topic) news_free(ga[g]->grp_topic);
            ga[g]->grp_topic = news_strdup(&xbuf[tpc],-1);
            fprintf(fpm,"Set Newsgroup %s/Title=\"%s\"\n",locname,&xbuf[tpc]);
            }
          else fprintf(fpm,"SET NEWSGROUP \"%s\"/TITLE=\"%s\"\n",
                           locname,quotes(&xbuf[tpc]));
          }
        }

      if (tpc && substrcmp(&xbuf[tpc],"(Moderated)")) {          /* moderated */
        if (!(ga[g]->grp_flags & NEWS_M_MAILMODERATE)) {    /* set mod status */
          ++mail_msg;
          if (pgp_accepted && (switches & EXECUTE_CONTROL_CHECKGROUPS)) {
            ga[g]->grp_flags |= NEWS_M_MAILMODERATE;
            fprintf(fpm,"Set Newsgroup %s/Moderated\n",locname);
            }
          else fprintf(fpm,"SET NEWSGROUP \"%s\"/MOD\n\n",locname);
          }
        }

      else if (ga[g]->grp_flags & NEWS_M_MAILMODERATE) {       /* unmoderated */
        ++mail_msg;                                       /* reset mod status */
        if (pgp_accepted && (switches & EXECUTE_CONTROL_CHECKGROUPS)) {
          ga[g]->grp_flags &= ~NEWS_M_MAILMODERATE;
          fprintf(fpm,"Set Newsgroup %s/Nomoderated\n",locname);
          }
        else fprintf(fpm,"SET NEWSGROUP \"%s\"/NOMOD\n",locname);
        }

      time((time_t *) &(ga[g]->grp_entdate));     /* update time stamp */
      update_newsgrp(g);
      }

    else if (!g) {                                        /* no local newsgroup match */
      ++mail_msg;
      if (pgp_accepted && (switches & EXECUTE_CONTROL_CHECKGROUPS)) {
        tmp = (struct glist *) news_malloc(sizeof *tmp);
        if (at) at->gnext = tmp; else add_head = tmp;
        at = tmp;
        at->gtitle = 0;
        at->gnext = 0;
        strcpy(at->glname,locname);
        at->glmod = 0;
        }
      else fprintf(fpm,"CREATE NEWSGROUP/NOCONFIRM \"%s\"\n",locname);

      if (tpc && xbuf[tpc]) {                          /* update topic string */
        if (pgp_accepted && (switches & EXECUTE_CONTROL_CHECKGROUPS))
          at->gtitle = news_strdup(&xbuf[tpc],-1);
        else fprintf(fpm,"SET NEWSGROUP \"%s\"/TITLE=\"%s\"\n",
                         locname,quotes(&xbuf[tpc]));
        }

      if (tpc && substrcmp(&xbuf[tpc],"(Moderated)")) {          /* moderated */
        if (pgp_accepted && (switches & EXECUTE_CONTROL_CHECKGROUPS))
          at->glmod = 1;
        else fprintf(fpm,"SET NEWSGROUP \"%s\"/MOD\n\n",locname);
        }
      }
    } /*----- END of pass through input file */
  fclose(fpr);

  removing_explained = 0;
  for (g=1; g <= ga_size; g++) {
    if (ca[g] && domain_match(ga[g]->grp_name) &&
        !(ga[g]->grp_flags & (NEWS_M_LOCAL | NEWS_M_MAILGROUP))) {
      ++mail_msg;
      if (pgp_accepted && (switches & EXECUTE_CONTROL_CHECKGROUPS)) {
        tmp = (struct glist *) news_malloc(sizeof *tmp);
        if (dt) dt->gnext = tmp; else del_head = tmp;
        dt = tmp;
        dt->gnext = 0;
        strcpy(dt->glname,ga[g]->grp_name);
        }
      else {
        if (!removing_explained) {
          if (switches & EXECUTE_CONTROL_RMGROUP_DELETE)
            fprintf(fpm,"! Removing newsgroups immediately (/RMGROUP=DELETE)\n");
          else if (switches & EXECUTE_CONTROL_RMGROUP_LOCAL)
            fprintf(fpm,"! Setting removed newsgroups to /LOCAL (/RMGROUP=LOCAL)\n");
          else
            fprintf(fpm,"! Setting removed newsgroups to /HOLD=14 (/RMGROUP=RETAIN)\n");
          removing_explained = 1;
          }
        if (switches & EXECUTE_CONTROL_RMGROUP_DELETE)
          fprintf(fpm,"DELETE NEWSGROUP \"%s\"/NOCONFIRM\n",ga[g]->grp_name);
        else if (switches & EXECUTE_CONTROL_RMGROUP_LOCAL)
          fprintf(fpm,"SET NEWSGROUP \"%s\"/LOCAL\n",ga[g]->grp_name);
        else
          fprintf(fpm,"SET NEWSGROUP \"%s\"/HOLD=14\n",ga[g]->grp_name);
        }
      }
    }
  news_free(ca);

  at = add_head;
  for (; add_head; add_head=add_head->gnext) {
    int sc = auto_cre_grp;

    auto_cre_grp = 1;
    tmp = add_head ;
    do_newg(tmp->glname,0,1);
    auto_cre_grp = sc;
    if ((g = ga_exact_name(tmp->glname)) != 0) {
      ++mail_msg;
      fprintf(fpm,"Created new Network Newsgroup: %s",tmp->glname);
      if (tmp->glmod) {
        fprintf(fpm,"/Moderated");
        ga[g]->grp_flags |= NEWS_M_MAILMODERATE;
        }
      if (tmp->gtitle) {
        if (ga[g]->grp_topic) news_free(ga[g]->grp_topic);
        ga[g]->grp_topic = news_strdup(tmp->gtitle,-1);
        fprintf(fpm,"/Title=\"%s\"",tmp->gtitle);
        }
      update_newsgrp(g);
      tmp->glmod = 0;
      fprintf(fpm,"\n");
      }
    else tmp->glmod = 1;
    }

  while (del_head) {
    tmp = del_head;
    ++mail_msg;
    if ((g = ga_exact_name(tmp->glname)) != 0) {
      if (switches & EXECUTE_CONTROL_RMGROUP_DELETE) {
        fprintf(fpm,"Delete Newsgroup %s\n",tmp->glname);
        delgrp_file(g);
        }
      else if (switches & EXECUTE_CONTROL_RMGROUP_LOCAL) {
        fprintf(fpm,"Set Newsgroup %s/LOCAL\n",tmp->glname);
        ga[g]->grp_flags |= NEWS_M_LOCAL;
        update_newsgrp(g);
        }
      else {
        fprintf(fpm,"Set Newsgroup %s/HOLD=14\n",tmp->glname);
        ga[g]->grp_life = 14;
        update_newsgrp(g);
        }
      }
    else fprintf(fpm,"Cannot locate Newsgroup %s (Delete required)\n",tmp->glname);
    del_head = del_head->gnext;
    news_free(tmp);
    }

  add_head = at;
  while (add_head) {
    tmp = add_head ;
    if (tmp->glmod) {
      ++mail_msg;
      fprintf(fpm,"\n!** NO Create of Newsgroup: %s - SYS filter reject\n",tmp->glname);
      }
    add_head = add_head->gnext;
    if (tmp->gtitle) news_free(tmp->gtitle);
    news_free(tmp);
    }

  if (!(pgp_accepted && (switches & EXECUTE_CONTROL_CHECKGROUPS)))
    fprintf(fpm,"EXIT\n");
  fclose(fpm);
  if (mail_msg) {
    strcpy(err_oline,"\tControl: checkgroups - Mail to Usenet\n");
    call_mail(mail_scratch,"Control: checkgroups",USENET,err_oline);
    }
  delete_file_versions(mail_scratch);
  clear_domain();
  if (mail_msg) return(CTLOK);
  return(CTLOKNOCHG);
}

/*
 *  control_cancel
 *
 *  Delete a newsitem - message id added to history file.
 */

static int control_cancel(argv,fn,switches)
  const char *argv[], *fn;
  int switches;
{
  char *id, *ide, control[132], net_sender[132];
  const char *mail_sender;

  mail_sender = argv[SENDER];
  if (!mail_sender || !*mail_sender) mail_sender = argv[FROM];
  if (!mail_sender || !*mail_sender) return(NOSENDERADDRESS);

  if (!mail_add_expiry) mail_add_expiry = 2;
  if (mail_add_expiry > 7) mail_add_expiry = 7;
  strnncpy(control,sizeof(control), argv[CONTROL],-1, 0);
  if ((id = strchr(control,'<')) && (ide = strchr(control,'>'))) {
    *(ide + 1) = '\0';
    if ((ide = strchr(mail_sender,'<')) != 0) {
      strnncpy(net_sender,sizeof(net_sender), ++ide,-1, 0);
      chop_str(net_sender,'>');
      chop_str(net_sender,'\n');
      }
    else {
      char *cp = net_sender;
      const char *cp1 = mail_sender;

      while (isspace(*cp1)) cp1++;
      while ((*cp1) && (!isspace(*cp1))) *cp++ = *cp1++;
      *cp = '\0';
      chop_str(net_sender,'(');
      }
    if (del_id(id,net_sender)) {
#if CANCELMAIL
      strcpy(err_oline,"Control: cancel - item located and deleted");
      call_mail(fn,"Control: cancel (performed)",USENET,err_oline);
#endif
      }
    else {
      hist_add(id);
#if CANCELMAIL
      strcpy(err_oline,"Control: cancel - item NOT located - added to History file");
      call_mail(fn,"Control: cancel (NOT successful)",USENET,err_oline);
#endif

      }
    return(CTLOK);
    }
  return(BADFORMAT);
}

/*
 *  c_ihave_id
 *
 *  If a message identifier is NOT located in the local id file, and the
 *  id is NOT in the history file, then write the id to the request file.
 */

static int c_ihave_id(id,id_l,f)
  const char *id;
  int id_l;
  FILE *f;
{
  int status;
  char l_id[IDLEN + 4];
  int l_id_l;

  id_l = (id_l >= 0) ? id_l : strlen(id);

  l_id_l = min(IDLEN-1, id_l);
  memcpy(l_id,id,l_id_l);
  memset(&l_id[l_id_l],0,sizeof(l_id)-l_id_l);

  itmrab.rab$l_kbf = l_id;
  itmrab.rab$b_ksz = IDLEN + 4;
  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_nornf(&itmrab) || strcmp(l_id,newsitm.itm_id))
      && !hist_check(id))
    return(fprintf(f,"%s\n",id),1);
  return(0);
}

/*
 *  control_ihave
 *
 *  Check the local database of message-ids against the incoming ihave
 *  set. Any ids not held locally are added to a request file for a
 *  sendme response.
 */

static int control_ihave(argv,fn,switches)
  const char *argv[], *fn;
  int switches;
{
  char send_scr_file[256], s_str[132], newsgroups[132], subject[132], xbuf[512];
  int s_str_l, newsgroups_l;
  char *cp;
  const char *il;
  FILE *fpw = 0, *fpr;
  int line_count = 0, cre_grp[2];

  il = argv[CONTROL] + 5;;
  strnncpy(s_str,sizeof(s_str), "to.",3, &s_str_l);
  strnncat(s_str,sizeof(s_str),s_str_l, news_pathname,-1, &s_str_l);
  strnncpy(newsgroups,sizeof(newsgroups), argv[NEWSGROUPS],-1, &newsgroups_l);
  lower_case(newsgroups);
  strnncpy(xbuf,sizeof(xbuf), newsgroups,newsgroups_l, 0);
  if ((cp = strrchr(xbuf, '.')) && !strcmp(".ctl",cp)) *cp = '\0';
  if (strcmp(xbuf,s_str)) return(NOTTOME);

  if ((cp = strrchr(il,'>')) != 0) il = ++cp;
  while (isspace(*il)) il++;
  if (!*il) return(NOREMOTESYS);

  strnncpy(s_str,sizeof(s_str), "to.",3, &s_str_l);
  strnncat(s_str,sizeof(s_str),s_str_l, il,-1, &s_str_l);
  cp = s_str;
  while (isgraph(*cp)) cp++;
  *cp++ = '\0';

  if (!s_str[3]) return(NOREMOTESYS);

  lower_case(s_str);
  sprintf(send_scr_file,"SYS$SCRATCH:SENDME_MAIL_%X.TMP",getpid());
  fpw = fopen(send_scr_file,"w","mbc=32");

  il = argv[CONTROL] + 5;
  while ((cp = strchr(il,'<')) != 0) {
    il = cp;
    if ((cp = strchr(il,'>')) == 0) ++il;
    else {
      line_count += c_ihave_id(il,cp-il+1,fpw);
      il = ++cp;
      }
    }

  if ((fpr = fopen(fn,"r","mbc=32","mbf=2")) != 0) {
    while ((fgets(xbuf,sizeof(xbuf)-2,fpr)) && (*xbuf != '\n'));
    while (fgets(xbuf,sizeof(xbuf)-2,fpr)) {
      if (*xbuf == '\n') continue;
      if ((cp = strchr(xbuf,'<')) != 0 && (il = strchr(cp,'>')) != 0)
        line_count += c_ihave_id(cp,il-cp+1,fpw);
      }
    }
  fclose(fpw);

  if (line_count) {
    char *p,
         post_dist = '\0',
         post_path[132],
         loc_id[IDLEN];
     int post_path_l;
     int cur_time;
     struct tm *stm;

     fpr = fopen(send_scr_file,"r","mbc=32","mbf=2");
     fpw = fopen(send_scr_file,"w","mbc=32");
     strnncpy(post_path,sizeof(post_path), " ",1, &post_path_l);
     strnncat(post_path,sizeof(post_path),post_path_l,
              news_pathname,-1, &post_path_l);
     strnncat(post_path,sizeof(post_path),post_path_l, "!",1, &post_path_l);
     strnncat(post_path,sizeof(post_path),post_path_l,
              usr_username,-1, &post_path_l);
     fprintf(fpw,"Path: %s\n",post_path);
     fprintf(fpw,"From: %s@%s\n",usr_username,Node_address);
     fprintf(fpw,"Newsgroups: %s\n",s_str);
     sprintf(subject,"sendme %.120s",news_pathname);
     fprintf(fpw,"Control: %s\n",subject);
     fprintf(fpw,"Subject: %s\n",subject);
     time((time_t *) &cur_time);
     p = ctime((time_t *) &cur_time);
     p += 4;
     stm = localtime((time_t *) &cur_time);
     strnncpy(loc_id,sizeof(loc_id), gen_id(),-1, 0);
     fprintf(fpw,"Message-ID: %s\n",loc_id);
     fprintf(fpw,"Date: %d %.3s %04d %02d:%02d:%02d %s\n",
             stm->tm_mday, p, stm->tm_year + 1900,
             stm->tm_hour, stm->tm_min, stm->tm_sec, news_timezone);
     fprintf(fpw,"Organization: %s\n",Organisation_name);
     fprintf(fpw,"Lines: %d\n",line_count);
     fprintf(fpw,"\n");
     while (fgets(xbuf,sizeof(xbuf)-2,fpr)) fputs(xbuf,fpw);
     fclose(fpr);
     fclose(fpw);
     sys_remote_send(post_path,s_str,&post_dist,send_scr_file,loc_id,0);
     if (sys_local_accept(s_str,&post_dist)) {
       do_new_group(s_str,0,(unsigned int *) cre_grp);
       if (cre_grp[0])
         do_new_item((unsigned int *)cre_grp, loc_id, subject, argv[FROM],
                     send_scr_file, 1, 0, line_count, 0);
       }
     }
  delete_file_versions(send_scr_file);
  return(CTLOK);
}

/*
 *  control_sendme
 *
 *  Check the requested message-id list against the local database
 *  and forward all requested messages that are held locally.
 */

static int control_sendme(argv,fn,switches)
  const char *argv[], *fn;
  int switches;
{
  int status;
  char s_str[132], l_id[IDLEN + 4], post_dist = '\0', post_path[132],
       fname[256], xbuf[512];
  int s_str_l, l_id_l, post_path_l;
  const char *il;
  char *cp;
  int gn;
  FILE *fpr = NULL, *fpi = NULL;

  il = argv[CONTROL] + 6;
  strnncpy(post_path,sizeof(post_path), " ",1, &post_path_l);
  strnncat(post_path,sizeof(post_path),post_path_l,
           news_pathname,-1, &post_path_l);
  strnncat(post_path,sizeof(post_path),post_path_l, "!",1, &post_path_l);
  strnncat(post_path,sizeof(post_path),post_path_l,
           usr_username,-1, &post_path_l);
  itmrab.rab$l_kbf = l_id;
  itmrab.rab$b_ksz = IDLEN + 4;
  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;

  strnncpy(s_str,sizeof(s_str), "to.",3, &s_str_l);
  strnncat(s_str,sizeof(s_str),s_str_l, news_pathname,-1, &s_str_l);
  strnncpy(xbuf,sizeof(xbuf), argv[NEWSGROUPS],-1, 0);
  lower_case(xbuf);
  if ((cp = strrchr(xbuf,'.')) && !strcmp(".ctl",cp)) *cp = '\0';
  if (strcmp(xbuf,s_str)) return(NOTTOME);

  if ((cp = strrchr(il,'>')) != 0) il = ++cp;
  while (isspace(*il)) il++;
  if (!*il) return(NOREMOTESYS);

  strnncpy(s_str,sizeof(s_str), "to.",3, &s_str_l);
  strnncat(s_str,sizeof(s_str),s_str_l, il,-1, &s_str_l);
  cp = s_str;
  while (isgraph(*cp)) cp++;
  *cp++ = '\0';
  if (!s_str[3]) return(NOREMOTESYS);

  lower_case(s_str);
  il = argv[CONTROL] + 6;
  while ((cp = strchr(il,'<')) != 0) {
    il = cp;
    if ((cp = strchr(il,'>')) == 0) ++il;
    else {
      l_id_l = min(IDLEN-1, cp-il+1);
      memcpy(l_id,cp,l_id_l);
      memset(&l_id[l_id_l],0,sizeof(l_id)-l_id_l);
      ++cp;
      if (   sys_get_nornf(&itmrab)
	  && !strcmp(l_id,newsitm.itm_id)
	  && (gn = ga_locate(newsitm.itm_grp))
	  && (fpr = do_oitem(&newsitm,"r",ga[gn]->grp_name,0,
	                     (ga[gn]->grp_flags & NEWS_M_RESTRICT_SET),0))) {
        fgetname(fpr,fname);
        fclose(fpr);
        sys_remote_send(post_path,s_str,&post_dist,fname,l_id,0);
        }
      il = ++cp;
      }
    }
  if ((fpi = fopen(fn,"r")) != 0) {
    while ((fgets(xbuf,sizeof(xbuf)-2,fpi)) && (*xbuf != '\n'));
    while (fgets(xbuf,sizeof(xbuf)-2,fpi)) {
      if (*xbuf == '\n') continue;
      if ((cp = strchr(xbuf,'<')) != 0 && (il = strchr(cp,'>')) != 0) {
        l_id_l = min(IDLEN-1, il-cp+1);
        memcpy(l_id,cp,l_id_l);
        memset(&l_id[l_id_l],0,sizeof(l_id)-l_id_l);
        if (   sys_get_nornf(&itmrab)
	    && !strcmp(l_id,newsitm.itm_id)
	    && (gn = ga_locate(newsitm.itm_grp))
	    && (fpr = do_oitem(&newsitm,"r",ga[gn]->grp_name,0,
	                       (ga[gn]->grp_flags & NEWS_M_RESTRICT_SET),0))) {
          fgetname(fpr,fname);
          fclose(fpr);
          sys_remote_send(post_path,s_str,&post_dist,itm_fname,l_id,0);
          }
        }
      }
    if (fpi) fclose(fpi);
    }
  return(CTLOK);
}

/*
 *  parse_control_item
 *
 *  Parse a Control: header item and call appropriate execution routine.
 */

int parse_control_item(argv,fn,switches,newsgroup)
  const char *argv[]; /* pointers to known headers */
  const char *fn;     /* file name of a temporary file containing the article */
  int switches;
  char *newsgroup;
{
  char *ng,*hier;
  char loc_cmd[132];
  int loc_cmd_l;

  strnncpy(loc_cmd,sizeof(loc_cmd), argv[CONTROL],-1, &loc_cmd_l);
  lower_case(loc_cmd);
/*
 *  make up a group name for the article, ie control.type.hier
 */
  ng = news_malloc(8+loc_cmd_l+strlen(argv[NEWSGROUPS])+1);
  strcpy(ng,"control.");
  strcat(ng,loc_cmd);
  chop_str(ng,' ');
  strcat(ng,".");
  hier = news_strdup(argv[NEWSGROUPS],-1);
  chop_str(hier,','); /* only use first newgroup on the line */
  chop_str(hier,'.'); /* only use the first component of the newsgroup */
  strcat(ng,hier);
  if ( ga_exact_name(ng) )
    strcpy(newsgroup,ng);
  else {
    strcpy(ng,"control.");
    strcat(ng,loc_cmd);
    chop_str(ng,' ');
    if ( ga_exact_name(ng) )
      strcpy(newsgroup,ng);
  }
  news_free(ng);
  news_free(hier);
/*
 * now newsgroup has changed to control.type.hier if that exists, 
 * or control.type if that exists, or no change if none exist.
 */
  if (!strncmp(loc_cmd,"version",7))
    return(control_version(argv,fn,switches));
  if (!strncmp(loc_cmd,"senduuname",10))
    return(control_senduuname(argv,fn,switches));
  if (!strncmp(loc_cmd,"sendsys",7))
    return(control_sendsys(argv,fn,switches));
  if (!strncmp(loc_cmd,"newgroup",8))
    return(control_newgroup(argv,fn,switches));
  if (!strncmp(loc_cmd,"rmgroup",7))
    return(control_rmgroup(argv,fn,switches));
  if (!strncmp(loc_cmd,"checkgroups",11))
    return(control_checkgroups(argv,fn,switches));
  if (!strncmp(loc_cmd,"cancel",6))
    return(control_cancel(argv,fn,switches));
  if (!strncmp(loc_cmd,"ihave",5))
    return(control_ihave(argv,fn,switches));
  if (!strncmp(loc_cmd,"sendme",6))
    return(control_sendme(argv,fn,switches));
  strcpy(err_oline,"Control: No handler defined for "); err_oline_cat(loc_cmd);
#if UNKCONTROLMAIL
  call_mail(fn,"Unrecognized NEWS Control: message",USENET,err_oline);
#endif
  return(UNKCTL);
}
#endif
