/*			NEWS ACCESS				HTNews.c
**			===========
**
** History:
**	26 Sep 90	Written TBL
**	29 Nov 91	Downgraded to C, for portable implementation.
**         Mar 95       Newsreader Enhancements (in progress) -B. Swetland
**         Mar 96       Newsreader Enhancements (part two) - P. Bleisch
**         Apr 96       More cleanup for 2.7b5 - P.Bleisch
*/

/*
** WARNING: This code is under development.  The only thing good about it
** right now is that it works (most of the time).  Read at your own risk.
**     -- BJS
**
** Do not take Brian's warnings lightly, this code gave me a headache, and
** caused long bouts of sleeplessness.  Be warned.  P. Bleisch
**
** Yes, it is in very poor shape.  I tried to reformat and clean it up
** so that it is at least readable.  GEC
**
*/
#include "../config.h"
#include "HTNews.h"
#include "HTAlert.h"
#include "HTTCP.h"
#include "HTAAUtil.h"		/* Need HTAA_NNTP_AUTHINFO */
#include "../src/mosaic.h"
#include "../src/newsrc.h"
#include "../src/prefs.h"

#include <pwd.h>

#define NEWS_PORT 119		/* See rfc977 */

#ifndef DEFAULT_NEWS_HOST
#define DEFAULT_NEWS_HOST "news"
#endif
#ifndef SERVER_FILE
#define SERVER_FILE "/usr/local/lib/rn/server"
#endif

#include <ctype.h>
#include "HTUtils.h"		/* Coding convention macros */
#include "tcp.h"

#include "HTML.h"
#include "HTParse.h"
#include "HTFormat.h"

#ifndef DISABLE_TRACE
extern int www2Trace;
#endif

struct _HTStructured {
  WWW_CONST HTStructuredClass *isa;
  /* ... */
};

#define LINE_LENGTH 512		   /* Maximum length of line of ARTICLE, etc. */
#define GROUP_NAME_LENGTH 256      /* Maximum length of group name */

extern int securityType;
static int news_securityType;

/*	Module-wide variables
*/
PUBLIC  NewsArt *CurrentArt = NULL;
PRIVATE NewsArt *FirstArt = NULL;
PRIVATE NewsArt *LastArt = NULL;
PUBLIC  char *NewsGroup = NULL;
PUBLIC  newsgroup_t *NewsGroupS = NULL;
PRIVATE int Count = 0;
PRIVATE int GroupFirst = 0;
PRIVATE int GroupLast = 0;
PRIVATE int ReadLast = 0;
PRIVATE int ReadFirst = 0;
PRIVATE newsgroup_t *LastGroup = NULL;
PRIVATE NewsArt *NextArt = NULL;

PUBLIC char *HTNewsHost = NULL;
PRIVATE int s;					/* Socket for NewsHost */
PRIVATE char response_text[LINE_LENGTH + 1];	/* Last response */

PRIVATE HTStructured *target;			/* The output sink */
PRIVATE HTStructuredClass targetClass;		/* Copy of fn addresses */
PRIVATE HTParentAnchor *node_anchor;		/* Its anchor */

PRIVATE HTList *NNTP_AuthInfo = NULL;		/* AUTHINFO database */

int ConfigView = 0;                      	/* View format configure */
int newsShowAllGroups = 0;
int newsShowReadGroups = 0;
int newsShowAllArticles = 0;
int newsNoThreadJumping = 0;
int newsGotList = 0;        
int newsUseNewsRC = 1;   
int newsNextIsUnread = 0;
int newsPrevIsUnread = 0;
extern int newsNoNewsRC;
int newsSubjWidth = 38;
int newsAuthWidth = 30;

#define PUTS(s) (*targetClass.put_string)(target, s)
#define START(e) (*targetClass.start_element)(target, e, 0, 0)
#define END(e) (*targetClass.end_element)(target, e)

PRIVATE int response (WWW_CONST char *command);

/*
 * escapeString
 * Expects: str -- String to escape
 *          buf -- Buffer to store escaped string
 * Returns: nothing
 *
 * Escapes all <'s and >'s and ...
 */
static void escapeString(char *str, char *buf)
{
  while (str && *str) {
    switch (*str) {
      case '<':
        *buf++ = '&';
        *buf++ = 'l';
        *buf++ = 't';
        *buf++ = ';';
        break;
      case '>':
        *buf++ = '&';
        *buf++ = 'g';
        *buf++ = 't';
        *buf++ = ';';
        break;
      case '&':
        *buf++ = '&';
        *buf++ = 'a';
        *buf++ = 'm';
        *buf++ = 'p';
        *buf++ = ';';
        break;
      default:
        *buf++ = *str;
    }
    str++;
  }
  *buf = '\0';;
}


/* HTSetNewsConfig ()
   Expects: artView    -- Article View configuration: 0 = Article View,
                          1 = Thread View
            artAll     -- Show All Articles? 0 = No, non zero = yes
	    grpAll     -- Show All Groups? 0 = no, non zero = yes
	    grpRead    -- Show Read Groups? 0 = no, non zero = yes
	    noThrJmp   -- Don't jump threads? 0 = no, non zero = yes
	    newsRC     -- Use the newsrc? 0 = no, non zero = yes
	    nxtUnread  -- Next thread should be the next unread?
	                  0 = no, non zero = yes
	    prevUnread -- Prev thread should be the prev unread?
	                  0 = no, non zero = yes
    Returns: Nothing

    Sets the current news config.
*/
void HTSetNewsConfig(int artView, int artAll, int grpAll, int grpRead, 
		     int noThrJmp, int newsRC, int nxtUnread, int prevUnread)
{
  if (artView != NO_CHANGE) {
    ConfigView = !artView;
    set_pref(eUSETHREADVIEW, &artView);
  }
  if (artAll != NO_CHANGE) {
    newsShowAllArticles = artAll;
    set_pref(eSHOWALLARTICLES, &newsShowAllArticles);
  }
  if (grpAll != NO_CHANGE) {
    newsShowAllGroups = grpAll;
    set_pref(eSHOWALLGROUPS, &newsShowAllGroups);
  }
  if (grpRead != NO_CHANGE) {
    newsShowReadGroups = grpRead;
    set_pref(eSHOWREADGROUPS, &newsShowReadGroups);
  }
  if (noThrJmp != NO_CHANGE) {
    newsNoThreadJumping = noThrJmp;
    set_pref(eNOTHREADJUMPING, &newsNoThreadJumping);
  }
  if (newsRC != NO_CHANGE) {
    newsUseNewsRC = newsRC;
    set_pref(eUSENEWSRC, &newsUseNewsRC);
  }
  if (nxtUnread != NO_CHANGE) {
    newsNextIsUnread = nxtUnread;
    set_pref(eNEXTISUNREAD, &newsNextIsUnread);
  }
  if (prevUnread != NO_CHANGE) {
    newsPrevIsUnread = prevUnread;
    set_pref(ePREVISUNREAD, &newsPrevIsUnread);
  }
}


/*	Case insensitive string comparisons
**	-----------------------------------
**
** On entry,
**	template must be already un upper case.
**	unknown may be in upper or lower or mixed case to match.
*/
PRIVATE BOOL match (WWW_CONST char *unknown, WWW_CONST char *template)
{
    WWW_CONST char *u = unknown;
    WWW_CONST char *t = template;

    for (; *u && *t && (TOUPPER(*u) == *t); u++, t++) 
    	;				/* Find mismatch or end */
    return (BOOL)(*t == 0);		/* OK if end of template */
}

/* parseemail
 * Expects: str -- string to parse
 *          name -- buffer for name
 *          em -- buffer for email
 * Returns: pointer to em
 *
 * Parse str for an email address and author name in the email@host (name) 
 * or name <email@host> forms.  This destroys the source string.  If 
 * either name of em is NULL, the value will not be returned.
 */
static char *parseemail(char *str, char *name, char *em)
{
  char *email, *end;

  /* Pull out email address */
  if ((email = strchr(str, '<')) && (end = strrchr(str, '>'))) {
    if (++email < end) {
      *end = '\0';
      if (em)
	strcpy(em, email);
      *--email = '\0';
      if (name) {
	while (*str && strchr(" \t\n", *str))
	  str++;
	strcpy(name, str);
      }
      return em;
    }
  } else if ((email = strchr(str, '(')) && (end = strrchr(str, ')'))) {
    if (++email < end) {
      *end = '\0';
      if (name)
	strcpy(name, email);
      *--email = '\0';
      if (em) {
	while (*str && strchr(" \t\n", *str))
	  str++;
	strcpy(em, str);
      }
      return em;
    }
  }

  if (name)
    *name = '\0';
  if (em)
    *em = '\0';

  return(NULL);
}


/* Compare two strings w/out crashing SGIs, etc */
static int strmatch(char *s1, char *s2)
{
    if (!s1 || !s2)
	return 0;

    while (*s1) {
	if (!*s2 || (*s1 != *s2))
	    return 0;
	s1++;
	s2++;
    }
    return 1;
}


/******************* UUDECODE STUFF ********************/
static FILE *startuudecode(char *s)
{
    return fopen(s, "w");
}

#define DEC(Char) (((Char) - ' ') & 077)

static int uudecodeline(FILE *fp, char *buf)
{
    int n;
    char *p = buf;
    char ch;

    if (!p || !strncmp(p, "end", 3)) {
        fclose(fp);
        return 1;
    }

    /* N is used to avoid writing out all the characters at end of the file */
    n = DEC(*p);
    if (n > 0) {
        for (++p; n > 0; p += 4, n -= 3) {
            if (n >= 3) {
                ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4;
                fputc(ch, fp);
                ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2;
                fputc(ch, fp);
                ch = DEC(p[2]) << 6 | DEC(p[3]);
                fputc(ch, fp);
            } else {
                if (n >= 1) {
                    ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4;
                    fputc(ch, fp);
                }
                if (n >= 2) {
                    ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2;
                    fputc(ch, fp);
                }
            }
        }
    }
    return 0;
}

static char b64_tab[256] = {
    '\177', '\177', '\177', '\177', '\177', '\177', '\177', '\177',  /*000-007*/
    '\177', '\177', '\177', '\177', '\177', '\177', '\177', '\177',  /*010-017*/
    '\177', '\177', '\177', '\177', '\177', '\177', '\177', '\177',  /*020-027*/
    '\177', '\177', '\177', '\177', '\177', '\177', '\177', '\177',  /*030-037*/
    '\177', '\177', '\177', '\177', '\177', '\177', '\177', '\177',  /*040-047*/
    '\177', '\177', '\177', '\76',  '\177', '\177', '\177', '\77',   /*050-057*/
    '\64',  '\65',  '\66',  '\67',  '\70',  '\71',  '\72',  '\73',   /*060-067*/
    '\74',  '\75',  '\177', '\177', '\177', '\100', '\177', '\177',  /*070-077*/
    '\177', '\0',   '\1',   '\2',   '\3',   '\4',   '\5',   '\6',    /*100-107*/
    '\7',   '\10',  '\11',  '\12',  '\13',  '\14',  '\15',  '\16',   /*110-117*/
    '\17',  '\20',  '\21',  '\22',  '\23',  '\24',  '\25',  '\26',   /*120-127*/
    '\27',  '\30',  '\31',  '\177', '\177', '\177', '\177', '\177',  /*130-137*/
    '\177', '\32',  '\33',  '\34',  '\35',  '\36',  '\37',  '\40',   /*140-147*/
    '\41',  '\42',  '\43',  '\44',  '\45',  '\46',  '\47',  '\50',   /*150-157*/
    '\51',  '\52',  '\53',  '\54',  '\55',  '\56',  '\57',  '\60',   /*160-167*/
    '\61',  '\62',  '\63',  '\177', '\177', '\177', '\177', '\177',  /*170-177*/
    '\177', '\177', '\177', '\177', '\177', '\177', '\177', '\177',  /*200-207*/
    '\177', '\177', '\177', '\177', '\177', '\177', '\177', '\177',  /*210-217*/
    '\177', '\177', '\177', '\177', '\177', '\177', '\177', '\177',  /*220-227*/
    '\177', '\177', '\177', '\177', '\177', '\177', '\177', '\177',  /*230-237*/
    '\177', '\177', '\177', '\177', '\177', '\177', '\177', '\177',  /*240-247*/
    '\177', '\177', '\177', '\177', '\177', '\177', '\177', '\177',  /*250-257*/
    '\177', '\177', '\177', '\177', '\177', '\177', '\177', '\177',  /*260-267*/
    '\177', '\177', '\177', '\177', '\177', '\177', '\177', '\177',  /*270-277*/
    '\177', '\177', '\177', '\177', '\177', '\177', '\177', '\177',  /*300-307*/
    '\177', '\177', '\177', '\177', '\177', '\177', '\177', '\177',  /*310-317*/
    '\177', '\177', '\177', '\177', '\177', '\177', '\177', '\177',  /*320-327*/
    '\177', '\177', '\177', '\177', '\177', '\177', '\177', '\177',  /*330-337*/
    '\177', '\177', '\177', '\177', '\177', '\177', '\177', '\177',  /*340-347*/
    '\177', '\177', '\177', '\177', '\177', '\177', '\177', '\177',  /*350-357*/
    '\177', '\177', '\177', '\177', '\177', '\177', '\177', '\177',  /*360-367*/
    '\177', '\177', '\177', '\177', '\177', '\177', '\177', '\177',  /*370-377*/
};

static int base64line(FILE *fp, char *buf)
{
    int last_data = 0;
    unsigned char *p = (unsigned char *) buf;
    char c1, c2, c3;

    if (!buf || !*buf || isspace(*buf)) {
        fclose(fp);
        return 1;
    }
            
    /* The following implementation of the base64 decoding might look
     * a bit clumsy, but I only try to follow the POSIX standard:
     * ``All line breaks or other characters not found in the table
     * [with base64 characters] shall be ignored by decoding software.''
     */
    while (*p) {
        while (b64_tab[*p] & '\100') {
            if (!*p || *p++ == '=')
		break;
        }     
        if (!*p)
	    continue;
        c1 = b64_tab[*p++];
        
        while (b64_tab[*p] & '\100') {
            if (!*p || *p++ == '=') {
                HTProgress("Illegal base64 line");
                return 1;
            }
        }
        c2 = b64_tab[*p++];
        
        while (b64_tab[*p] == '\177') {
            if (!*p++) {
                HTProgress("Illegal base64 line");
                return 1;
            }
        }
        if (*p == '=') {
            fputc(c1 << 2 | c2 >> 4, fp);
            last_data = 1;
            break;
        }
        c3 = b64_tab[*p++];

        while (b64_tab[*p] == '\177') {
            if (!*p++) {
                HTProgress("Illegal base64 line");
                return 1;
            }
        }
        
        fputc(c1 << 2 | c2 >> 4, fp);
        fputc(c2 << 4 | c3 >> 2, fp);
        if (*p == '=') {
            break;
        } else {
            fputc(c3 << 6 | b64_tab[*p++], fp);
        }
    }
    return 0;
}

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

/* Article list management */

/* freeart
 * Walks the article list passed in and frees the important stuff.
 */
static void freeart(NewsArt *art)
{
    if (art->ID)
	free(art->ID);
    if (art->SUBJ)
	free(art->SUBJ);
    if (art->FROM)
	free(art->FROM);
    if (art->FirstRef)
	free(art->FirstRef);
    if (art->LastRef)
	free(art->LastRef);
    free(art);
}


/* ClearArtList
 *  Walks the global thread information and frees the important stuff.
 */
PRIVATE void ClearArtList (void)
{
    NewsArt *temp = FirstArt;
    NewsArt *tnext, *temp2, *tnext2;
    
    while (temp) {
	tnext = temp->nextt;
	if (temp->next) {
	    temp2 = temp->next;
	    while (temp2) {
		tnext2 = temp2->next;
		freeart(temp2);
		temp2 = tnext2;
	    }
	}
	freeart(temp);
	temp = tnext;
    }
    FirstArt = LastArt = CurrentArt = NULL;
    GroupLast = GroupFirst = ReadLast = ReadFirst = 0;

    if (NewsGroup) {
	free(NewsGroup);
	NewsGroup = NULL;
    }
    Count = 0;
    return;
}

/* NewArt
 *  Allocates a new article list.
 */
PRIVATE NewsArt *NewArt (void)
{
    NewsArt *temp;

    if (!(temp = (NewsArt *) calloc(1, sizeof(NewsArt))))
	outofmem(__FILE__, "NewArt");

    return temp;
}

/* AddArtTop
 *   Add an Article to the thread chain 
 */
PRIVATE void AddArtTop (WWW_CONST NewsArt *add)
{
    NewsArt *temp;

    /* Easy case ... */
    if (!FirstArt) {
	add->prev = NULL;
	add->next = NULL;
	add->prevt = NULL;
	add->nextt = NULL;
	FirstArt = LastArt = add;
	return;
    }

    /* If threaded, try to find some more of me ... */
    if (add->FirstRef){
	for (temp = LastArt; temp; temp = temp->prevt)
	    if (strmatch(add->FirstRef, temp->FirstRef) ||
		strmatch(add->FirstRef, temp->ID))
		break;
    } else { 
	for (temp = LastArt; temp; temp = temp->prevt) {
	    if (strmatch(add->ID, temp->FirstRef))
		break;
	}
    }

    /* If we found a thread point ... */
    if (temp) {
	/* Follow the thread on down... */
	while (temp->next)
	    temp = temp->next;
	add->prevt = add->nextt = NULL;
	add->next = NULL;
	add->prev = temp;
	temp->next = add;
    } else {
	/* Otherwise, tack it onto the back of the list */
	add->prev = NULL;
	add->next = NULL;
	add->prevt = LastArt;
	add->nextt = NULL;
	LastArt->nextt = add;
	LastArt = add;
    }
}


/* start_anchor
 *   Start anchor element.
 */
PRIVATE void start_anchor (WWW_CONST char *href)
{
    PUTS("<A HREF=\"");
    PUTS(href);
    PUTS("\">");
}

/*	Paste in an Anchor
**	------------------
**
**
** On entry,
**	HT 	has a selection of zero length at the end.
**	text 	points to the text to be put into the file, 0 terminated.
**	addr	points to the hypertext refernce address,
**		terminated by white space, comma, NULL or '>' 
*/
PRIVATE void write_anchor (WWW_CONST char *text, WWW_CONST char *addr)
{
    char href[LINE_LENGTH + 1];
    WWW_CONST char *p;

    strcpy(href, "news:");
    for (p = addr; *p && (*p != '>') && !WHITE(*p) && (*p != ','); p++)
	;
    strncat(href, addr, p - addr);	/* Make complete hypertext reference */
    start_anchor(href);
    PUTS(text);
    PUTS("</A>");
}


/*	Write list of anchors
**	---------------------
**
**	We take a pointer to a list of objects, and write out each,
**	generating an anchor for each.
**
** On entry,
**	HT 	has a selection of zero length at the end.
**	text 	points to a comma or space separated list of addresses.
** On exit,
**	*text	is NOT any more chopped up into substrings.
*/
PRIVATE void write_anchors (char *text)
{
    char *start = text;
    char *end;
    char c;

    for (;;) {
        for (; *start && WHITE(*start); start++)  /* Find start */
	    ;
	if (!*start)
	    return;			/* Done */
        for (end = start; *end && (*end != ' ') && (*end != ','); end++)
	    ;
	if (*end)
	    end++;	/* Include comma or space but not NULL */
	c = *end;
	*end = '\0';
	write_anchor(start, start);
	*end = c;
	start = end;			/* Point to next one */
    }
}


/*
 *  Abort the current connection.
 */
PRIVATE void abort_socket (void)
{
#ifndef DISABLE_TRACE
    if (www2Trace)
	fprintf(stderr, "HTNews: closing socket %d\n", s);
#endif
    NETCLOSE(s);
    PUTS("Network Error: connection lost\n");
    s = -1;		/* End of file on response */
    return;
}


/*
 * HTGetNewsHost() && HTSetNewsHost()
 * Return/Set the newshost name.
 */
PUBLIC WWW_CONST char *HTGetNewsHost (void)
{
    return HTNewsHost;
}

PUBLIC void HTSetNewsHost (WWW_CONST char *value)
{
#ifndef DISABLE_TRACE
    if (www2Trace)
	fprintf(stderr, "HTNews: changing server host to %s\n", value);
#endif
    StrAllocCopy(HTNewsHost, value);
    /* Close any connection to previous host */
    if (s != -1) {
        NETCLOSE(s);
        s = -1;		/* End of file on response */
    }
}

/*	Initialization for this module
**	------------------------------
**
**	We pick up the NewsHost name from
**
**	1.	Environment variable NNTPSERVER
**	2.	File SERVER_FILE
**	3.	Compilation time macro DEFAULT_NEWS_HOST
*/
PRIVATE BOOL initialized = NO;
PRIVATE BOOL initialize (void)
{
    if (getenv("NNTPSERVER")) {
	StrAllocCopy(HTNewsHost, (char *)getenv("NNTPSERVER"));
#ifndef DISABLE_TRACE
	if (www2Trace)
	    fprintf(stderr, "HTNews: NNTPSERVER defined as `%s'\n", HTNewsHost);
#endif
    } else {
	char server_name[256];
	FILE *fp = fopen(SERVER_FILE, "r");

	if (fp) {
	    if (fscanf(fp, "%255s", server_name) == 1) {
		StrAllocCopy(HTNewsHost, server_name);
#ifndef DISABLE_TRACE
		if (www2Trace)
		    fprintf(stderr,
			    "HTNews: File %s defines news host as `%s'\n",
			    SERVER_FILE, HTNewsHost);
#endif
	    }
	    fclose(fp);
	}
    }
    if (!HTNewsHost) {
	StrAllocCopy(HTNewsHost, DEFAULT_NEWS_HOST);
  
#ifndef DISABLE_TRACE
	if (www2Trace)
	    fprintf(stderr, "HTNews: Using default news host '%s'\n",
		    HTNewsHost);
#endif
    }

    s = -1;		/* Disconnected */
    return YES;
}

typedef enum {
    NNTPAUTH_ERROR = 0,		/* general failure */
    NNTPAUTH_OK = 281,		/* authenticated successfully */
    NNTPAUTH_CLOSE = 502	/* server probably closed connection */
} NNTPAuthResult;

typedef struct _NNTPAuth {
    char *host;
    char *user;
    char *pass;
} NNTPAuth;

/*
 * Initialize the authentication list by loading the user's $HOME/.newsauth
 * file.  That file is part of tin's configuration and is used by a few other
 * programs.
 */
static void load_NNTP_AuthInfo(void)
{
    FILE *fp;
    char buffer[LINE_LENGTH + 1];
    char *fname, *home;
    char *name = get_pref_string(eNEWS_AUTHENTICATION_FILE);
#ifndef VMS
    struct passwd *pwdent;
#endif

    /*
     * Try the HOME environment variable, then the password file
     */
    if (!(home = getenv("HOME"))) {
#ifndef VMS
        if (!(pwdent = getpwuid(getuid()))) {
	    home = "";
        } else {
            home = pwdent->pw_dir;
        }
#else
        home = "";
#endif
    }

    fname = (char *)malloc(strlen(home) + strlen(name) + 2);
#ifndef VMS
    sprintf(fname, "%s/%s", home, name);
#else
    sprintf(fname, "%s%s", home, name);
#endif

    if (fp = fopen(fname, "r")) {
	while (fgets(buffer, sizeof(buffer), fp)) {
	    char the_host[LINE_LENGTH + 1];
	    char the_pass[LINE_LENGTH + 1];
	    char the_user[LINE_LENGTH + 1];

	    if ((buffer[0] != '#') &&
		(sscanf(buffer, "%s%s%s", the_host, the_pass, the_user) == 3) &&
		the_host[0] && the_pass[0] && the_user[0]) {
		NNTPAuth *auth = calloc(1, sizeof(NNTPAuth));

		if (!auth)
		    break;
		StrAllocCopy(auth->host, the_host);
		StrAllocCopy(auth->pass, the_pass);
		StrAllocCopy(auth->user, the_user);

		HTList_addObject(NNTP_AuthInfo, auth);
	    }
	}
	fclose(fp);
    }
    free(fname);
}

/*
 *  This function handles nntp authentication. - FM
 */
static NNTPAuthResult HTHandleAuthInfo(char *host)
{
    HTList *cur = NULL;
    NNTPAuth *auth = NULL;
    char *UserName = NULL;
    char *PassWord = NULL;
    char *msg = NULL;
    char buffer[512];
    int status, tries;

    /*
     * Make sure we have an interactive user and a host.  - FM
     */
    if (!(host && *host))
	return NNTPAUTH_ERROR;

    /*
     * Check for an existing authorization entry.  - FM
     */
    if (!NNTP_AuthInfo) {
	NNTP_AuthInfo = HTList_new();
	load_NNTP_AuthInfo();
    }

    cur = NNTP_AuthInfo;
    while (auth = (NNTPAuth *) HTList_nextObject(cur)) {
	if (!my_strcasecmp(auth->host, host)) {
	    UserName = auth->user;
	    PassWord = auth->pass;
	    break;
	}
    }

    /*
     * Handle the username.  - FM
     */
    buffer[sizeof(buffer) - 1] = '\0';
    tries = 3;

    while (tries) {
	if (!UserName) {
	    sprintf(buffer, "Username for news host '%s':", host);
	    UserName = HTPrompt(buffer, "guest");
	    if (!(UserName && *UserName)) {
		free(UserName);
		return NNTPAUTH_ERROR;
	    }
	}
	sprintf(buffer, "AUTHINFO USER %.*s\r\n",
		(int) sizeof(buffer) - 17, UserName);
	if ((status = response(buffer)) < 0) {
	    if (status == HT_INTERRUPTED) {
		HTProgress("Connection interrupted.");
	    } else {
		HTAlert("Connection aborted.");
	    }
	    if (auth) {
		if (auth->user != UserName) {
		    free(auth->user);
		    auth->user = UserName;
		}
	    } else {
		free(UserName);
	    }
	    return NNTPAUTH_CLOSE;
	}
	if (status == 281) {
	    /*
	     * Username is accepted and no password is required.  - FM
	     */
	    if (auth) {
		if (auth->user != UserName) {
		    free(auth->user);
		    auth->user = UserName;
		}
	    } else if (auth = calloc(1, sizeof(NNTPAuth))) {
		/*
		 * Store the accepted username and no password.  - FM
		 */
		StrAllocCopy(auth->host, host);
		auth->user = UserName;
		HTList_addObject(NNTP_AuthInfo, auth);
	    }
	    return NNTPAUTH_OK;
	}
	if (status != 381) {
	    /*
	     * Not success, nor a request for the password, so it must be an
	     * error.  - FM
	     */
	    HTAlert(response_text);
	    if ((--tries > 0) && prompt_for_yes_or_no("Change username?")) {
		if (!auth || auth->user != UserName)
		    free(UserName);
		if ((UserName = HTPrompt("Username:", "guest")) && *UserName)
		    continue;
	    }
	    if (auth) {
		if (auth->user != UserName) {
		    free(auth->user);
		    auth->user = NULL;
		}
		if (auth->pass) {
		    free(auth->pass);
		    auth->pass = NULL;
		}
	    }
	    free(UserName);
	    return NNTPAUTH_ERROR;
	}
	break;
    }

    if (status == 381) {
	/*
	 * Handle the password.  - FM
	 */
	tries = 3;
	while (tries) {
	    if (!PassWord) {
		sprintf(buffer, "Password for news host '%s':", host);
		PassWord = HTPromptPassword(buffer);
		if (!(PassWord && *PassWord)) {
		    free(PassWord);
		    return NNTPAUTH_ERROR;
		}
	    }
	    sprintf(buffer, "AUTHINFO PASS %.*s\r\n",
		    (int) sizeof(buffer) - 17, PassWord);
	    if ((status = response(buffer)) < 0) {
		if (status == HT_INTERRUPTED) {
		    HTProgress("Connection interrupted.");
		} else {
		    HTAlert("Connection aborted.");
		}
		if (auth) {
		    if (auth->user != UserName) {
			free(auth->user);
			auth->user = UserName;
		    }
		    if (auth->pass != PassWord) {
			free(auth->pass);
			auth->pass = PassWord;
		    }
		} else {
		    free(UserName);
		    free(PassWord);
		}
		return NNTPAUTH_CLOSE;
	    }
	    if (status == 502) {
		/*
		 * That's what INN's nnrpd returns.  It closes the connection
		 * after this.  - kw
		 */
		HTAlert(response_text);
		if (auth) {
		    if (auth->user == UserName)
			UserName = NULL;
		    free(auth->user);
		    auth->user = NULL;
		    if (auth->pass == PassWord)
			PassWord = NULL;
		    free(auth->pass);
		    auth->pass = NULL;
		}
		if (UserName)
		    free(UserName);
		if (PassWord)
		    free(PassWord);
		return NNTPAUTH_CLOSE;
	    }
	    if (status == 281) {
		/*
		 * Password also is accepted, and everything has been stored. 
		 * - FM
		 */
		if (auth) {
		    if (auth->user != UserName) {
			free(auth->user);
			auth->user = UserName;
		    }
		    if (auth->pass != PassWord) {
			free(auth->pass);
			auth->pass = PassWord;
		    }
		} else if (auth = calloc(1, sizeof(NNTPAuth))) {
		    StrAllocCopy(auth->host, host);
		    auth->user = UserName;
		    auth->pass = PassWord;
		    HTList_addObject(NNTP_AuthInfo, auth);
		}
		return NNTPAUTH_OK;
	    }
	    /*
	     * Not success, so it must be an error.  - FM
	     */
	    HTAlert(response_text);
	    if (!auth || auth->pass != PassWord)
		free(PassWord);
	    PassWord = NULL;

	    if ((--tries > 0) && HTConfirm("Change password?"))
		continue;

	    if (auth) {
		if (auth->user == UserName)
		    UserName = NULL;
		free(auth->user);
		auth->user = NULL;
		free(auth->pass);
		auth->pass = NULL;
	    }
	    if (UserName)
		free(UserName);
	    break;
	}
    }
    return NNTPAUTH_ERROR;
}

/*	Send NNTP Command line to remote host & Check Response
**	------------------------------------------------------
**
** On entry,
**	command	points to the command to be sent, including CRLF, or is null
**		pointer if no command to be sent.
** On exit,
**	Negative status indicates transmission error, socket closed.
**	Positive status is an NNTP status.
*/
PRIVATE int newswrite (WWW_CONST char *msg)
{
    int status;

    if ((status = NETWRITE(s, msg, strlen(msg))) < 0) {
#ifndef DISABLE_TRACE
	if (www2Trace) 
	    fprintf(stderr, "HTNews: Unable to send command. Disconnecting.\n");
#endif
	NETCLOSE(s);
	s = -1;
    }
    return status;
}


PRIVATE int response (WWW_CONST char *command)
{
    int result;    
    char *p = response_text;
    int auth_tried = 0;

 retry:
    if (command) {
        int status;
        int length = strlen(command);

#ifndef DISABLE_TRACE
        if (www2Trace) 
            fprintf(stderr, "NNTP command to be sent: %s", command);
#endif
        status = NETWRITE(s, command, length);
        if (status < 0) {
#ifndef DISABLE_TRACE
            if (www2Trace)
		fprintf(stderr,
			"HTNews: Unable to send command. Disconnecting.\n");
#endif
            NETCLOSE(s);
            s = -1;
            return status;
        }
    }

    for (;;) {  
	if (((*p++ = HTGetCharacter()) == LF) ||
	    (p == &response_text[LINE_LENGTH])) {
	    *p++ = '\0';			/* Terminate the string */
#ifndef DISABLE_TRACE
	    if (www2Trace)
		fprintf(stderr, "NNTP Response: %s\n", response_text);
#endif
	    sscanf(response_text, "%d", &result);

	    if ((result == 480) && !auth_tried) {
		/* Authentication needed */
		NNTPAuthResult auth_result = HTHandleAuthInfo(HTNewsHost);

		if ((auth_result == NNTPAUTH_CLOSE) && (s != -1)) {
		    NETCLOSE(s);
		    s = -1;
		}
		if ((auth_result == NNTPAUTH_OK) && !auth_tried) {
		    auth_tried = 1;
		    securityType = news_securityType = HTAA_NNTP_AUTHINFO;
		    goto retry;
		}
		securityType = news_securityType = HTAA_NONE;
	    }
	    return result;	    
	}
    
	if (*(p - 1) < 0) {
#ifndef DISABLE_TRACE
	    if (www2Trace)
		fprintf(stderr, "HTNews: EOF on read, closing socket %d\n", s);
#endif
 	    NETCLOSE(s);
	    return s = -1;	/* End of file on response */
	}
    }
}


/* Setup our networking */
PRIVATE int OpenNNTP (void) 
{
    /* CONNECTING to news host */
    char url[1024];
    int status;

    sprintf(url, "lose://%s/", HTNewsHost);

#ifndef DISABLE_TRACE
    if (www2Trace)
	fprintf(stderr, "News: doing HTDoConnect on '%s'\n", url);
#endif

    news_securityType = HTAA_NONE;

    status = HTDoConnect(url, "NNTP", NEWS_PORT, &s);

#ifndef DISABLE_TRACE
    if (www2Trace)
	fprintf(stderr, "News: Done DoConnect; status %d\n", status);
#endif
    if (status == HT_INTERRUPTED)
	/* Interrupt cleanly. */
	return 3;

    if (status < 0) {
	NETCLOSE(s);
	s = -1;
#ifndef DISABLE_TRACE
	if (www2Trace) 
	    fprintf(stderr, "HTNews: Unable to connect to news host.\n");
#endif
	return 2;
    }
#ifndef DISABLE_TRACE

    if (www2Trace) 
	fprintf(stderr, "HTNews: Connected to news host %s.\n", HTNewsHost);
#endif
    HTInitInput(s);		/* Set up buffering */
    if ((response(NULL) / 100) != 2) {
	NETCLOSE(s);
	s = -1;
	return 1;
    }
    return 0;
} 


/* Interface with news-gui.c and various others... */
static void NNTPconfig(int viewtype)
{
    ConfigView = viewtype;
}

/* This is VERY non-reentrant.... */
static char qline[LINE_LENGTH + 1];
char *NNTPgetquoteline(char *art)
{
    char *p;
    int i, status;

    if (!initialized)
	initialized = initialize();
    if (!initialized) {
#ifndef DISABLE_TRACE
	if (www2Trace)
	    fprintf(stderr, "No init?\n");
#endif
	HTProgress("Could not set up news connection.");
	return NULL;
    }
    if (s < 0) {
	HTProgress("Attempting to connect to news server");
	if (OpenNNTP()) {
#ifndef DISABLE_TRACE
	    if (www2Trace)
		fprintf(stderr, "No OpenNNTP?\n");
#endif
	    HTDoneWithIcon();
	    HTProgress("Could not connect to news server.");
	    return NULL;
	}
    }

    securityType = news_securityType;

    if (art) {
	/* FLUSH!!! */
	HTInitInput(s);
	sprintf(qline, "BODY <%s>\r\n", art);
	status = response(qline);
	if (status != 222) {
	    HTDoneWithIcon();
	    return NULL;
	}
    }
    qline[0] = '>';
    qline[1] = ' ';

    for (p = &qline[2], i = 0; ; p++, i++) {
	*p = HTGetCharacter();
	if (*p == (char)EOF) {
	    HTDoneWithIcon();
	    NETCLOSE(s);
	    s = -1;
	    return NULL;	/* End of file on response */
	}
	if (*p == '\n') {
	    *++p = '\0';
	    break;
	}
	if (i == LINE_LENGTH - 4) {
	    *p = '\0';
	    break;
	}
    }
    HTDoneWithIcon();
    if (qline[2] == '.' && qline[3] < ' ')
	return NULL;
    return qline;
}

int NNTPgetarthdrs(char *art, char **ref, char **grp, char **subj, char **from)
{
    int status, done;
    char *aname, *p;
    char line[LINE_LENGTH + 1];
    char buffer[LINE_LENGTH + 1];

    *ref = *grp = *subj = *from = NULL;

    if (!initialized)
	initialized = initialize();
    if (!initialized) {
#ifndef DISABLE_TRACE
	if (www2Trace)
	    fprintf(stderr, "No init?\n");
#endif
	HTProgress("Could not set up news connection.");
	return HT_NOT_LOADED;	/* Fail */
    }
    if (s < 0) {
	HTProgress("Attempting to connect to news server");
	if (OpenNNTP()) {
#ifndef DISABLE_TRACE
	    if (www2Trace)
		fprintf(stderr, "No OpenNNTP?\n");
#endif
	    HTDoneWithIcon();
	    HTProgress("Could not connect to news server.");
	    return HT_NOT_LOADED;	/* Fail */
	}
    }

    securityType = news_securityType;

    /* FLUSH!!! */
    HTInitInput(s);
    sprintf(buffer, "HEAD <%s>\r\n", art);
    status = response(buffer);

    if (status == 221) {	/* Head follows - parse it: */
	char ch;

	p = line;				/* Write pointer */
	done = NO;
	while (!done) {
	    ch = *p++ = HTGetCharacter();
	    if (ch == (char)EOF) {
		NETCLOSE(s);
		s = -1;
		HTDoneWithIcon();
		return HT_NOT_LOADED;	/* End of file on response */
	    }
	    if ((ch == LF) || (p == &line[LINE_LENGTH])) {
		*--p = '\0';		/* Terminate and chop LF */
		p = line;		/* Restart at beginning */
#ifndef DISABLE_TRACE
		if (www2Trace)
		    fprintf(stderr, "G %s\n", line);
#endif
		switch (tolower(line[0])) {
		    case '.':
		        done = (line[1] < ' ');	 /* End of article? */
		        break;
		    case 's':
		        if (match(line, "SUBJECT:"))
			    StrAllocCopy(*subj, line + 9);  /* Save subject */
		        break;
		    case 'r':
		        if (match(line, "REFERENCES:")) {
			    p = line + 12;
			    StrAllocCopy(*ref, p + 1);
		        }
		        break;
		    case 'n':
		        if (match(line, "NEWSGROUPS:")) {
			    p = line + 11;
			    StrAllocCopy(*grp, p + 1);
		        }
		        break;
		    case 'f':
		        if (match(line, "FROM:")) {
		            char author[1024 + 1];

			    parseemail(strchr(line, ':') + 1, author, NULL);
			    aname = author;
			    if (aname && *aname) {
			        StrAllocCopy(*from, aname);
			        p = *from + strlen(*from) - 1;
			        if (*p == LF)
			            *p = '\0';	 /* Chop off newline */
			    } else {
			        StrAllocCopy(*from, "Unknown");
			    }
		        }
		}  /* End switch on first character */
		p = line;		/* Restart at beginning */
	    }  /* If end of line */
	}  /* Loop over characters */
    }  /* If good response */

    HTDoneWithIcon();
    return 1;
}

int NNTPpost(char *from, char *subj, char *ref, char *groups, char *msg)
{
    char buf[1024];

    if (!initialized)
	initialized = initialize();
    if (!initialized) {
#ifndef DISABLE_TRACE
	if (www2Trace)
	    fprintf(stderr, "No init?\n");
#endif
	HTProgress("Could not set up news connection.");
	return HT_NOT_LOADED;	/* Fail */
    }
    if (s < 0) {
	HTProgress("Attempting to connect to news server");
	if (OpenNNTP()) {
#ifndef DISABLE_TRACE
	    if (www2Trace)
		fprintf(stderr, "No OpenNNTP?\n");
#endif
	    HTDoneWithIcon();
	    HTProgress("Could not connect to news server.");
	    return HT_NOT_LOADED;	/* Fail */
	}
    }

    securityType = news_securityType;

    if (response("POST\r\n") != 340) {
	HTDoneWithIcon();
	HTProgress("Server does not allow posting.");
	return 0;
    }
    HTProgress("Posting your article...");
    sprintf(buf, "From: %s\r\n", from);
    newswrite(buf);
    sprintf(buf, "Subject: %s\r\n", subj);
    newswrite(buf);
    if (ref) {
	sprintf(buf, "References: %s\r\n", ref);
	newswrite(buf);
    }
    sprintf(buf, "Newsgroups: %s\r\n", groups);
    newswrite(buf);
    sprintf(buf, "X-Newsreader: VMS Mosaic\r\n\r\n");
    newswrite(buf);
    newswrite(msg);
    if (response("\r\n.\r\n") != 240) {
	HTProgress("Article was not posted.");
    } else {
	HTProgress("Article was posted successfully.");
    }
    HTDoneWithIcon();

    return 1;
}


/* Take a url and return the news article for it if it's in the news cache */
static NewsArt *is_news_url(char *s)
{
    if (!s || strchr(s, '*'))
        return NULL;

    if ((strlen(s) > 5) && !strncmp("news:", s, 5)) {
	NewsArt *art, *art2;

	s = &s[5];
	/* Check the obvious */
	if (CurrentArt && strmatch(s, CurrentArt->ID))
	    return CurrentArt;
	for (art = FirstArt; art; art = art->nextt) {
	    if (strmatch(s, art->ID))
		return art;
	    if (art->next) {
		for (art2 = art->next; art2; art2 = art2->next) {
		    if (strmatch(s, art2->ID))
			return art2;
		}
	    }
	}
    }
    return NULL;
}

/* These are called by their gui_news_* counterparts in gui-news.c */

/* Beginning in 2.7b4, these now return the next/prev unread article/thread,
 * unless newsShowAllArticles is True.  The previous unread article/thread 
 * is set in the news_next functions.
 *
 * news_next will now continue onto the next thread unless newsNoThreadJumping
 * is True.
 */
static NewsArt *nextUnreadThread(NewsArt *art);
static NewsArt *prevUnreadThread(NewsArt *art);

/* Return first unread article in list (thread) */
static NewsArt *firstUnread(NewsArt *art)
{
  NewsArt *a;
  newsgroup_t *tempNewsGroupS = NULL;

  if (!art)
    return NULL;

  if (!newsUseNewsRC || newsShowAllArticles || !newsNextIsUnread ||
      !(tempNewsGroupS = findgroup(NewsGroup)))
    return art;

  while (art && art->prev)
    art = art->prev;
  a = art;
  while (a) {
    if (!isread(tempNewsGroupS, a->num))
      return a;
    a = a->next;
  }
  return NULL;
}


/* Return next unread article after art */
static NewsArt *nextUnread(NewsArt *art, int probe)
{
  NewsArt *a;
  newsgroup_t *tempNewsGroupS = NULL;

  if (!art)
    return NULL;

  if (!newsUseNewsRC || newsShowAllArticles || !newsNextIsUnread ||
      !(tempNewsGroupS = findgroup(NewsGroup)))
    return art->next;

  a = art;
  art = art->next;
  while (art) {
    if (!isread(tempNewsGroupS, art->num))
      break;
    art = art->next;
  }
  if (probe && !art && !newsNoThreadJumping)
    art = nextUnreadThread(a);

  return art;
}

/* Return first unread thread in list */
static NewsArt *firstUnreadThread(NewsArt *art)
{
  NewsArt *t, *a;

  if (!art)
    return NULL;

  if (!newsUseNewsRC || newsShowAllArticles || !newsNextIsUnread)
    return art;

  while (art && art->prev)
    art = art->prev;
  t = art;
  while (t) {
    if (a = firstUnread(t))
      return a;
    t = t->nextt;
  }
  return NULL;
}

/* Return next unread thread in list */
static NewsArt *nextUnreadThread(NewsArt *art)
{
  NewsArt *t;

  if (!art)
    return NULL;

  while (art && art->prev)
    art = art->prev;
  if (!newsUseNewsRC || newsShowAllArticles || !newsNextIsUnread)
    return art ? art->nextt : NULL;

  t = art->nextt;
  while (t) {
    if (art = firstUnread(t))
      return art;
    t = t->nextt;
  }
  return t;
}


static NewsArt *prevUnread(NewsArt *art, int probe)
{
  NewsArt *a;
  newsgroup_t *tempNewsGroupS = NULL;

  if (!art)
    return NULL;

  if (!newsUseNewsRC || newsShowAllArticles || !newsPrevIsUnread ||
      !(tempNewsGroupS = findgroup(NewsGroup)))
    return art->prev;

  a = art;
  art = art->prev;
  while (art) {
    if (!isread(tempNewsGroupS, art->num))
      break;
    art = art->prev;
  }
  if (probe && !art && !newsNoThreadJumping)
    art = prevUnreadThread(a);

  return art;
}

static NewsArt *prevUnreadThread(NewsArt *art)
{
  NewsArt *t;

  if (!art)
    return NULL;
  while (art && art->prev)
    art = art->prev;
  if (!newsUseNewsRC || newsShowAllArticles || !newsPrevIsUnread)
    return art->prevt;

  t = art->prevt;
  while (t) {
    if (art = firstUnread(t))
      return art;
    t = t->prevt;
  }
  return t;
}


/* Goto the previous (unread) thread */
void news_prevt(char *url)
{
  NewsArt *art, *p;

  if ((art = is_news_url(url)) && (p = prevUnreadThread(art))) {
    sprintf(url, "news:%s", p->ID);
  } else {
    *url = '\0';
  }
  return;
}

/* Goto first (unread) article in next (unread) thread */
void news_nextt(char *url)
{
  NewsArt *art, *p;

  if ((art = is_news_url(url)) && (p = nextUnreadThread(art))) {
    sprintf(url, "news:%s", p->ID);
  } else {
    *url = '\0';
  }
  return;
}


/* Goto the previous (unread) article */
void news_prev(char *url)
{
  NewsArt *art, *p;

  if (!(art = is_news_url(url))) {
    *url = '\0';
  } else if (p = prevUnread(art, 0)) {
    sprintf(url, "news:%s", p->ID);
  } else if (!newsNoThreadJumping && (p = prevUnreadThread(art))) {
    sprintf(url, "news:%s", p->ID);
  } else {
    *url = '\0';
  }
  return;
}

/* Goto next (unread) article in this thread */
void news_next(char *url)
{
  NewsArt *art, *p;

  if (!(art = is_news_url(url))) {
    *url = '\0';
  } else if (p = nextUnread(art, 0)) {
    sprintf(url, "news:%s", p->ID);
  } else if (!newsNoThreadJumping && (p = nextUnreadThread(art))) {
    sprintf(url, "news:%s", p->ID);
  } else {
    *url = '\0';
  }
  return;
}

void news_index(char *url)
{
    if (NewsGroup && is_news_url(url)) {
        sprintf(url, "news:%s", NewsGroup);
    } else {
        *url = '\0';
    }
}


/* Returns the status of the news buttons */
void news_status(char *url, int *prevt, int *nextt, int *prev, int *next,
		 int *follow)
{
    NewsArt *art;

    if (art = is_news_url(url)) {
        if (prevUnread(art, !newsNoThreadJumping)) {
	    *prev = 1;
        } else {
	    *prev = 0;
        }
        if (prevUnreadThread(art)) {
	    *prevt = 1;
        } else {
	    *prevt = 0;
        }
        if (nextUnread(art, !newsNoThreadJumping)) {
	    *next = 1;
        } else {
	    *next = 0;
        }
        if (nextUnreadThread(art)) {
	    *nextt = 1;
        } else {
	    *nextt = 0;
        }
        *follow = 1;
    } else {
        *follow = 0;
        *prevt = 0;
        *nextt = 0;
        *next = 0;
        *prev = 0;
    }
    return;
}


/* makespaces
 * Expects: str -- a string to figure out the number of spaces.
 *          len -- number of spaces to pad to.
 *
 * Returns: pointer to a static spaces string, each call to make spaces will
 *          overwrite this buffer.
 *
 * This takes the string in str and makes a string of spaces that will
 * (when concatenated with str) form a string len spaces long.
 */
static char *makespaces(char *str, int len)
{
    static char spaces[300 + 1];
    char *p;
    int l = strlen(str);

    if (l < len) {
	p = spaces;
	len -= l;
	while (len--)
	    *p++ = ' ';
	*p = '\0';
    } else {
	spaces[0] = '\0';
    }
    return spaces;
}


/*	Read in an Article					read_article
**	------------------
**
**
**	Note the termination condition of a single dot on a line by itself.
**	RFC 977 specifies that the line "folding" of RFC850 is not used, so we
**	do not handle it here.
**
** On entry,
**	s	Global socket number is OK
**	HT	Global hypertext object is ready for appending text
*/
PRIVATE void read_article (char *artID)
{
    FILE *fp;
    char line[LINE_LENGTH + 1];
    char buf[LINE_LENGTH + 1], duff[LINE_LENGTH + 1];
    char *references = NULL;			/* Hrefs for other articles */
    char *newsgroups = NULL;			/* Newsgroups list */
    char *from = NULL;
    char *subj = NULL;
    char *org = NULL;
    char *date = NULL;
    char *filename = NULL;
    char *l = line;
    char *p = line;
    char *tmp, *pp, *m;
    int f;  /* ':' flag */
    int i, ll;
    int linecount = 0;
    int linenum = 1;
    int lineinc = 0;
    int decode = 0;  /* uudecoding... */
    BOOL done = NO;
    NewsArt *art, *art2, *next;

    HTMeter(0, NULL);

    ll = strlen(artID) - 3;    /* ">\n\r" should be stripped outside !!! */
    for (art = FirstArt; art; art = art -> nextt) {
      if (!strncmp(art->ID, artID, ll))
	break;
      if (art->next) {
	for (art2 = art->next; art2; art2 = art2->next) {
	  if (!strncmp(art2->ID, artID, ll))
	    break;
	}
	if (art2) {
	  art = art2;
	  break;
	}
      }
    }
    CurrentArt = art;

    /*	Read in the HEADer of the article:
    **
    **	The header fields are either ignored, or formatted 
    **  and put into the text.
    */
    while (!done) {
      char ch = *p++ = HTGetCharacter();

      if (ch == (char)EOF) {
	abort_socket();
	return;		/* End of file on response */
      }
      if ((ch == LF) || (p == &line[LINE_LENGTH])) {            
	*--p = '\0';			/* Terminate the string */
#ifndef DISABLE_TRACE
	if (www2Trace)
	  fprintf(stderr, "H %s\n", line);
#endif
	if (line[0] < ' ') {
	  done = 1;
	} else {
	  switch (tolower(line[0])) {
	    case '.':
	      if (line[1] < ' ') 
	        done = 1;
	      break;
	    case 's':
	      if (match(line, "SUBJECT:")) 
	        subj = strdup(&line[8]);
	      break;
	    case 'd':
	      if (match(line, "DATE:")) 
	        date = strdup(&line[5]);
	      break;
	    case 'l':
	      if (match(line, "LINES:"))
	        linecount = atoi(&line[6]);
	      break;
	    case 'f':
	      if (match(line, "FROM:")) 
	        from = strdup(&line[5]);
	      break;
	    case 'o':
	      if (match(line, "ORGANIZATION:")) 
	        org = strdup(&line[13]);
	      break;
	    case 'n':
	      if (match(line, "NEWSGROUPS:")) 
	        newsgroups = strdup(&line[11]);
	      break;
	    case 'r':
	      if (match(line, "REFERENCES:")) 
	        references = strdup(&line[11]);
	      break;
	    default:
	      /* Unknown headers ignored */
	      break;
	  }
	}
	p = line;			/* Restart at beginning */
      }  /* If end of line */
    }  /* Loop over characters */
    
    if (subj) {
      /* Escape <>& */
      escapeString(subj, duff);
      sprintf(buf, "<H2>%s</H2>\n", duff);
      PUTS(buf);
      START(HTML_TITLE);
      sprintf(buf, "Article: %s", duff);
      PUTS(buf);
      END(HTML_TITLE);
      free(subj);
    }
    if (date) {
      PUTS("<I>");
      PUTS(date);
      free(date);
      if (org) {
	PUTS(", ");
	PUTS(org);
	free(org);
      }       
      PUTS("</I><BR>\n");
    }
    if (from) {
      PUTS("<B>From:</B> <I>");
      if (parseemail(from, duff, buf)) {
	sprintf(line, "<A HREF=\"mailto:%s\"> %s </A>   ", buf, duff);  
	PUTS(line);
      } else {
	PUTS (from);
      }
      PUTS("</I><BR>");
      free(from);
    }
    if (newsgroups) {
      PUTS("<B>Newsgroups:</B><I>");
      write_anchors(newsgroups);
      PUTS("</I><BR>\n");
    }
    if (references) {
      PUTS("<B>References:</B><I>");
      i = 1;
      for (p = references; *p; p++) {
	if (*p == '<') {
	  for (pp = ++p; *p; p++) {
	    if (*p == '>') {
	      *p++ = '\0';
              if (strlen(pp) < LINE_LENGTH) {
		if (i > 1)
		  PUTS(", ");
		sprintf(line, "<A HREF=\"news:&lt;%s&gt;\">%d</A>", pp, i++);
		PUTS(line);
              }
              break;   
	    }
	  }
	  if (!*p)
	    break;
        }
      }
      free(references);
      PUTS("</I><BR>\n");
    }
    if (linecount) {
      lineinc = linecount / 100;
      if (lineinc < 1)
	lineinc = 1;
    }
    PUTS("<HR>\n");
    
    /*	Read in the BODY of the Article:
     */
    (*targetClass.start_element)(target, HTML_PRE , 0, 0);
    
    p = line;
    done = 0;
    while (!done) {
      char ch = *p++ = HTGetCharacter();

      if (ch == (char)EOF) {
	if (decode)
	  fclose(fp);
	if (filename)
	  free(filename);
	abort_socket();	 /* End of file, close socket */
	return;
      }
      if ((ch == LF) || (p == &line[LINE_LENGTH])) {
	  *p++ = '\0';				/* Terminate the string */
#ifndef DISABLE_TRACE
	  if (www2Trace)
	    fprintf(stderr, "B %s", line);
#endif
	  if (line[0] == '.') {
	    if (line[1] < ' ') {		/* End of article? */
	      done = YES;
	      switch (decode) {
		case 1:
		  uudecodeline(fp, NULL);
		  sprintf(line, "%s\n%s", filename, filename);
		  PUTS("<BR><A HREF=\"");
		  tmp = (char *)malloc(sizeof(char) * (strlen(filename) + 19));
		  sprintf(tmp, "file://localhost/%s", filename);
		  PUTS(tmp);
		  free(tmp);
		  PUTS("\">Decoded uuencode file</A><BR>");
                  break;
		case 2:
		  base64line(fp, NULL);
		  sprintf(line, "%s\n%s", filename, filename);
		  PUTS("<BR><A HREF=\"");
		  tmp = (char *)malloc(sizeof(char) * (strlen(filename) + 19));
		  sprintf(tmp, "file://localhost/%s", filename);
		  PUTS(tmp);
		  free(tmp);
		  PUTS("\">Decoded base64 file</A><BR>");
		  break;
              }
	      break;
	    }
          }
          linenum++;
          if (linecount && !(linenum % lineinc))
	    HTMeter((linenum * 100) / linecount, NULL);

          switch (decode) {
            case 1:                        
	      /* uuencoded */
	      if (uudecodeline(fp, line)) {
		decode = 6;
		sprintf(line, "%s\n%s", filename, filename);
		PUTS("<BR><A HREF=\"");
		tmp = (char *)malloc(sizeof(char) * (strlen(filename) + 19));
		sprintf(tmp, "file://localhost/%s", filename);
		PUTS(tmp);
		free(tmp);
		PUTS("\">Decoded uuencode file</A><BR>");
	      }
	      f++;
	      p = line;
	      continue;
            case 2:
	      /* base64 encoded */
	      if (base64line(fp, line)) {
		decode = 6;
		sprintf(line, "%s\n%s", filename, filename);
		PUTS("<BR><A HREF=\"");
		tmp = (char *)malloc(sizeof(char) * (strlen(filename) + 19));
		sprintf(tmp, "file://localhost/%s", filename);
		PUTS(tmp);
		free(tmp);
		PUTS("\">Decoded base64 file</A><BR>");
	      }
	      p = line;
	      continue;                       
            case 3:
	      /* Is mime, looking for encoding... */
	      if (match(line, "CONTENT-TRANSFER-ENCODING: BASE64")) {
		decode = 4;
		HTProgress("base64 image decoding");
	      } else if (!line[0] || isspace(line[0])) {
		decode = 0;  /* Possible begin */
	      }
	      break;
            case 4:
	      /* base64, looking for blank start line */
	      if (!line[0] || isspace(line[0])) {
		if (filename)
	 	   free(filename);
		fp = startuudecode(filename = mo_tmpnam(NULL));
		decode = 2;
		p = line;
		continue;
	      }
	      break;
            case 5:  /* Possible mime encap encoding crud */
	      if (match(line, "CONTENT-TYPE: IMAGE")) {
		decode = 3;
		break;
	      } else {
		decode = 0;
	      }
	      break;
            case 6:  /* Reg text, don't search */
	      break;
            default:  /* Regular text, look for encoding start tags */
	      if (match(line, "CONTENT-TYPE: IMAGE")){
		decode = 3;
		break;
	      }
	      if (!strncmp(line, "begin", 5)) {    
		decode = 1;
		if (filename)
	 	   free(filename);
		fp = startuudecode(filename = mo_tmpnam(NULL));
		HTProgress("uudecoding image data...");
		p = line;
		continue;
	      }
          }
            
	  /* HTTP, FTP, MAILTO, GOPHER,  */
          for (f = 0, l = pp = line; *pp; pp++) { 
	      if (f) {
		if (isspace(*pp) || (*pp == '"') || (*pp == '<') ||
		    (*pp == '>')) {
		  ll = *pp;
		  *pp = '\0';
		  PUTS("<A HREF=\"");
		  PUTS(l);
		  PUTS("\">");
		  PUTS(l);
		  PUTS("</A>");
		  *pp = ll;
		  l = pp;
		  f = 0;
		}
	      }           
	      if (*pp == '<') {
		*pp = '\0';
		PUTS(l);
		PUTS("&lt;");
		l = pp + 1;
		continue;
	      }
	      if (*pp == '>') {
		*pp = '\0';
		PUTS(l);
		PUTS("&gt;");
		l = pp + 1;
		continue;
	      }
	      if (*pp == ':') {
		m = pp;
		while (m > l) {
		  m--;
		  if (!isalpha(*m)) {
		    m++;
		    break;
		  }
		}
		if ((pp - m) > 2) {
		  if (match(m, "HTTP:") || match(m, "FTP:") ||
		      match(m, "MAILTO:") || match(m, "NEWS:") ||
		      match(m, "GOPHER:") || match(m, "HTTPS:") ||
		      match(m, "FINGER:")) {
		    ll = *m;
		    *m = '\0';
		    PUTS(l);
		    f = 1;
		    *m = ll;
		    l = m;
		  }
		}
		continue;
	      }                        
          }
          PUTS(l);
          p = line;	/* Restart at beginning */
      }
    }  /* Loop over characters */
    
    (*targetClass.end_element)(target, HTML_PRE);
    
    /* Mark this article read in all the groups we care about.
     * Also figure out the next article to read.
     */
    if (!(next = nextUnread(CurrentArt, 0)) &&
        !(next = nextUnreadThread(CurrentArt))) {
      next = CurrentArt;
      while (next && next->prev)
	next = next->prev;
      while (next && next->prevt)
	next = next->prevt;
      next = firstUnread(next);
    }

    NextArt = next;
    if (CurrentArt) {
      newsgroup_t *ng;

      if (newsgroups) {
	char *tok = strtok(newsgroups, ", ;:\t\n");

	while (tok) {
	  if (ng = findgroup(tok))
	    markread(ng, CurrentArt->num);
	  tok = strtok(NULL, ", ;:\t\n");
	}
      } else if (NewsGroup && (ng = findgroup(NewsGroup))) {
	markread(ng, CurrentArt->num);
      }
    }
    HTMeter(100, NULL);
}


/*
 * read_list
 * Expects: Nothing.
 * Returns Nothing.
 *
 * Notes:
 * pre 2.7b4: The termination condition of a single dot on a line by itself.
 *	      RFC 977 specifies that the line "folding" of RFC850 is not used,
 *	      so we do not handle it here.
 *     2.7b4: Added support for newsrc and subscribed news.
 *     2.7b5: Made it faster.
 *     4.1:   Removed all use of compact_string.
 */        
PRIVATE void read_list (void)
{
  char line[LINE_LENGTH + 1], group[LINE_LENGTH];
  char postable;
  char *p;
  int first, last, junk, intr;
  int m = 0;
  int next_m = 20;
  int done = 0;
  int g = 0;
  int next_g = 50;
  int l = 0;
  int lastg = 0;
  int mark = 0;  
  int did99 = 0;
  newsgroup_t *n = NULL;
  newsgroup_t *nn = NULL;
  extern int twirl_increment;

  START(HTML_TITLE);
  PUTS("Newsgroup Listing");
  END(HTML_TITLE);

  HTMeter(0, NULL);
  HTProgress("Getting newsgroup information from NNTP server");

  /* Display our list of groups */
  START(HTML_H1);
  PUTS("Newsgroup Listing");
  END(HTML_H1);
  START(HTML_PRE);
  if (newsNoNewsRC || !newsUseNewsRC || newsShowAllGroups) {
    if (response("LIST\r\n") < 0) {
      START(HTML_H1);
      PUTS("Error retrieving newsgroup listing from server");
      END(HTML_H1);
      NETCLOSE(s);
      s = -1;
      END(HTML_PRE);
      return;
    }
    p = line;
    while (!done) {
      char ch = *p++ = HTGetCharacter();

      if (ch == (char)EOF) {
	abort_socket();	 /* End of file, close socket */
	return;
      }
      if ((ch == LF) || (p == &line[LINE_LENGTH])) {
	*p++ = '\0';		/* Terminate the string */
	/* Do globe twirly */
	if (g++ > next_g) {
	  next_g = g + 50;
	  intr = HTCheckActiveIcon(1);
	} else {
	  intr = HTCheckActiveIcon(0);
	}
	if (intr) {
	  HTProgress("Transfer Interrupted");
	  return;
	}
	/* Do progress meter */
	if (m++ > next_m) {
	  next_m = m + 20;
	  /* Attempt to estimate where we are at in the big list */
	  if (((m * 100) / 8000) < 100) {
	    HTMeter((m * 100) / 8000, NULL);
	  } else if (!did99) {
	    HTMeter(99, "99%");
	    did99 = 1;
	  }
	}
	/* Check for end of transfer */
	if (line[0] == '.') {
	  if (line[1] < ' ') {		/* End of list */
	    done = 1;
	    break;
	  } else {			/* Line starts with dot */
	    PUTS(&line[1]);
	  }
	} else {
	  /* Normal lines are scanned for references to newsgroups. */
	  if (sscanf(line, "%511s %d %d %c", group, &last, &first, &postable) ==
	      4) {
	    n = findgroup(group);
	    if (!LastGroup)
	      LastGroup = n;
	    if (n) {   
	      if (LastGroup == n)
		lastg = 1;
	      if (!(n->attribs & naSEQUENCED)) {
		setminmax(n, first, last);  /* Update sequencer info */
		rereadseq(n);
		n->attribs |= naSHOWME;
		n->attribs |= naSEQUENCED;
	      }
	      if (postable == 'y')
		n->attribs |= naPOST;
	      
      	      if (n->attribs&naSUBSCRIBED && 
		  (newsShowAllGroups || (n->unread > 0) ||
		   newsShowReadGroups)) {
		sprintf(line, "%s % 7d S <A HREF=\"news:%s\">%s</A> \n",
			(lastg == 1) ? "<B>&gt;&gt;&gt;</B>" : "   ",
			n->unread, n->name, group);
		PUTS(line);
		if (lastg == 1)
		  lastg = 2;
	      } 
	    } else {
	      sprintf(line,"    % 7d U <A HREF=\"news:%s\">%s</A> \n",
		      last < first ? 0 : last-first, group, group);
	      PUTS(line);
	    }
	    l++;
	  }
	}  /* If not dot */
	p = line;  /* Restart at beginning */
      }
    }  /* Loop over characters */
  } else {  /* Pull down info on our subscribed groups. */
    nn = NULL;
    n = firstgroup(naSUBSCRIBED);
    while (n) {
      if (n->unread > 0 || newsShowAllGroups)
	nn = n;
      n = nextgroup(n);
    }
    n = firstgroup(naSUBSCRIBED);
    if (!LastGroup)
      LastGroup = n;
    while (n) {
      /* Contact the server about this group */
      sprintf(line, "GROUP %s\r\n", n->name);
      if ((first = response(line)) != 211) {
#ifndef DISABLE_TRACE
	if (www2Trace)
	  fprintf(stderr, "Group not found:\n %s\n", response_text);
#endif
	sprintf(line, "??????? ? %s <I>Group not found on server</I>\n",
		n->name);
	PUTS(line);
	n = nextgroup(n);
	continue;
      }

      /* Reset the sequencer data and set some flags for this group */
      sscanf(response_text, "%d %d %d %d", &junk, &junk, &first, &last);
      if (!(n->attribs & naSEQUENCED)) {
	setminmax(n, first, last);  /* Update sequencer info */
	rereadseq(n);
	n->attribs |= naSHOWME;
	n->attribs |= naSEQUENCED;
      }
      if ((LastGroup == n) && ((n->unread > 0) || newsShowAllGroups)) {
	lastg = 1;
      } else if (LastGroup == n) {
	lastg = 2;
      } else if ((nn == n) && !mark) {
	lastg = 1;
      }
      if (newsShowAllGroups  || (n->unread > 0) || newsShowReadGroups) {
	sprintf(line, "%s % 7d %s <A HREF=\"news:%s\">%s</A> \n",
		(lastg == 1) ? "<b>&gt;&gt;&gt;</b>" : "   ",
		n->unread, n->attribs & naSUBSCRIBED ? "S" : "U",
		n->name, n->name);
	PUTS(line);
	l++;
	if (lastg-- == 1)
	  mark = 1;
      } 
      n = nextgroup(n);
    }
  }

  if (!l) {
    sprintf(line, "No %snewsgroups on server\n", newsShowAllGroups ? "" :
	    "unread ");
    PUTS(line);
  }
  HTMeter(100, NULL);
  END(HTML_PRE);
}


static int parsexover(char *x, char **num, char **title, char **from,
	              char **date, char **msgid, char **ref, char **bytes,
	              char **lines)
{
    *num = x;

    while (*x && *x != '\t')	    /* Step to next tab */
        x++;
    if (!*x)
        return 0;                   /* Early end of string - bad record */
    *x = '\0';                      /* Terminate */
    x++;                            /* Bump to start of next field */
    *title = x;

    while (*x && *x != '\t')	    /* See above comments */
        x++;
    if (!*x)
        return 0;
    *x++ = '\0';
    *from = x;

    while (*x && *x != '\t')
        x++;
    if (!*x)
        return 0;
    *x++ = '\0';
    *date = x;

    while (*x && *x != '\t')
        x++;
    if (!*x)
        return 0;
    *x++ = '\0';
    *msgid = x;

    while (*x && *x != '\t')
        x++;
    if (!*x)
        return 0;
    *x++ = '\0';
    *ref = x;

    while (*x && *x != '\t')
        x++;
    if (!*x)
        return 0;
    *x++ = '\0';
    *bytes = x;

    while (*x && *x != '\t')
        x++;
    if (!*x)
        return 0;
    *x++ = '\0';
    *lines = x;

    while (*x && *x != '\t')
        x++;
    *x = '\0';

    return 1;
}

PRIVATE void XBuildArtList (WWW_CONST char *groupName,
			    int first_required, int last_required)
{
    NewsArt *art;
    char abuf[1024 + 1];
    char buf[2048];
    char *p, *aname, *aref;
    char *num, *title, *date, *msgid, *ref, *bytes, *lines;
    char *author = NULL;
    char *from = NULL;
    char *reference = NULL;		/* Href for article */
    int lineinc;
    int status, count, first, last;	/* Response fields */
					/* count is only an upper limit */

    HTMeter(0, NULL);
    
#ifndef DISABLE_TRACE
    if (www2Trace)
	fprintf(stderr, "[%s]\n", response_text);
#endif
    sscanf(response_text, "%d %d %d %d", &status, &count, &first, &last);
#ifndef DISABLE_TRACE
    if (www2Trace)
	fprintf(stderr, "Newsgroup status=%d, count=%d, (%d-%d)\n",
    		status, count, first, last);
#endif
    Count = 0;

    last_required = last;
    if (NewsGroup && !strcmp(NewsGroup, groupName)) {
	first_required = ReadLast + 1;
    } else {
	first_required = first;
	ClearArtList();
	StrAllocCopy(NewsGroup, groupName);
    }
    GroupFirst = first;
    GroupLast = last; 
    ReadLast = last;

    if (first_required < GroupFirst) 
 	first_required = GroupFirst;     
    if ((last_required == 0) || (last_required > GroupLast)) 
	last_required = GroupLast;
    if (first_required > last_required)
	return;

    /* FLUSH!!! */
    HTInitInput(s);

    sprintf(buf, "XOVER %d-%d\r\n", first_required, last_required);
    if (response(buf) != 224)
	return; 

    if (!ConfigView) {
	HTProgress("Threading Articles");
    } else {
	HTProgress("Getting Article List");
    }
    lineinc = count / 100;
    if (lineinc < 1)
	lineinc = 1;
    
    for (;;) {
        if (!(Count % lineinc) && count)
            HTMeter((Count * 100) / count, NULL);            
	
	/* EOS test needed */
	for (p = buf; *p = HTGetCharacter(); p++) {
	    if ((*p == '\r') || (*p == '\n')) {
		*p = '\0';
		break;
	    }
	    if (*p == (char)EOF) {
		abort_socket();
		return;		/* End of file on response */
	    }
	}
	if (*buf == '.')
	    break;  /* End of list */

	art = NewArt();

	parsexover(buf, &num, &title, &from, &date, &msgid, &ref,
		   &bytes, &lines);
	art->num = atoi(num);
	Count++;
	     	
	StrAllocCopy(art->SUBJ, title);
	
	if (*ref) {
	    p = ref;
	    aref = p;
	    if (*p == '<') {
		while (*aref && (*aref != '>'))
		    aref++;
		*aref++ = '\0';
		StrAllocCopy(art->FirstRef, p + 1);
	    }
	    do aref++;
	        while(*aref);
	    p = aref - 1;
	    while (*p) {
		if (*p == '>') {
		    *p = '\0';
		    while (*--p && (*p != '<'))
			;
		    if (*p == '<')
			StrAllocCopy(art->LastRef, p + 1);
		    break;
		}
		p--;
	    }
	}
	msgid++;				/* Chop < */
	msgid[strlen(msgid) - 1] = '\0';	/* Chop > */
	StrAllocCopy(art->ID, msgid);

	parseemail(from, abuf, NULL);
	aname = abuf;
	if (aname && *aname) {
	    StrAllocCopy(art->FROM, aname);
	    p = art->FROM + strlen(art->FROM) - 1;
	    if (*p == LF)
		*p = '\0';	 /* Chop off LF */
	} else {
	    StrAllocCopy(art->FROM, "Unknown");
	}
	AddArtTop(art);

    }
    HTMeter(100, NULL);
    
    if (!ConfigView) {
	HTProgress("Done Threading Articles");
    } else {
	HTProgress("Done Getting Article List");
    }
}


PRIVATE void BuildArtList (WWW_CONST char *groupName,
			   int first_required, int last_required)
{
    NewsArt *art;
    BOOL done;
    char abuf[1024 + 1];
    char buffer[LINE_LENGTH];
    char line[LINE_LENGTH];
    char *p, *aname, *aref;
    char *reference = NULL;		/* Href for article */
    int *artlist = NULL;
    int c = 0;
    int i;
    int artno;				/* Article number WITHIN GROUP */
    int status, count, first, last;	/* Response fields */
					/* count is only an upper limit */

#ifndef DISABLE_TRACE
    if (www2Trace)
	fprintf(stderr, "[%s]", response_text);
#endif
    sscanf(response_text, "%d %d %d %d", &status, &count, &first, &last);
#ifndef DISABLE_TRACE
    if (www2Trace)
	fprintf(stderr, "Newsgroup status=%d, count=%d, (%d-%d)\n",
    		status, count, first, last);
#endif
    if (NewsGroup && !strcmp(NewsGroup, groupName)) {
	last_required = last;
	first_required = ReadLast + 1;
    } else {
	ClearArtList();
	StrAllocCopy(NewsGroup, groupName);
    }
    if (first_required < GroupFirst) 
	first_required = GroupFirst;           /* Clip */
    if ((last_required == 0) || (last_required > GroupLast)) 
	last_required = GroupLast;

    /*	Read newsgroup using individual fields:  */
    if (count) {
	if (!(artlist = (int *) malloc(sizeof(int) * (count + 2))))
	    outofmem(__FILE__, "BuildArtList");
	if (response("listgroup\r\n") != 211) {
	    /* Try XHDR if LISTGROUP fails */
	    /* Thanks to Martin Hamilton for this bit 'o code...
             * His choice of header, not mine 
             */
	    sprintf(buffer, "xhdr anarchy-in-the-uk %d-%d\r\n", first, last);
 
	    if (response(buffer) != 221) {
		HTProgress("Cannot get article list from news server");    
		return;
	    }
	}

	/* Read the list of available articles from the NNTP server */
	artlist[0] = 0;
	while (c < (count + 2)) {
	    char ch = HTGetCharacter();
	    
	    if (ch == (char)EOF) {
		abort_socket();
		return;		/* End of file on response */
	    }
	    if (ch == '.')
		break;
	    if (ch == LF) {
#ifndef DISABLE_TRACE
		if (www2Trace)
		    fprintf(stderr, "[%d]", artlist[c]);
#endif
		artlist[++c] = 0;
	    } else if (isdigit(ch)) {
		artlist[c] = artlist[c] * 10 + ch - '0';
	    } 
	}
    }
    for (i = 0; i < c; i++) {
	artno = artlist[i];
	if (artno <= GroupLast) 
	  continue;
	/* FLUSH!!! */
	HTInitInput(s);
	sprintf(buffer, "HEAD %d\r\n", artno);
	status = response(buffer);
	
	if (status == 221) {	/* Head follows - parse it: */
	  art = NewArt();
	  art->num = artno;
	  Count++;
	  if (!(Count % 25)) {
	    if (!ConfigView) {
	       sprintf(buffer, "Threading Article %d of %d", Count, count);
	    } else {
	       sprintf(buffer, "Doing Article %d of %d", Count, count);
	    }
	    HTProgress(buffer);
	  }
	  if (!ReadFirst || (artno < ReadFirst))
	    ReadFirst = artno;
	  if (!ReadLast || (artno > ReadLast))
	    ReadLast = artno;
	  
	  p = line;				/* Write pointer */
	  done = NO;
	  while (!done) {
	    char ch = *p++ = HTGetCharacter();

	    if (ch == (char)EOF) {
	      abort_socket();
	      return;		/* End of file on response */
	    }
	    if ((ch == LF) || (p == &line[LINE_LENGTH]) ) {
	      *--p = '\0';	/* Terminate and chop LF */
	      p = line;		/* Restart at beginning */
#ifndef DISABLE_TRACE
	      if (www2Trace)
		fprintf(stderr, "G %s\n", line);
#endif
	      switch (*line) {
	       case '.':
		done = (line[1] < ' '); 	/* End of article? */
		break;
		
	       case 'S':
	       case 's':
		if (match(line, "SUBJECT:"))
		  StrAllocCopy(art->SUBJ, line + 9);  /* Save subject */
		break;
		
	       case 'R':
	       case 'r':
		if (match(line, "REFERENCES:")) {
		  p = line + 12;
		  aref = p;
		  if (*p == '<') {
		    while (*aref && (*aref != '>'))
		      aref++;
		    *aref++ = '\0';
		    StrAllocCopy(art->FirstRef, p + 1);
		  }
		  do aref++;
		    while(*aref);
		  p = aref - 1;
		  while (*p) {
		    if (*p == '>') {
		      *p = '\0';
		      while (*--p && (*p != '<'))
			;
		      if (*p == '<')
			StrAllocCopy(art->LastRef, p + 1);
		      break;
		    }
		    p--;
		  }
		}
		break;
		
	       case 'M':
	       case 'm':
		if (match(line, "MESSAGE-ID:")) {
		  char *addr = HTStrip(line + 11) + 1;  /* Chop < */

		  addr[strlen(addr) - 1] = '\0';	/* Chop > */
		  StrAllocCopy(art->ID, addr);
		}
		break;
		
	       case 'f':
	       case 'F':
		if (match(line, "FROM:")) {
		  parseemail(strchr(line, ':') + 1, abuf, NULL);
		  aname = abuf;
		  if (aname && *aname) {
		    StrAllocCopy(art->FROM, aname);
		    p = art->FROM + strlen(art->FROM) - 1;
		    if (*p == LF)
		      *p = '\0';	 /* Chop off LF */
		  } else {
		    StrAllocCopy(art->FROM, "Unknown");
		  }
		}
		break;
	      }  /* End switch on first character */

	      p = line;	 /* Restart at beginning */
	    }  /* If end of line */
	  }  /* Loop over characters */
	  AddArtTop(art);

	  /*	 Indicate progress!   @@@@@@
	   */

	}  /* If good response */
    }  /* Loop over article */

    if (artlist)
	free(artlist);

    GroupLast = last;
    GroupFirst = first;

    if (!ConfigView) {
	HTProgress("Done Threading Articles");
    } else {
	HTProgress("Done Getting Article List");
    }
}


/*	Read in a Newsgroup
**	-------------------
**	Unfortunately, we have to ask for each article one by one
**	if we want more than one field.
**
*/
PRIVATE void read_group (WWW_CONST char *groupName, int first, int last)
{
    NewsArt *art, *art2, *f;
    char buffer[LINE_LENGTH], subj[LINE_LENGTH], esubj[LINE_LENGTH];
    char from[LINE_LENGTH], efrom[LINE_LENGTH];
    char *ori_subj;
    int i;
    int mark = 0;

    if (!(NewsGroupS = findgroup(groupName))) {
      /* Add group unsub'd to hash table */
      if (!(NewsGroupS = addgroup(groupName, first, last, 1))) {
	sprintf(buffer, "\nMosaic appears to be out of memory.\n");
	PUTS(buffer);
	return;
      }
      NewsGroupS->attribs |= naUPDATE;
    }
    if (NewsGroup) {
      free(NewsGroup);
      NewsGroup = strdup(groupName);
    }

    if (!(f = firstUnreadThread(FirstArt))) {
      sprintf(buffer, "\nNo %sarticles in this group.\n",
	      newsShowAllArticles ? "" : "unread ");
      markrangeread(NewsGroupS, NewsGroupS->minart, NewsGroupS->maxart);
      PUTS(buffer);
      return;
    }

    /*	Set window title */
    START(HTML_TITLE);
    sprintf(buffer, "Newsgroup: %s", groupName);
    PUTS(buffer);
    END(HTML_TITLE);

    /* If !ConfigView then the format is:
     *
     *	   [THREADCOUNT] <A HREF="news:msgID"> SUBJECT </A>
     *
     * otherwise:
     *
     *	   <A HREF="news:msgID"> SUBJECT </A>        Author One's Name
     *	   <A HREF="news:msgID2"> SUBJECT </A>       Author Two's Name
     *	   <A HREF="news:msgID2"> SUBJECT </A>       Author Three's Name
     */

    /* nextUnreadThread determines the next thread to go to.
     * It returns the next article in the next thread and
     * looks at a whole bunch of globals to see what the next article
     * should be ...
     */
    if (!CurrentArt)
      NextArt = f;
    START(HTML_PRE);

    for (art = f; art; art = nextUnreadThread(art)) {
      if (compact_string(art->SUBJ, subj, newsSubjWidth, 3, 3) == 2) {
        escapeString(art->SUBJ, esubj);
	ori_subj = art->SUBJ;
      } else {
        escapeString(subj, esubj);
	ori_subj = subj;
      }
      if (compact_string(art->FROM, from, newsAuthWidth, 3, 3) == 2) {
        escapeString(art->FROM, efrom);
      } else {
        escapeString(from, efrom);
      }
      if (!ConfigView) {  /* Thread view */
	/* Get article count */
	for (i = 0, art2 = art; art2; art2 = nextUnread(art2, 0)) {
	  if (NextArt == art2)
	    mark = 1;
	  if (!newsShowAllArticles && isread(NewsGroupS, art2->num))
	    continue;
	  i++;
	}
	if (!i && !newsShowAllArticles)
	  continue;
	/* Write summary */
	sprintf(buffer, "%s % 4d <A HREF=\"news:%s\">%s</A>%s %s\n",
		(mark == 1) ? "<b>&gt;&gt;&gt;</b>" : "   ", i, art->ID, esubj, 
		makespaces(ori_subj, newsSubjWidth), efrom);
	PUTS(buffer);
	if (mark == 1)
	  mark = 2;
      } else {
	/* Write out subject info for each article */
	for (art2 = art; art2; art2 = nextUnread(art2, 0)) {
	  if (!newsShowAllArticles && isread(NewsGroupS, art2->num))
	    continue;
	  if (NextArt == art2)
	    mark = 1;
          if (compact_string(art2->SUBJ, subj, newsSubjWidth, 3, 3) == 2) {
            escapeString(art2->SUBJ, esubj);
	    ori_subj = art2->SUBJ;
          } else {
            escapeString(subj, esubj);
	    ori_subj = subj;
          }
          if (compact_string(art2->FROM, from, newsAuthWidth, 3, 3) == 2) {
            escapeString(art2->FROM, efrom);
          } else {
            escapeString(from, efrom);
          }
	  sprintf(buffer, "%s      <A HREF=\"news:%s\">%s</A>%s %s\n",
		  (mark == 1) ? "<b>&gt;&gt;&gt;</b>" : "   ", art2->ID, esubj, 
		  makespaces(ori_subj, newsSubjWidth), efrom);
	  PUTS(buffer);
	  if (mark == 1)
	    mark = 2;
	}
      }
    }
    END(HTML_PRE);
    sprintf(buffer, "Done listing %s", NewsGroup ? NewsGroup : "newsgroup");
    HTProgress(buffer);
    LastGroup = NewsGroupS;
    return;
}


/*		Load by name					HTLoadNews
**		============
*/
PUBLIC int HTLoadNews (WWW_CONST char *arg, HTParentAnchor *anAnchor,
		       HTFormat format_out, HTStream *stream)
{
  char command[257];			/* The whole command */
  char groupName[GROUP_NAME_LENGTH];	/* Just the group name */
  char buf[LINE_LENGTH + 1];
  char *p1;
  int status;				/* tcp return */
  int retries;			        /* A count of how hard we have tried */ 
  BOOL group_wanted;			/* Flag: group asked for, not article */
  BOOL list_wanted;			/* Flag: group asked for, not article */
  long first, last;			/* First and last articles asked for */
  static int has_xover = -1;

#ifndef DISABLE_TRACE
  if (www2Trace)
    fprintf(stderr, "HTNews: Looking for %s\n", arg);
#endif

  if (!initialized)
    initialized = initialize();
  if (!initialized) {
    HTProgress("Could not set up news connection.");
    return HT_NOT_LOADED;	/* Fail */
  }

  /* Pull in the newsrc data if necessary */
  if (newsrc_init(HTNewsHost))
    HTProgress("Not using newsrc data...");

  /* Update the preferences so we don't have to make a function call for
   * every newsShow** resource access.
   */
  newsShowAllGroups = get_pref_boolean(eSHOWALLGROUPS);
  newsShowReadGroups = get_pref_boolean(eSHOWREADGROUPS);
  newsShowAllArticles = get_pref_boolean(eSHOWALLARTICLES);
  newsNoThreadJumping = get_pref_boolean(eNOTHREADJUMPING);
  ConfigView = !get_pref_boolean(eUSETHREADVIEW);
  newsAuthWidth = get_pref_int(eNEWSAUTHORWIDTH);
  newsSubjWidth = get_pref_int(eNEWSSUBJECTWIDTH);
  newsPrevIsUnread = get_pref_boolean(ePREVISUNREAD);
  newsNextIsUnread = get_pref_boolean(eNEXTISUNREAD);

  /*	We will ask for the document, omitting the host name & anchor.
  **
  **	Syntax of address is
  **		xxx@yyy			Article
  **		<xxx@yyy>		Same article
  **		xxxxx			News group (no "@")
  */        
  group_wanted = !strchr(arg, '@') && !strchr(arg, '*');
  list_wanted  = !strchr(arg, '@') && strchr(arg, '*');

  /* Don't use HTParse because news: access doesn't follow traditional
   * rules.  For instance, if the article reference contains a '#',
   * the rest of it is lost -- JFG 10/7/92, from a bug report
   */
  if (!my_strncasecmp(arg, "news:", 5))
    p1 = arg + 5;  /* Skip "news:" prefix */
  if (group_wanted) {
    strcpy(command, "GROUP ");
    first = last = 0;
    strcpy(groupName, p1);
    strcat(command, groupName);
  } else if (!list_wanted) {
    strcpy(command, "ARTICLE ");
    if (!strchr(p1, '<'))
      strcat(command, "<");
    strcat(command, p1);
    if (!strchr(p1, '>'))
      strcat(command, ">");
  } else {
    command[0] = '\0';
  }

  {
    char *p = command + strlen(command);

    *p++ = CR;		/* Macros to be correct on Mac */
    *p++ = LF;
    *p = '\0';
  }
  if (!*arg) {
    HTProgress("Could not load data.");
    return HT_NOT_LOADED;			/* Ignore if no name */
  }

  /*  Make a hypertext object with an anchor list. */
  node_anchor = anAnchor;
  target = HTML_new(anAnchor, format_out, stream);
  targetClass = *target->isa;

  /*  Now, let's get a stream setup up from the NewsHost: */
  for (retries = 0; retries < 2; retries++) {
      target = HTML_new(anAnchor, format_out, stream);
      targetClass = *target->isa;	 /* Copy routine entry points */
      if ((s < 0) && (status = OpenNNTP())) {
	char message[256];

	switch (status) {
	  case 1:
	    /* Couldn't get it. */
	    START(HTML_TITLE);
	    PUTS("Could Not Retrieve Information");
	    END(HTML_TITLE);
	    PUTS("Sorry, could not retrieve information.");
	    (*targetClass.end_document)(target);
	    (*targetClass.free)(target);
	    return HT_LOADED;
	  case 2:
	    if (retries <= 1) {
	      /* Since we reallocate on each retry, free here. */
	      (*targetClass.end_document)(target);
	      (*targetClass.free)(target);
	      continue;
	    }
	    HTProgress("Could not access news host.");
#if !defined(VMS) || defined(__DECC)  /* VAX C is different */
	    sprintf(message, "\nCould not access news host %s.  "
		    "Try setting environment variable <code>NNTPSERVER</code> "
		    "to the name of your news host, and restart Mosaic.", 
#else
	    sprintf(message, "\nCould not access news host %s.  \
Try setting environment variable <code>NNTPSERVER</code> \
to the name of your news host, and restart Mosaic.",
#endif /* VAX C, GEC */ 
		    HTNewsHost);
	    
	    PUTS(message);
	    (*targetClass.end_document)(target);
	    (*targetClass.free)(target);
	    return HT_LOADED;
	  case 3:
	    HTProgress("Connection interrupted.");
	    (*targetClass.handle_interrupt)(target);
	    (*targetClass.free)(target);
	    return HT_INTERRUPTED;
	}
      }

      securityType = news_securityType;

      /* Only check for XOVER the first time thru */
      if (has_xover == -1) {
	status = response("XOVER\r\n");
	if (status != 500) {
	  has_xover = 1;
	} else {
	  has_xover = 0;
        }
      }

      /* FLUSH!!! */
      HTInitInput(s);

      /* read_list() will actually take care of its own command stuff */
      if (!list_wanted) {
	status = response(command);
	strcpy(buf, response_text);
      } else {
	status = 211;
      }
#ifndef DISABLE_TRACE
      if (www2Trace)
	fprintf(stderr, "News: Sent %s     Got status %d\n", command, status);
#endif
      if (status < 0)
	break;
      if ((status/100) != 2) {
	PUTS(response_text);
	(*targetClass.end_document)(target);
	(*targetClass.free)(target);
	NETCLOSE(s);
	s = -1;
	continue;	/* Try again */
      }

      /* Load a group, article, etc. */
      if (list_wanted) {
	/* Destroy all the article stuff */
	ClearArtList();
	if (NewsGroupS)
	  NewsGroupS = NULL;
	if (NewsGroup) {
	  free(NewsGroup);
	  NewsGroup = NULL;
	}
	NextArt = NULL;
	read_list();
	HTMeter(100, NULL);
	HTDoneWithIcon();
	HTProgress("Rendering newsgroup listing... one moment please");
      } else if (group_wanted) {
	int l, h, j;

	if (has_xover == 1) {
	  XBuildArtList(groupName, first, last);
	} else {
	  BuildArtList(groupName, first, last);
	}
	if (sscanf(buf, "%d %d %d %d", &j, &j, &l, &h) == 4) {
	  read_group(groupName, l, h);
	} else {
	  read_group(groupName, 0, -1);
	}
	HTMeter(100, NULL);
	HTDoneWithIcon();
      } else {
	read_article(&command[9]);
	HTMeter(100, NULL);
	HTDoneWithIcon();
      }
      (*targetClass.end_document)(target);
      (*targetClass.free)(target);
      return HT_LOADED;
  }  /* Retry loop */
  return HT_LOADED;
}

PUBLIC HTProtocol HTNews = { "news", HTLoadNews, NULL };
PUBLIC HTProtocol HTNNTP = { "nntp", HTLoadNews, NULL };
