/*
 * W-NEWS       A simple NEWS processing package.
 *              Inews is the program that posts a message to the local
 *              system, and optionally queues it for transmission to
 *              the news feed system.  It is called by the programs
 *              'unbatch' (for posting locally) and 'pnews' (for
 *              posting locally and to the network).
 *
 *		Rnews is the program called by the UUCP system to queue
 *		incoming batches; however some programs (NNTP!) like to call
 *		it to post one article. Therefore rnews functionality has
 *		been moved to inews.
 *
 * Usage:       inews -h | {-t title -n newsgroups} [-e expires]
 *                    [-f sender] [-d distribution] [-F refs]
 *                    [-o organization] [-a approved] [-r reply-to]
 *                    [-x dontsend] [-c control] [artname]
 *
 *              inews [-d] -p artname
 *
 *		rnews [-d] < news_batch
 *		rnews [-d] -U
 *
 * Version:     @(#)inews.c	4.50	03-Feb-1995
 *
 * Authors:     Fred N. van Kempen, <waltje@Infomagic.COM>
 *		Edvard Tuinder, <ed@ow.org>
 *		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"


typedef struct _hdr_ {                  /* the header field definition  */
  struct _hdr_  *next;
  char          *field;
  char          *text;
} HDR;
#define HDRSIZE (sizeof(HDR))
#define NILHDR  ((HDR *)NULL)


char *Release = VERSION;
char *Version = "@(#) inews 4.50 (03-Feb-1995)";


char *program;				/* for logmsg()			*/
int opt_h = 0;                          /* use article hdr for info     */
int opt_p = 0;                          /* post an incoming article     */
int opt_R = 0;				/* repost an article		*/
int old_uid;
char *myname;                           /* various environment values   */
char myorg[128];
char *mypname;
time_t mytime;                          /* current time and date        */
struct tm *mydate;
char remote[128];                       /* name of remote news feed     */
off_t bytes;                            /* length of completed article  */
#if INEWS_UNBATCH
int isbatch;				/* Input is a batch of articles */
int a_count;				/* Article count		*/
int a_posted;				/* Articles posted.		*/
time_t a_time;				/* Time started.		*/
#endif
char *dontsend = (char *)NULL;          /* list of "don't send" hosts   */
struct {                                /* this is the in-core header   */
  char  *path,  *path_t;
  char  *from,  *from_t;
  char  *send,  *send_t;
  char  *repl,  *repl_t;
  char  *flup,  *flup_t;
  char  *ngrp,  *ngrp_t;
  char  *dist,  *dist_t;
  char  *ctrl,  *ctrl_t;
  char  *subj,  *subj_t;
  char  *keyw,  *keyw_t;
  char  *summ,  *summ_t;
  char  *mrk1,  *mrk1_t;
  char  *refs,  *refs_t;
  char  *iden,  *iden_t;
  char  *orgn,  *orgn_t;
  char  *dept,  *dept_t;
  char  *date,  *date_t;
  char  *expr,  *expr_t;
  char  *appr,  *appr_t;
  char  *lins,  *lins_t;
  char  *xref,  *xref_t;
  char  *mrk2,  *mrk2_t;
  char  *null;
  HDR   *fields;
} hdr = {
  "Path:",              (char *)NULL,
  "From:",              (char *)NULL,
  "Sender:",            (char *)NULL,
  "Reply-To:",          (char *)NULL,
  "Followup-To:",       (char *)NULL,
  "Newsgroups:",        (char *)NULL,
  "Distribution:",      (char *)NULL,
  "Control:",           (char *)NULL,
  "Subject:",           (char *)NULL,
  "Keywords:",          (char *)NULL,
  "Summary:",           (char *)NULL,
  "",                   (char *)NULL,
  "References:",        (char *)NULL,
  "Message-ID:",        (char *)NULL,
  "Organization:",      (char *)NULL,
  "Department:",        (char *)NULL,
  "Date:",              (char *)NULL,
  "Expires:",           (char *)NULL,
  "Approved:",          (char *)NULL,
  "Lines:",             (char *)NULL,
  "Xref:",              (char *)NULL,
  "",                   (char *)NULL,
  (char *)NULL,
  NILHDR
};

/* The required headers. */
struct {
  char **hdr;
  char **hdr_t;
} hdr_req[] = {
  { &hdr.path, &hdr.path_t },
  { &hdr.from, &hdr.from_t },
  { &hdr.subj, &hdr.subj_t },
  { &hdr.ngrp, &hdr.ngrp_t },
  { &hdr.dist, &hdr.dist_t },
  { &hdr.date, &hdr.date_t },
  { &hdr.iden, &hdr.iden_t },
  { (char **)NULL, (char **)NULL }
};


extern char *months[];		/* Is in logmsg.c */


_PRO( void acct_upd, (char *what, char *dist, off_t bytes, char *hosts)	);
_PRO( void ctrl_msg, (char *cmd, char *fname)				);
_PRO( int hdr_upd, (char *text, int level)				);
_PRO( void hdr_end, (int lines)						);
_PRO( int hdr_out, (char *temp, char *fname, int lines)			);
_PRO( int del_chk, (char *dir)						);
_PRO( void del_one, (char *temp, char *group, char *aname, int found)	);
_PRO( void del_loc, (char *fname, char *aname)				);
_PRO( int del_rmt, (char *fname, char *aname, SYS *ap)			);
_PRO( int do_post, (char *fname)					);
_PRO( int app_signature, (FILE *ofp)					);
_PRO( void check_ctrl, (void)						);
_PRO( int inews, (FILE *ifp)						);
_PRO( int mk_batch, (FILE *input)					);
#ifdef COHERENT
_PRO( char *getenv, (char *text)					);
#endif


/* Copy newsbatch to the spool directory (if called as rnews) */
int
mk_batch(input)
register FILE *input;
{
  char batch[128], buff[1024];
  register FILE *fp;
  struct tm *tm;
  time_t now;
  int st;

  /* Get the current time. */
  (void) time(&now);
  tm = localtime(&now);

  /* Create a batch file name. */
  wmkspooldir(INCOMING, 0);
  sprintf(batch, "%s/%s/%2d%02d%02d%02d%02d%04x",
	SPOOLDIR, INCOMING,
	tm->tm_year, tm->tm_mon + 1, tm->tm_mday,
	tm->tm_hour, tm->tm_min, getpid());
  if ((fp = fopen(batch, "wb")) == (FILE *)NULL) {
	logmsg(LOGERR|LOGSYS, "cannot create(%s)\n", batch);
	return(ERROR);
  } else logmsg(LOGINFO, "creating batch %s\n", batch);

  /* Copy the incoming batch to the file. */
  do {
	st = fread(buff, sizeof(char), 1024, input);
	fprintf(stderr, "I read (%s)\n", buff);
	fwrite(buff, sizeof(char), st, fp);
  } while (st > 0);
  if (ferror(input) || ferror(fp)) {
	logmsg(LOGERR, "error while copying batch file\n");
	(void) fclose(input);
	(void) fclose(fp);
	return(ERROR);
  }
  (void) fclose(input);
  (void) fclose(fp);
  return(OK);
}


#if INEWS_UNBATCH
/* Free all memory taken up by the in-core header. */
void
free_hdr()
{
  char **pp;
  HDR *h, *next;

  /* 1. Free all the "known" fields. */
  pp = &hdr.path;
  while(*pp) {
	pp++;
	if (*pp) {
		free(*pp);
		*pp = (char *)NULL;
	}
	pp++;
  }

  /* 2. Free all the other fields. */
  for(h = hdr.fields; h; h = next) {
	next = h->next;
	if (h->field) free(h->field);
	if (h->text)  free(h->text);
	free(h);
  }
  hdr.fields = (HDR *)NULL;
}
#endif


/* Perform some resource accouting. */
void
acct_upd(what, dist, bytes, hosts)
char *what;
char *dist;
off_t bytes;
char *hosts;
{
#if HAVE_ACCT
  FILE *fp;
  char fname[128];

  sprintf(fname, "%s/%s", VARDIR, ACCTFILE);

  if ((fp = fopen(fname, "a")) == (FILE *)NULL) {
	logmsg(LOGERR, "cannot append(%s)\n", fname);
	return;
  }
  fprintf(fp, "%02d/%02d/%02d %02d:%02d\t%s\t%s\t%ld\t%s\n",
	mydate->tm_year, mydate->tm_mon + 1, mydate->tm_mday, 
	mydate->tm_hour, mydate->tm_min,
	what, dist, (long) bytes, hosts);
  (void) fclose(fp);
#endif
}


/* Process a received control message. */
void
ctrl_msg(cmd, fname)
char *cmd;
char *fname;
{
  char buff[256], status[128], mailbuff[512];
  int mailit = 1;
#if HAVE_AUTO
  int st;
  char cname[32];
  char *sp;

  /* Check for invalid control messages. */
  if (cmd[0] == '/' || cmd[0] == '.') {
	logmsg(LOGERR|LOGSYS,
		"Received invalid Control: message: `%s' From: %s\n",
		cmd, hdr.from_t);
	log_close();
	exit(1);
  }

  /* Get the first word. */
  strncpy(cname, cmd, 32);
  cname[31] = 0;
  for(sp = cname; *sp; sp++) if (strchr(" \t\n", *sp)) *sp = 0;

  logmsg(LOGINFO, "Processing control message `%s'\n", cmd);

  /* Close the active file. */
  act_close();

  /* Execute the command. */
  sprintf(buff, "%s/control -c %s", LIBDIR, cmd);
  if ((st = wspawn(buff, fname, NULL)) == 127) {
	/* Only complain when it _really_ goes wrong. */
	logmsg(LOGERR|LOGSYS, "EXEC error in %s\n", buff);
	sprintf(status, "%s: error in %s", cname, buff);
  } else {
	sprintf(status, "Report of %s", cmd);
	/* Don't bother USENET with cancel messages. */
	if (strcmp(cname, "cancel") == 0) mailit = 0;
  }
#else
  logmsg(LOGINFO, "Received control message `%s'\n", cmd);
  sprintf(status, "Incoming control message: %s", cmd);
  sprintf(buff,   "Not executed automatically.");
#endif

#ifdef COHERENT
  /* Woah man, is this a hack or what !! - MvS. */
  sprintf(mailbuff,
	"%s %s <<@EOF\nSubject: control: %s\n\n`cat %s`\n@EOF",
  		   MAIL, USENET, status, fname);
#else
  sprintf(mailbuff, "%s -s\"control: %s\" %s < %s",
  		   MAIL, status, USENET, fname);
#endif

  if (mailit && wsystem(mailbuff) != 0)
	logmsg(LOGERR|LOGSYS, "could not mail to %s subj '%s' fn %s\n",
						USENET, buff, fname);
}


/* Update our in-core header information. */
int
hdr_upd(text, level)
char *text;
int level;
{
  static char **op = (char **)NULL;
  char buff[1024];
  struct passwd *pw;
  register char *sp;
  register HDR *hp;
  char *user, *name;
  char unknown = 0;
  char **xp, **yp;

  /* Is this the "initialize header" call? */
  if (text == (char *)NULL) {
	hdr.fields = NILHDR;
	op = (char **)NULL;

	/* Is this generated locally or is it an incoming article? */
	if (opt_p == 0) {
		pw = getpwuid(old_uid);
		if (pw == (struct passwd *)NULL) {
			user = strsave("unknown");
			name = strsave("John Doe");
			unknown = 1;
		} else {
			user = pw->pw_name; name = pw->pw_gecos;
		}
 		sp = name;
		while (*sp != ',' && *sp) sp++;
		if (*sp == ',') *sp = '\0';

		sprintf(buff, "From: %s@%s (%s)\n", user, myname, name);
		(void) hdr_upd(buff, ++level);

#if PATH_VALID
		sprintf(buff, "Path: %s!%s\n", mypname, user);
#else
		sprintf(buff, "Path: %s!not-for-mail\n", mypname);
#endif
		(void) hdr_upd(buff, level);

		sprintf(buff, "Message-ID: <%d%02d%02d%d.%02d%02d%02d@%s>\n",
			mydate->tm_year, mydate->tm_mon + 1, mydate->tm_mday,
			getpid(), mydate->tm_hour, mydate->tm_min,
			mydate->tm_sec, myname);
		(void) hdr_upd(buff, level);
		sprintf(buff, "X-News-Software: %s\n", Release);
		(void) hdr_upd(buff, level);

		if (myorg[0] != '\0') sprintf(buff, "Organization: %s\n",
								myorg);
		(void) hdr_upd(buff, level);

		sprintf(buff, "Date: %s\n", xtime(&mytime));
		(void) hdr_upd(buff, level);
		strcpy(remote, "local");
		if (unknown) {
			free(user);
			free(name);
		}
	}

	if (opt_h == 1) return(0);      /* go into "scan header" state */
	  else return(1);               /* go into "read text" state */
  }

  /* Can this be a valid part of the header? */
  if (level == 0 && opt_h == 0) return(1);

  /* Yes.  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) {
	if (op == (char **)NULL || (*text != ' ' && *text != '\t')) {
		logmsg(LOGINFO, "bad header field: \"%s\"\n", text);
		return(0);
	}
	/* This is a continuation line. Add it to the existing line. */
	while (*text == ' ' || *text == '\t') text++;
	sp = (char *)malloc(strlen(--text) + strlen(*op) + 1);
	if (sp == (char *)NULL) {
		logmsg(LOGERR, "out of memory\n");
		return(-1);
	}
	strcpy(sp, *op);
	strcat(sp, text);
	free(*op);
	*op = sp;
	return(0);
  } else {
	*++sp = '\0';
	sp++;
	while (*sp == ' ' || *sp == '\t') sp++;
  }

  /* Then, add this header field to the in-core hdr. */
  xp = (char **) &hdr.path;
  yp = (char **)NULL;

  /* First look for all known header lines. */
  while (*xp != (char *)NULL) {
	if (!strnicmp(text, *xp, strlen(text))) {
		yp = xp; yp++;
		break;
	}
	xp++;
	xp++;
  }
  if (yp == (char **)NULL) {
	/* Unknown type, so add to the "common" list. */
	if (hdr.fields != NILHDR) {
		hp = hdr.fields;
		while (hp->next != NILHDR) hp = hp->next;
		hp->next = (HDR *)malloc(HDRSIZE);  
		hp = hp->next;
	} else {
		hdr.fields = (HDR *)malloc(HDRSIZE);  
		hp = hdr.fields;
	}
	if (hp == NILHDR) {
		logmsg(LOGERR, "out of memory\n");
		return(-1);
	}
	hp->next = NILHDR;
	yp = &hp->text;
	if ((hp->field = strsave(text)) == (char *)NULL) {
		logmsg(LOGERR, "out of memory\n");
		return(-1);
	}
  }

  /* Here we know where to store the data. */
  if ((*yp = strsave(sp)) == (char *)NULL) {
	logmsg(LOGERR, "out of memory\n");
	return(-1);
  }

  /* Remember where we stored it in case of a continuation line. */
  op = yp;

  return(0);
}


/* Patch the in-core header for various things. */
void
hdr_end(lines)
int lines;
{
  char buff[1024];
  register char *sp;
#if 0
  /* For the From: rewrite code. */
  char addr[512];
  char temp[512];
  char *rest;
  register char *bp;
#endif

  /* Include the "Lines:" field if not present. */
  if (hdr.lins_t == (char *)NULL) {
	sprintf(buff, "%d", lines);
	hdr.lins_t = strsave(buff);
	if (hdr.lins_t == (char *)NULL) {
		logmsg(LOGERR, "out of memory\n");
		return;
	}
  }

  /* Check the "Distribution" line. */
  if (hdr.dist_t == NULL || hdr.dist_t[0] == 0)
	hdr.dist_t = strsave("world");

  /*
   * We are now going to update the Path: line. If the article was
   * generated locally we do not want to do this _unless_ there
   * is no '!' in the path line, meaning our host name is not yet
   * there. This can be caused by a buggy news reader/poster (nntpd).
   */
  if (opt_p == 0 && strchr(hdr.path_t, '!') != (char *)NULL) return;

#if PATH_VALID
  sprintf(buff, "%s!%s", myname, hdr.path_t);
#else
  if (strchr(hdr.path_t, '!') == (char *)NULL)
	sprintf(buff, "%s!not-for-mail", mypname);
  else
	sprintf(buff, "%s!%s", mypname, hdr.path_t);
#endif

  /* Fetch the "previous" host name. */
  sp = strchr(hdr.path_t, '!');
  if (sp != (char *)NULL) {
	*sp = '\0';
	strcpy(remote, hdr.path_t);
	sp = strchr(remote, '.');
	if (sp != (char *)NULL) *sp = '\0';
  } else strcpy(remote, "unknown");

  /* Make a new "Path: myname!oldpath" field. */
  free(hdr.path_t);
  hdr.path_t = strsave(buff);
  if (hdr.path_t == (char *)NULL) {
	logmsg(LOGERR, "out of memory\n");
	return;
  }

  /* Now return if article was generated locally. */
  if (opt_p == 0) return;

  /* Change the "From: " line if needed. */
  /* FIXME: - Fred, why do you rewrite the From: line? - MvS. */
#if 0
  if (hdr.from_t == (char *)NULL) return;
  if ((sp = strchr(hdr.from_t, '<')) == (char *)NULL) {
	bp = hdr.from_t;
	while (*bp && *bp != ' ' && *bp != '\t' && *bp != '(') bp++;
	if (*bp == ' ' || *bp == '\t' || *bp == '(') *bp++ = '\0';
	strcpy(addr, hdr.from_t);
	if ((sp = strchr(bp, '(')) != (char *)NULL) *sp++ = '\0';
	  else sp = bp;
	rest = sp;
	if ((bp = strchr(sp, ')')) != (char *)NULL) *bp = '\0';
  } else {
	*sp++ = '\0';
	if ((bp = strchr(sp, '>')) != (char *)NULL) *bp = '\0';
	strcpy(addr, sp);
	sp -= 2;
	rest = hdr.from_t;
	while (*sp == ' ' || *sp == '\t') *sp-- = '\0';
  }
  if ((sp = strchr(addr, ' ')) != (char *)NULL) *sp = '\0';
  if ((sp = strchr(addr, '@')) == (char *)NULL) {
	sprintf(temp, "%s!%s", myname, addr);
	strcpy(addr, temp);
  }
  if (rest == hdr.from_t) sprintf(temp, "%s <%s>", rest, addr);
    else sprintf(temp, "%s (%s)", addr, rest);
  free(hdr.from_t);
  hdr.from_t = strsave(temp);
  if (hdr.from_t == (char *)NULL) {
	logmsg(LOGERR, "out of memory\n");
	return;
  }
#endif
}


/* Write out a new header. */
int
hdr_out(temp, fname, lines)
char *temp;
char *fname;
int lines;
{
  char buff[1024];
  register HDR *hp;
  register char *bp;
  FILE *ifp, *ofp;
  register char **xp, **yp;

  /* Before writing it to disk, patch the in-core hdr. */
  hdr_end(lines);

  if ((ifp = fopen(temp, "r")) == (FILE *)NULL) {
	logmsg(LOGERR, "cannot open(%s)\n", temp);
	return(-1);
  }
  if ((ofp = fopen(fname, "w")) == (FILE *)NULL) {
	(void) fclose(ifp);
#ifndef LINUX
	logmsg(LOGERR, "cannot create(%s)\n", fname);
#else
	logmsg(LOGERR, "cannot create(%s): %s\n", fname, sys_errlist[errno]);
#endif
	return(-1);
  }

  /* Write the in-core header to the file. */
  xp = (char **) &hdr.path;
  while (**xp != '\0') {
	yp = xp; yp++;
	/* Don't write "Distribution: world" as this is default. */
	if (*yp != (char *)NULL)
	    if (*xp != hdr.dist || strcmp(*yp, "world") != 0)
			fprintf(ofp, "%s %s\n", *xp, *yp);
	xp = ++yp;
  }
  hp = hdr.fields;
  while (hp != NILHDR) {
	fprintf(ofp, "%s %s\n", hp->field, hp->text);
	hp = hp->next;
  }
  xp = (char **) &hdr.refs;
  while (**xp != '\0') {
	yp = xp; yp++;
	if (*yp != (char *)NULL) {
		/* FIXME: we should generate Xref: headers. */
		if (*xp != hdr.xref)
			 fprintf(ofp, "%s %s\n", *xp, *yp);
	}
	xp = ++yp;
  }
  fprintf(ofp, "\n");

  /* Now, write the message body to the file. */
  do {
	if ((bp = fgets(buff, 1024, ifp)) == (char *)NULL) break;
	fprintf(ofp, "%s", buff);
  } while(1);

  (void) fclose(ifp);
  (void) fclose(ofp);
  return(0);
}


/* Check if we have a chance of a safe local delivery. */
int
del_chk(group)
char *group;
{
  struct stat st;
  char dir[512];

  /* Directory present? */
  sprintf(dir, "%s/%s", SPOOLDIR, gtof(group));
  if (stat(dir, &st) < 0) {
	if (wmkspooldir(group, 1) != 0)
		return(-1);
	(void) stat(dir, &st);
	logmsg(LOGINFO, "created directory %s\n", dir);
  }

  /* Access OK? */
  if (!S_ISDIR(st.st_mode)) {
	logmsg(LOGERR|LOGSYS, "%s: not a directory\n", dir);
	return(-1);
  }
  if ((st.st_mode & 0700) != 0700) {
	logmsg(LOGERR|LOGSYS, "%s: not writable\n", dir);
	return(-1);
  }

  return(0);
}


/* Deliver the article into one newsgroup. */
void
del_one(temp, group, aname, found)
char *temp;
char *group;
char *aname;
int found;
{
  static char fname[128];
  ACT *a;

retry:
  /* Get info about this group. */
  a = get_group(group, MAKE_LOCK);
  if (a == (ACT *)NULL) {
	if (found) return;
	if (strcmp(group, JUNK) != 0) {
#if QUIET
		if (debug)
#endif
		logmsg(LOGINFO, "%s ng %s not localized, moved to JUNK (%s)\n",
						hdr.iden_t, group, JUNK);
		group = JUNK;
		a = get_group(group, MAKE_LOCK);
		if (a == (ACT *)NULL) {
			logmsg(LOGERR|LOGSYS,
			    "%s ng JUNK (%s) not localized, article is lost\n",
							hdr.iden_t, group);
			return;
		}
	} else {
		logmsg(LOGERR|LOGSYS, "%s ng %s not localized, article is lost\n",
							hdr.iden_t, group);
		return;
	}
  }

  /* See if there is a directory for this newsgroup. */
  if (del_chk(group) != 0) {
#if QUIET
	if (debug)
#endif
	logmsg(LOGERR, "%s ng %s has no directory, moved to JUNK (%s)\n",
					hdr.iden_t, group, JUNK);
	group = JUNK;
	if (del_chk(group) != 0) {
		logmsg(LOGERR|LOGSYS, "%s ng JUNK (%s) has no directory, article is lost\n",
							hdr.iden_t, group);
		/* Unlock */
		act_upd(a);
		return;
	}
	goto retry;
  }

  /* Update the active file. */
  a->last++;
  act_upd(a);

  /* We updated "active" sucessfully.  Link the article. */
  sprintf(fname, "%s/%s/%lu", SPOOLDIR, gtof(group), a->last);
  if (link(temp, fname) < 0) {
	logmsg(LOGERR, "cannot link(%s,%s)\n", temp, fname);
	return;
  } 

  /* Log the receipt of this article in the history file. */
  if (opt_R == 0) hist_add(hdr.iden_t, group, a->last, mytime, 0);
  a_posted++;

  /* Create a file name that can be used by the batching scheme. */
  if (aname != (char *)NULL) strcpy(aname, fname);
}


/* Post an article on the local machine. */
void
del_loc(fname, aname)
char *fname;
char *aname;
{
  register char *sp, *bp;
  char buff[128];
  int f, round, found;

  /* Check for and execute the control message. */
  if (hdr.ctrl_t != (char *)NULL) {
	ctrl_msg(hdr.ctrl_t, fname);
	if (!strstr(hdr.ngrp_t, "control")) {
		/*
		 * Only post it to the "control" newsgroup, so that
		 * normal users don't see the control message.
		 * (except those that read control and junk too :-))
		 */
		sp = hdr.ngrp_t;
		bp = hdr.ctrl_t;
		hdr.ngrp_t = "control";
		hdr.ctrl_t = (char *)NULL;
		del_loc(fname, aname);
		hdr.ngrp_t = sp;
		hdr.ctrl_t = bp;
		return;
	}
  }

  /* Loop over the Newsgroups: field, and post the message. */
  found = 0;
  if (aname != (char *)NULL) aname[0] = 0;
  for(round = 0; round < 2; round++) {
	sp = hdr.ngrp_t;
	while(*sp) {
		/* Gather newsgroup name. */
		f = 0;
		while(*sp && *sp != ' ' && *sp != '\t' && *sp != ',')
			buff[f++] = *sp++;
		buff[f] = 0;

		/* Skip to begin of next newsgroup name */
		while(*sp && strchr(" \t,", *sp)) sp++;

		if (round == 0) {
			/* See if newsgroup exists */
			if (get_group(buff, NO_LOCK) != NULL)
				found++;
		} else {
			/* Deliver it. */
			del_one(fname, buff, aname, found);
		}
	}
  }
  if (aname != NULL && aname[0] == 0)
	logmsg(LOGERR|LOGSYS, "%s not posted anywhere, article is lost\n",
							hdr.iden_t);

  /* Add a record to the resource accounting database. */
  if (opt_R == 0) acct_upd("rcvd", hdr.dist_t, bytes, remote);
}


/* Send an article to a remote system. */
int
del_rmt(fname, aname, ap)
char *fname;
char *aname;
SYS *ap;
{
  char buff[1024];
  FILE *fp;
  char c;
  int st;

  /* If no article available, fake it is "OK". */
  if ((aname == (char *)NULL) || (aname[0] == '\0')) return(0);

  c = (ap->prog == (char *)NULL) ? 'B' : *ap->prog;
  switch(c) {
	case 'A':       /* "A News" not supported */ 
		logmsg(LOGERR|LOGSYS, "%s 'A' News format not supported.\n", hdr.iden_t);
		return(-1);

	case 'B':       /* "B News" format, single article */
		sprintf(buff, "%s %s!rnews < %s", UUX, ap->name, fname);
		st = wsystem(buff);
		break;

	case 'F':       /* "B News" format, batched */
		/* Add this article to the system's batch list. */
		wmkspooldir(BATCHDIR, 0);
		if (ap->file == (char *)NULL) {
			sprintf(buff, "%s/%s", BATCHDIR, ap->name);
		} else strcpy(buff, ap->file);
		if ((fp = fopen(buff, "a")) != (FILE *)NULL) {
			fprintf(fp, "%s\n", aname);
			(void) fclose(fp);
			st = 0;
		} else st = -1;
		break;

	case 'X':       /* use a non-standard program */
		sprintf(buff, "%s < %s", ap->file, fname);
		st = wsystem(buff);
		break;

	default:
		/* Oops!  What else can we do but to cry out and ignore? */
		logmsg(LOGERR|LOGSYS, "<%s> bad delivery code (%s) for %s\n",
					hdr.iden_t, ap->prog, ap->name);
		return(-1);
  }
  return(st);
}


/*
 * Check if this system is already in the path.
 * Sysname may be multiple comma separated names.
 */
int
inpath(path, sysname)
char *path;
char *sysname;
{
  char *p, *s;
  int matches = 0;
  int len;
  char buf[128], *b;

  s = path;
  while(*s) {
	/* Find next component separator. */
	p = s;
	while(*p && *p != '!') p++;

	/* Don't take the last element - it's a user name. */
	if (*p == 0) break;
	len = p - s;

	/* Compare. */
	strcpy(buf, sysname);
	b = strtok(buf, ",");
	while(b) {
		if (strlen(b) == len &&
	    		strnicmp(b, s, len) == 0) matches++;
		b = strtok(NULL, ",");
	}

	/* Take next. */
	s = p + 1;
  }

  /* Return number of matches. */
  return(matches);
}


/* Post a completed article. */
int
do_post(fname)
char *fname;
{
  char allsys[1024];
  char artname[128];
  struct stat buf;
  register SYS *ap;
  int f, ok = 1;
  char mailbuff[128];
  int loop = 0;

  for(f = 0; hdr_req[f].hdr; f++) {
	if (*(hdr_req[f].hdr_t) == (char *)NULL) {
		ok = 0;
		break;
	}
  }

  if (!ok) {
	/* Hmm, article is incomplete. Log this fact. */
	logmsg(LOGERR, "incomplete article header (no %s line)\n",
		*(hdr_req[f].hdr));

	/* Now mail the article to the news administrator account. */
#ifdef COHERENT
	/* Fixme: how to give a Subject: to the Coherent mailer? - MvS */
	sprintf(mailbuff, "%s %s < %s", MAIL, USENET, fname);
#else
	sprintf(mailbuff, "%s -s\"incomplete article header\" %s < %s",
  		   MAIL, USENET, fname);
#endif
	if (wsystem(mailbuff) != 0)
		logmsg(LOGERR|LOGSYS, "could not mail to %s fn %s\n",
						USENET, fname);
	/* Return OK, so that unbatch keeps running. */
	return(0);
  }


  /*
   * Check if we saw this article before.
   * This only causes us to forget all about it, and exit, so
   * any "unbatch" program can continue running.
   */
  if (opt_R == 0) {
	if (hist_chk(hdr.iden_t) == 1) {
		logmsg(LOGINFO, "%-8.8s	%s duplicate article rejected\n",
			remote, hdr.iden_t);
		return(0);
	}
  }

  /* Determine the length of the article. */
  if (stat(fname, &buf) < 0) {
	logmsg(LOGERR, "cannot stat(%s)\n", fname);
	return(-1);
  }
  bytes = buf.st_size;

  /* First deliver it to the local host. */
  strcpy(allsys, "");
  strcpy(artname, "");
  del_loc(fname, artname);

  /* Loop over the in-core "sys" file to send the article to all hosts. */
  for(ap = sys; ap; ap = ap->next) {

	/* If this is "ME" skip it. */
	if (!strcmp(ap->name, "ME") || !strcmp(ap->name, myname)) continue;

	/* Skip this if posted locally, or if it is a repost. */
	if (opt_R || !strcmp(hdr.dist_t, "local")) continue;

	/* Is this host already in the Path: field? */
	if (inpath(hdr.path_t, ap->name)) continue;
	if (ap->nsys && inpath(hdr.path_t, ap->nsys)) continue;

	/* Are WE already in the path? */
	if (!loop && (inpath(hdr.path_t, myname) > 1 ||
		      inpath(hdr.path_t, mypname) > 1)) {
		logmsg(LOGERR,
		     "news feed loop detected, ng %s, Path line %s\n",
			hdr.ngrp_t, hdr.path_t);
		loop = 1;
	}
	if (loop) continue;

	/* Take care of the "-x sys" option. */
	if (dontsend != (char *)NULL)
		if (strstr(dontsend, ap->name)) continue;

	if (ngmatch(ap, hdr.dist_t, hdr.ngrp_t)) {
		if (del_rmt(fname, artname, ap) == 0) {
			strcat(allsys, ap->name);
			strcat(allsys, " ");
		} else logmsg(LOGERR, "cannot del_rmt(%s,%s,%s)\n",
					fname, artname, ap->name);
	}
  }

  if (strlen(allsys) > 0) {
	allsys[strlen(allsys) - 1] = '\0';
#if QUIET
	if (debug)
#endif
	logmsg(LOGINFO, "%-8.8s  %s sent to %s\n", remote, hdr.iden_t, allsys);

	/* Add a record to the resource accounting database. */
	acct_upd("sent", hdr.dist_t, bytes, allsys);
  }
  return(0);
}


/* Add the ".signature" file to fp. */
int
app_signature(ofp)
FILE *ofp;
{
  char fname[128];
  char buff[1024];
  struct passwd *pw;
  FILE *fp;
  char *sp;
  int lines = 0;
  int nlseen = 0;

  if ((sp = getenv("SIGNATURE")) == (char *)NULL) {
	pw = getpwuid(getuid());
	if (pw == (struct passwd *)NULL) {
		fprintf(stderr, "inews: cannot get home directory!\n");
		return(0);
	}
	sprintf(fname, "%s/%s", pw->pw_dir, SIGNATURE);
  } else strcpy(fname, sp);

  if ((fp = fopen(fname, "r")) != (FILE *)NULL) {
  	fprintf(ofp, "--\n");
	lines++;
	while (fgets(buff, 1024, fp) != (char *)NULL) {
		fprintf(ofp, "%s", buff);
		nlseen = (buff[0] == '\n');
		lines++;
	}
	/* Add newline if needed. */
	if (nlseen == 0) {
		fprintf(ofp, "\n");
		lines++;
	}
	(void) fclose(fp);
  }
  return(lines);
}

/* Check for old style control messages (in the Subject: line) */
void
check_ctrl()
{
  char *sp;

  /* See if it is an old style control message. */
  if (hdr.ctrl_t != (char *)NULL || hdr.subj_t == (char *)NULL) return;
  if (strncmp(hdr.subj_t, "cmsg", 4) != 0) return;

  /* It is. Rewrite into new style. */
  sp = hdr.subj_t + 4;
  while(*sp == ' ' || *sp == '\t') sp++;
  hdr.ctrl_t = strsave(sp);
}


/* Process an incoming article. */
int
inews(ifp)
register FILE *ifp;
{
  char buff[1024];
  char artname[128];
  char tempart[128];
  int state, ret;
  int lines, l;
  int nlseen = 0;
  FILE *fp;
  int rlen = strlen(RNEWSHDR);
#if INEWS_UNBATCH
  int first = 1;
  long count = 0L, maxcount = -1L;
#endif

  /* Create a temporary article file. */
  strcpy(artname, "/tmp/inXXXXXX"); mktemp(artname);
  sprintf(tempart, "%s/inXXXXXX", SPOOLDIR); mktemp(tempart);

  if ((fp = fopen(artname, "w")) == (FILE *)NULL) {
	logmsg(LOGERR, "cannot create(%s)\n", artname);
	return(ERROR);
  }

#if INEWS_UNBATCH
  /* Free memory. */
  if (opt_p) free_hdr();
#endif

  /* Check if we have enough disk space. */
  if ((ret = free_space(SPOOLDIR)) < 100) {
	logmsg(LOGERR|LOGSYS, "only %dKb free on %s - quitting\n",
		ret, SPOOLDIR);
	return(ERROR);
  }

  /* Now, go and fetch the incoming article. */
  ret = ERROR;
  lines = 0;
  state = hdr_upd((char *)NULL, 0);

  while (state != -1) {
#if INEWS_UNBATCH
	/* See if we have reached the end of the article. */
	if (maxcount >= 0 && count >= maxcount) state = 2;
	  else {
		if (fgets(buff, 1024, ifp) == (char *)NULL) {
			/* If EOF on the first line, ignore it. */
			if (first) {
				(void) fclose(fp);
				(void) wunlink(artname);
				return(OK);
			}
			state = 2;
		} else count += strlen(buff);
	}

	/* If this is the first line, check for the '#! rnews' header. */
	if (first && state != 2) {
		first = 0;
		if(strncmp(buff, RNEWSHDR, rlen) == 0) {
start_of_batch:
			maxcount = atol(buff + rlen);
			count = 0L;
			isbatch = 1;
			continue;
		}
		if (isbatch) {
			logmsg(LOGERR, "lost sync in batch - trying to re-sync\n");
			while(fgets(buff, 1024, ifp) != (char *)NULL) {
				if (strncmp(buff, RNEWSHDR, rlen) == 0)
					logmsg(LOGERR,
					"re-synced, maybe lost one article.\n");
				goto start_of_batch;
			}
			logmsg(LOGERR, "rest of batch contained garbage - dumped\n");
			log_close();
			exit(1);
		}
		/* Probably a single article, fall through. */
	}
#else
	if (fgets(buff, 1024, ifp) == (char *)NULL) state = 2;
#endif
	switch(state) {
		case -1:
			break;

		case 0:
			state = hdr_upd(buff, 0);
			break;

		case 1:
			fprintf(fp, "%s", buff);
			nlseen = (buff[0] == '\n');
			lines++;
			break;

		case 2:
			/* End of the article. */
			if (!opt_p) {
				/* Add signature. */
				l = app_signature(fp);
				if (l) nlseen = 1;
				lines += l;
			}
			/* Add a newline at the end if needed. */
			if (!nlseen) {
				fprintf(fp, "\n");
				lines++;
			}
			(void) fclose(fp);

			/* Check for old style control messages. */
			check_ctrl();

			/* And write the new completed article. */
			ret = hdr_out(artname, tempart, lines);
			state = -1;
			break;
	}
  }

  /* Record this in the log file. */
  if (opt_p == 1) {
#if QUIET
	if (debug)
#endif
	logmsg(LOGINFO, "%-8.8s  received %s ng %s subj '%s' from %s\n",
		remote, hdr.iden_t, hdr.ngrp_t, hdr.subj_t,
							hdr.from_t);
  } else {
	logmsg(LOGINFO, "%-8.8s  %sposted %s ng %s subj '%s' from %s\n",
		remote, (opt_R ? "re-" : ""), hdr.iden_t, hdr.ngrp_t,
					hdr.subj_t, hdr.from_t);
  }

  /* Now check the article to see if it's allowed, moderated etc. */
  if (opt_p == 0 && opt_R == 0) {
	if (check_group(hdr.ngrp_t, tempart, hdr.appr_t) != OK) {
		(void) wunlink(tempart);
		return(ERROR);
	}
	/* Any newsgroups left? */
	if (hdr.ngrp_t[0] == 0) {
		(void) wunlink(artname);
		(void) wunlink(tempart);
		return(OK);
	}
  }

  /* We now have an article. Post it. */
  state = do_post(tempart);
  if (state != 0) ret = ERROR;
    else (void) wunlink(artname);

  (void) wunlink(tempart);
  return(ret);
}


/* Usage message for the rnews program. */
void
rnews_usage()
{
  fputs("Usage: rnews [-d] [-U] < input_batch_file\n", stderr);
  exit(1);
}


/* Usage message for the inews program. */
void
usage()
{
  fputs ("Usage: inews [-v] -h | {-t title -n newsgroups} [-e expires] ",
	 stderr);
  fputs ("[-f sender]\n             [-d distribution] [-F refs] [-o ", stderr);
  fputs ("organization] [-a approved]\n             [-r reply-to] ", stderr);
  fputs ("[-x dontsend] [-c control] [artname]\n", stderr);
  fputs ("       inews [-v] -p artname\n", stderr);
  exit (1);
}


int
main(argc, argv)
int argc;
char *argv[];
{
  char buff[128];
  FILE *ifp, *fp;
  register char *sp;
  char *fname, *allflags;
  int c, st;
  SYS *ap;
  int unbatch = 0;
  int rnews = 0;

  /* See how we were called. */
  program = strrchr(*argv, '/');
  if (program == (char *)NULL) program = *argv;
    else program++;

  /* Is this rnews? */
  if (strcmp(program, "rnews") == 0) {
	/* Get the options for the rnews program. */
  	while ((st = getopt(argc, argv, "dU")) != EOF) switch(st) {
		case 'd':
			debug = 1;
			break;

		case 'U':
			unbatch = 1;
			break;

		default:
			rnews_usage();
  	}

	(void) umask(077);            /* set protection mask to rwx------ */

	if (unbatch == 1) {
		/* Called to act as unbatch? */
		if (argc != optind) rnews_usage();
		sprintf(buff, "%s/unbatch%s", LIBDIR, debug ? " -d" : "");
		wexec(buff, 0);
		logmsg(LOGERR, "cannot exec %s\n", buff);
		exit(1);
	} else {
		/* Find out where the batch file is. */
 		fp = stdin;
		if (optind == argc - 1) {
			/* File name as argument. */
			fname = argv[optind];
			fp = fopen(fname, "rb");
			if (fp == (FILE *)NULL) {
				logmsg(LOGERR, "cannot open %s\n", fname);
				exit(1);
			}
		} else if (optind < argc) usage();

		/* See if this is a batch or an incoming article. */
		c = fgetc(fp);
		ungetc(c, fp);
		if (c == '#') exit(mk_batch(fp) ? 1 : 0);
	}
	/* Fall through to original code. */
	opt_p = 1;
	opt_h = 1;
	rnews = 1;
	program = "rnews/inews";
	if (fp != stdin) fclose(fp);
   }

  /* No, continue as inews. */
  (void) umask(0133);
  (void) signal(SIGPIPE, SIG_IGN);

  old_uid = getuid();
  (void) time(&mytime);
  mydate = localtime(&mytime);

  /* First of all, fetch our HOST.DOMAIN names. */
  myname = hostname();

#ifndef COHERENT
  opterr = 0;
#endif
  allflags = "DhpvF:Ra:c:d:e:f:n:o:r:t:x:";
  while (!rnews && (st = getopt(argc, argv, allflags)) != EOF) switch(st) {
	case 'h':
		opt_h = 1;
		break;

	case 'p':
		opt_p = 1;
		opt_h = 1;      /* -p implies -h */
		break;

	case 'v':
		debug = 1;
		break;

	case 'F':
		hdr.refs_t = strsave(optarg);
		break;

	case 'R':
		opt_R = 1;
		break;

	case 'a':
		if((sp = strchr(optarg, '@')) != 0) {
			if (*(sp + 1)) {
				hdr.appr_t = strsave(optarg);
				break;
			}
			*sp = 0;
		}
		sprintf(buff, "%s@%s", optarg, myname);
		hdr.appr_t = strsave(buff);
		break;

	case 'c':
		hdr.ctrl_t = strsave(optarg);
		break;

	case 'd':
		hdr.dist_t = strsave(optarg);
		break;

	case 'e':
		hdr.expr_t = strsave(optarg);
		break;

	case 'f':
		hdr.send_t = strsave(optarg);
		break;

	case 'n':
		hdr.ngrp_t = strsave(optarg);
		break;

	case 'o':
		hdr.orgn_t = strsave(optarg);
		break;

	case 'r':
		hdr.repl_t = strsave(optarg);
		break;

	case 't':
		hdr.subj_t = strsave(optarg);
		break;

	case 'x':
		dontsend = strsave(optarg);
		break;

	default:
		usage();
  }

  if (opt_p == 1) {
	/* Need one more argument if we are inews. */
	if (!rnews && optind != (argc - 1)) usage();

	/* No arguments, or '-', means we read from stdin. */
	if (optind < argc && strcmp(argv[optind], "-") != 0) {
		if ((ifp = fopen(argv[optind], "r")) == (FILE *)NULL) {
			logmsg(LOGERR, "cannot open file %s\n", argv[optind]);
			exit(1);
		}
	} else ifp = stdin;
  } else {
	if (opt_h == 0) {
		if (hdr.subj_t == (char *)NULL ||
		    hdr.ngrp_t == (char *)NULL) usage();
	}
	if (optind != argc) {
		if ((ifp = fopen(argv[optind], "r")) == (FILE *)NULL) {
			logmsg(LOGERR, "cannot open(%s)\n", argv[optind]);
			exit(1);
		}
	} else ifp = stdin;
  }

  /* Now the files are opened go to the configuration directory. */
  if (chdir(CONFDIR)) {
    logmsg(LOGSYS|LOGERR, "Can't change to `%s'\n", CONFDIR);
    exit(1);
  }

  /* Read the "sys" file into core. */
  if (sys_rd() != 0) {
	logmsg(LOGERR, "cannot read sys file\n");
	exit(1);
  }

  /* See what we need to put into the Path: line. */
  mypname = myname;
  for(ap = sys; ap; ap = ap->next)
	if ((!strcmp(ap->name, "ME") || !strcmp(ap->name, myname)) &&
		ap->nsys != (char *)NULL) {
		mypname = ap->nsys;
		break;
	}

  /* Now, get our organizational description. */
  if ((fp = fopen(ORGANIZAT, "r")) != (FILE *)NULL) {
	if ((sp = fgets(buff, 128, fp)) != (char *)NULL && buff[0] != '\n') {
		if ((sp = strchr(buff, '\n')) != (char *)NULL) *sp = '\0';
		strcpy(myorg, buff);
	} else strcpy(myorg, "");
	(void) fclose(fp);
  } else strcpy(myorg, "");

  /* Keep logfile opened. */
  log_open();
#if INEWS_UNBATCH
  time(&a_time);
  do {
  	st = inews(ifp);
	a_count++;
  } while(st == OK && !feof(ifp));
  a_count--;
  if (a_count > 1) {
  	time(&mytime);
	if (mytime <= a_time) mytime = a_time + 1;
	logmsg(LOGINFO, "stats\treceived %d articles (%d/sec) posted %d articles (%d/sec)\n", 
	a_count, a_count / (int)(mytime - a_time),
	a_posted, a_posted / (int)(mytime - a_time));
  }
#else
  st = inews(ifp);
#endif
  (void) fclose(ifp);
  log_close();

  /* Always exit with 0 (success) or 1 (failure). */
  exit(st ? 1 : 0);
}

