/*
 * W-NEWS       A simple NEWS processing package for LINUX and friends.
 *
 * Usage:       updgroups
 *
 * Version:     @(#)updgroups.c	4.43	28-Feb-1994
 *
 * Author:      Miquel van Smoorenburg, <miquels@drinkel.ow.org>
 *		Copyright 1993 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.
 */
#if __STDC__
#  define ANSI
#endif
#include "wnews.h"


struct group {
  struct group	*next;
  int		new;
  char		group[1];
};

struct article {
  struct article *next;
  long		artno;
  char		msgid[64];
  char		grp[1];
};


char *Release = VERSION;
char *Version = "@(#) updgroups 4.43 (28-Feb-1994)";


/* Unlink article after repost? FIXME: - MvS */
#define DO_UNLINK			0

char *program;				/* program name			*/
int errflg = 0;                         /* an error has occurred        */
int verbose = 0;                        /* be verbose about it all      */

struct group *groups;                   /* the in-core "active" file    */
struct article *articles;		/* List of articles processed   */

extern int getopt(), optind;
extern char *optarg;


/* Read all newsgroups in /usr/lib/news/active into structures. */
int my_act_rd() 
{
  char active[128];
  char line[128];
  char *s;
  FILE *fp;
  struct group *sp;
  
  groups = (struct group *)0;

  sprintf(active, "%s/%s", CONFDIR, ACTIVE);
  if ((fp = fopen(active, "r")) == (FILE *)NULL) {
	logmsg(LOGERR, "cannot open (%s)\n", active);
	return(1);
  }
  while(fgets(line, 128, fp) != (char *)NULL) {
	if (debug && verbose >= 2) fprintf(stderr, "updgroups: my_act_rd: reading %s", line);
	if ((s = strtok(line, " \t")) == (char *)0) {
		logmsg(LOGERR, "active file format error\n");
		return(1);
	}
	sp = (struct group *)malloc(sizeof(struct group) + strlen(s));
	if (sp == (struct group *)NULL) {
		logmsg(LOGERR, "out of memory - too many newsgroups.\n");
		return(1);
	}
	strcpy(sp->group, s);
	sp->next = groups;
	sp->new = 0;
	groups = sp;
  }
  (void) fclose(fp);
  return(0);
}


/* Read the header of an article */
struct article *read_header(s)
char *s;
{
  FILE *fp;
  char line[1024];
  char msgid[64];
  char grp[1024];
  struct article *art;
  int f;
  char *p;

  if ((fp = fopen(s, "r")) == (FILE *)0) {
	logmsg(LOGERR, "cannot open junk/%s\n", s);
	return(NULL);
  }

  msgid[0] = 0;
  grp[0] = 0;

  while(fgets(line, 1024, fp) != (char *)0) {
	if (line[0] == '\n') break;
	if (strncmp(line, "Newsgroups: ", 12) == 0) {
		strcpy(grp, line + 12);
		continue;
	}
	for(f = 0; f < 10; f++) line[f] = tolower(line[f]);
	if (strncmp(line, "message-id: ", 12) == 0) {
		strncpy(msgid, line+12, 64);
		msgid[63] = 0;
	}
  }
  fclose(fp);

  if (line[0] != '\n' || msgid[0] == 0 || grp[0] == 0) {
	logmsg(LOGERR, "malformed header junk/%s\n", s);
	return(NULL);
  }
  for(p = msgid; *p; p++)
	if (*p == '\n') {
		*p = 0;
		break;
	}
  for(p = grp; *p; p++)
	if (*p == '\n') {
		*p = 0;
		break;
	}

  /* See if we already know this Message-ID. */
  for(art = articles; art; art = art->next)
	if (strcmp(art->msgid, msgid) == 0) {
#if DO_UNLINK
		if (verbose > 1) logmsg(LOGINFO, "unlink(%s)\n", s);
		if (!debug) unlink(s);
#endif
		return(NULL);
	}

  /* We don't, so malloc space, fill in and return. */
  if (verbose > 1) logmsg(LOGINFO, "registering %s\n", s);
  art = malloc(sizeof(struct article) + strlen(grp));
  memset(art, 0, sizeof(struct article));
  strcpy(art->msgid, msgid);
  strcpy(art->grp,   grp);

  return(art);
}


/* Create a new newsgroup. */
void newgroup(s, doit)
char *s;
int doit;
{
  struct group *grp;
  FILE *fp;
  char buf[256];
  time_t t;

  /* Link into our active list. */
  if (doit == 0) {
    if (verbose > 2) logmsg(LOGINFO, "newgroup: adding %s to list\n", s);
    if ((grp = malloc(sizeof(struct group) + strlen(s))) != (struct group *)0) {
	strcpy(grp->group, s);
	grp->next = groups;
	grp->new = 1;
	groups = grp;
    } else {
	logmsg(LOGERR, "out of memory\n");
	exit(1);
    }
    return;
  }

  logmsg(LOGINFO, "newgroup %s\n", s);

  if (debug) return;

  /* And execute the newgroup command. */
  sprintf(buf, "%s/inews -h", LIBDIR);
  if ((fp = wpopen(buf, "w")) == (FILE *)0) {
	logmsg(LOGERR, "cannot popen(%s, \"w\")\n", buf);
	exit(1);
  }
  time(&t);
  fprintf(fp, "From: news\n");
  fprintf(fp, "Newsgroups: control\n");
  fprintf(fp, "Subject: newgroup %s\n", s);
  fprintf(fp, "Control: newgroup %s\n", s);
  fprintf(fp, "Approved: news\n");
  fprintf(fp, "Distribution: local\n");
  fprintf(fp, "Date: %s", ctime(&t));
  fprintf(fp, "Lines: 1\n");
  fprintf(fp, "\n");
  fprintf(fp, "Automatically created.\n");

  wpclose(fp);
}


/* Post an article with inews. Filter out unwanted header lines. */
int post(s)
char *s;
{
  FILE *infp, *outfp;
  char buf[256];
  int inh = 1;

  if (verbose > 0) logmsg(LOGINFO, "re-posting %s\n", s);
  if (debug && verbose < 3) return(0);

  sprintf(buf, "%s/inews -R -h", LIBDIR);

  if ((infp = fopen(s, "r")) == (FILE *)0) return(-1);
  if (debug && verbose >= 3) outfp = stdout;
    else if ((outfp = wpopen(buf, "w")) == (FILE *)0) {
	logmsg(LOGERR, "cannot popen(%s, \"w\")\n", buf);
	exit(1);
  }
  while(fgets(buf, 256, infp) != (char *)0) {
	if (inh && strcmp(buf, "\n") == 0) {
		inh = 0;
		fprintf(outfp, "X-Reposted-By: updgroups\n");
	}
	fprintf(outfp, "%s", buf);
  }
  (void) fclose(infp);
  if (outfp != stdout) wpclose(outfp);
#if DO_UNLINK
  if (!debug) {
	/* Remove article from junk directory. */
	if (verbose > 1) logmsg(LOGINFO, "unlinking %s\n", s);
	unlink(s);
  }
#endif
  return(0);
}


/* Walk through the "junk" newsgroup, checking each article. */
int walkjunk()
{
  char dir[256];
  DIR *dd;
  struct dirent *d;
  struct stat st;
  struct article *art, *last = (struct article *)NULL;
  struct group *grp;
  char *s, *first;
  int quit;
  int create_group;
  int post_article;

  sprintf(dir, "%s/junk", SPOOLDIR);
  
  if (chdir(dir) < 0 || (dd = opendir(dir)) == (DIR *)0) {
	logmsg(LOGERR, "cannot opendir(%s)\n", dir);
	exit(1);
  }
  while((d = readdir(dd)) != (struct dirent *)0) {

	/* See if this is a valid article (a file, numeric name.) */
	if (atol(d->d_name) == 0) continue;
	stat(d->d_name, &st);
	if (!S_ISREG(st.st_mode)) continue;
	
	/* Read the header of this article. */
	if ((art = read_header(d->d_name)) == (struct article *)0) continue;

	/* Hmm, it has an unique message ID. Link into list. */
	if (articles) last->next = art;
	  else articles = art;
	art->next = (struct article *)NULL;
	last = art;

	/* See if all newsgroups lines are unknown. */
	s = strtok(art->grp, ", \t");
	first = s;
	quit = 0;
	post_article = 1;
	create_group = 1;
	while(s) {
		for(grp = groups; grp; grp = grp->next)
			if (strcmp(s, grp->group) == 0) {
				/* Group exists, so don't create. */
				create_group = 0;
				/* If this is a crossposting, don't post. */
				if (grp->new == 0) post_article = 0;
				break;
			}
		s = strtok(NULL, ", \t");
	}
	if (s != (char *)0) continue;

	/* Create this newsgroup? Then add to list.*/
	if (create_group) newgroup(first, 0);

	/* Post this article? Then save name. */
	if (post_article) art->artno = atol(d->d_name);
  }

  /* Walk through list of new groups and create them. */
  for(grp = groups; grp; grp = grp->next) {
	if (grp->new)
		newgroup(grp->group, 1);
  }

  /* Now walk through articles and post them if needed. */
  for(art = articles; art; art = art->next) {
	if (art->artno) {
		sprintf(dir, "%ld", art->artno);
		post(dir);
	}
  }

  return(0);
}


/* Trap signals (SIGINT and SIGPIPE).  Useful for debugging purposes. */
void trap(sig)
int sig;
{
  (void) signal(sig, SIG_IGN);
  
  logmsg(LOGERR, "received signal %d. Goodbye!\n", sig);
  exit(1);
}


void usage()
{
  logmsg(LOGERR, "Usage: updgroups [-d] [-v012]\n");
  exit(1);
}


/*
 * Main part of the program.
 * Process options and call appropriate functions.
 */
int main(argc, argv)
int argc;
char **argv;
{
  int c;
  
  program = "updgroups";

  (void) signal(SIGINT, trap);
  (void) signal(SIGPIPE, trap);
  
  if (isatty(0)) verbose = 1;
 
  articles = (struct article *)NULL;
  groups = (struct group *)NULL;

  while((c = getopt(argc, argv, "dv:")) != EOF) {
	switch(c) {
		case 'd':
			debug = 1;
			verbose = 1;
			break;

		case 'v':
			verbose = atoi(optarg);
			break;

		default:
			errflg++;
			break;
	}
	if (errflg) usage();
  }

  if (my_act_rd()) exit(1);

  walkjunk();
  
  exit(0);
}
