/*
 * W-NEWS	A simple NEWS processing package.
 *		sys.c - routines to handle the "sys" or "newsfeeds" file.
 *
 * Version:	@(#)sys.c     4.50    14-Nov-1993
 *
 * Author:	Miquel van Smoorenburg, <miquels@drinkel.ow.org>
 *		Copyright 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 "libwnews.h"


/* Flag to see which format we're using. */
#define F_SYS	1
#define F_FEED	2
static int format = -1;
SYS *sys;

#define SYSSIZE	sizeof(SYS)


/* Check which format we're using. */
void
check_format()
{
  char buff[128];
  struct stat st;
  int have_feed = 0;
  int have_sys = 0;

  sprintf(buff, "%s/%s", CONFDIR, SYSFILE);
  have_sys = stat(buff, &st) < 0 ? 0 : 1;

  sprintf(buff, "%s/%s", CONFDIR, FEEDSFILE);
  have_feed = stat(buff, &st) < 0 ? 0 : 1;

  if (have_feed && have_sys)
	logmsg(LOGSYS|LOGERR,
		"Warning: both sys and newsfeeds detected, using sys!\n");
  format = have_sys ? F_SYS : F_FEED;
}


/* Read the "sys" file into core. */
int
sys_rd()
{
  char *mem = "out of memory\n";
  char buff[256];
  register SYS *ap;
  register FILE *fp;
  register char *sp, *bp, *xp;
  register char *bufp, *s;
  char c;
  int first, contline, len;

  /* See if we have already run. */
  if (sys != (SYS *)NULL) return(OK);

  /* Check sys or newsfeeds file type. */
  if (format < 0) check_format();

  /* Get the name of the file. */
  sprintf(buff, "%s/%s", CONFDIR, format == F_FEED ? FEEDSFILE : SYSFILE);

  /* Try to open the "sys" or "newsfeeds" file. */
  if ((fp = fopen(buff, "r")) == (FILE *)NULL) {
	logmsg(LOGERR, "cannot open(%s)\n", buff);
	return(ERROR);
  }

  /* Now, read the file, and store it in core. */
  s = fgets(buff, 256, fp);
  while (s != (char *)NULL) {

	/* Read one line. This can be split onto multiple lines! */
	bufp = (char *)NULL;
	first = 1;
	contline = 1;
	len = 1;

	while(contline && s != (char *)NULL) {

		/* Skip any initial spaces. */
		bp = s;
		while(*s == ' ' || *s == '\t') s++;
		contline = 0;

		/* Comment or empty line, skip. */
		if (*s != '#' && *s != '\n' && *s != 0) {
    
			/* Strip any trailing spaces too. */
			bp = NULL;
			for (sp = s; *sp; sp++) {
				if (*sp == '\\') bp = sp;
				if (*sp != ' ' && *sp != '\t' &&
						*sp != '\n') bp = sp;
			}

			/* See if it ends in a backslash. */
			if (bp && *bp == '\\') {
				contline = 1;
				*bp = 0;
			}

			/* Save string into malloc'ed memory. */
			len += strlen(s);
			if ((bufp = (char *)realloc(bufp, len)) ==
							(char *)NULL) {
				logmsg(LOGERR, mem);
				return(ERROR);
			}
			if (first) strcpy(bufp, s);
			  else strcat(bufp, s);
			first = 0;
		}

		/* Read next line if we need to. */
		if (contline) s = fgets(buff, 256, fp);
	}

	/* An empty line? */
	if (*s == 0 || *s == '\n' || *s == '#') {
		if (bufp) free(bufp);
		if (s) s = fgets(buff, 256, fp);
		continue;
	}

	/* Allocate a new frame for this system. */
	if (sys == NILSYS) {
		sys = (SYS *)malloc(SYSSIZE);
		ap = sys;
	} else {
		ap = sys;
		while (ap->next != NILSYS) ap = ap->next;
		ap->next = (SYS *)malloc(SYSSIZE);
		ap = ap->next;
	}
	if (ap == NILSYS) {
		logmsg(LOGERR, mem);
		(void) fclose(fp);
		return(ERROR);
	}
	ap->next = NILSYS;

	/* Decode the "sysname" field. */
	sp = bufp;
	while (*sp && *sp != ':' && *sp != '/') sp++;
	c = *sp;
	*sp++ = '\0';
	if ((ap->name = (char *)malloc(strlen(bufp) + 1)) == (char *)NULL) {
		logmsg(LOGERR, mem);
		(void) fclose(fp);
		return(ERROR);
	}
	strcpy(ap->name, bufp);

	/* Check for and decode the "not_to_sys" field. */
	if (c == '/') {
		bp = sp;
		while (*sp && *sp != ':') sp++;
		*sp++ = '\0';
		ap->nsys = (char *)malloc(strlen(bp) + 1);
		if (ap->nsys == (char *)NULL) {
			logmsg(LOGERR, mem);
			(void) fclose(fp);
			return(ERROR);
		}
		strcpy(ap->nsys, bp);
	} else ap->nsys = (char *)NULL;

	/* Decode the "newsgroups" field. */
	bp = sp;
	while (*sp && *sp != ':') sp++;
	*sp++ = '\0';

	/* Is there a slash in it? Yes, then there are the distributions. */
	if ((xp = strchr(bp, '/')) != NULL) *xp++ = 0;

	/* Save subscription-list. */
	if ((ap->ngrp = (char *)malloc(strlen(bp) + 1)) == (char *)NULL) {
		logmsg(LOGERR, mem);
		(void) fclose(fp);
		return(ERROR);
	}
	strcpy(ap->ngrp, bp);

	/* Save distributions list. */
	if (xp != NULL) {
		if ((ap->dist = (char *)malloc(strlen(xp) + 1)) ==
							(char *)NULL) {
			logmsg(LOGERR, mem);
			(void) fclose(fp);
			return(ERROR);
		}
		strcpy(ap->dist, xp);
	} else ap->dist = (char *)NULL;
	
	/* Decode the "transport" field. */
	bp = sp;
	while (*sp && *sp != ':') sp++;
	if (*sp) *sp++ = '\0';
	if (*bp != '\0') {
		ap->prog = (char *)malloc(strlen(bp) + 1);
		if (ap->prog == (char *)NULL) {
			logmsg(LOGERR, mem);
			(void) fclose(fp);
			return(ERROR);
		}
		strcpy(ap->prog, bp);
	} else ap->prog = (char *)NULL;

	/* Decode the "filename" field. */
	bp = sp;
	while (*sp && *sp != '\n') sp++;
	*sp = '\0';
	if (*bp != '\0') {
		ap->file = (char *)malloc(strlen(bp) + 1);
		if (ap->file == (char *)NULL) {
			logmsg(LOGERR, mem);
			(void) fclose(fp);
			return(ERROR);
		}
		strcpy(ap->file, bp);
	} else ap->file = (char *)NULL;

	/* Free memory and read next line. */
	free(bufp);
	if (s) s = fgets(buff, 256, fp);
  }
  (void) fclose(fp);
  return(OK);
}


/*
 * Wildcard mathing with either "all" or "*".
 * Returns 0 if a match was found.
 */
int
wildcard(s, p, fmt)
register char *s, *p;
int fmt;
{
  register int sc, pc;
  char *t;
  
  /* With the BNEWS format, abbreviations match too. */
  if (fmt == F_SYS && strncmp(s, p, strlen(p)) == 0) return(0);

  if (s == (char *)NULL || p == (char *)NULL) return(1);
  while (*(t = p++) != '\0') {
	pc = *t;
	sc = *s++;
	if ((fmt == F_SYS && strncmp(t, "all", 3) == 0) ||
	    (fmt == F_FEED && *t == '*')) {
		s--;
		if (*t != '*') p += 2;
		do {
			if (*p == '\0' || !wildcard(s, p, fmt)) return(0);
		} while (*s++ != '\0');
		return(1);
	}
	if (sc != pc) return(1);
  }
  return(*s != '\0');
}


/* Generic match a list of patterns with a newsgroup. */
static int
_group_match(name, list, fmt, ok)
char *name;
char *list;
int fmt, ok;
{
  char buff[128];
  int f;
  char *s;

  /* Loop over the patterns in the list. */
  s = list;
  while(*s) {
	/* Get pattern. */
	f = 0;
	while(*s && *s != ',') buff[f++] = *s++;
	if (*s) s++;
	buff[f] = 0;

	/* Check pattern. */
	if (buff[0] == '!') {
		if (!wildcard(name, buff + 1, fmt)) ok = 0;
	} else {
		if (!wildcard(name, buff, fmt)) ok = 1;
	}
  }
  /* Done, return result. */
  return(ok);
}


/* Match a list of groups using either newsfeeds or sys file format. */
int
group_match(name, list)
char *name;
char *list;
{
  int ok = 0;

  /* Since this function can be called stand alone.. */
  if (format < 0) check_format();

#if 0 /* FIXME: what should _we_ do? -MvS */
  /* By default all articles match with the INN format. */
  if (format == F_FEED) ok = 1;
#endif

  return(_group_match(name, list, format, ok));
}


/* Match a list of groups using newfeeds format without default. */
int
mod_match(name, list)
char *name;
char *list;
{
  return(_group_match(name, list, F_FEED, 0));
}


/* Match the distributions. This does NO pattern matching. */
int
dist_match(name, list)
char *name;
char *list;
{
  int ok = -1;
  int notok = 0;
  int ex_seen = 0;
  char buff[128];
  int f;
  char *s;

  /* Without a list it always matches. */
  if (list == (char *)NULL || *list == 0) return(1);

  /* Loop over the distributions in the list. */
  s = list;
  while(*s) {
	/* Get distribution. */
	f = 0;
	while(*s && *s != ',') buff[f++] = *s++;
	if (*s) s++;
	buff[f] = 0;

	/* In the "sys" file, this might be a newsgroup. */
	if (format == F_SYS && strchr(buff, '.')) continue;

	/* The "sys" file format allows wildcards. */
	if (format == F_SYS) {
		if (buff[0] == '!') {
			ex_seen++;
			if (wildcard(buff + 1, name, format) == 0) notok = 1;
		} else {
			if (wildcard(buff, name, format) == 0) ok = 1;
		}
	} else {
		if (buff[0] == '!') {
			ex_seen++;
			if (strcmp(buff + 1, name) == 0) notok = 1;
		} else {
			if (strcmp(buff, name) == 0) ok = 1;
		}
	}
  }

  /* See what we had. THIS IS TRICKY. */
  if (notok && ok != 1) ok = 0;
  if (ex_seen && ok < 0) return(1);
  if (ok < 0) ok = 0;
  return(ok);
}


/*
 * News group matching.
 * This function checks if one of the newsgroups from the Newsgroups:
 * line is in the subscription list for this system. It also checks
 * for the distribution.
 */
int
ngmatch(sysent, ds, ng)
SYS *sysent;
char *ds;
char *ng;
{
  static char buff[128];
  int group_ok = 0;
  int dist_ok = 0;
  char *s;
  int f;

  /* The "sys" file has no separate distribution field. */
  if (format == F_SYS) ds = ng;

  /* Loop over all distributions. */
  s = ds;
  while(*s) {
	/* Seek for next space or comma. */
	f = 0;
	for(; *s != ',' && *s != '\t' && *s != ' ' && *s; s++)
		buff[f++] = *s;
	buff[f] = 0;

	/* We have it. Now find start of next entry. */
	for(; *s == ' ' || *s == '\t' || *s == ','; s++)
		;

	/* See if this matches one of the distributions. */
	if (dist_match(buff, sysent->dist)) dist_ok++;
  }

  /* Do the same for the newsgroups list. */
  s = ng;
  while(*s) {
	/* Seek for next space or comma. */
	f = 0;
	for(; *s != ',' && *s != '\t' && *s != ' ' && *s; s++)
		buff[f++] = *s;
	buff[f] = 0;

	/* We have it. Now find start of next entry. */
	for(; *s == ' ' || *s == '\t' || *s == ','; s++)
		;

	/* See if this matches one of the newsgroups. */
	if (group_match(buff, sysent->ngrp))
		group_ok++;
  }

  /* Done. Return the result. */
  return(dist_ok && group_ok);
}
