/*
 * nntp module for nn.
 *
 * The original taken from the nntp 1.5 clientlib.c
 * Modified heavily for nn.
 *
 * Rene' Seindal (seindal@diku.dk) Thu Dec  1 18:41:23 1988
 *
 * I have modified Rene's code quite a lot for 6.4 -- I hope he
 * can still recognize a bit here and a byte there; in any case,
 * any mistakes are mine :-)  ++Kim
 */

#include <stdarg.h>
#include <sys/param.h>
#include "config.h"
#include "nn_term.h"

/*
 *	nn maintains a cache of recently used articles to improve efficiency.
 *	To change the size of the cache, define NNTPCACHE in config.h to be
 *	the new size of this cache.
 */

#ifndef NNTPCACHE
#define NNTPCACHE	10
#endif

#ifdef NNTP
#include <stdio.h>
#include "nntp.h"
#include "patchlevel.h"
#include <sys/socket.h>
#include <string.h>
#include <strings.h>
#ifndef EXCELAN
#include <netdb.h>
#endif
#include <errno.h>
#include <pwd.h>
#include <ctype.h>

#ifdef NOV
#include "hash.h"
#include "hdbm.h"
#include "newsoverview.h"
#endif

/* This is necessary due to the definitions in m-XXX.h */
#if !defined(NETWORK_DATABASE) || defined(NETWORK_BYTE_ORDER)
#include <netinet/in.h>
#endif

#ifdef EXCELAN
#ifndef IPPORT_NNTP
#define	IPPORT_NNTP	119
#endif
#endif

/* nntp.c */

static int	reconnect_server(int);
static int	connect_server(void);
static void debug_msg __APROTO((char *prefix, char *str));
static void find_server __APROTO((void));
static int get_server_line __APROTO((char *string, int size));
static int get_server __APROTO((char *string, int size));
static int get_socket __APROTO((void));
static void put_server __APROTO((char *string));
static int copy_text __APROTO((register FILE *fp));
static struct cache *search_cache __APROTO((article_number art, group_header *gh));
static struct cache *new_cache_slot __APROTO((void));
static void clean_cache __APROTO((void));
static void nntp_doauth(void);
#ifndef NOV
static sort_art_list __APROTO((register article_number *f1, register article_number *f2));
#endif	/* !NOV */


import char *db_directory, *tmp_directory, *news_active;


static char host_name[MAXHOSTNAMELEN];
static char domain[MAXHOSTNAMELEN];
static char last_put[NNTP_STRLEN];

export int nntp_failed = 0;	/* bool: t iff connection is broken in
				   nntp_get_article() or nntp_get_active() */

export int nntp_cache_size = NNTPCACHE;
export char *nntp_cache_dir = NULL;
export char *nntp_server = NNTP_SERVER;	/* name of nntp server */
export char *nntp_user, *nntp_password; /* so can set on command line */

export int nntp_local_server = 0;
export int nntp_debug = 0;

import char *home_directory;
import int silent, no_update;

#ifndef __FreeBSD__
#ifndef __NetBSD__
#ifndef __linux__
extern int sys_nerr;
extern char *sys_errlist[];
#endif	/* __linux__ */
#endif	/* __NetBSD__ */
#endif	/* __FreeBSD__ */

#define syserr() (errno >= 0 && errno < sys_nerr ? \
		  sys_errlist[errno] : "Unknown error.")

import char *mktemp();

static FILE *nntp_in = NULL;		/* fp for reading from server */
static FILE *nntp_out = NULL;		/* fp for writing to server */
static int reconnecting = 0;
static int is_connected = 0;		/* bool: t iff we are connected */
static int need_auth = 0;
static group_header *group_hd;		/* ptr to servers current group */
static int can_post = 0;		/* bool: t iff NNTP server accepts postings */

#define ERR_TIMEOUT	503		/* Response code for timeout */
					/* Same value as ERR_FAULT */

#ifdef NO_RENAME
static int
rename(char *old, char *new)
{
    if (unlink(new) < 0 && errno != ENOENT) return -1;
    if (link(old, new) < 0) return -1;
    return unlink(old);
}
#endif /* NO_RENAME */

/*
 * debug_msg: print a debug message.
 *
 *	The master appends prefix and str to a log file, and clients
 *	prints it as a message.
 *
 *	This is controlled via the nntp-debug variable in nn, and
 *	the option -D2 (or -D3 if the normal -D option should also
 *	be turned on).	Debug output from the master is written in
 *	$TMP/nnmaster.log.
 */

static void
debug_msg(char *prefix, char *str)
{
    static FILE *f = NULL;
    
    if (who_am_i == I_AM_MASTER) {
	if (f == NULL) {
	    f = open_file(relative(tmp_directory, "nnmaster.log"), OPEN_CREATE);
	    if (f == NULL) {
		nntp_debug = 0;
		return;
	    }
	}	    
	fprintf(f, "%s %s\n", prefix, str);
	fflush(f);
	return;
    }

    msg("NNTP%s %s", prefix, str);
    user_delay(3);
}


/*
 * find_server: Find out which host to use as NNTP server.
 *
 *	This is done by consulting the file NNTP_SERVER (defined in
 *	config.h).  Set nntp_server[] to the host's name.
 */

static void
find_server()
{
    char *cp, *name;
    char buf[BUFSIZ];
    FILE *fp;

    /*
     * This feature cannot normally be enabled, because the database and
     * the users rc file contains references to articles by number, and
     * these numbers are not unique across NNTP servers.
     */
#ifdef NOV
    /*
     * But, since we no longer have a database to worry about, give the user
     * the rope. If he wants to hang himself, then let him! :-)
     * Let the user worry about keeping his .newsrc straight.
     */
    if ((cp = getenv("NNTPSERVER")) != NULL) {
	nntp_server = cp;
	return;
    }
    else if(*nntp_server != '/')
	return; /* variable was set on cmd line, or in init file */
#endif /* NOV */

    name = nntp_server; /* default, or variable was set to a filename */

    if ((fp = open_file(name, OPEN_READ)) != NULL) {
	while (fgets(buf, sizeof buf, fp) != 0) {
	    if (*buf == '#' || *buf == '\n')
		continue;
	    if ((cp = strchr(buf, '\n')) != 0)
		*cp = '\0';
	    nntp_server = strdup(buf);
	    if(!nntp_server)
		sys_error("Failed to allocate space for name of NNTP server!");
	    fclose(fp);
	    return;
	}
	fclose(fp);
    }

    if (who_am_i != I_AM_MASTER)
	printf("\nCannot find name of NNTP server.\nCheck %s\n", name);

    sys_error("Failed to find name of NNTP server!");
}

/*
 * get_server_line: get a line from the server.
 *
 *	Expects to be connected to the server.
 *	The line can be any kind of line, i.e., either response or text.
 *	Returns length of line if no error.
 *	If error and master, then return -1, else terminate.
 */

static int
get_server_line(register char *string, register int size)
{
    int retry = 2;

    while (fgets(string, size, nntp_in) == NULL)
	    retry = (reconnect_server(retry));

    size = strlen(string);
    if (size < 2 || !(string[size-2] == '\r' && string[size-1] == '\n'))
	return size;			/* XXX */

    string[size-2] = '\0';		/* nuke CRLF */
    return size-2;
}


/*
 * get_server: get a response line from the server.
 *
 *	Expects to be connected to the server.
 *	Returns the numerical value of the reponse, or -1 in case of errors.
 */

static int
get_server(char *string, int size)
{
    if (get_server_line(string, size) < 0)
	return -1;

    if (nntp_debug) debug_msg("<<<", string);

    return isdigit(*string) ? atoi(string) : 0;
}

/*
 * get_socket:	get a connection to the nntp server.
 *
 * Errors can happen when YP services or DNS are temporarily down or
 * hung, so we log errors and return failure rather than exitting if we
 * are the master.  The effects of retrying every 15 minutes (or whatever
 * the -r interval is) are not that bad.  Dave Olson, SGI
 */

static int
get_socket()
{
    int s;
    struct sockaddr_in sin;
#ifndef EXCELAN
    struct servent *sp;
    struct hostent *hp;

#ifdef h_addr
    int     x = 0;
    register char **cp;
#endif /* h_addr */

	if ((sp = getservbyname("nntp", "tcp")) ==  NULL)
	    return sys_warning("nntp/tcp: Unknown service.\n");

    s = who_am_i == I_AM_MASTER ? 10 : 2;
    while ((hp = gethostbyname(nntp_server)) == NULL) {
	if (--s < 0) goto host_err;
	sleep(10);
    }

    clearobj(&sin, sizeof(sin), 1);
    sin.sin_family = hp->h_addrtype;
    sin.sin_port = sp->s_port;

#else /* EXCELAN */
    char *machine;

    clearobj(&sin, sizeof(sin), 1);
    sin.sin_family = AF_INET;
    sin.sin_port = htons(0);
#endif	/* EXCELAN */

#ifdef h_addr
    /* get a socket and initiate connection -- use multiple addresses */

    s = x = -1;
    for (cp = hp->h_addr_list; cp && *cp; cp++) {
	s = socket(hp->h_addrtype, SOCK_STREAM, 0);
	if (s < 0) goto sock_err;
#ifdef NO_MEMMOVE
	bcopy(*cp, (char *)&sin.sin_addr, hp->h_length);
#else 
	memmove((char *)&sin.sin_addr, *cp, hp->h_length);
#endif /* NO_MEMMOVE */

	/* Quick hack to work around interrupting system calls.. */
	while((x = connect(s, (struct sockaddr *)&sin, sizeof (sin))) < 0 &&
		errno == EINTR) sleep(1);
	if (x == 0)
	    break;
	if (who_am_i != I_AM_MASTER)
	    msg("Connecting to %s failed: %s", nntp_server, syserr());
	(void) close(s);
	s = -1;
    }
    if (x < 0)
	sys_warning("Giving up on NNTP server %s!", nntp_server);
#else /* h_addr */				/* no name server */
#ifdef EXCELAN
    if ((s = socket(SOCK_STREAM, NULL, &sin, SO_KEEPALIVE)) < 0)
	goto sock_err;
    
    sin.sin_port = htons(IPPORT_NNTP);
    machine = nntp_server;
    if ((sin.sin_addr.s_addr = rhost(&machine)) == -1) {
	(void) close(s);
	goto host_err;
    }

    /* And then connect */
    if (connect(s, &sin) < 0)
	goto conn_err;
#else /* not EXCELAN */
    if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	goto sock_err;
    
    /* And then connect */
#ifdef NO_MEMMOVE
    bcopy(hp->h_addr, (char *) &sin.sin_addr, hp->h_length);
#else
    memmove((char *)&sin.sin_addr, hp->h_addr, hp->h_length);
#endif /* NO_MEMMOVE */
    if (connect(s, (struct sockaddr *) &sin, sizeof(sin)) < 0)
	goto conn_err;
#endif	/* EXCELAN */
#endif /* h_addr */
    return s;

 host_err:
    sys_warning("NNTP server %s unknown.\n", nntp_server);
    return -1;

 sock_err:
    sys_warning("Can't get NNTP socket: %s", syserr());
    return -1;

 conn_err:
    (void) close(s);
    if (who_am_i == I_AM_MASTER)
	sys_warning("Connecting to %s failed: %s", nntp_server, syserr());
    return -1;
}


/*
 * connect_server: initialise a connection to the nntp server.
 *
 *	It expects nntp_server[] to be set previously, by a call to
 *	nntp_check.  It is called from nntp_get_article() and
 *	nntp_get_active() if there is no established connection.
 *	This gets called at initialization, and whenever it looks
 *	like the server may have closed the connection on us.
 */

static int
connect_server()
{
    int sockt_rd, sockt_wr;
    int response;
    int triedauth = 0;
    char line[NNTP_STRLEN];

    if (who_am_i != I_AM_MASTER && !silent && !reconnecting)
	msg("Connecting to NNTP server %s ...", nntp_server);

    nntp_failed = 1;
    is_connected = 0;

    sockt_rd = get_socket();
    if (sockt_rd < 0)
	return -1;

    if ((nntp_in = fdopen(sockt_rd, "r")) == NULL) {
	close(sockt_rd);
	return -1;
    }
    sockt_wr = dup(sockt_rd);
    if ((nntp_out = fdopen(sockt_wr, "w")) == NULL) {
	close(sockt_wr);
	fclose(nntp_in);
	nntp_in = NULL; 	      /* from above */
	return -1;
    }

    /* Now get the server's signon message */
    response = get_server(line, sizeof(line));

    if (who_am_i == I_AM_MASTER) {
	if (response != OK_CANPOST && response != OK_NOPOST) {
	    log_entry('N', "Failed to connect to NNTP server");
	    log_entry('N', "Response: %s", line);
	    fclose(nntp_out);
	    fclose(nntp_in);
	    return -1;
	}
    } else if (reconnecting && need_auth) {
	nntp_doauth();
	triedauth++;
    } else {
	switch (response) {
	case OK_AUTH:
	    break;
	case OK_CANPOST:
	    can_post = 1;
	    break;
	case OK_NOPOST:
	    can_post = 0;
	    break;
	default:
	    nn_exitmsg(1, "%s", line);
	    /* NOTREACHED */
	}
    }

retrymode:
    /* Try and speak NNRP style reader protocol */
    sprintf(line, "MODE READER");
    put_server(line);
    /* See what we got if from a NNRP compiant server like INN's nnrpd */
    response = get_server(line, sizeof(line));

    switch (response) {
    case OK_CANPOST:
	can_post = 1;
	break;
    case OK_NOPOST:
	can_post = 0;
	break;
    case ERR_NEEDAUTH:
	if(!triedauth) {
	    nntp_doauth();
	    triedauth++;
	    goto retrymode;
	}
	break;	/* will probably fail hard later */
    case ERR_COMMAND:
    default:
	/* if it doesn't understand MODE READER, we dont care.. :-) */
	break;
    }

    if(!can_post && !triedauth) {
	/* sometimes servers say no posting until auth done, so try.
	 * it might be better if this was controlled by some variable,
	 * since some people are OK with readonly access */
	msg("No post, so trying authorization; if readonly OK, just skip");
	nntp_doauth();
	triedauth++;
	goto retrymode;
    }

    if (who_am_i != I_AM_MASTER && !silent && !reconnecting)
	msg("Connecting to NNTP server %s ... ok (%s)",
	    nntp_server, can_post ? "posting is allowed" : "no posting");

    is_connected = 1;
    nntp_failed = 0;
    return 0;
}


/*
 * reconnect_server -- reconnect to server after a timeout.
 * Re-establish correct group and resend last command to get back to the
 * correct state.
 *
 *	Returns:	-1 if the error is fatal (and we should exit).
 *			# of retries left otherwise.
 *
 *	Side Effects:	If fails to connect and retry==0, calls nn_exitmsg.
 */

static int
reconnect_server(int retry)
{
    char buf[NNTP_STRLEN];

    retry--;
    msg("Reconnecting");
    if (nntp_debug) {
	msg("retry = %d", retry);
	user_delay(2);
    }
    reconnecting = 1;
    strcpy (buf, last_put);
    nntp_close_server();
    if (connect_server() < 0) {
	if (nntp_debug)
	    debug_msg("failed to connect", "");
	if (retry > 0)
	    return(retry);
	else
	    nn_exitmsg(1, "failed to reconnect to server");
    }

    if (nntp_set_group(group_hd) < 1)
	nn_exitmsg(1, "unable to set group");

    if (nntp_debug)
	debug_msg(" Resend last command", buf);
    put_server(buf);
    reconnecting = 0;
    return(retry);
}


/*
 * put_server:	send a line to the nntp server.
 *
 *	Expects to be connected to the server.
 */

static void
put_server(char *string)
{
    if (nntp_debug)
	debug_msg(">>>", string);

    strcpy(last_put, string);
    fprintf(nntp_out, "%s\r\n", string);
    (void) fflush(nntp_out);
    return;
}

/*
 * ask_server:	ask the server a question and return the answer.
 *
 *	Expects to be connected to the server.
 *	Returns the numerical value of the reponse, or -1 in case of
 *	errors.
 *	Contains some code to handle server timeouts intelligently.
 */

/* LIST XXX returns fatal ERR_FAULT code if requested list does not exist */
/* This is only fatal for LIST ACTIVE -- else change to ERR_NOGROUPS */
static int fix_list_response = 0;
static char ask_reply[NNTP_STRLEN];

static int
ask_server(char *fmt, ...)
{
    char ask[NNTP_STRLEN];
    int response;
    int fix_err;
    va_list	ap;

    fix_err = fix_list_response;
    fix_list_response = 0;

    va_start(ap, fmt);
    vsprintf(ask, fmt, ap);
    va_end(ap);

    put_server(ask);
    response = get_server(ask_reply, sizeof(ask_reply));

    /*
     * Handle the response from the server.  Responses are handled as
     * followes:
     *
     * 100-199	Informational.	Passed back. (should they be ignored?).
     * 200-299	Ok messages.  Passed back.
     * 300-399	Ok and proceed.  Can not happen in nn.
     * 400-499	Errors (no article, etc).  Passed up and handled there.
     * 480 (NEED_AUTH) handle specially.  Done here because some servers
     * can send it at any point, after an idle period, as well as at
     * various points during connect.
     * 500-599	Fatal NNTP errors.  Handled below.
     */

    if (response == ERR_TIMEOUT) {
	(void)reconnect_server(1);
	response = get_server(ask_reply, sizeof(ask_reply));
    }

    if(response == ERR_NEEDAUTH) {
	/* try auth, but just once, at least for now, and then retry
	 * the original command, and continue */
	nntp_doauth();
	put_server(ask);
	response = get_server(ask_reply, sizeof(ask_reply));
    }
    if (response == ERR_GOODBYE || response > ERR_COMMAND) {
	if (fix_err && (response == ERR_FAULT || response == ERR_CMDSYN)) return ERR_NOGROUP;

	nntp_failed = 1;
	nntp_close_server();

	if (response != ERR_GOODBYE) {
			/* if not goodbye, complain */
	    sys_error("NNTP %s response: %d", ask_reply, response);
	    /* NOTREACHED */
	}
	if (response == ERR_GOODBYE) {
	    sys_warning("NNTP %s response: %d", ask_reply, response);
	}
    }
    return response;
}

/*
 * copy_text: copy text response into file.
 *
 *	Copies a text response into an open file.
 *	Return -1 on error, 0 otherwise.  It is treated as an error, if
 *	the returned response it not what was expected.
 */

static int last_copy_blank;

static int
copy_text(register FILE *fp)
{
    char buf[NNTP_STRLEN*2];
    register char *cp;
    register int nlines;

    nlines = 0;
    last_copy_blank = 0;
    while (get_server_line(buf, sizeof buf) >= 0) {
	cp = buf;
	if (*cp == '.')
	    if (*++cp == '\0') {
		if (nlines <= 0) break;
		if (nntp_debug) {
		    sprintf(buf, "%d lines", nlines);
		    debug_msg("COPY", buf);
		}
		return 0;
	    }
	last_copy_blank = (*cp == NUL);
	fputs(cp, fp);
	putc('\n', fp);
	nlines++;
    }
    fclose(fp);
    if (nntp_debug) debug_msg("COPY", "EMPTY");
    return -1;
}


/*
 * The following functions implements a simple lru cache of recently
 * accessed articles.  It is a simple way to improve effeciency.  Files
 * must be kept by name, because the rest of the code expects to be able
 * to open an article multiple times, and get separate file pointers.
 */

struct cache {
    char		*file_name;	/* file name */
    article_number	art;		/* article stored in file */
    group_header	*grp;		/* from this group */
    unsigned		time;		/* time last accessed */
} cache[NNTPCACHE];

static unsigned time_counter = 1;		/* virtual time */

/*
 * search_cache: search the cache for an (article, group) pair.
 *
 *	Returns a pointer to the slot where it is, null otherwise
 */

static struct cache *
search_cache(article_number art, group_header *gh)
{
    struct cache *cptr = cache;
    int i;

    if (who_am_i == I_AM_MASTER) return NULL;

    if (nntp_cache_size > NNTPCACHE) nntp_cache_size = NNTPCACHE;

    for (i = 0; i < nntp_cache_size; i++, cptr++)
	if (cptr->art == art && cptr->grp == gh) {
	    cptr->time = time_counter++;
	    return cptr;
	}
    return NULL;
}

/*
 * new_cache_slot: get a free cache slot.
 *
 *	Returns a pointer to the allocated slot.
 *	Frees the old filename, and allocates a new, unused filename.
 *	Cache files can also stored in a common directory defined in
 *	~/.nn or CACHE_DIRECTORY if defined in config.h.
 */

static struct cache *
new_cache_slot()
{
    register struct cache *cptr = cache;
    int i, lru = 0;
    unsigned min_time = time_counter;
    char name[FILENAME];

    if (nntp_cache_dir == NULL) {
#ifdef CACHE_DIRECTORY
	nntp_cache_dir = CACHE_DIRECTORY;
#else /* CACHE_DIRECTORY */
	if (who_am_i == I_AM_MASTER)
	    nntp_cache_dir = db_directory;
	else
	    nntp_cache_dir = nn_directory;
#endif /* CACHE_DIRECTORY */
    }

    if (who_am_i == I_AM_MASTER) {
	cptr = &cache[0];
	if (cptr->file_name == NULL)
	    cptr->file_name = mk_file_name(nntp_cache_dir, "master_cache");
	return cptr;
    }

    for (i = 0; i < nntp_cache_size; i++, cptr++)
	if (min_time > cptr->time) {
	    min_time = cptr->time;
	    lru = i;
	}
    cptr = &cache[lru];

    if (cptr->file_name == NULL) {
	sprintf(name, "%s/nn-%d.%02d~", nntp_cache_dir, process_id, lru);
	cptr->file_name = copy_str(name);
    } else
	unlink(cptr->file_name);

    cptr->time = time_counter++;
    return cptr;
}

/*
 * clean_cache: clean up the cache.
 *
 *	Removes all allocated files.
 */

static void
clean_cache()
{
    struct cache *cptr = cache;
    int i;

    for (i = 0; i < nntp_cache_size; i++, cptr++)
	if (cptr->file_name)
	    unlink(cptr->file_name);
}

/*
 * nntp_check: Find out whether we need to use NNTP.
 *
 *	This is done by comparing the NNTP servers name with whatever
 *	nn_gethostname() returns.
 *	use_nntp and news_active are initialised as a side effect.
 */

void
nntp_check()
{
    char *server_real_name = NULL;
    struct hostent *hp;
#ifndef DOMAIN
    char *cp;
#endif

    nn_gethostname(host_name, sizeof host_name);

#ifndef DOMAIN
    cp = index(host_name, '.');
    if (cp)
#ifdef HIDDENNET
	strncpy(domain, ++cp, MAXHOSTNAMELEN);
#else
	strncpy(domain, host_name, MAXHOSTNAMELEN);
#endif
    else
	nn_exitmsg(1, "hostname=%s, You need a fully qualified domain name",host_name);
#else	/* DOMAIN */
#ifdef HIDDENNET
    strncpy(domain, DOMAIN, MAXHOSTNAMELEN);
#else
    strcpy(domain, host_name);
    cp = index(domain, '.');
    if (cp == NULL) {
	strcat(domain, ".");
	strncat(domain, DOMAIN, MAXHOSTNAMELEN - sizeof(host_name) - 1 );
    }
#endif	/* HIDDENNET */
#endif	/* DOMAIN */

    last_put[0] = '\0';

    if (nntp_local_server) return;

    find_server();
    strncpy(host_name, (gethostbyname(host_name))->h_name, sizeof host_name);

    if ((hp = gethostbyname(nntp_server)) != NULL)
	server_real_name = hp->h_name;
    else
	nn_exitmsg(1, "NNTPSERVER is invalid");
    use_nntp = (strcmp(host_name, server_real_name) != 0);

    if (use_nntp) {
	freeobj(news_active);
#ifndef NOV
	news_active = mk_file_name(db_directory, "ACTIVE");
#else /* NOV */
	news_active = mk_file_name(nn_directory, "ACTIVE");
#endif /* NOV */
    }
}

/*
 * nntp_no_post: Check to see whether posting is allowed.
 */

int
nntp_no_post()
{
    if (!is_connected && connect_server() < 0)
	return 1;			/* If we cannot connect, neither can inews */
    if (can_post == 0) {
	msg("NNTP server does not allow postings from this host.  Sorry!");
	return 1;
    }
    return 0;
}


/*
 * nntp_set_group -- set the server's current group.
 *
 *      Parameters:     gh is the structure containing the group name.
 *
 *      Returns:         1 if OK.
 *                      -1 if no such group.
 *                       0 otherwise.
 */

int
nntp_set_group(group_header * gh)
{
    int             n;

    switch (n = ask_server("GROUP %s", gh->group_name)) {
        case OK_GROUP:
            group_hd = gh;
            return 1;

        case ERR_NOGROUP:
            log_entry('N', "NNTP: group %s not found\n", gh->group_name);
            return -1;

        default:        /* ??? returns this if connection down */
            log_entry('N', "GROUP %s response: %d", group_hd->group_name, n);
	    nntp_failed = 1;
            return -1;           /* What did it return? */
    }
}


#ifndef NOV
/*
 * nntp_get_active:  get a copy of the active file.
 *
 *	If we are the master get a copy of the file from the nntp server.
 *	nnadmin just uses the one we already got.  In this way the master
 *	can maintain a remote copy of the servers active file.
 *	We try to be a little smart, if not inefficient, about the
 *	modification times on the local active file.
 *	Even when the master is running on the nntp server, a separate
 *	copy of the active file will be made for access via NFS.
 */

int
nntp_get_active()
{
    FILE *old, *new;
    char bufo[NNTP_STRLEN], bufn[NNTP_STRLEN];
    char *new_name;
    int same, n;

    if (who_am_i != I_AM_MASTER)
	return access(news_active, 4);

    if (!is_connected && connect_server() < 0)
	return -1;

    new_name = mktemp(relative(db_directory, ".actXXXXXX"));

    switch (n = ask_server("LIST")) {
     case OK_GROUPS:
	new = open_file(new_name, OPEN_CREATE_RW|MUST_EXIST);
	if (copy_text(new) == 0) {
	    if (fflush(new) != EOF) break;
	    fclose(new);
	}
	unlink(new_name);
	if (!nntp_failed) {
	    log_entry('N', "LIST empty");
	    nntp_failed = 1;
	}
	return -1;
     default:
	log_entry('N', "LIST response: %d", n);
	return -1;
    }

    rewind(new);
    same = 0;
    if ((old = open_file(news_active, OPEN_READ)) != NULL) {
	do {
	    fgets(bufo, sizeof bufo, old);
	    fgets(bufn, sizeof bufn, new);
	} while (!feof(old) && !feof(new) && strcmp(bufo, bufn) == 0);
	same = feof(old) && feof(new);
	fclose(old);
    }

    fclose(new);

    if (same)
	unlink(new_name);
    else
	if (rename(new_name, news_active) != 0)
	    sys_error("Cannot rename %s to %s", new_name, news_active);

    return 0;
}
#endif /* NOV */

/*
 * nntp_get_newsgroups:  get a copy of the newsgroups file.
 *
 *	Use the "LIST NEWSGROUPS" command to get the newsgroup descriptions.
 *	Based on code from: olson%anchor.esd@sgi.com (Dave Olson)
 */

FILE *
nntp_get_newsgroups()
{
    char *new_name;
    FILE *new;
    int n;

    new_name = mktemp(relative(tmp_directory, "nngrXXXXXX"));
    new = open_file(new_name, OPEN_CREATE_RW|OPEN_UNLINK);
    if (new == NULL) return NULL;

    if (!is_connected && connect_server() < 0) goto err;

    fix_list_response = 1;
    switch (n = ask_server("LIST NEWSGROUPS")) {
     case ERR_NOGROUP:		/* really ERR_FAULT */
	goto err;

     case OK_GROUPS:
	if (copy_text(new) == 0) {
	    if (fflush(new) != EOF) break;
	    fclose(new);
	}
	if (!nntp_failed) {
	    log_entry('N', "LIST NEWSGROUPS empty");
	    nntp_failed = 1;
	}
	return NULL;

     default:
	log_entry('N', "LIST NEWSGROUPS response: %d", n);
	goto err;
    }
    rewind(new);
    return new;

 err:
    fclose(new);
    return NULL;
}

#ifndef NOV
/*
 * nntp_get_article_list: get list of all article numbers in group
 *
 *	Sends XHDR command to the server, and parses the following
 *	text response to get a list of article numbers which is saved
 *	in a list and returned.
 *	Return NULL on error.  It is treated as an error, if
 *	the returned response it not what was expected.
 */

static article_number *article_list = NULL;
static long art_list_length = 0;

static int
sort_art_list(register article_number *f1, register article_number *f2)
{
    return (*f1 < *f2) ? -1 : (*f1 == *f2) ? 0 : 1;
}

article_number *
nntp_get_article_list(group_header *gh)
{
    char buf[NNTP_STRLEN];
    register article_number *art;
    register char *cp;
    register long count = 0;	/* No. of completions plus one */
    int n;
    static int try_listgroup = 1;

    if (!is_connected && connect_server() < 0)
	return NULL;

    /* it is really an extreme waste of time to use XHDR since all we	*/
    /* are interested in is the article numbers (as we do locally).	*/
    /* If somebody hacks up an nntp server that understands LISTGROUP	*/
    /* they will get much less load on the nntp server			*/
    /* It should simply return the existing article numbers is the group*/
    /* -- they don't even have to be sorted (only XHDR needs that)	*/

    if (try_listgroup) {
	switch (n = ask_server("LISTGROUP %s", group_hd->group_name)) {
	 case OK_GROUP:
	    break;
	 default:
	    log_entry('N', "LISTGROUP response: %d", n);
	    return NULL;
	 case ERR_COMMAND:
	    try_listgroup = 0;
	}
    }
    if (!try_listgroup) {
	switch (n = ask_server("XHDR message-id %ld-%ld",
		(long)gh->first_db_article, (long)gh->last_db_article)) {
	 case OK_HEAD:
	    break;
	 default:
	    log_entry('N', "XHDR response: %d", n);
	    return NULL;
	 case ERR_COMMAND:
	    nntp_failed = 2;
	    return NULL;
	}
    }

    count = 0;
    art = article_list;
    
    while (get_server_line(buf, sizeof buf) >= 0) {
	cp = buf;
	if (*cp == '.' && *++cp == '\0') break;

	if (count == art_list_length) {
	    art_list_length += 250;
	    article_list = resizeobj(article_list, article_number, art_list_length + 1);
	    art = article_list + count;
	}
	*art++ = atol(cp);
	count++;
    }

    if (article_list != NULL) {
	*art = 0;
	if (try_listgroup && count > 1)
	    quicksort(article_list, count, article_number, sort_art_list);
    }
    return article_list;
}
#endif	/* NOV */

/*
 * nntp_get_article: get an article from the server.
 *
 *	Returns a FILE pointer.
 *	If necessary the server's current group is set.
 *	The article (header and body) are copied into a file, so they
 *	are seekable (nn likes that).
 */

static char *mode_cmd[] = {
    "ARTICLE",
    "HEAD",
    "BODY"
};

FILE *
nntp_get_article(article_number article, int mode)
	/* mode: 0 => whole article, 1 => head only, 2 => body only */
{
    FILE *tmp;
    static struct cache *cptr;
    int n;

    if (!is_connected && connect_server() < 0) {
	return NULL;
    }

    /*
     * Search the cache for the requested article, and allocate a new
     * slot if necessary (if appending body, we already got it).
     */

    if (mode != 2) {
	cptr = search_cache(article, group_hd);
	if (cptr != NULL) goto out;
	cptr = new_cache_slot();
    }
    
    /*
     * Copy the article.
     */
    switch (n = ask_server("%s %ld", mode_cmd[mode], (long)article)) {
     case OK_ARTICLE:
     case OK_HEAD:
	tmp = open_file(cptr->file_name, OPEN_CREATE|MUST_EXIST);
	if (copy_text(tmp) < 0)
	    return NULL;

	if (mode == 1 && !last_copy_blank)
	    fputc(NL, tmp); /* add blank line after header */

	if (fclose(tmp) == EOF) goto err;
	cptr->art = article;
	cptr->grp = group_hd;
	goto out;

     case OK_BODY:
	tmp = open_file(cptr->file_name, OPEN_APPEND|MUST_EXIST);
	fseek(tmp, (off_t)0, 2);
	if (copy_text(tmp) < 0)
	    return NULL;
	if (fclose(tmp) == EOF) goto err;
	goto out;
	
     case ERR_NOARTIG:
		 /* Matt Heffron: ANUNEWS on VMS uses no such article error */
     case ERR_NOART:
	return NULL;

     default:
	/* Matt Heffron: Which group? */
	log_entry('N', "ARTICLE %ld response: %d (in Group %s)",
		    (long)article, n, group_hd->group_name);
	nntp_failed = 1;
	return NULL;
    }

 out:
    return open_file(cptr->file_name, OPEN_READ|MUST_EXIST);

 err:
    /* sys_error('N', "Cannot write temporary file %s", cptr->file_name); */
    nn_exitmsg(1, "Cannot write temporary file %s", cptr->file_name);
    return NULL;	/* for lint, we can't actually get here */
}

/*
 *	Return local file name holding article
 */

char *
nntp_get_filename(article_number art, group_header *gh)
{
    struct cache *cptr;

    cptr = search_cache(art, gh);

    return cptr == NULL ? NULL : cptr->file_name;
}

/*
 * nntp_close_server: close the connection to the server.
 */

void
nntp_close_server()
{
    if (!is_connected)
	return;

    if (!nntp_failed) 		/* avoid infinite recursion */
	put_server("QUIT");

    (void) fclose(nntp_out);
    (void) fclose(nntp_in);

    is_connected = 0;
}


/*
 * nntp_cleanup:  clean up after an nntp session.
 *
 *	Called from nn_exit().
 */

void
nntp_cleanup()
{
    if (is_connected)
	nntp_close_server();
    is_connected = 0;
    clean_cache();
}


/*************************************************************/

#ifdef NOV

/*
** Prime the nntp server to snarf the overview file for a newsgroup.
** Sends the XOVER command and prepares to read the result.
*/
struct novgroup *
nntp_get_overview(group_header *gh, article_number first, article_number last)
{
  int	n;

  if (!is_connected && connect_server() < 0) {
    return NULL;
  }
  switch (nntp_set_group(gh)) {
	case -1:
	case 0:
	    return NULL;
  }

  n = ask_server("XOVER %d-%d", first, last);
  switch (n) {
    
  case OK_NOV:
    return novstream(nntp_in);

  default:
    log_entry('N', "XOVER response: %d", n);
    return NULL;

  }
}

/*
 * nntp_fopen_list(cmd):  Send some variant of a LIST command to the
 * NNTP server.  returns NULL if the file to be LISTed doesn't exist
 * on the server, else returns the nntp_in FILE descriptor, thus
 * simulating fopen().
 * nntp_fgets() is later used to read a line from the nntp_in FILE.
 */

FILE *
nntp_fopen_list(char *cmd)
{
  int	n;

  if (!is_connected && connect_server() < 0) 
    return NULL;

  fix_list_response = 1;
  switch (n = ask_server(cmd)) {
  case ERR_NOGROUP:		/* really ERR_FAULT - no such file on server */
    return NULL;

  case OK_GROUPS:		/* aka NNTP_LIST_FOLLOWS_VAL */
    return nntp_in;

  default:
    log_entry('N', "`%s' response: %d", cmd, n);
    return NULL;
  }
}

/*
 * nntp_fgets() - Get a line from a file stored on the NNTP server.
 * Strips any hidden "." at beginning of line and returns a pointer to
 * the line.  line will be terminated by NL NUL.
 * Returns NULL when NNTP sends the terminating "." to indicate EOF.
 */
char *
nntp_fgets(char *buf, int bufsize)
{
    char *cp;
    register int size;

    if ((size = get_server_line(buf, bufsize - 1)) < 0)
	return NULL;	/* Can't happen with NOV (we'd rather die first) */

    cp = buf;
    if (*cp == '.') {
	if (*++cp == '\0')
	    return NULL;
    }
    cp[size]   = '\n';
    cp[size+1] = '\0';
    return cp;
}

#endif /* NOV */

/* do all the AUTH stuff; used from 3 places that I've found where
 * various news servers ask for authentication.  Given that we exit
 * on failed auth, probably should just be void.
 * OLSON: oops, loops on empty user input "forever". Should fix
*/
static void
nntp_doauth(void)
{
    int len;
    char line[NNTP_STRLEN], *nl, *pass = NULL;

    strcpy(line, "authinfo user ");
    len = strlen(line);

    log_entry('N', "NNTP Authentication");

    if(nntp_user) {
	msg("NNTP authentication");
	strcpy(line+len, nntp_user);
    } else {
	init_term(1);
	clrdisp();
	prompt("Authentication required; enter username: ");
	(void)fgets(line+len, sizeof(line)-len, stdin);
	if((nl=strpbrk(line, "\r\n")))
	    *nl = '\0';
	nntp_user = strdup(line+len);
    }
    put_server(line);
    if((len=get_server(line, sizeof(line))) == OK_NEEDPASS) {
	strcpy(line, "authinfo pass ");
	len = strlen(line);

	if(nntp_password)
	    strcpy(line+len, nntp_password);
	else {
#ifdef USEGETPASSPHRASE
	    pass = getpassphrase("Enter password: ");
#else
	    pass = getpass("Enter password: ");
#endif
	    strcat(line, pass);
	}
	put_server(line);
	if((len=get_server(line, sizeof(line))) == OK_AUTH) {
	    if(!nntp_password) {
		nntp_password = strdup(pass);
		clrdisp();
		msg("Remembering password for reconnections");
	    }
	} else {
	    nn_exitmsg(1, "Authentication failed (%d): %s", len, line);
	}
    }
    need_auth = 1;
}


/* ZZZZZ */

/*
 * The rest of this is cribbed (and modified) from the old mini-inews.
 *
 * Simply accept input on stdin (or via a named file) and dump this
 * to the server; add a From: and Path: line if missing in the original.
 * Print meaningful errors from the server.
 * Limit .signature files to MAX_SIGNATURE lines.
 *
 * Original by Steven Grady <grady@ucbvax.Berkeley.EDU>, with thanks from
 * Phil Lapsley <phil@ucbvax.berkeley.edu>
 */

/*
 * gen_frompath -- generate From: and Path: lines, in the form
 *
 *	From: Full Name <user@host.domain>
 *	Path: host!user
 *
 * This routine should only be called if the message doesn't have
 * a From: line in it.
 */

static void
gen_frompath()
{
	struct	passwd *passwd;

	passwd = getpwuid(getuid());

	fprintf(nntp_out, "From: ");
	fprintf(nntp_out, "%s ", full_name());

	fprintf(nntp_out, "<%s@%s>\r\n",
		passwd->pw_name,
		domain);

#ifdef HIDDENNET
	/* Only the login name - nntp server will add uucp name */
	fprintf(nntp_out, "Path: %s\r\n", passwd->pw_name);
#else /* HIDDENNET */
	fprintf(nntp_out, "Path: %s!%s\r\n", host_name, passwd->pw_name);
#endif /* HIDDENNET */
}


/*
 * lower -- convert a character to lower case, if it's
 *	upper case.
 *
 *	Parameters:	"c" is the character to be
 *			converted.
 *
 *	Returns:	"c" if the character is not
 *			upper case, otherwise the lower
 *			case eqivalent of "c".
 *
 *	Side effects:	None.
 */

static char
lower(register char c)
{
	if (isascii(c) && isupper(c))
		c = c - 'A' + 'a';
	return(c);
}

/*
 * strneql -- determine if two strings are equal in the first n
 * characters, ignoring case.
 *
 *	Parameters:	"a" and "b" are the pointers
 *			to characters to be compared.
 *			"n" is the number of characters to compare.
 *
 *	Returns:	1 if the strings are equal, 0 otherwise.
 *
 *	Side effects:	None.
 */

static int
strneql(register char *a, register char *b, int n)
{
	while (n && lower(*a) == lower(*b)) {
		if (*a == '\0')
			return (1);
		a++;
		b++;
		n--;
	}
	if (n)
		return (0);
	else
		return (1);
}


/*
 * valid_header -- determine if a line is a valid header line
 *
 *	Parameters:	"h" is the header line to be checked.
 *
 *	Returns:	1 if valid, 0 otherwise
 *
 *	Side Effects:	none
 *
 */

static int
valid_header(register char *h)
{
  char *colon, *space;

  /*
   * blank or tab in first position implies this is a continuation header
   */
  if (h[0] == ' ' || h[0] == '\t')
    return (1);

  /*
   * just check for initial letter, colon, and space to make
   * sure we discard only invalid headers
   */
  colon = index(h, ':');
  space = index(h, ' ');
  if (isalpha(h[0]) && colon && space == colon + 1)
    return (1);

  /*
   * anything else is a bad header -- it should be ignored
   */
  return (0);
}

int
nntp_post(char *temp_file)
{
	import char	delayed_msg[];
	int	n, response;
	FILE	*in = fopen(temp_file, "r");
	char	s[4*NNTP_STRLEN];
	int	seen_fromline, in_header, seen_header;
	register char	*cp;

	if(!in) {
	    sprintf(delayed_msg,"Posting failed because we couldn't re-open file %s.", temp_file);
	    return 1;
	}

	if (!is_connected && connect_server() < 0)
	    return 1;

	switch (n=ask_server("POST")) {
	  case CONT_POST:
	    break;
	  default:
	    sprintf(delayed_msg,"Request to post failed with error %d, %s", n, ask_reply);
	    fclose(in);
	    return 1;
	}

	in_header = 1;
	seen_header = 0;
	seen_fromline = 0;

	while (fgets(s, sizeof(s), in) != NULL) {
		if ((cp = strchr(s, '\n')))
			*cp = '\0';
		if (s[0] == '.')    /* Single . is eof, so put in extra one */
			(void) fputc('.', nntp_out);
		if (in_header && strneql(s, "From:", sizeof("From:")-1)) {
			seen_header = 1;
			seen_fromline = 1;
		}
		if (in_header && s[0] == '\0') {
			if (seen_header) {
				in_header = 0;
				if (!seen_fromline)
					gen_frompath();
				fprintf(nntp_out, "User-Agent: nn/%s.%s\r\n", RELEASE, PATCHLEVEL);
			} else {
				continue;
			}
		} else if (in_header) {
			if (valid_header(s))
				seen_header = 1;
			else
				continue;
		}
		fprintf(nntp_out, "%s\r\n", s);
	}

	fprintf(nntp_out, ".\r\n");
	(void) fflush(nntp_out);
	response = get_server(ask_reply, sizeof(ask_reply));
	switch (response) {
	  case OK_POSTED:
	    break;
	  case ERR_POSTFAIL:
	    msg("Article not accepted by server; not posted.");
	    user_delay(2);
	    return 1;
	  default:
	    msg("Remote error: %s", ask_reply);
	    user_delay(2);
	    return 1;
	}
	return 0;
}

#endif /* NNTP */

