/*
 * $Id: suck_util.c,v 1.4 2002/06/06 14:46:32 conrads Exp $
 * 
 * Various routines used by suck program
 */

#include <sys/types.h>
#include <glob.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/socket.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <regex.h>
#include <stdlib.h>
#include <stdio.h>
#include <pwd.h>

#include "msuck.h"
#include "nntp.h"

extern SERVER server[];
extern ACTIVE_INFO active[];
extern CONNECTION local;
extern FILTER filter[];
extern FILE *sucklog, *killlog, *errlog;
extern int local_active_groups;

extern char *errlogpath;
extern char *killlogpath;
extern char *sucklogpath;
extern char *filterspath;
extern char *newsrc_path_template;

/*
 * get a socket and try connecting until we get a 200 message, return socket
 * on success, 0 on failure to connect, or -1 on hard error
 * 
 * FILE pointers to input/output streams for socket are opened as well.  Output
 * stream is set to line-buffered mode.
 * 
 * Note: eats the server's greeting message
 */
int
connect_to_server(const char *servername, CONNECTION *server)
{
	struct hostent *serverinfo;
	struct sockaddr_in serversock;
	char linebuf[MAXLINE + 1];

	if ((serverinfo = gethostbyname(servername)) == NULL)
	{
		herror("connect_to_server(): server lookup failed");
		return -1;
	}
	bzero((void *)&serversock, sizeof(serversock));
	serversock.sin_family = AF_INET;
	serversock.sin_port = htons(119);
	bcopy(serverinfo->h_addr, (char *)&serversock.sin_addr, serverinfo->h_length);

	for (;;)
	{
		if ((server->sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1)
		{
			perror("connect_to_server(): couldn't obtain a new socket");
			return -1;
		}
		if (connect(server->sockfd, (struct sockaddr *) & serversock, sizeof(serversock)) == 0)
		{
			if ((server->in = fdopen(server->sockfd, "r")) == NULL)
			{
				perror("connect_to_server(): error on fdopen for input stream");
				close(server->sockfd);
				return -1;
			}
			if ((server->out = fdopen(server->sockfd, "w")) == NULL)
			{
				perror("connect_to_server(): error on fdopen for output stream");
				fclose(server->in);
				return -1;
			}

			/*
			 * set output stream line buffered so we don't have
			 * to fflush() constantly
			 */
			if (setvbuf(server->out, NULL, _IOLBF, 0) == EOF)
			{
				perror("connect_to_server(): error changing buffering mode on output stream");
				close_server(*server);
				return -1;
			}
			/* try reading the server greeting line */
			if (fgets(linebuf, MAXLINE, server->in) == NULL)
			{
				perror("connect_to_server(): read error on socket in connect_to_server()");
				close_server(*server);
				return -1;
			}
			/* should have gotten server greeting */
			if (strncmp(linebuf, NNTP_POSTOK, 3) == 0)
				return server->sockfd;
			else
			{
				close_server(*server);
				sleep(30);
			}
		}
		else
		{
			perror("connect_to_server(): connection to server failed");
			close(server->sockfd);
			return 0;
		}
	}
}

/*
 * close connection to server (abort, don't do "quit")
 */
void
close_server(CONNECTION server)
{
	fclose(server.in);
	fclose(server.out);
}

/*
 * quit a server the "correct" way
 */
void
quit_server(CONNECTION server)
{
	char linebuf[MAXLINE + 1];

	/*
	 * send quit command (we may still be in a sane state on the server)
	 */
	fprintf(server.out, "quit\r\n");

	/* eat any server output, look for terminating string */
	do
	{
		if (fgets(linebuf, MAXLINE, server.in) == NULL)
		{
			/*
			 * ignore the cause of any error here, as we may have
			 * been sent here *because* of an error already!
			 */
			break;
		}
	}
	while (strncmp(linebuf, NNTP_GOODBYE_ACK, 3) != 0);

	close_server(server);
}

/*
 * get active info for group from server,
 * assumes connection has already been opened
 * 
 * returns 1 on success or 0
 */
int
get_active_info(CONNECTION server,
		const char *group,
		ARTNUM *active_lo,
		ARTNUM *active_hi)
{
	char linebuf[MAXLINE + 1];
	char grp[MAXGROUP + 1];
	int responsecode, numarts;

	fprintf(server.out, "mode reader\r\n");
	if (fgets(linebuf, MAXLINE, server.in) == NULL)
	{
		perror("get_active_info(): read error in get_active_info()");
		return 0;
	}
	/* should have gotten second server greeting */
	if (strncmp(linebuf, NNTP_POSTOK, 3) != 0)
	{
		return 0;
	}
	fprintf(server.out, "group %s\r\n", group);
	if (fgets(linebuf, MAXLINE, server.in) == NULL)
	{
		perror("get_active_info(): read error in get_active_info()");
		return 0;
	}
	/* should have gotten group info */
	if (strncmp(linebuf, NNTP_GROUPOK, 3) != 0)
	{
		return 0;
	}
	if (sscanf(linebuf, "%d %d %lu %lu %s",
		   &responsecode, &numarts, active_lo, active_hi, grp) != 5)
	{
		perror("get_active_info(): read error in get_active_info()");
		return 0;
	}
	return 1;
}

/*
 * Get list of active groups from local server
 * 
 * Returns number of groups read or -1 on error
 */
int
read_active(void)
{
	char buf[MAXLINE + 1];
	int numgroups, n;

	if (connect_to_server("localhost", &local) <= 0)
	{
		fprintf(stderr, "read_active(): can't get active groups from local server\n");
		return -1;
	}

	fprintf(local.out, "list active\r\n");
	if (fgets(buf, MAXLINE, local.in) == NULL)
	{
		perror("read_active(): read error on socket after LIST ACTIVE command");
		close_server(local);
		return -1;
	}
	/* should have gotten "215" (list follows) reply */
	if (strncmp(buf, NNTP_LIST_FOLLOWS, 3) != 0)
	{
		fprintf(stderr, "read_active(): unexpected reply to LIST ACTIVE command\n");
		quit_server(local);
		return -1;
	}

	/* just read up to the terminating "." */
	for (numgroups = 0; numgroups < MAX_LOCAL_GROUPS; ++numgroups)
	{
		if (fgets(buf, MAXLINE, local.in) == NULL)
		{
			perror("read_active(): read error on socket while fetching list data");
			quit_server(local);
			return -1;
		}

		if (END_OF_OUTPUT(buf))
			break;

		n = sscanf(buf, "%s ", (char *)&active[numgroups]);

		if (n != 1)
		{
			fprintf(stderr, "read_active(): read error while reading list data\n");
			quit_server(local);
			return -1;
		}
	}

	quit_server(local);
	return numgroups;
}

/*
 * convert a newline-termintated numeric string in newsrc
 * to an article number type
 */
ARTNUM
ptrtonum(char *ptr)
{
	return (strtoul(ptr, NULL, 10));
}

/*
 * convert a space-termintated string in newsrc
 * to a C string
 */
void
ptrtostr(char *ptr, char *str)
{
	while (*ptr != ' ')
	{
		*str++ = *ptr++;
	}
	*str = '\0';
}

/*
 * update newsrc file entry for a group
 */
void
update_newsrc(char *newsrc, ARTNUM artnum)
{
	char num[MAXART + 1];

	sprintf(num, "%010lu", artnum);
	memcpy(newsrc, num, MAXART);
}

/*
 * delete any temporary files left from an earlier run
 */
void
cleanup(void)
{
	glob_t globstuff;
	int i;

	if (glob("/tmp/article.*", 0, NULL, &globstuff) != 0)
		perror("cleanup(): glob error");
	if (glob("/tmp/fetch.*", GLOB_APPEND, NULL, &globstuff) != 0)
		perror("cleanup(): glob error");
	if (glob("/tmp/xover.*", GLOB_APPEND, NULL, &globstuff) != 0)
		perror("cleanup(): glob error");
	if (globstuff.gl_pathc > 0)
	{
		for (i = 0; globstuff.gl_pathv[i] != NULL; ++i)
		{
			unlink(globstuff.gl_pathv[i]);
		}
	}
}

/*
 * check to see if a file exists
 * 
 * returns 1 if file exists, else 0
 */
int
file_exists(const char *pathname)
{
	FILE *f;

	if ((f = fopen(pathname, "r")) != NULL)
	{
		fclose(f);
		return 1;
	}
	else
		return 0;
}

/*
 * do the equivalent of touch(1)
 *
 * Returns -1 if file already exists, else 0
 */
int
touch(const char *pathname)
{
	int fd;

	if ((fd = open(pathname, O_CREAT | O_RDONLY | O_EXCL, 0644)) == -1)
	{
		return -1;
	}
	else
	{
		close(fd);
		return 0;
	}
}

/*
 * open log files
 */
int
open_logs(void)
{

	if ((sucklog = fopen(sucklogpath, "a")) == NULL)
	{
		perror("open_logs(): can't open sucklog");
		return -1;
	}
	if (setvbuf(sucklog, NULL, _IOLBF, 0) == EOF)
	{
		perror("open_logs(): error changing buffering mode on sucklog");
		return -1;
	}
	if ((killlog = fopen(killlogpath, "a")) == NULL)
	{
		perror("open_logs(): can't open killlog");
		return -1;
	}
	if (setvbuf(killlog, NULL, _IOLBF, 0) == EOF)
	{
		perror("open_logs(): error changing buffering mode on killlog");
		return -1;
	}
	/* redirect error output to errlog */
	if ((errlog = freopen(errlogpath, "a", stderr)) == NULL)
	{
		perror("open_logs(): can't open errlog");
		return -1;
	}
	if (setvbuf(errlog, NULL, _IOLBF, 0) == EOF)
	{
		perror("open_logs(): error changing buffering mode on errlog");
		return -1;
	}
	return 0;
}


/*
 * Parse global vars from user's config file
 * 
 * returns number of servers on success, or -1
 */
int
read_config()
{
	char cfg[MAXLINE + 1];
	char home[PATH_MAX + 1];
	char cfgpathname[PATH_MAX + 1];
	struct passwd * uinfo;
	char * var, * val;
	FILE * fp;
	int numservers = 0;

	/* find the home dir of user running suck */
	uinfo = getpwuid(getuid());
	strncpy(home, uinfo->pw_dir, PATH_MAX);

	/* concatenate $HOME and filename with bounds checking */
	snprintf(cfgpathname, PATH_MAX, "%s/%s", home, CONFIGFILENAME);

	if ((fp = fopen(cfgpathname, "r")) == NULL)
	{
		perror("read_config(): can't open configuration file");
		return -1;
	}

	printf("Running as user ID %d (%s) using configuration file %s\n",
			uinfo->pw_uid, uinfo->pw_name, cfgpathname);
	
	while (fgets(cfg, MAXLINE, fp) != NULL)
	{
		if ((var = strtok(cfg, " \t\n")) == NULL)
		{
			continue;	/* blank line is OK */
		}

		if ((val = strtok(NULL, " \t\n")) == NULL)	/* blank value is *not*
								 * OK */
		{
			fprintf(stderr, "read_config(): variable/server %s has NULL value!\n", var);
			return -1;
		}

		if (strcasecmp(var, "ERRLOG") == 0)
		{
			if ((errlogpath = malloc((strlen(val) + 1) * sizeof(char))) == NULL)
			{
				perror("read_config()");
				exit(EXIT_FAILURE);
			}
			strcpy(errlogpath, val);
			continue;
		}
		if (strcasecmp(var, "KILLLOG") == 0)
		{
			if ((killlogpath = malloc((strlen(val) + 1) * sizeof(char))) == NULL)
			{
				perror("read_config()");
				exit(EXIT_FAILURE);
			}
			strcpy(killlogpath, val);
			continue;
		}
		if (strcasecmp(var, "SUCKLOG") == 0)
		{
			if ((sucklogpath = malloc((strlen(val) + 1) * sizeof(char))) == NULL)
			{
				perror("read_config()");
				exit(EXIT_FAILURE);
			}
			strcpy(sucklogpath, val);
			continue;
		}
		if (strcasecmp(var, "FILTERS") == 0)
		{
			if ((filterspath = malloc((strlen(val) + 1) * sizeof(char))) == NULL)
			{
				perror("read_config()");
				exit(EXIT_FAILURE);
			}
			strcpy(filterspath, val);
			continue;
		}
		if (strcasecmp(var, "NEWSRC_PATH_TEMPLATE") == 0)
		{
			if ((newsrc_path_template = malloc((strlen(val) + 1) * sizeof(char))) == NULL)
			{
				perror("read_config()");
				exit(EXIT_FAILURE);
			}
			strcpy(newsrc_path_template, val);
			continue;
		}

		/*
		 * Anything else is supposed to be a server definition
		 */
		if (numservers < MAXNEWSRCS)
		{
			strncpy(server[numservers].hostname, var, MAXHOSTNAMELEN);
			server[numservers++].maxconns = atoi(val);
		}
		else
			fprintf(stderr, "suck: too many server definitions in config, ignoring\n");
	}
#ifdef DEBUG
	printf("errlog: %s\nkilllog: %s\nsucklog: %s\nfilters: %s\nnewsrc_path_template: %s\n", \
	       ERRLOG, KILLLOG, SUCKLOG, FILTERS, NEWSRC_PATH_TEMPLATE);
#endif

	fclose(fp);
	return numservers;
}


syntax highlighted by Code2HTML, v. 0.9.1