
#if !defined(lint) && !defined(SABER)
static char XRNrcsid[] = "$Header: /wrld/mnt11/ricks/src/X/xrn/RCS/server.c,v 1.24 91/12/04 11:29:31 ricks Exp $";
#endif

/*
 * xrn - an X-based NNTP news reader
 *
 * Copyright (c) 1988, 1989, 1990, 1991, Ellen M. Sentovich and Rick L. Spickelmier.
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted, provided
 * that the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of the University of California not
 * be used in advertising or publicity pertaining to distribution of 
 * the software without specific, written prior permission.  The University
 * of California makes no representations about the suitability of this
 * software for any purpose.  It is provided "as is" without express or
 * implied warranty.
 *
 * THE UNIVERSITY OF CALIFORNIA DISCLAIMS ALL WARRANTIES WITH REGARD TO 
 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 
 * FITNESS, IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE FOR
 * ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
 * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
 * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/*
 * server.c: routines for communicating with the NNTP remote news server
 *
 */

#include "copyright.h"
#include <X11/Xos.h>
#include <ctype.h>
#include <stdio.h>
#include <assert.h>
#ifndef VMS
#include <sys/param.h>
#else
#define MAXPATHLEN 512
#define index strchr
#endif
#include <errno.h>
#include "config.h"
#include "utils.h"
#include "avl.h"
#include "news.h"
#include "mesg.h"
#include "error_hnds.h"
#include "resources.h"
#include "server.h"

extern int errno;

#define BUFFER_SIZE 1024
#define MESSAGE_SIZE 1024

int ServerDown = 0;
static char mybuf[MESSAGE_SIZE+100] = 
	"The news server is not responding correctly, aborting\n";

static void
get_data_from_server(str, size)
char *str;     /* string for message to be copied into */
int size;      /* size of string                       */
/*
 * get data from the server (active file, article)
 *
 *  on error, sets 'ServerDown'
 *
 *   returns: void
 */
{
    if (get_server(str, size) < 0) {
	ServerDown = 1;
    } else {
	ServerDown = 0;
    }
    return;
}


static void
check_time_out(command, response, size)
char *command;  /* command to resend           */
char *response; /* response from the command   */
int size;       /* size of the response buffer */
{
    /*
     * try to recover from a timeout
     *
     *   this assumes that the timeout message stays the same
     *   since the error number (503) is used for more than just
     *   timeout
     *
     *   Message is:
     *     503 Timeout ...
     */

    if (ServerDown || STREQN(response, "503 Timeout", 11)) {

	mesgPane(XRN_SERIOUS, "Lost connection to the NNTP server, attempting to reconnect");
	start_server(NIL(char));
	mesgPane(XRN_INFO, "Reconnected to the NNTP server");
	
	/*
	 * if it was an ARTICLE or XHDR command, then you must get the
	 * server into the right state (GROUP mode), so resend the last
	 * group command
	 */
	if (STREQN(command, "ARTICLE", 7) || STREQN(command, "XHDR", 4)) {
	    if (getgroup(NIL(char), NIL(art_num), NIL(art_num), NIL(int)) == NO_GROUP) {
		return;
	    }
	    /* XXX should do some processing of changed first/last numbers */
	}
	
	put_server(command);
	get_data_from_server(response, size);
    }
    
    return;
}


char *
getarticle(artnumber, position, header, rotation)
art_num artnumber;  /* number of article in the current group to retrieve */
long *position;     /* byte position of header/body seperation            */
int header, rotation;
/*
 * retrieve article number 'artnumber' in the current group, update structure
 *
 *   returns:  filename that the article is stored in or NIL(char) if
 *             the article is not avaiable
 *
 */
{
    char command[MESSAGE_SIZE], message[MESSAGE_SIZE], *msg;
#ifdef REALLY_USE_LOCALTIME
    char temp[MESSAGE_SIZE];
#endif
    FILE *articlefp;
    char *filename, *ptr;
#ifdef VMS
    char dummy[MAXPATHLEN];
#endif
    char field[BUFFER_SIZE];
    int byteCount = 0, lineCount = 0;
    int error = 0;
    int last_stripped = 0;

    *position = 0;

    /* send ARTICLE */
    (void) sprintf(command, "ARTICLE %ld", artnumber);
    put_server(command);
    get_data_from_server(message, sizeof(message));

    check_time_out(command, message, sizeof(message));

    if (*message != CHAR_OK) {
	/* can't get article */
	return(NIL(char));
    }

#ifndef VMS
    if ((filename = utTempnam(app_resources.tmpDir, "xrn")) == NIL(char)) {
	mesgPane(XRN_SERIOUS, "Can not create a file name for the article file");
	/* read till end of article */
	do {
	    get_data_from_server(message, sizeof(message));
	} while ((message[0] != '.') || (message[1] != '\0'));
	return(NIL(char));
    }
#else
    (void) sprintf(dummy, "%sxrn%ld-XXXXXX", app_resources.tmpDir, artnumber);
    if ((filename = mktemp(dummy)) == NIL(char)) {
	mesgPane(XRN_SERIOUS, "Can not create a file name for the article file");
	/* read till end of article */
	do {
	    get_data_from_server(message, sizeof(message));
	} while ((message[0] != '.') || (message[1] != '\0'));
	return(NIL(char));
    }
    filename = XtNewString(filename);
#endif

    if ((articlefp = fopen(filename, "w")) == NULL) {
	mesgPane(XRN_SERIOUS, "Can not open a temporary file for the article, `%s' may not be writable", app_resources.tmpDir);
	/* read till end of article */
	do {
	    get_data_from_server(message, sizeof(message));
	} while ((message[0] != '.') || (message[1] != '\0'));
	FREE(filename);
	return(NIL(char));
    }

    for (;;) {
	get_data_from_server(message, sizeof(message));

	/* the article is ended by a '.' on a line by itself */
	if ((message[0] == '.') && (message[1] == '\0')) {
	    /* check for a bogus message */
	    if (byteCount == 0) {
		(void) fclose(articlefp);
		(void) unlink(filename);
		FREE(filename);
		return(NIL(char));
	    }
	    break;
	}

	msg = &message[0];

	/* find header/body seperation */
	if (*position == 0) {
	    if (*msg == '\0') {
		*position = byteCount;
	    }
	}
	      
	if (*msg == '.') {
	    msg++;
	}

	if (*msg != '\0') {
	    /* strip leading ^H */
	    while (*msg == '\b') {
		msg++;
	    }
	    /* strip '<character>^H' */
	    for (ptr = index(msg + 1, '\b'); ptr != NIL(char); ptr = index(ptr, '\b')) {
		if (ptr - 1 < msg) {
		    /* too many backspaces, kill all leading back spaces */
		    while (*ptr == '\b') {
		        (void) strcpy(ptr, ptr + 1);
			ptr++;
		    }
		    break;
		}
		(void) strcpy(ptr - 1, ptr + 1);
		ptr--;
	    }

#ifdef REALLY_USE_LOCALTIME
  	    if (app_resources.displayLocalTime && !strncmp(msg, "Date: ", 6)) {
		  tconvert(temp, msg+6);
		  (void) strcpy(msg+6, temp);
	    }
#endif
	    /* strip the headers */
	    if ((*position == 0) && (header == NORMAL_HEADER)) {
		if ((*msg == ' ') || (*msg == '\t')) { /* continuation line */
		    if (last_stripped)
			continue;
		}
		else {
		    if ((ptr = index(msg, ':')) == NIL(char)) {
			continue; /* weird header line, skip */
		    }
		    if (*(ptr+1) == '\0') {
			continue; /* empty field, skip */
		    }
		    (void) strncpy(field, msg, (int) (ptr - msg));
		    field[(int) (ptr - msg)] = '\0';
		    utDowncase(field);
		    if (avl_lookup(app_resources.headerTree, field, &ptr)) {
			if (app_resources.headerMode == STRIP_HEADERS) {
			    last_stripped = 1;
			    continue;
			}
			else
			    last_stripped = 0;
		    } else {
			if (app_resources.headerMode == LEAVE_HEADERS) {
			    last_stripped = 1;
			    continue;
			}
			else
			    last_stripped = 0;
		    }
		}
	    }

	    /* handle rotation of the article body */
	    if ((rotation == ROTATED) && (*position != 0)) {
		for (ptr = msg; *ptr != '\0'; ptr++) {
		    if (isalpha(*ptr)) {
			if ((*ptr & 31) <= 13) {
			    *ptr = *ptr + 13;
			} else {
			    *ptr = *ptr - 13;
			}
		    }
		}
	    }

	    /* handle ^L (poorly?) */
	    if (*msg == '\014') {
		int i, lines;
		lines = articleLines();
		lines -= lineCount % lines;
		for (i = 0; i < lines; i++) {
		    if (putc('\n', articlefp) == EOF) {
			error++;
			break;
		    }
		}
		if (error) {
		    break;
		}
		byteCount += lines;
		lineCount += lines;
		msg++;
	    }
	    if (fputs(msg, articlefp) == EOF) {
		error++;
		break;
	    }
	}
	if (putc('\n', articlefp) == EOF) {
	    error++;
	    break;
	}
	byteCount += utStrlen(msg) + 1;
	lineCount++;
    }

    if (!error) {
	if (fclose(articlefp) == 0) {
	    return(filename);
	}
    } else {
	(void) fclose(articlefp);
	/* read till end of article */
	do {
	    get_data_from_server(message, sizeof(message));
	} while ((message[0] != '.') || (message[1] != '\0'));
    }
    mesgPane(XRN_SERIOUS, "Error when writing the article file: %s",
	     errmsg(errno));
    (void) unlink(filename);
    FREE(filename);
    return(NIL(char));
}


int
getgroup(name, first, last, number)
char *name;     /* group name                 */
art_num *first; /* first article in the group */
art_num *last;  /* last article in the group  */
int *number;    /* number of articles in the group, if 0, first and last are bogus */
/*
 * enter a new group and get its statistics (and update the structure)
 *   allocate an array for the articles and process the .newsrc article
 *   info for this group
 *
 *   returns: NO_GROUP on failure, 0 on success
 *
 */
{
    char command[MESSAGE_SIZE], message[MESSAGE_SIZE];
    char group[GROUP_NAME_SIZE];
    long code, num, count, frst, lst;
    static char lastGroup[GROUP_NAME_SIZE];

    if (name == NIL(char)) {
	name = XtNewString(lastGroup);
    } else {
	(void) strcpy(lastGroup, name);
    }


    (void) sprintf(command, "GROUP %s", name);
    put_server(command);
    get_data_from_server(message, sizeof(message));

    check_time_out(command, message, sizeof(message));
    
    if (*message != CHAR_OK) {
	if (atoi(message) != ERR_NOGROUP) {

		(void) strcat(mybuf, "        Request was: ");
		(void) strcat(mybuf, command);
		(void) strcat(mybuf, "\n");
		(void) strcat(mybuf, "        Failing response was: ");
		(void) strcat(mybuf, message);
		ehErrorExitXRN(mybuf);
	}
	mesgPane(XRN_SERIOUS, "Can't get the group, looks like it was deleted out from under us");
	
	/* remove the group from active use ??? */
	
	return(0);
    }

    /* break up the message */
    count = sscanf(message, "%ld %ld %ld %ld %s", &code, &num, &frst, &lst, group);
    assert(count == 5);

    if (number != NIL(int)) {
	*number = num;
    }
    if (first != NIL(art_num)) {
	*first = frst;
    }
    if (last != NIL(art_num)) {
	*last = lst;
    }

    return(1);
}


void
getactive()
/*
 * get a list of all active newsgroups and create a structure for each one
 *
 *   returns: void
 */
{
    char command[MESSAGE_SIZE], message[MESSAGE_SIZE], group[GROUP_NAME_SIZE];
    char type[MESSAGE_SIZE];
    struct newsgroup *newsgroup;
    art_num first, last;
    char *ptr;


    (void) strcpy(command, "LIST");
    put_server(command);
    get_data_from_server(message, sizeof(message));

    check_time_out(command, message, sizeof(message));
    
    if (*message != CHAR_OK) {
	(void) strcat(mybuf, "        Request was: ");
	(void) strcat(mybuf, command);
	(void) strcat(mybuf, "\n");
	(void) strcat(mybuf, "        Failing response was: ");
	(void) strcat(mybuf, message);
	ehErrorExitXRN(mybuf);
    }

    for (;;) {
	get_data_from_server(message, sizeof(message));
	
	/* the list is ended by a '.' at the beginning of a line */
	if (*message == '.') {
	    break;
	}

	/* server returns: group last first y/m/x/=otherGroup */

	if (sscanf(message, "%s %ld %ld %s", group, &last, &first, type) != 4) {
	    mesgPane(XRN_SERIOUS, "Bogus active file entry, skipping\n%s", message);
	    continue;
	}

	if (type[0] == 'x') {
	    /* bogus newsgroup, pay no attention to it */
	    continue;
	}

	if (type[0] == '=') {
	    /* This newsgroup doesn't exist, it's just an alias */
	    continue;
	}

	if (first == 0) {
	    first = 1;
	}

	if (!avl_lookup(NewsGroupTable, group, &ptr)) {

	    /* no entry, create a new group */
	    newsgroup = ALLOC(struct newsgroup);
	    newsgroup->name = XtNewString(group);
	    newsgroup->newsrc = NOT_IN_NEWSRC;
	    newsgroup->status = NG_NOENTRY;
	    newsgroup->first = first;
	    newsgroup->last = last;
	    newsgroup->nglist = 0;
#ifdef notdef
	    if (last >= first) {
		    (void) fprintf(stderr,"allocate %d bytes for %d articles in %s\n", (newsgroup->last - newsgroup->first + 1) * sizeof (struct article),(newsgroup->last - newsgroup->first + 1), group);
		newsgroup->articles = ARRAYALLOC(struct article, newsgroup->last - newsgroup->first + 1);
		for (art = newsgroup->first; art <= newsgroup->last; art++) {
		    long indx = INDEX(art);
	
		    newsgroup->articles[indx].subject = NIL(char);
		    newsgroup->articles[indx].author = NIL(char);
		    newsgroup->articles[indx].lines = NIL(char);
		    newsgroup->articles[indx].filename = NIL(char);
		    newsgroup->articles[indx].status = ART_CLEAR;
		}
	    } else {
		newsgroup->articles = NIL(struct article);
	    }
#else
		newsgroup->articles = NIL(struct article);
#endif
	    
	    switch (type[0]) {
		case 'y':
		newsgroup->status |= NG_POSTABLE;
		break;

		case 'm':
		newsgroup->status |= NG_MODERATED;
		break;

		case 'n':
		newsgroup->status |= NG_UNPOSTABLE;
		break;

		default:
		/*
		fprintf(stderr, "unexpected type (%s) for newsgroup %s\n",
			type, newsgroup->name);
		*/
		break;
	    }
	
	    (void) avl_insert(NewsGroupTable, newsgroup->name, (char *) newsgroup);

	    ActiveGroupsCount++;
	    
	} else {
	    
	    /*
	     * entry exists, use it; must be a rescanning call
	     *
	     * just update the first and last values and adjust the
	     * articles array
	     */
	    
	    newsgroup = (struct newsgroup *) ptr;

	    /*
	     * only allow last to increase or stay the same
	     * - don't allow bogus last values to trash a group
	     */
	    if (IS_SUBSCRIBED(newsgroup) && last >= newsgroup->last) {
		/* XXX really should save up the resync and use the GROUP info also */
		articleArrayResync(newsgroup, first, last, 1);
	    }
	}
    }

    return;
}


void
badActiveFileCheck()
/*
 * check the case where the first and last article numbers are equal
 * - unfortunately, this means two different things:
 *   1) there are no articles in the group
 *   2) there is one article in the group
 *
 * - so, to get rid of the ambiguity, we make a GROUP call
 *   and look at the 'number' of articles field to determine
 *   whether there are 0 or 1 articles
 */
{
    avl_generator *gen;
    char *key, *value;
    int number;

    /* check out first == last groups */
    gen = avl_init_gen(NewsGroupTable, AVL_FORWARD);

    while (avl_gen(gen, &key, &value)) {
	struct newsgroup *newsgroup = (struct newsgroup *) value;

	if (IS_SUBSCRIBED(newsgroup) &&
	    (newsgroup->first == newsgroup->last) &&
	    (newsgroup->first != 0)) {

	    (void) getgroup(newsgroup->name, NIL(art_num), NIL(art_num), &number);
	    if (number == 0) {
		articleArrayResync(newsgroup, newsgroup->first, newsgroup->last, number);
	    }
	}
    }
    avl_free_gen(gen);

    return;
}


void
start_server(nntpserver)
char *nntpserver;
/*
 * initiate a connection to the news server
 *
 * nntpserver is the name of an alternate server (use the default if NULL)
 *
 * the server eventually used is remembered, so if this function is called
 * again (for restarting after a timeout), it will use it.
 *
 *   returns: void
 *
 */
{
    static char *server = NIL(char);   /* for restarting */
    int response, connected;

    if (server == NIL(char)) {
	
	if (nntpserver != NIL(char)) {
	    server = nntpserver;
	
	} else {
	    
	    if ((server = getserverbyfile(SERVER_FILE)) == NULL) {
		mesgPane(XRN_SERIOUS, "Can't get the name of the news server from `%s'.\n%s",
			 SERVER_FILE,
#ifndef VMS
		 "Either fix this file, or put NNTPSERVER in your environment."
		
#else
			 "Either fix this file, or define logical NNTPSERVER."
#endif
			 );
		ehErrorExitXRN("");
	    }
	}
    }

    do {
	if ((response = server_init(server)) < 0) {
	    connected = 0;
	    mesgPane(XRN_SERIOUS, "Failed to reconnect to the NNTP server (server_init), sleeping...");
	    sleep(60);
	    continue;
	}
	if (handle_server_response(response, server) < 0) {
	    connected = 0;
	    close_server();
	    mesgPane(XRN_SERIOUS, "Failed to reconnect to the NNTP server (handle_response), sleeping...");
	    sleep(60);
	    continue;
	}
	connected = 1;
    } while (!connected);
    
    return;
}
    

void
getsubjectlist(newsgroup, first, last)
struct newsgroup *newsgroup;
art_num first;
art_num last;
/*
 * get a list of subject lines for the current group in the range
 *  'first' to 'last'
 *
 *   returns: void
 *
 * Note that XHDR is not part of the rfc977 standard, but is implemented
 * by the Berkeley NNTP server
 *
 */
{
    char command[MESSAGE_SIZE], message[MESSAGE_SIZE], buffer[MESSAGE_SIZE];
    char *subjectline;
    long number;


    (void) sprintf(command, "XHDR subject %ld-%ld", first, last);
    put_server(command);
    get_data_from_server(message, sizeof(message));

    check_time_out(command, message, sizeof(message));

    /* check for errors */
    if (*message != CHAR_OK) {
	mesgPane(XRN_SERIOUS,
"XRN: serious error, your NNTP server does not have XHDR support.\n\
Either you are running a pre-1.5 NNTP server or XHDR has\n\
not been defined in 'nntp/common/conf.h'\n\
XRN requires XHDR support to run.");
	return;
    }

    for(;;) {

	get_data_from_server(message, sizeof(message));
	
	if (*message == '.') {
	    break;
	}

	/*
	 * message is of the form:
	 *
	 *    Number SubjectLine
	 *
	 *    203 Re: Gnumacs Bindings
	 *
	 * must get the number since not all subjects will be returned
	 */

	number = atol(message);
	subjectline = index(message, ' ');
	(void) sprintf(buffer, "  %5ld\t%s", number, ++subjectline);

	newsgroup->articles[INDEX(number)].subject = XtNewString(buffer);
    }
    return;
}


void
getauthorlist(newsgroup, first, last)
struct newsgroup *newsgroup;
art_num first;
art_num last;
/*
 * get a list of author lines for the current group in the range
 *  'first' to 'last'
 *
 *   returns: void
 *
 * Note that XHDR is not part of the rfc977 standard, but is implemented
 * by the Berkeley NNTP server
 *
 */
{
    char command[MESSAGE_SIZE], message[MESSAGE_SIZE];
    char *author, *end, *brackbeg, *brackend;
    long number;

    
    (void) sprintf(command, "XHDR from %ld-%ld", first, last);
    put_server(command);
    get_data_from_server(message, sizeof(message));

    check_time_out(command, message, sizeof(message));

    /* check for errors */
    if (*message != CHAR_OK) {
	mesgPane(XRN_SERIOUS,
"XRN: serious error, your NNTP server does not have XHDR support.\n\
Either you are running a pre-1.5 NNTP server or XHDR has\n\
not been defined in 'nntp/common/conf.h'\n\
XRN requires XHDR support to run.");
	return;
    }
    
    for(;;) {

	get_data_from_server(message, sizeof(message));
	
	if (*message == '.') {
	    break;
	}

	/*
	 * message is of the form:
	 *
	 *    Number Author
	 *
	 *    201 ricks@shambhala (Rick L. Spickelmier)
	 *    202 Jens Thommasen <jens@ifi.uio.no>
	 *    203 <oea@ifi.uio.no>
	 *    302 "Rein Tollevik" <rein@ifi.uio.no>
	 *
	 * must get the number since not all authors will be returned
	 */

	number = atol(message);
      if (app_resources.authorFullName) {
	/* Can be made fancyer at the expence of extra cpu time */
	author = index(message, ' ');
	assert(author != NIL(char));
	author++;

	/* First check for case 1, user@domain ("name") -> name */

	brackbeg = index(message, '(');
	brackend = index(message, '\0') - sizeof(char);
	/* brackend now points at the last ')' if this is case 1 */
	if (brackbeg != NIL(char) && (brackend > brackbeg) &&
	    (*brackend == ')')) {
	    author = brackbeg + sizeof(char);

	    /* Remove surrounding quotes ? */
	    if ((*author == '"') && (*(brackend - sizeof(char)) == '"')) {
	      author++;
	      brackend--;
	    }

	    /* Rather strip trailing spaces here */

	    *brackend = '\0';
	} else {
	    /* Check for case 2, "name" <user@domain> -> name */
	    brackbeg = index(message, '<');
	    if (brackbeg != NIL(char) && (index(brackbeg, '>') != NIL(char))
		&& (brackbeg > message)) {
		while (*--brackbeg == ' ')
		  ;

		/* Remove surrounding quotes ? */
		if ((*brackbeg == '"') && (*author ==  '"')) {
		    *brackbeg = '\0';
		    author++;

		    /* Rather strip trailing spaces here */

		} else {
		    *++brackbeg = '\0';
		}
	    } else {

		/* 
		 * Check for case 3, <user@domain> -> usr@domain
	         *
		 * Don't need to do this again:
	         * brackbeg = index(message, '<');
                 */

		brackend = index(message, '>');
		if ((author == brackbeg) && (brackend != NIL(char))) {
		    author++;
		    *brackend = '\0';
		} else {
		    if ((end = index(author, ' ')) != NIL(char)) {
			*end = '\0';
		    }
		}
	    }
	}
      } else {
	if ((author = index(message, '<')) == NIL(char)) {
	    /* first form */
	    author = index(message, ' ');
	    assert(author != NIL(char));
	    author++;
	    if ((end = index(author, ' ')) != NIL(char)) {
		*end = '\0';
	    }
	} else {
	    /* second form */
	    author++;
	    if ((end = index(author, '>')) != NIL(char)) {
		*end = '\0';
	    }
	}
      }
      /*
       * do a final trimming - just in case the authors name ends
       * in spaces or tabs - it does happen
       */
      end = author + utStrlen(author) - 1;
      while ((end > author) && ((*end == ' ') || (*end == '\t'))) {
	*end = '\0';
	end--;
      }
      newsgroup->articles[INDEX(number)].author = XtNewString(author);
    }
    return;
}

void
getlineslist(newsgroup, first, last)
struct newsgroup *newsgroup;
art_num first;
art_num last;
/*
 * get a list of number of lines per message for the current group in the
 *  range 'first' to 'last'
 *
 *   returns: void
 *
 * Note that XHDR is not part of the rfc977 standard, but is implemented
 * by the Berkeley NNTP server
 *
 */
{
    char command[MESSAGE_SIZE], message[MESSAGE_SIZE];
    char *numoflines, *end;
    long number;
    int lcv;
 
    if (!app_resources.displayLineCount) {
	return;
    }

    (void) sprintf(command, "XHDR lines %ld-%ld", first, last);
    put_server(command);
    get_data_from_server(message, sizeof(message));

    check_time_out(command, message, sizeof(message));

    /* check for errors */
    if (*message != CHAR_OK) {
        mesgPane(XRN_SERIOUS,
"XRN: serious error, your NNTP server does not have XHDR support.\n\
Either you are running a pre-1.5 NNTP server or XHDR has\n\
not been defined in 'nntp/common/conf.h'\n\
XRN requires XHDR support to run.");
        return;
    }

    for(;;) {

        get_data_from_server(message, sizeof(message));

        if (*message == '.') {
            break;
        }

        /*
         * message is of the form:
         *
         *    Number NumberOfLines
         *
         *    203 ##
         *
         * must get the number since not all subjects will be returned
         */

        number = atol(message);
        numoflines = index(message, ' ');
	assert(numoflines != NIL(char));
	numoflines++;
	if ((end = index(numoflines, ' ')) != NIL(char)) {
		*end = '\0';
	}
	if (numoflines[0] != '(') {
	    numoflines[utStrlen(numoflines)+1] = '\0';
	    numoflines[utStrlen(numoflines)] = ']';
	    for (lcv=utStrlen(numoflines) ; lcv >= 0 ; lcv--) {
		numoflines[lcv+1] = numoflines[lcv];
	    }
	    numoflines[0] = '[';
	} else {
	    numoflines[0] = '[';
	    numoflines[utStrlen(numoflines)-1] = ']';
	}
	if (strcmp(numoflines, "[none]") == 0) {
	    (void) strcpy(numoflines, "[?]");
	}
        newsgroup->articles[INDEX(number)].lines = XtNewString(numoflines);
    }
    return;
}


#ifndef INEWS
static void
sendLine(str)
char *str;
{
    if (*str == '.') {
	char *str2 = XtMalloc(utStrlen(str) + 2); /* one for the extra period,
						   * and one for the null at
						   * the end */
	str2[0] = '.';
	strcpy(str2 + 1, str);
	put_server(str2);
	XtFree(str2);
    }
    else {
	put_server(str);
    }
    return;
}
#endif


static char *
getLine(ptr)
char **ptr;
{
    static char line[512];
    char *end = index(*ptr, '\n');

    if (end) {
	(void) strncpy(line, *ptr, end - *ptr);
	line[end - *ptr] = '\0';
	*ptr = end + 1;
    } else {
	(void) strcpy(line, *ptr);
	*ptr = 0;
    }
    return line;
}


/*
 * Takes a block of text, wraps the text based on lineLength and
 * breakLength resources, and returns a NULL-terminated allocated
 * array of allocated strings representing the wrapped lines.  The
 * procedure which calls wrapText should use the wrapped Text and then
 * free each string and free the array.
 */
static char **
wrapText(ptr)
char *ptr;
{
     unsigned int c = 0;		/* current line length */
     char **lines, *this_line;
     unsigned int num_lines = 0;
     int breakAt = app_resources.breakLength;

     lines = (char **) XtMalloc((Cardinal) 0);

     if (app_resources.breakLength && app_resources.lineLength) {
	 /*
	  * Do text wrapping.
	  */
	 this_line = XtMalloc((Cardinal) (app_resources.breakLength + 1));

	 while (*ptr != '\0') {
	     if (c >= breakAt) {
		 /*
		  * Everything after the first line in a paragraph
		  * should be wrapped at lineLength, not breakLength.
		  * This prevents the last line of a paragraph from
		  * ending up a little bit longer than all the other
		  * lines (and extending into the margin), but not quite
		  * breakLength characters lines long.
		  */
		 breakAt = app_resources.lineLength;
		 /* backoff to app_resources.lineLength */
		 ptr -= c - app_resources.lineLength;
		 c = app_resources.lineLength;
		 for (; c > 0 && *ptr != ' ' && *ptr != '\t'; ptr--) {
#ifdef notdef
		     if (*ptr == '\t') {
		     }
#endif
		     c--;
		 }

		 if (c == 0) {
		     /* pathological, cut to app_resources.lineLength */
		     c = app_resources.lineLength;
		     ptr += app_resources.lineLength - 1;
		 }

		 /* output */
		 this_line[c] = '\0';
		 lines = (char **) XtRealloc((char *) lines, (Cardinal)
					     (sizeof(char *) * ++num_lines));
		 lines[num_lines-1] = this_line;
		 this_line = XtMalloc((Cardinal)
				      (app_resources.breakLength + 1));
		 c = 0;
		 if (strncmp(lines[num_lines-1],
			    app_resources.includePrefix,
			    utStrlen(app_resources.includePrefix)) == 0) {
		     strcpy(this_line, app_resources.includePrefix);
		     c += utStrlen(app_resources.includePrefix);
		 }

		 /*
		  * Delete any extra spaces, tabs or carriage returns at 
		  * the beginning of the next line.  This is necessary
		  * because we may break a line in the middle of a
		  * multi-space word break (e.g. the end of a sentence),
		  * or right before the paragraph-ending carriage
		  * return, which we've already printed as part of the
		  * line above.
		  */
		 while ((*ptr == ' ') || (*ptr == '\t') || (*ptr == '\n')) {
		     ptr++;
		     if (*(ptr-1) == '\n')
			  /* We only one to get rid of one carriage return */
			  break;
		 }
	       
		 continue;
	     }

	     if (*ptr == '\n') {
		 this_line[c] = '\0';
		 lines = (char **) XtRealloc((char *) lines, (Cardinal)
					     (sizeof(char *) * ++num_lines));
		 lines[num_lines-1] = this_line;
		 this_line = XtMalloc((Cardinal)
				      (app_resources.breakLength + 1));
		 if (c == 0)
		     breakAt = app_resources.breakLength;
		 c = 0, ptr++;
		 continue;
	     }

#ifdef notdef
	     if (*ptr == '\t') {
		 c += c % 8;
		 continue;
	     }
#endif
	  
	     this_line[c++] = *ptr++;
	 }

	 if (c != 0) {
	     this_line[c] = '\0';
	     lines = (char **) XtRealloc((char *) lines, (Cardinal)
					 (sizeof(char *) * ++num_lines));
	     lines[num_lines-1] = this_line;
	 }
     }
     else {
	 /*
	  * Don't do text wrapping, just break the text at linefeeds.
	  */
	 while (*ptr) {
	     c = 0;
	     for (; *ptr && (*ptr != '\n'); ptr++, c++) ;
	     if (c || *ptr) {
		 this_line = XtMalloc((Cardinal) (c + 1));
		 lines = (char **) XtRealloc((char *) lines,
					     (Cardinal) (sizeof(char *) *
							 ++num_lines));
		 strncpy(this_line, &ptr[-c], c);
		 this_line[c] = '\0';
		 lines[num_lines-1] = this_line;
		 if (*ptr)
		     ptr++;
	     }
	 }
     }
     
     lines = (char **) XtRealloc((char *) lines,
				 (Cardinal) (sizeof(char *) * ++num_lines));
     lines[num_lines-1] = NULL;

     return(lines);
}



int
mailArticle(article)
char *article;
{
     extern FILE *popen _ARGUMENTS((const char *, const char *)); /* sequent */

     FILE *fp;
     char **lines_ptr, **lines;
     char *ptr;

     if ((fp = popen(app_resources.mailer, "w")) == NULL)
	  return POST_FAILED;

     /* First, send everything up to the first blank line without any */
     /* wrapping. 						      */
     while (1) {
	  ptr = index(article, '\n');
	  if ((ptr == article) || (ptr == NULL))
	       /* line has nothing but newline or end of article */
	       break;
	  (void) fwrite(article, sizeof(char), (unsigned) (ptr - article + 1), fp);
	  article = ptr + 1;
     }
     
     lines_ptr = lines = wrapText(article);
     while (*lines) {
	  (void) fwrite(*lines, sizeof(char), utStrlen(*lines), fp);
	  (void) fwrite("\n", sizeof(char), 1, fp); /* wrapText deletes newlines */
	  FREE(*lines);
	  lines++;
     }
     FREE(lines_ptr);

     return pclose(fp) ? POST_FAILED : POST_OKAY;
}




int
postArticle(article, mode, ErrMsg)
char *article;
int mode;   /* XRN_NEWS or XRN_MAIL */
char **ErrMsg;
/*
 * post an article
 *
 *   returns 1 for success, 0 for failure
 */
{
    char command[MESSAGE_SIZE], message[MESSAGE_SIZE];
    char *ptr, *saveptr;
    char **lines, **lines_ptr;
    char *tempfile;

#ifdef INEWS
    int exitstatus;
    char buffer[1024];
    FILE *inews;
#endif

    *ErrMsg = 0;

    if (mode == XRN_MAIL) {
	return mailArticle(article);
    }

#ifdef INEWS
    tempfile = XtNewString(utTempnam(app_resources.tmpDir, "xrn"));
    (void) sprintf(buffer, "%s -h > %s 2>&1",INEWS, tempfile);
    if ((inews = popen(buffer, "w")) == NULL) {
	mesgPane(XRN_SERIOUS, "Failed to start inews\n");
	(void) unlink(tempfile);
	FREE(tempfile);
	return POST_FAILED;
    }
#else

    (void) strcpy(command, "POST");
    put_server(command);
    get_data_from_server(message, sizeof(message));

    check_time_out(command, message, sizeof(message));

    if (*message != CHAR_CONT) {
	mesgPane(XRN_SERIOUS, "NNTP Serious Error: %s", message);
	if (atoi(message) == ERR_NOPOST) {
	    return(POST_NOTALLOWED);
	} else {
	    return(POST_FAILED);
	}
    }
#endif

    ptr = article;

    while (1) {
	char *line;

	saveptr = ptr;

	line = getLine(&ptr);
	if (index(line, ':') || (*line == ' ') || (*line == '\t')) {
#ifdef INEWS
	    fputs(line, inews);
	    fputc('\n', inews);
#else
	    sendLine(line);
#endif
	    continue;
	}
	break;
    }

#ifdef INEWS
    fputs("\n\n", inews);
#else
    sendLine("");		/* send a blank line */
#endif

    if (*saveptr != '\n') {
	 /* if the skipped line was not blank, point back to it */
	 ptr = saveptr;
    }

    lines_ptr = lines = wrapText(ptr);
    while (*lines) {
#ifdef INEWS
         fputs(*lines, inews);
	 fputc('\n', inews);
#else
	 sendLine(*lines);
#endif
	 XtFree(*lines);
	 lines++;
    }
    FREE(lines_ptr);
    
#ifdef INEWS
    if (exitstatus = pclose(inews)) {
	FILE *filefp;
	char *p
	struct stat buf;
	char temp[1024];

	(void) sprintf(temp, "\n\ninews exit value: %d\n", exitstatus);
	if ((filefp = fopen(tempfile, "r")) != NULL) {
	    if (fstat(fileno(filefp), &buf) != -1) {
		p = XtMalloc(buf.st_size + utStrlen(temp) + 10);
		(void) fread(p, sizeof(char), buf.st_seize, filefp);
		p[buf.st_size] = '\0';
		(void) strcat(p, temp);
		(void)_ fclose(filefp);
		*ErrMsg = p;
	    }
	}
	(void) unlink(tempfile);
	FREE(tempfile);
	return(POST_FAILED);
    }
#else
    put_server(".");

    get_data_from_server(message, sizeof(message));

    if (*message != CHAR_OK) {
	*ErrMsg = XtNewString(message);
	return(POST_FAILED);
    }
#endif

    return(POST_OKAY);
}


#ifdef DONT_USE_XHDR_FOR_A_SINGLE_ITEM

void
xhdr(article, field, string)
art_num article;
char *field;
char **string;
/*
 * get header information about 'article'
 *
 *   the results are stored in 'string'
 */
{
    char buffer[BUFFER_SIZE], message[MESSAGE_SIZE], *ptr, *cmp, *found = 0;

    /*
     * In some implementations of NNTP, the XHDR request on a
     * single article can be *very* slow, so we do a HEAD request
     * instead and just search for the appropriate field.
     */
    (void) sprintf(buffer, "HEAD %ld", article);
    put_server(buffer);
    get_data_from_server(message, sizeof(message));

    check_time_out(buffer, message, sizeof(message));

    if (*message != CHAR_OK) {
	/* can't get header */
	*string = NIL(char);
	return;
    }

    for (;;) {
	get_data_from_server(message, sizeof(message));

	/* the header information is ended by a '.' on a line by itself */
	if (message[0] == '.')
	    break;

	if (!found) {
	    for (ptr = message, cmp = field; *ptr; ptr++, cmp++) {
		/* used to be 'mklower' */
		if (tolower(*cmp) != tolower(*ptr))
		    break;
	    }
	    if (*cmp == 0 && *ptr == ':') {
		while (*++ptr == ' ')
		    ;
		found = XtNewString(ptr);
	    }
	}
    }

    if (found)
	*string = found;
    else
	*string = NIL(char);

    return;
}

#else

void
xhdr(article, field, string)
art_num article;
char *field;
char **string;
/*
 * get header information about 'article'
 *
 *   the results are stored in 'string'
 */
{
    char buffer[BUFFER_SIZE], message[MESSAGE_SIZE], *ptr;
    

    (void) sprintf(buffer, "XHDR %s %ld", field, article);
    put_server(buffer);
    get_data_from_server(message, sizeof(message));
    
    check_time_out(buffer, message, sizeof(message));
    
    /* check for errors */
    if (*message != CHAR_OK) {
	fprintf(stderr, "NNTP error: %s\n", message);
	*string = NIL(char);
	mesgPane(XRN_SERIOUS,
"XRN: serious error, your NNTP server does not have XHDR support.\n\
Either you are running a pre-1.5 NNTP server or XHDR has\n\
not been defined in 'nntp/common/conf.h'\n\
XRN requires XHDR support to run.");
	return;
    }
    
    get_data_from_server(message, sizeof(message));

    /* no information */
    if (*message == '.') {
	*string = NIL(char);
	return;
    }

    ptr = index(message, ' ');

    /* malformed entry */
    if (ptr == NIL(char)) {
	mesgPane(XRN_SERIOUS,
"XRN debugging message: malformed XHDR return\n\
command: %s, return: %s",
		       buffer, message);
	get_data_from_server(message, sizeof(message));
	return;
    }

    ptr++;

    /* no information */
    if (STREQ(ptr, "(none)")) {
	*string = NIL(char);
	/* ending '.' */
	do {
	    get_data_from_server(message, sizeof(message));
	} while (*message != '.');
	return;
    }

    *string = XtNewString(ptr);

    /* ending '.' */
    do {
	get_data_from_server(message, sizeof(message));
    } while (*message != '.');

    return;
}
#endif


struct article *
getarticles(newsgroup)
struct newsgroup *newsgroup;
{
#if	SLOW
    art_num first = newsgroup->first, last = newsgroup->last, art;
#else	SLOW
    register art_num first = newsgroup->first, last = newsgroup->last, art;
#endif	SLOW
#ifdef STUPIDMMU
    void cornered();
#endif

#ifdef	SLOW
    if (last >= first && last != 0) {
	newsgroup->articles = ARRAYALLOC(struct article, last - first + 1);

	for (art = first; art <= last; art++) {
	    long indx = INDEX(art);
    
	    newsgroup->articles[indx].subject = NIL(char);
	    newsgroup->articles[indx].author = NIL(char);
	    newsgroup->articles[indx].lines = NIL(char);
	    newsgroup->articles[indx].filename = NIL(char);
	    newsgroup->articles[indx].status = ART_CLEAR;
	}
    }
#else	SLOW
    if (last >= first && last != 0) {
	register struct article	*ap;
	newsgroup->articles = ARRAYALLOC(struct article, last - first + 1);

	ap = &newsgroup->articles[INDEX(first)];
    
	for (art = first; art <= last; art++) {
	    ap->subject = NIL(char);
	    ap->author = NIL(char);
	    ap->lines = NIL(char);
	    ap->filename = NIL(char);
	    ap->status = ART_CLEAR;
	    ap++;
	}
    }
#endif	SLOW
#ifdef STUPIDMMU
    cornered(newsgroup);
#endif
    return(newsgroup->articles);
}


#ifndef POPEN_USES_INEXPENSIVE_FORK

static int popen_pid = 0;

FILE *
popen(command, type)
/* const */ char *command;
/* const */ char *type;
{
    int pipes[2];
    int itype = (strcmp(type, "w") == 0 ? 1 : 0);

    if (pipe(pipes) == -1)
	return NULL;

    switch (popen_pid = vfork()) {
    case -1:
	(void)close(pipes[0]);
	(void)close(pipes[1]);
	return NULL;

    case 0:
	if (itype) {
	    dup2(pipes[0], fileno(stdin));
	    close(pipes[1]);
	} else {
	    dup2(pipes[1], fileno(stdout));
	    close(pipes[0]);
	}
	execl("/bin/sh", "/bin/sh", "-c", command, 0);
	fprintf(stderr, "XRN Error: failed the execlp\n");
	_exit(-1);
	/* NOTREACHED */

    default:
	    if (itype) {
		close(pipes[0]);
		return fdopen(pipes[1], "w");
	    } else {
		close(pipes[1]);
		return fdopen(pipes[0], "r");
	    }
    }
}


int
pclose(str)
FILE *str;
{
    int pd = 0;
    int	status;
    int	err;

    err = fclose(str);

    do {
	if ((pd = wait(&status)) == -1)
	{
		err = EOF;
		break;
	}
    } while (pd !=  popen_pid);

    if (err == EOF)
	return  -1;
	
    if (status)
	status >>= 8;	/* exit status in high byte */

    return status;
}

#endif

