/*
 * slurp - a passive nntp news client
 *
 * Copyright (C) 1992/93/94/95 Stephen Hebditch <steveh@tqmcomms.co.uk>.
 * All rights reserved. TQM Communications, BCM Box 225, London, WC1N 3XX.
 *
 * See README for more information and disclaimers
 *
 * This is the main routine for slurp together with the routines to
 * handle the slurp.sys configuration file and command line arguments.
 *
 *
 * $Id: slurp.c,v 1.10 1995/02/07 14:34:00 root Exp root $
 *
 * $Log: slurp.c,v $
 * Revision 1.10  1995/02/07  14:34:00  root
 * Don't write new time in hostfile if didn't do a NEWNEWS.
 * Added copyright notice.
 *
 * Revision 1.9  1995/01/10  13:02:42  root
 * Moved includes from slurp.h.
 * Removed test_time() function.
 * Added -c, -h, -n, -o, -p and -x options.
 * Rewrote read_sys yet again, fixing a bug which caused it to be
 * wrongly parsed when items were missing, adding support for new
 * features and generally making it rather cleaner.
 * Removed get_ntime() and set_ntime() functions, now renamed to
 * read_hostfile() and write_hostfile() in hostfiles.c
 * Moved do_authinfo() and do_mode_reader() to sockets.c.
 * General tidying-up.
 *
 * Revision 1.8  1993/08/20  10:34:50  root
 * Unlink backup time file before renaming or an error occurs
 * with the rename under SVR3.
 *
 * Revision 1.7  1993/06/07  11:15:00  root
 * Added support for users to supply a filename for the time file
 * on the command line, for use with machines with short filenames.
 * Rewrote read_sys (again!) for a much cleaner implementation.
 * Fixed problem of time file being wrongly set if slurp was
 * interrupted before the newnews phase had completed.
 * If can't rename time file then don't abort, just write the new
 * file.
 *
 * Revision 1.6  1993/04/22  18:22:20  root
 * Added signal handler to catch SIGHUP, SIGINT, SIGQUIT and SIGTERM.
 * If occur then report signal in syslog and possibly submit open
 * batch to news and dump message ids of uncollected articles to
 * slurp.<hostname>
 *
 * Revision 1.5  1993/03/01  17:51:33  root
 * read_sys can cope with lines longer than BUFSIZ.
 * report when attempting to load unretrieved message ids.
 * Move sublist to a parameter after the hostname, separate by a slash.
 * Changed some system error checking.
 *
 * Revision 1.4  1993/02/14  14:57:43  root
 * Added support for simple authorisation protocol.
 * Added support for INN's 'MODE READER' command.
 * Re-arranged command line options.
 * Rewrote read_sys and added flags and authorisation options to it.
 * Rewrote get_ntime and set_ntime to use a filename of slurp.<hostname>
 * instead of slurp.tim, solving lack of locking and allowing the file
 * to contain a list of unretrieved message ids on the lines following
 * the time.
 * Don't care if slurp.<hostname> doesn't exist already.
 * If RNEWS is not defined, then change to INDIR for writing out batch
 * files.
 *
 * Revision 1.3  1992/12/15
 * Open syslog *before* we start doing things that might write to it.
 * Informational messages logged as LOG_INFO.
 * Assorted minor tidy-ups.
 *
 * Revision 1.2  1992/12/07
 * Corrected test for 4.2/4.3 BSD syslog open.
 *
 * Revision 1.1  1992/12/06
 * Made no_time_flag global.
 * Fixed null dereferencing of nn_distributions.
 *
 * Revision 1.0  1992/08/07
 * Initial coding.
 *
 */

#include "conf.h"

/* POSIX headers */

#define _POSIX_SOURCE 1
#include <sys/types.h>
#include <ctype.h>
#include <limits.h>
#include <signal.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#ifndef NOUNISTD
	#include <unistd.h>
#endif

/* Local headers */

#include "nntp.h"
#include "patchlevel.h"
#include "slurp.h"
#include "syslog.h"


/* Command line / configuration file flags */

int debug_flag = FALSE;
int no_history_flag = FALSE;
int no_id_read_flag = FALSE;
int no_id_write_flag = FALSE;
int no_time_write_flag = FALSE;
int pipe_flag = FALSE;
int progress_flag = FALSE;
static int authinfo_flag = FALSE;
static int local_time_flag = FALSE;
static int mode_reader_flag = FALSE;
static int no_collect_flag = FALSE;
static int no_newnews_flag = FALSE;
static int output_flag = FALSE;

/* Host information */

char *hostname = NULL;
static int portno = 0;
static char *ai_username = NULL;
static char *ai_password = NULL;

/* NEWNEWS information */

char *nn_newsgroups    = NULL;
char *nn_time          = NULL;
char *nn_distributions = NULL;

/* Misc options */

FILE *overflowfp = NULL;
char *output = NULL;
char hostfile [PATH_MAX];
static char *sublist = NULL;

/* Counters */

int waiting = 0;
int dupart = 0;
int misart = 0;
#ifdef SITEEXCLUDING
int exclart = 0;
#endif /* SITEEXCLUDING */
int xfrart = 0;
long totalsize = 0;

/* Misc variables */

char *pname;
time_t nexttime;


/*
 * parse_args - Parse the command line arguments. Returns TRUE if there
 * is an error, otherwise returns FALSE.
 */

	static int
parse_args (int argc, char **argv)
	{
	char *extn;
	char *pos;
	extern char *optarg;
	extern int optind;
	int c;
	
	while ((c = getopt (argc, argv, "a:g:o:p:t:cdhilmnrwx")) != EOF)
		switch (c)
			{
			case 'a':	/* Do an authinfo */
				if ((pos = strchr (optarg, '/')) != NULL)
					{
					ai_username = optarg;
					*pos++ = '\0';
					ai_password = pos;
					authinfo_flag++;
					}
				else
					{
					(void) fprintf (stderr, "No authinfo password supplied\n");
					return (TRUE);
					}
				break;
			case 'g':	/* Newsgroups list */
				if ((pos = strchr (optarg, '/')) != NULL)
					{
					*pos++ = '\0';
					nn_distributions = pos;
					}
				else
					nn_distributions = "";
				nn_newsgroups = optarg;
				break;
			case 'o':	/* Set output command or directory */
				output = optarg;
				output_flag++;
				break;
			case 'p':	/* Set port */
				portno = atoi (optarg);
				break;
			case 't':	/* Start time */
				nn_time = optarg;
				if (!(isdigit (nn_time [0]) &&
			          isdigit (nn_time [1]) &&
			          isdigit (nn_time [2]) &&
			          isdigit (nn_time [3]) &&
			          isdigit (nn_time [4]) &&
			          isdigit (nn_time [5]) &&
			          isspace (nn_time [6]) &&
			          isdigit (nn_time [7]) &&
			          isdigit (nn_time [8]) &&
			          isdigit (nn_time [9]) &&
			          isdigit (nn_time [10]) &&
			          isdigit (nn_time [11]) &&
			          isdigit (nn_time [12])))
					{
					(void) fprintf (stderr, "Invalid time specification - should be 'YYMMDD HHMMSS'\n");
					return (TRUE);
					}
				break;
			case 'c':	/* Don't collect articles */
				no_collect_flag++;
				break;
			case 'd':	/* Debugging on */
				debug_flag++;
				progress_flag = FALSE;
				break;
			case 'h':	/* Don't do history lookups */
				no_history_flag++;
				break;
			case 'i':	/* Don't load unprocessed ids */
				no_id_read_flag++;
				break;
			case 'l':	/* Use local time */
				local_time_flag++;
				break;
			case 'm':	/* Don't write next time to hostfile */
				no_time_write_flag++;
				break;
			case 'n':	/* Don't do a NEWNEWS */
				no_newnews_flag++;
				no_time_write_flag++;
				break;
			case 'r':	/* Do a 'MODE READER' */
				mode_reader_flag++;
				break;
			case 'w':	/* Don't write uncollected ids to hostfile */
				no_id_write_flag++;
				break;
			case 'x':	/* Print progress information */
				progress_flag++;
				debug_flag = FALSE;
				break;
			default:
				return (TRUE);
			}

	/* Get server name and possible host file and sublist */
	if (optind < argc)
		{
		hostname = argv [optind];
		if ((extn = strchr (hostname, ':')) != NULL)
			*extn++ = '\0';
		if ((sublist = strchr (hostname, '/')) != NULL)
			*sublist++ = '\0';
		(void) strcpy (hostfile, HOSTFILE);
		if (extn == NULL)
			(void) strcat (hostfile, hostname);
		else
			(void) strcat (hostfile, extn);
		if (sublist != NULL)
			{
			(void) strcat (hostfile, ".");
			(void) strcat (hostfile, sublist);
			}
		}
	else
		{
		(void) fprintf (stderr, "No server name supplied\n");
		return (TRUE);
		}

	return (FALSE);
	}


/*
 * read_sys - Read in the appropriate entry from the slurp.sys file
 * for the specified hostname. Stores the relevant newsgroups for that
 * host in nn_newsgroups and the relevant distribution in nn_distributions.
 */

	static void
read_sys (void)
	{
	FILE *sysfp;
	char *flag;
	char *p1;
	char *p2;
	char line [BUFSIZ];
	char searchname [BUFSIZ];
	int foundhost = FALSE;
	int j;
	int object = 0;
	int more = FALSE;

	/* Attempt to open the sys file */
	if ((sysfp = fopen (SYSFILE, "r")) == NULL)
		log_sys ("read_sys: error opening %s", SYSFILE);

	/* Create search name */
	(void) strcpy (searchname, hostname);
	if (sublist != NULL)
		{
		(void) strcat (searchname, "/");
		(void) strcat (searchname, sublist);
		}

	for (;;)
		{
		/* Read in a line */
		(void) fgets (line, sizeof (line), sysfp);

		/* If end of file then error unless we have a complete entry */
		if (feof (sysfp))
			{
			if (foundhost)
				break;
			else
				{
				log_msg ("read_sys: no entry found for host %s in %s",
						 searchname, SYSFILE);
				exit (2);
				}
			}

		/* If a read error then report it */
		if (ferror (sysfp))
			log_sys ("read_sys: error reading %s", SYSFILE);

		/* Zap anything after the comment character */
		if ((p1 = strchr (line, '#')) != NULL)
			*p1 = '\0';

		/* If continuation character then indicate more to follow */
		if ((p1 = strchr (line, '\\')) != NULL)
			{
			*p1 = '\0';
			more = TRUE;
			}
 
		/* Strip leading spaces */
		if (isspace (line [0]) != NULL)
			{
			/* Find first non-blank */
			for (p1 = line; isspace (*p1); p1++);
			/* Copy down the remainder of the line */
			for (j = 0; p1 [j] != '\0'; j++)
				line [j] = p1 [j];
			}

		/* If NL found, then there isn't any more of this entry */
		if ((p1 = strchr (line, '\n')) != NULL)
			{
			*p1 = '\0';
			more = FALSE;
			}
		else
			more = TRUE;

		/* Found a hostname */
		if (object == 0)
			{
			if ((p1 = strchr (line, ':')) != NULL)
				{
				*p1++ = '\0';
				if (strcmp (line, searchname) == 0)
					foundhost = TRUE;
				(void) strcpy (line, p1);
				object = 1;							/* Newsgroups next */
				}
			}
		
		/* Currently adding newsgroups */
		if (object == 1)
			{
			p1 = strchr (line, '/');
			p2 = strchr (line, ':');
			if ((p1 != NULL) && ((p2 == NULL) || (p1 < p2)))
				{
				*p1++ = '\0';
				object = 2;							/* Distributions next */
				}
			else if (p2 != NULL)
				{
				*p2++ = '\0';
				object = 3;							/* Flags next */
				}
			if (foundhost)
				nn_newsgroups = stradd (nn_newsgroups, line);
			if (object == 2)
				(void) strcpy (line, p1);
			else if (object == 3)
				(void) strcpy (line, p2);
			}

		/* Currently adding distributions */
		if (object == 2)
			{
			if ((p1 = strchr (line, ':')) != NULL)
				{
				*p1++ = '\0';
				object = 3;							/* Flags next */
				}
			if (foundhost)
				nn_distributions = stradd (nn_distributions, line);
			if (p1 != NULL)
				(void) strcpy (line, p1);
			}

		/* Currently setting flags */
		if (object == 3)
			{
			if ((p1 = strchr (line, ':')) != NULL)
				{
				*p1++ = '\0';
				object = 4;							/* authinfo user next */
				}
			if (foundhost)
				{
				for (flag = line; *flag != '\0'; flag++)
					switch (*flag)
						{
						case 'i':
							no_id_read_flag++;
							break;
						case 'l':
							local_time_flag++;
							break;
						case 'r':
							mode_reader_flag++;
						 	break;
						case 'w':
							no_id_write_flag++;
							break;
						default:
							break;
						}
					}
			if (p1 != NULL)
				(void) strcpy (line, p1);
			}

		/* Currently setting username */
		if (object == 4)
			{
			p1 = strchr (line, '/');
			p2 = strchr (line, ':');
			if ((p1 != NULL) && ((p2 == NULL) || (p1 < p2)))
				{
				*p1++ = '\0';
				object = 5;							/* authinfo passwd next */
				}
			else if (p2 != NULL)
				{
				*p2++ = '\0';
				object = 6;							/* output next */
				}
			if ((foundhost) && (!authinfo_flag) && (*line != '\0'))
				ai_username = stradd (ai_username, line);
			if (object == 5)
				(void) strcpy (line, p1);
			else if (object == 6)
				(void) strcpy (line, p2);
			}

		/* Currently setting password */
		if (object == 5)
			{
			if ((p1 = strchr (line, ':')) != NULL)
				{
				*p1++ = '\0';
				object = 6;							/* output next */
				}
			if ((foundhost) && (!authinfo_flag) && (*line != '\0'))
				ai_password = stradd (ai_password, line);
			if (p1 != NULL)
				(void) strcpy (line, p1);
			}

		/* Currently setting output */
		if (object == 6)
			{
			if ((foundhost) && (!output_flag) && (*line != '\0'))
				output = stradd (output, line);
			}

		/* If no more, then either got complete entry or not found yet */
		if (!more)
			{
			if (foundhost)
				break;
			else
				object = 0;
			}
		}

	(void) fclose (sysfp);

	if (nn_newsgroups == NULL)
		{
		log_msg ("read_sys: newsgroups missing for host %s in %s",
		searchname, SYSFILE);
		exit (2);
		}

	if (nn_distributions == NULL)
		nn_distributions = "";

	if ((ai_username == NULL) && (ai_password != NULL))
		{
		log_msg ("read_sys: authinfo username missing for host %s in %s",
		searchname, SYSFILE);
		exit (2);
		}

	if ((ai_username != NULL) && (ai_password == NULL))
		{
		log_msg ("read_sys: authinfo password missing for host %s in %s",
		searchname, SYSFILE);
		exit (2);
		}

	return;
	}


/*
 * interrupt - signal handler to report signal in log and possibly
 * submit remaining batch to news and dump uncollected message ids.
 */

	static void
interrupt (int signo)
	{
	log_msg ("interrupt: received signal %d", signo);

	enqueue_batch ();
	write_hostfile ();
	exit (1);
	}


/*
 * set_signals - set up signal handler to catch appropriate signals.
 */

	static void
set_signals (void)
	{
	if (signal (SIGHUP, interrupt) == SIG_ERR)
		log_sys ("set_signals: can't catch SIGHUP");
	if (signal (SIGINT, interrupt) == SIG_ERR)
		log_sys ("set_signals: can't catch SIGINT");
	if (signal (SIGQUIT, interrupt) == SIG_ERR)
		log_sys ("set_signals: can't catch SIGQUIT");
	if (signal (SIGTERM, interrupt) == SIG_ERR)
		log_sys ("set_signals: can't catch SIGTERM");
	}


/*
 * MAIN PROCEDURE
 */

	int
main (int argc, char **argv)
	{
    int ret;
	time_t starttime, endtime;

	/* Set the name of the program */
	pname = (pname = (char *) strrchr (argv [0], '/')) ? pname + 1 : argv [0];

	/* Parse the arguments */
	if (parse_args (argc, argv))
		{
		(void) fprintf (stderr, "\nSlurp version %s. Copyright (C) 1992/93/94/95 TQM Communications.\n", PATCHLEVEL);
		(void) fprintf (stderr, "All rights reserved. Slurp information: slurp-info@tqmcomms.co.uk.\n\n");
		(void) fprintf (stderr, "Usage: %s [-g newsgroups/distribution] [-t time] [-a username/password]\n", pname);
		(void) fprintf (stderr, "       [-o output] [-p portno] [-c] [-d] [-h] [-i] [-l] [-m] [-n] [-r] [-w] [-x]\n");
		(void) fprintf (stderr, "       server[/sublist][:hostfile]\n");
		exit (2);
		}

	/* Open syslog if required with appropriate BSD 4.2/4.3 call */
#ifdef SYSLOG
	openlog (pname, LOG_PID, SYSLOG);
#endif

	/* If groups not supplied in args, then get from slurp.sys file */
	if (nn_newsgroups == NULL)
		read_sys ();

	/* If output not specified, then use default */
	if (output == NULL)
		output = OUTPUT;

	/* Set variables for when output is a pipe to another program */
	if (*output == '|')
		{
		output++;
		pipe_flag++;
		}

	/* Open the history file */
	if (open_history ())
		log_sys ("can't open history file %s", HISTORY_FILE);

#ifdef SITEEXCLUDING
	/* Get site names to exclude */
	site_init();
#endif /* SITEEXCLUDING */

	/* Load start time or ids from hostfile if appropriate */
	read_hostfile ();

	if (debug_flag)
		{
		(void) fprintf (stderr, "server: %s\n", hostname);
		(void) fprintf (stderr, "time: %s\n", nn_time);
		(void) fprintf (stderr, "newsgroups: '%s'\n", nn_newsgroups);
		(void) fprintf (stderr, "distributions: '%s'\n", nn_distributions);
		}

	/* Change to the incoming batch directory */
	if (!pipe_flag)
		if (chdir (output))
			log_sys ("can't change directory to %s", output);

	/* Unless don't write flag set, get time for next NEWNEWS */
	if (!no_time_write_flag)
		{
		if (local_time_flag)
			nexttime = time ((time_t *) 0);
		else
			if ((nexttime = server_time (hostname)) == 0)
				exit (3);
		}

	/* Set up the connection to the server */
	switch (ret = server_init (hostname, portno))
		{
		case -1 :
			exit (3);
		case OK_CANPOST:
		case OK_NOPOST:
			break;
		default:
			log_msg ("can't talk to %s: got response code %d", hostname, ret);
			exit (4);
		}

	/* If authinfo details supplied, then use 'em */
	if (ai_username != NULL)
		do_authinfo (ai_username, ai_password);

	/* Switch INN to nnrpd instead of innd if needed */
	if (mode_reader_flag)
		do_mode_reader ();

	/* Get a list of the new articles unless told not to */
	if (!no_newnews_flag)
		get_ids ();

	/* Now get the actual articles unless told not to */
	if (!no_collect_flag)
		{
#ifdef SPEEDSTATS
		starttime = time ((time_t *) 0);
#endif
		if (waiting > 0)
			{
			set_signals ();
			get_articles ();
			}
#ifdef SPEEDSTATS
		endtime = time ((time_t *) 0);
#endif
		}

	/* Time to say goodbye */
	close_server ();
	close_history ();
#ifdef SITEEXCLUDING
	site_close();
#endif /* SITEEXCLUDING */

	/* Submit the remaining batch, if present */
	enqueue_batch ();

	/* Update new time and / or uncollected ids */
	write_hostfile ();

#ifdef SYSLOG
	if (!debug_flag)
#ifdef SITEEXCLUDING
		syslog (LOG_INFO, "Processed %d new, %d duplicate, %d missing, %d excluded articles",
				xfrart, dupart, misart, exclart);
#else
		syslog (LOG_INFO, "Processed %d new, %d duplicate, %d missing articles",
				xfrart, dupart, misart);
#endif /* SITEEXCLUDING */
	else
#endif
#ifdef SITEEXCLUDING
		(void) fprintf (stderr, "Processed %d new, %d duplicate, %d missing, %d excluded articles\n",
						xfrart, dupart, misart, exclart);
#else
		(void) fprintf (stderr, "Processed %d new, %d duplicate, %d missing articles\n",
						xfrart, dupart, misart);
#endif /* SITEEXCLUDING */

#ifdef SPEEDSTATS
  #ifdef SYSLOG
	if (!debug_flag)
		syslog (LOG_INFO, "Average transfer speed %ld cps",
				totalsize / (starttime == endtime ? 1 : endtime - starttime));
	else
  #endif
		(void) fprintf (stderr, "Average transfer speed %ld cps\n",
				totalsize / (starttime == endtime ? 1 : endtime - starttime));
#endif

	exit (0);
	}

/* END-OF-FILE */


syntax highlighted by Code2HTML, v. 0.9.1