/*
 * W-NEWS       A simple NEWS processing package for MINIX.
 *              This program implements the functions of several of the
 *              standard "control messages" that can be sent to a NEWS-
 *              running system.  Since we can save much effort by doing
 *              certain things only once, we have only one program with
 *              links to the names of the possible control messages.
 *
 * Usage:       newgroup newsgroup_name [ moderated ]
 *              rmgroup newsgroup_name
 *              version
 *              cancel <message_id>
 *              sendsys
 *              senduuname
 *              checkgroups
 *
 * Version:     @(#)control.c	4.51	07-May-1995
 *
 * Authors:	Fred N. van Kempen, <waltje@Infomagic.COM>
 *		Miquel van Smoorenburg, <miquels@drinkel.ow.org>
 *		Copyright 1988-1993 MicroWalt Corporation
 *		Copyright 1994,1995 Cistron Software C.V.
 *
 *		This program is free software; you can redistribute it and/or
 *		modify it under the terms of the GNU General Public License
 *		as published by the Free Software Foundation; either version
 *		2 of the License, or (at your option) any later version.
 */
#include "wnews.h"
#if HAVE_DBM
#   if HAVE_SDBM
#	include <sdbm.h>
#   else
#	include <ndbm.h>
#   endif
#endif


char *Release = VERSION;
char *Version = "@(#) control 4.51 (07-May-1995)";


char *program;                          /* what is our name?            */
ACT *active;                            /* in-core "active" file        */
struct {                                /* this is the in-core header   */
  char  *path,  *path_t;
  char  *from,  *from_t;
  char  *send,  *send_t;
  char  *repl,  *repl_t;
  char  *ngrp,  *ngrp_t;
  char  *dist,  *dist_t;
  char  *ctrl,  *ctrl_t;
  char  *subj,  *subj_t;
  char  *iden,  *iden_t;
  char  *appr,  *appr_t;
  char  *null;
} header = {
  "Path:",              (char *)NULL,
  "From:",              (char *)NULL,
  "Sender:",            (char *)NULL,
  "Reply-To:",          (char *)NULL,
  "Newsgroups:",        (char *)NULL,
  "Distribution:",      "local",
  "Control:",           (char *)NULL,
  "Subject:",           (char *)NULL,
  "Message-ID:",        (char *)NULL,
  "Approved:",          (char *)NULL,
  (char *)NULL
};


_PRO( int ctrl_newgroup, (char *name, int mod)				);
_PRO( int ctrl_rmgroup, (char *name)					);
_PRO( int ctrl_cancel, (char *msgid)					);
_PRO( int ctrl_checkgroups, (void)					);
_PRO( int ctrl_version, (void)						);
_PRO( int ctrl_ssys, (void)						);
_PRO( int ctrl_suuname, (void)						);
_PRO( int hdr_upd, (char *text)						);


/* Perform the "newgroup groupname [moderated]" command. */
int
ctrl_newgroup(name, mod)
char *name;
int mod;
{
  char buff[512];
  char temp[128];
  int fynf = 0;
  register char *sp;
  register FILE *fp;

  /* Read the "active" file into core. */
  act_lock();
  active = act_rd();
  if (active == (ACT *)NULL) {
	logmsg(LOGERR, "cannot read active file\n");
	act_unlock();
	exit(1);
  }

  /* Does it already exist? */
  if (testgroup(name) == OK) {
	logmsg(LOGERR, "newsgroup %s already exists\n", name);
	act_unlock();
	return(ERROR);
  }

  /* No, update the "active" file. */
  sprintf(temp, "%s/%s", CONFDIR, ACTIVE);
  if ((fp = fopen(temp, "a")) == (FILE *)NULL) {
	logmsg(LOGERR, "cannot append(%s)\n", temp);
	act_unlock();
	return(ERROR);
  }
#if HAVE_LONGACT
  if (have_longact)
	fprintf(fp, "%s 0000000000 0000000000 %c\n", name, (mod == 1) ? 'm' : 'y');
  else
#endif
	fprintf(fp, "%s 00000 00000 %c\n", name, (mod == 1) ? 'm' : 'y');
  (void) fclose(fp);
  act_unlock();

  /* Read the news group description from the input article. */
  sp = (char *)NULL;
  temp[0] = 0;
  while(fgets(buff, 128, stdin) != (char *)NULL) {
	if (buff[0] == 0 || buff[0] == '\n') continue;

	/* Save first line. */
	if (temp[0] == 0) strcpy(temp, buff);

	/* See if this is the description. */
	if (strncmp(name, buff, strlen(name)) == 0) {
		if (sp == NULL || fynf) sp = buff;
		if (fynf) break;
	}
	/* Is this a special line? */
	if (strncmp(buff, "For your newsgroups", 19) == 0)
		fynf = 1;
	else
		fynf = 0;
  }
  /* Found a description? If not, just use first line. */
  if (sp == (char *)NULL) {
	strcpy(buff, temp);
	sp = buff;
  } else {
	/* Skip newsgroup name. */
	sp += strlen(name);
	if (*sp == ':') sp++;
	while(*sp == ' ' || *sp == '\t') sp++;
  }

  /* Update the "newsgroups" file. */
  sprintf(temp, "%s/%s", CONFDIR, NWGROUPS);
  if ((fp = fopen(temp, "a")) == (FILE *)NULL) {
	/* Complain, but do not fail. */
	logmsg(LOGERR, "cannot append(%s)\n", temp);
  } else {
	fprintf(fp, "%s\t\t%s%s", name, sp, sp[0] ? "" : "\n");
	(void) fclose(fp);
  }
  logmsg(LOGINFO, "newsgroup `%s' created\n", name);

  /* Finally, create the directory(ies) for the new group. */
  return(wmkspooldir(name, 1));
}


/* Perform the "rmgroup groupname" command. */
int
ctrl_rmgroup(name)
char *name;
{
  char temp[128], more[128];
  char buff[512], *sp;
  register FILE *fp, *fp2;
  ACT *a;

  /* Read the active file into core. */
  act_lock();
  active = act_rd();
  if (active == (ACT *)NULL) {
	logmsg(LOGERR, "cannot read active file\n");
	act_unlock();
	exit(1);
  }

  /* Does it exist at all? */
  if (testgroup(name) != OK) {
	logmsg(LOGERR, "newsgroup %s does not exist\n", name);
	act_unlock();
	return(ERROR);
  }

  /* Yes, create a new active file - overwrite old one. */
  sprintf(temp, "%s/%s", CONFDIR, ACTIVE);
  if ((fp = fopen(temp, "w")) == (FILE *)NULL) {
	logmsg(LOGERR, "cannot open(%s)\n", temp);
	act_unlock();
	return(ERROR);
  }

  /* Write it. */
  for(a = active; a; a = a->next) {
	if (strcmp(a->group, name) != 0)
#if HAVE_LONGACT
	    if (have_longact)
		fprintf(fp, "%s %010lu %010lu %c\n",
			a->group, a->last, a->first, a->type);
	    else
#endif
		fprintf(fp, "%s %05lu %05lu %c\n",
			a->group, a->last, a->first, a->type);
  }
  fclose(fp);
  act_unlock();

  logmsg(LOGINFO, "newsgroup `%s' deleted\n", name);

  /* Update the "newsgroups" file. */
  sprintf(temp, "%s/%s", CONFDIR, NWGROUPS);
  sprintf(more, "%s2", temp);
  if ((fp = fopen(temp, "r")) == (FILE *)NULL) {
	logmsg(LOGERR, "cannot open(%s)\n", temp);
	return(ERROR);
  }
  if ((fp2 = fopen(more, "w")) == (FILE *)NULL) {
	logmsg(LOGERR, "cannot create(%s)\n", more);
	(void) fclose(fp);
	return(ERROR);
  }
  while (fgets(buff, 512, fp) != (char *)NULL) {
	if (buff[0] == '0' || buff[0] == '\n') continue;
	if ((sp = strchr(buff, '\t')) != (char *)NULL) *sp++ = '\0';
	if (strcmp(buff, name)) fprintf(fp2, "%s\t%s", buff, sp);
  }
  (void) fclose(fp);
  (void) fclose(fp2);
  if (unlink(temp) < 0) {
	logmsg(LOGERR, "cannot unlink(%s)\n", temp);
	(void) unlink(more);
	return(ERROR);
  }
  if (rename(more, temp) < 0) {
	logmsg(LOGERR, "cannot rename(%s,%s)\n", more, temp);
	(void) unlink(more);
	return(ERROR);
  }
  (void) unlink(more);
  (void) wchmod(temp, 0644);

  /* Finally, remove the directory(ies) for this group. */
  sprintf(temp, "%s/%s", SPOOLDIR, gtof(name));
  if (chdir(temp) == 0) {
	sprintf(buff, "%s -f .??* * 2>/dev/null", SYS_RM);
	wsystem(buff);
	chdir("..");
	(void)wrmdir(temp);
  }
  return(OK);
}


/* Perform the "cancel <msgid>" command. */
int ctrl_cancel(msgid)
char *msgid;
{
#if HAVE_HIST
  char buff[512], temp[128];
  char more[128], fname[128];
  register FILE *fp, *fp2;
  register char *sp, *bp;
#if HAVE_DBM
/* This is beautifully simple :-) ET */
  DBM *db;
  datum key;
  int st;

  sprintf(fname, "%s/%s", VARDIR, HISTFILE);
  db = dbm_open(fname, O_RDWR, 0);
  if (db == (DBM *)NULL) {
    logmsg (LOGERR, "cannot open dbase %s\n", fname);
    return (ERROR);
  }
  key.dptr = strdup(msgid);
  key.dsize = strlen(msgid) + 1;
  st = dbm_delete(db, key);
  free(key.dptr);
  dbm_close(db);

  /*
   * If msgid is not in history dbase, it is also not
   * in the normal history file. So we can return here.
   */
  if (st != 0) {
	logmsg(LOGINFO, "Message %s not located - cancel failed.\n", msgid);
	return(ERROR);
  }

  logmsg(LOGINFO, "Message %s canceled\n", msgid);
	
#else
  /* fname is not yet filled, otherwise it is !   ET */
  sprintf(fname, "%s/%s", VARDIR, HISTFILE);

#endif

  /*
   * One drawback, we also need to adjust the original
   * history file.  Too bad, no speedup. ET
   */

  /* Step through the "history" file, deleting articles. */
  sprintf(more, "%s.tmp", fname);
  if ((fp = fopen(fname, "r")) == (FILE *)NULL) {
	logmsg(LOGERR, "cannot open(%s)\n", fname);
	return(ERROR);
  }
  if ((fp2 = fopen(more, "w")) == (FILE *)NULL) {
	logmsg(LOGERR, "cannot create(%s)\n", more);
	(void) fclose(fp);
	return(ERROR);
  }

  /* Now step through the file, looking for <msgid>. */
  while (fgets(buff, 512, fp) != (char *)NULL) {
	if (buff[0] == '\0' || buff[0] == '\n') continue;
	if ((sp = strchr(buff, '\t')) != (char *)NULL) *sp++ = '\0';
	if (!strcmp(buff, msgid)) {
		if ((bp = strchr(sp, '\n')) != (char *)NULL) *bp = '\0';
		if ((bp = strrchr(sp, '\t')) != (char *)NULL) {
			sp = ++bp;
			while (*sp != '\0') {
				if (*sp == '.') *sp = '/';
				sp++;
			}
			sprintf(temp, "%s/%s", SPOOLDIR, bp);
			if (unlink(temp) < 0) logmsg(LOGERR,
				"cannot unlink(%s)\n", temp);
		} else logmsg(LOGERR|LOGSYS, "%s format error!\n", fname);
	} else fprintf(fp2, "%s\t%s", buff, sp);
  }
  (void) fclose(fp);
  (void) fclose(fp2);

  if (unlink(fname) < 0) {
	logmsg(LOGERR, "cannot unlink(%s)\n", fname);
	(void) unlink(more);
	return(ERROR);
  }
  if (rename(more, fname) < 0) {
	logmsg(LOGERR, "cannot rename(%s,%s)\n", more, fname);
	(void) unlink(more);
	return(ERROR);
  }
  (void) wunlink(more);
  (void) wchmod(fname, 0660);
#endif
  return(OK);
}



/*
 * Perform the "checkgroups" command.
 * This is defined as "check the list of newsgroup names with
 * the list of newsgroups found in our "newsgroups" file, and
 * update the latter if needed." -FvK
 */
int
ctrl_checkgroups()
{
  logmsg(LOGERR|LOGSYS, "this has not yet been implemented!\n");
  return(ERROR);
}


/*
 * Perform the "version" command.
 * This is defined as "Mail back a message telling the sender
 * of the message which NEWS processing software is in use on
 * the sending site." -FvK
 */
int
ctrl_version()
{
  logmsg(LOGERR|LOGSYS, "this has not yet been implemented!\n");
  return(ERROR);
}


/*
 * Perform the "sendsys" command.
 * This is defined as "Mail back a message containing the
 * contents of the "sys" (or "newsfeeds") file." -FvK
 */
int
ctrl_ssys()
{
  logmsg(LOGERR|LOGSYS, "this has not yet been implemented!\n");
  return(ERROR);
}


/*
 * Perform the "senduuname" command.
 * This is defined as "Mail back a message containing the
 * output of the "uuname" command." -FvK
 */
int
ctrl_suuname()
{
  logmsg(LOGERR|LOGSYS, "this has not yet been implemented!\n");
  return(ERROR);
}


/* Update our in-core header information. */
int
hdr_upd(text)
char *text;
{
  register char *sp;
  char **xp, **yp;

  /* Is this the end-of-header marker? */
  if (*text == '\n') return(1);

  /* No. Remove the trailing newline. */
  if ((sp = strchr(text, '\n')) != (char *)NULL) *sp = '\0';

  /* First, split up the "Field: " and "value" parts. */
  if ((sp = strchr(text, ':')) == (char *)NULL) {
	logmsg(LOGERR, "bad header field: \"%s\"\n", text);
	return(-1);  
  } else {
	*++sp = '\0';
	sp++;
	while (*sp == ' ' || *sp == '\t') sp++;
  }

  /* Then, add this header field to the in-core header. */
  xp = (char **) &header.path;
  yp = (char **)NULL;
  while (*xp != (char *)NULL) {
	if (!strncmp(text, *xp, strlen(text))) {
		yp = xp; yp++;
		break;
	}
	xp++;
	xp++;
  }

  /* Ignore bad header fields. */
  if (yp == (char **)NULL) return(0);

  if ((*yp = (char *)malloc(strlen(sp) + 1)) == (char *)NULL) {
	logmsg(LOGERR, "out of memory\n");
	return(-1);
  }
  strcpy(*yp, sp);
  return(0);            /* stay in "scan header" state */
}


int
main(argc, argv)
int argc;
char **argv;
{
  char buff[1024];
  int c, st = -1;
  extern int getopt(), optind;
  extern char *optarg;
  int args;

  /* Try to see if we were called with a specific name */
  program = strrchr(*argv, '/');
  if (program == (char *)NULL) program = *argv;
    else program++;

  /* Get the options. */
  while((c = getopt(argc, argv, ":dc:")) != EOF) {
	switch(c) {
		case 'd':
			debug++;
			break;
		case 'c':
			program = optarg;
			break;
		default:
			fprintf(stderr,
				"Usage: control [-d] [-c program] [args]\n");
			exit(1);
			break;
	}
  }

  /* How many arguments left? */
  args = argc - optind;

  /* Read the header of the incoming article. */
  while (1) {
	if (fgets(buff, 1024, stdin) == (char *)NULL) break;
	if (hdr_upd(buff) != 0) break;
  }

  (void) umask(022);                    /* drwxr-xr-x */

  /* Carry out the correct control command. */
  if (! strcmp(program, "newgroup")) {
	switch(args) {
		case 1:
			st = ctrl_newgroup(argv[optind], 0);
			break;

		case 2:
			if (argv[optind+1][0] == 'm') {
				st = ctrl_newgroup(argv[optind], 1);
				break;
			}
			/*FALLTHROUGH*/

		default:
			fprintf(stderr,
			    "Usage: newgroup newsgroup_name [ moderated ]\n");
			st = -1;
			break;
	}
  } else if (!strcmp(program, "rmgroup")) {
	if (args != 1) fprintf(stderr, "Usage: rmgroup newsgroup_name\n");
	  else st = ctrl_rmgroup(argv[optind]);
  } else if (!strcmp(program, "cancel")) {
	if (args != 1) fprintf(stderr, "Usage: cancel <message_id>\n");
	else st = ctrl_cancel(argv[optind]);
  } else if (!strcmp(program, "sendsys")) {
	if (args != 0) fprintf(stderr, "Usage: sendsys\n");
	  else st = ctrl_ssys();
  } else if (!strcmp(program, "senduuname")) {
	if (args != 0) fprintf(stderr, "Usage: senduuname\n");
	  else st = ctrl_suuname();
  } else if (!strcmp(program, "version")) {
	if (args != 0) fprintf(stderr, "Usage: version\n");
	  else st = ctrl_version();
  } else if (!strcmp(program, "checkgroups")) {
	if (args != 0) fprintf(stderr, "Usage: checkgroups\n");
	  else st = ctrl_checkgroups();
  } else {
	fprintf(stderr, "Usage: newgroup newsgroup_name [ moderated ]\n");
	fprintf(stderr, "       rmgroup newsgroup_name\n");
	fprintf(stderr, "       version\n");
	fprintf(stderr, "       cancel <message_id>\n");
	fprintf(stderr, "       sendsys\n");
	fprintf(stderr, "       senduuname\n");
	fprintf(stderr, "       checkgroups\n");
	st = -1;
  }

  exit(st ? 1 : 0);
}
