/*
 *      Copyright (c) 1996-2005 Michael T Pins.  All rights reserved.
 *
 * 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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#include <sys/param.h>
#include <ctype.h>
#include "config.h"
#include "global.h"
#include "fullname.h"
#include "hash.h"
#include "hostname.h"
#include "libnov.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 "nntp.h"
#include "patchlevel.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <strings.h>

#ifndef EXCELAN
#include <netdb.h>
#endif

#include <errno.h>
#include <pwd.h>

#ifdef NOV
#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(char *prefix, char *str);
static void     find_server(void);
static char    *find_domain(const char *domainFile);
static int      get_server_line(char *string, int size);
static int      get_server(char *string, int size);
static int      get_socket(void);
static void     put_server(char *string);
static int      copy_text(register FILE * fp);
static struct cache *search_cache(article_number art, group_header * gh);
static struct cache *new_cache_slot(void);
static void     clean_cache(void);
static void     set_domain(void);
static void     nntp_doauth(void);

#ifndef NOV
static int      sort_art_list(register article_number * f1, register article_number * f2);
#endif				/* !NOV */

#ifdef NeXT
static char    *strdup(char *);
#endif				/* NeXT */

extern char    *db_directory, *tmp_directory, *news_active;
extern char     delayed_msg[];

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

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

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

int             nntp_local_server = 0;
int             nntp_debug = 0;

extern char    *home_directory;
extern int      silent;

extern 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(void)
{
    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!");
}


/*
 * find_domain		Get the domain name for posting from a named file.
 *			Handle blank lines and comments.
 *
 *	Parameters:	"file" is the name of the file to read.
 *
 *	Returns:	Pointer to static data area containing the
 *			first non-blank/comment line in the file.
 *			NULL on error (or lack of entry in file).
 *
 *	Side effects:	None.
 */

static char *
find_domain(const char *domainFile)
{
    register FILE  *fp;
    register char  *cp;
    static char     buf[MAXHOSTNAMELEN];
    char           *index();

    if (domainFile == NULL)
	return (NULL);

    fp = fopen(domainFile, "r");
    if (fp == NULL)
	return (NULL);

    while (fgets(buf, sizeof (buf), fp) != NULL) {
	if (*buf == '\n' || *buf == '#')
	    continue;
	cp = index(buf, '\n');
	if (cp)
	    *cp = '\0';
	(void) fclose(fp);
	return (buf);
    }

    (void) fclose(fp);
    return (NULL);
}


/*
 * 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(void)
{
    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, strerror(errno));
	(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", strerror(errno));
    return -1;

#ifndef h_addr
conn_err:
    (void) close(s);
    if (who_am_i == I_AM_MASTER)
	sys_warning("Connecting to %s failed: %s", nntp_server, strerror(errno));
    return -1;
#endif

}


/*
 * 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(void)
{
    int             sockt_rd, sockt_wr;
    int             response;
    int             triedauth = 0;
    char            line[NNTP_STRLEN];

    if (who_am_i == I_AM_VIEW)
	nntp_check();

    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 (group_hd)
	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(void)
{
    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(void)
{
    struct cache   *cptr = cache;
    int             i;

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

/*
 * set_domain: set the domain name
 */

static void
set_domain(void)
{

#if !defined(DOMAIN) || !defined(HIDDENNET)
    char           *cp;
#endif

#ifdef 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 */

#else				/* DOMAIN */

    /*
     * if domain is defined in DOMAIN_FILE, use it
     */
    cp = find_domain(DOMAIN_FILE);
    if (cp) {
	strncpy(domain, cp, MAXHOSTNAMELEN);
	domain[MAXHOSTNAMELEN-1] = 0;	/* ensure nul-terminated */
	return;
    }

    domain[0] = '\0';

    cp = index(host_name, '.');
    if (cp == NULL) {
	FILE           *resolv;

	if ((resolv = fopen("/etc/resolv.conf", "r")) != NULL) {
	    char            line[MAXHOSTNAMELEN + 1];
	    char           *p;

	    line[MAXHOSTNAMELEN] = '\0';
	    while (fgets(line, MAXHOSTNAMELEN, resolv)) {
		if (strncmp(line, "domain", 6) == 0) {
		    for (p = &line[6]; *p && isspace(*p); p++);
		    (void) strncpy(domain, p, MAXHOSTNAMELEN);
		    p = domain + strlen(domain) - 1;
		    while ((p >= domain) && (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n'))
			p--;
		    *++p = '\0';
		    break;
		}
	    }
	}
	if (domain[0] == '\0')
	    nn_exitmsg(1, "hostname=%s, You need a fully qualified domain name", host_name);
    } else {

#ifdef HIDDENNET
	strncpy(domain, ++cp, MAXHOSTNAMELEN);
#else
	strncpy(domain, host_name, MAXHOSTNAMELEN);
#endif
    }
#endif				/* DOMAIN */
    domain[MAXHOSTNAMELEN-1] = 0;	/* ensure nul-terminated */
}

/*
 * 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(void)
{
    char           *server_real_name = NULL;
    struct hostent *hp;

    nn_gethostname(host_name, sizeof host_name);
    set_domain();

    last_put[0] = '\0';

    if (nntp_local_server)
	return;

    find_server();

    if ((hp = gethostbyname(host_name)) != NULL)
	strncpy(host_name, hp->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_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(void)
{
    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(void)
{
    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, 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(void)
{
    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(void)
{
    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(void)
{
    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)
{
    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;
}

#ifdef NeXT
/*
 * posted to comp.sys.next.programmer:
 *
 *
 * From: moser@ifor.math.ethz.ch (Dominik Moser,CLV A4,2 40 19,720 49 89)
 * Subject: Re: Compile problems (pgp 2.6.3i)
 * Date: 10 Jul 1996 06:50:42 GMT
 * Organization: Swiss Federal Institute of Technology (ETHZ)
 * References: <4rrhvj$6fr@bagan.srce.hr>
 * Message-ID: <4rvjs2$6oh@elna.ethz.ch>
 *
 * Most systems don't have this (yet)
 */
static char    *
strdup(char *str)
{
    char           *p;

    if ((p = malloc(strlen(str) + 1)) == NULL)
	return ((char *) NULL);

    (void) strcpy(p, str);

    return (p);
}

#endif				/* NeXT */

#endif				/* NNTP */


syntax highlighted by Code2HTML, v. 0.9.1