/* :ts=4
 * transmit - transmit incoming articles to neighbouring machines
 *
 *	$Log$
 */

#include <stdio.h>
#ifdef unix
# include <sys/types.h>
#endif /* unix */
#include "libc.h"
#include "news.h"
#include "config.h"
#include "headers.h"
#include "active.h"
#include "article.h"
#include "msgs.h"
#include "system.h"
#include "trbatch.h"
#include "transmit.h"

/* forwards */
FORWARD boolean oktransmit();
FORWARD void ejaculate(), trappend();

/* private */
static boolean debug = NO;

void
transdebug(state)
boolean state;
{
	debug = state;
}

/*
 * For each system in "sys" other than this one,
 * transmit this article when its ng pattern matches
 * art->h.h_distr (which may be just a copy of art->h.h_ngs).
 */
void
transmit(art, exclude)
register struct article *art;
char *exclude;					/* no copy to this site */
{
	register struct system *sys;
	register int bsysno = 0;	/* ordinal # of batch sys entry */

	rewndsys();
	if (debug)
		(void) fprintf(stderr, "just rewound sys file\n");
	while ((sys = nextsys()) != NULL) {
#ifndef AMIGA
		if (debug)
			(void) fprintf(stderr,
				"sy_name=%s sy_ngs=%s sy_distr=%s\n",
				sys->sy_name, sys->sy_ngs, sys->sy_distr);
#endif /* !AMIGA */
		if (oktransmit(art, sys, exclude))
			ejaculate(art, sys, bsysno);
		if (sys->sy_flags&FLG_BATCH)
			++bsysno;
	}
	if (debug)
		(void) fprintf(stderr, "just finished reading sys file\n");
}

/*
 * Is it okay to send the article corresponding to "art" to site "sys",
 * excluding site "exclude"?
 *
 * If L(n) flag is on, must have been posted within n hops of here.
 * Never send to this site, nor the "exclude" site, nor any site with a host
 * in sys->sy_excl named in Path:, nor any site named in Path:.
 *
 * Newsgroups: must match sys's subscription list.
 * Distribution: must match sys's distribution list.  (RFC 850 is wrong:
 * Distribution:s are *not* patterns, they are lists.  See RFC 1036.)
 *
 * If m flag is on, group(s) must be moderated; if u flag is on,
 * must be unmoderated.  (If both are on, act as if neither is on.)
 */
STATIC boolean
oktransmit(art, sys, exclude)
register struct article *art;
register struct system *sys;
char *exclude;				/* no copy to him */
{
	static char *canpath;
	static long lastid = -1;
	register int flags = sys->sy_flags;
	register char *site = sys->sy_name;
	register char *path;
	register int result = YES;

	if (art->a_id != lastid) {	/* new article? */
		lastid = art->a_id;
		nnfree(&canpath);
		canpath = canonpath(art->h.h_path, art->h.h_approved, art->h.h_sender);
#ifdef notdef			/* DEBUG */
		fprintf(stderr, "path=%s canonpath=%s\n", art->h.h_path,
			canpath);
#endif
	}
	path = canpath;
	if ((flags&FLG_LOCAL && hopcount(path) > sys->sy_lochops) ||
		!strcmp(hostname(), site) ||
		(exclude != NULL && STREQ(exclude, site)) ||
		hostin(site, path) ||
		(sys->sy_excl != NULL && anyhostin(sys->sy_excl, path)) ||
		!ngmatch(sys->sy_ngs, art->h.h_ngs) ||
		!ngmatch(sys->sy_distr, art->h.h_distr))
		result = NO;
	else if (flags&(FLG_MOD|FLG_UNMOD)) {	/* u, m flag selection */
		if ((flags&(FLG_MOD|FLG_UNMOD)) != (FLG_MOD|FLG_UNMOD))
			result = (flags&FLG_MOD? moderated(art->h.h_ngs):
						!moderated(art->h.h_ngs));
	}
	return result;
}

/*
 * Send the article denoted by art to the system denoted by sys.
 *
 * When a filename is needed, we use the first one in art->a_files
 * rather than art->a_tmpf because we want a permanent name, and
 * translate it to a full path name to avoid ambiguity.
 *
 * Side-effect: prints the system name on stdout for logging.
 */
STATIC void
ejaculate(art, sys, bsysno)
register struct article *art;
register struct system *sys;
int bsysno;
{
	register char *fullname;	/* sometimes is a message-id */

	if (debug)
		(void) fprintf(stderr, "transmitting %s to %s\n",
			art->h.h_msgid, sys->sy_name);
    	(void) printf(" %s", sys->sy_name);	/* logging */
	if (sys->sy_flags&FLG_IHAVE)
		fullname = art->h.h_msgid;
	else {
		register char *filename = first(art->a_files);

		mkfilenm(filename);
    	fullname = fullartfile(filename);
		free(filename);
	}
#ifdef PARANOID
	fullname = strsave(fullname);
#endif
	if (sys->sy_flags&FLG_BATCH)
		trbatch(art, sys, fullname, bsysno);
	else
		trcmd(art, sys, fullname);
#ifdef PARANOID
	free(fullname);
#endif
}

/*
 * Execute sys->sy_cmd with the current article as stdin
 * and filename substituted for %s in sys->sy_cmd (if any).
 *
 * Search path includes $NEWSCTL/bin and $NEWSBIN/relay.
 * redirect stdin to prevent consuming my stdin & so cmd's stdin
 * is filename by default.
 *
 * We use strcat instead of sprintf if syscmd contains no %.
 * this avoids the 128-byte restriction on printf output
 * (see printf(3) BUGS, at least in V7).
 */
void
trcmd(art, sys, filename)
struct article *art;
struct system *sys;
char *filename;
{
	register char *cmd;
	int exitstat;
	char *syscmd = sys->sy_cmd, *percent;
	static char *ctlcmd = NULL, *bincmd = NULL;

#if defined(AZTEC_C) || defined(LATTICE)
	if (debug)
		fprintf(stderr, "  shell(\"%s\", %s, NIL)...", syscmd, filename);
	exitstat = shell(syscmd, filename, NULL);
	if (debug)
		fprintf(stderr, "  return was %d\n", exitstat);
#else /* Unix? */
	if (ctlcmd == NULL)
		ctlcmd = strsave(ctlfile("bin"));
	if (bincmd == NULL)
		bincmd = strsave(binfile("relay"));
	cmd = nemalloc((unsigned)(strlen("PATH=") + strlen(ctlcmd) +
		STRLEN(":") + strlen(bincmd) + STRLEN(":") + strlen(newspath()) +
		STRLEN(";<") + strlen(filename) + STRLEN(" (") +
		strlen(syscmd) + strlen(filename) + STRLEN(")") + SIZENUL));
	(void) strcpy(cmd, "PATH=");
	(void) strcat(cmd, ctlcmd);
	(void) strcat(cmd, ":");
	(void) strcat(cmd, bincmd);
	(void) strcat(cmd, ":");
	(void) strcat(cmd, newspath());
	(void) strcat(cmd, ";<");
	(void) strcat(cmd, filename);
	(void) strcat(cmd, " (");
	percent = strchr(syscmd, '%');
	if (percent == NULL)
		(void) strcat(cmd, syscmd);
	else {
		char *pcent2;

		++percent;
		pcent2 = strchr(percent, '%');
		if (pcent2 != NULL) {
			art->a_status |= ST_DROPPED;
			(void) fprintf(stderr, "%s: `%s' contains two %%'s\n",
				progname, syscmd);
		} else if (*percent != 's' && *percent != '%') {
			art->a_status |= ST_DROPPED;
			(void) fprintf(stderr, "%s: `%s' contains %%%c, not %%s\n",
				progname, syscmd, *percent);
		} else
			(void) sprintf(cmd+strlen(cmd), syscmd, filename);
	}
	(void) strcat(cmd, ")");
	exitstat = system(cmd);
#endif /* Amiga compilers */
	if (exitstat != 0) {
		art->a_status |= ST_DROPPED;
		(void) fprintf(stderr, "%s: `", progname);
		(void) fputs(cmd, stderr);
		(void) fprintf(stderr, "' returned exit status 0%o\n", exitstat);
	}
	free(cmd);
}

/*
 * Append "filename" to sys->sy_cmd.  bsysno is the ordinal # of this batch
 * sys line.  If bsysno is low enough, use the batchfile cache of batch file
 * descriptors.
 */
void
trbatch(art, sys, filename, bsysno)
register struct article *art;
struct system *sys;
char *filename;
register int bsysno;
{
	register struct batchfile *bf = bfopen(sys->sy_cmd, bsysno);

	if (bf == NULL || bf->bf_str == NULL)
		art->a_status |= ST_DROPPED;
	else {
		trappend(art, sys, bf, filename);
		art->a_status |= bffkclose(bsysno);
	}
}

/*
 * write filename, message-id or size on batch file "bf".
 * under the 'f' flag (FLG_SZBATCH), include the size in bytes of the article
 * after "name" to assist the C news batcher.  under the 'n' flag (FLG_NBATCH),
 * write the article's message-id.  afterward, flush "bf" in case
 * the machine crashes before the stream is closed.
 */
STATIC void
trappend(art, sys, bf, name)
register struct article *art;
register struct system *sys;
register struct batchfile *bf;
char *name;
{
	if (fputs(name, bf->bf_str) == EOF)
		fulldisk(art, bf->bf_name);
	if (sys->sy_flags&FLG_SZBATCH &&
	    fprintf(bf->bf_str, " %ld", art->a_charswritten) == EOF)
		fulldisk(art, bf->bf_name);
	if (sys->sy_flags&FLG_NBATCH &&
	    fprintf(bf->bf_str, " %s", art->h.h_msgid) == EOF)
		fulldisk(art, bf->bf_name);

	/* don't check putc return value for portability; use ferror */
	(void) putc('\n', bf->bf_str);
	if (ferror(bf->bf_str) || bfflush(bf) == EOF)
		fulldisk(art, bf->bf_name);		
}

/*
 * really close all the open batch files
 */
statust
trclose()
{
	return bfrealclose();
}
