#if !defined(lint) && !defined(SABER)
static char XRNrcsid[] = "$Header: /wrld/mnt11/ricks/src/X/xrn/RCS/compose.c,v 1.29 91/12/04 11:28:08 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.
 */

/*
 * compose.c: routines for composing messages and articles
 */

#include "copyright.h"
extern char *getinfofromfile(/* char *filename */);
#include <X11/Xos.h>
#include <ctype.h>
#include <stdio.h>
#include <errno.h>
#include "utils.h"
#include "config.h"
#include <sys/stat.h>
#include <signal.h>
#include "error_hnds.h"
#ifdef RESOLVER
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#endif
#ifndef VMS
#include <pwd.h>
#else
#define index strchr
#endif
#if defined(aiws) || defined(DGUX)
struct passwd *getpwuid();
struct passwd *getpwnam();
#endif

#include <X11/StringDefs.h>
#include <X11/Intrinsic.h>
#include <X11/Shell.h>
#include <X11/cursorfont.h>

#ifndef MOTIF
#include <X11/Xaw/Paned.h>
#include <X11/Xaw/Label.h>
#include <X11/Xaw/Command.h>
#include <X11/Xaw/Box.h>
#include <X11/Xaw/AsciiText.h>
#include <X11/Xaw/Dialog.h>
#else
#include <Xm/PanedW.h>
#include <Xm/Label.h>
#include <Xm/PushB.h>
#include <Xm/RowColumn.h>
#include <Xm/Text.h>
#include <Xm/MessageB.h>
#endif

#include "avl.h"
#include "xthelper.h"
#include "resources.h"
#include "dialogs.h"
#include "server.h"
#include "mesg.h"
#include "news.h"
#include "internals.h"
#include "xrn.h"
#include "xmisc.h"


#ifndef MOTIF
#define XAWSEARCH(widget, string, dir, pos) \
    {\
	XawTextBlock block; \
	block.firstPos = 0; \
	block.ptr = string; \
	block.length = utStrlen(block.ptr); \
	block.format = FMT8BIT; \
	pos = XawTextSearch(widget, dir, &block); \
    }

#define XAWTEXTINSERT(widget, loc, string) \
    { \
	XawTextBlock block; \
	block.firstPos = 0; \
	block.ptr = string; \
	block.length = utStrlen(block.ptr); \
	block.format = FMT8BIT; \
	(void) XawTextReplace(widget, loc, loc, &block); \
    }
#else

/**********************************************************************
Yuck, yuck, yuck.  Provide Xaw-like routines to manipulate the Motif
text widget instead of the Xaw one.  We can't use the stuff from
MotifXawHack.h, because it manipulates a Motif list widget.
**********************************************************************/

#define MOTIF_FORWARD 1
#define MOTIF_BACKWARD 2
#define XawsdRight MOTIF_FORWARD
#define XawsdLeft MOTIF_BACKWARD
#define XawTextSearchError (-1)
#define XawTextPosition XmTextPosition

int MotifSearch(w, str, dir)
Widget w;
char *str;
int dir;
{
  char *data, *area, *p, *q;
  int result;
  extern char *strstr _ARGUMENTS((const char *, const char *));

  data = XmTextGetString(w);
  if (dir == MOTIF_FORWARD) {
    area = data;
    area += XmTextGetCursorPosition(w);
  } else {
    area = data;
    data[XmTextGetCursorPosition(w)] = '\0';
  }
  p = strstr(area, str);
  if (p && (dir == MOTIF_BACKWARD)) {
    q = p+1;
    while (q) {
      q = strstr(q, str);
      if (q) {
	p = q;
	q++;
      }
    }
  }
  if (p) {
    result = p-data;
  } else {
    result = XawTextSearchError;
  }
  XtFree(data);
  return result;
}

void MotifInsert(w, loc, str)
Widget w;
XmTextPosition loc;
char *str;
{
  XmTextInsert(w, (XmTextPosition) loc, str);
}

#define XAWSEARCH(widget, string, dir, pos) \
	{ pos = MotifSearch(widget, string, dir); }

#define XAWTEXTINSERT(widget, loc, string) \
	{ MotifInsert(widget, loc, string); }

#endif

/* entire pane */
static Widget ComposeTopLevel = (Widget) 0;
/* text window */
static Widget ComposeText = (Widget) 0;

#define BUFFER_SIZE 1024

#define POST     0
#define FOLLOWUP 1
#define REPLY    2
#define FORWARD  3
#define GRIPE    4
static int PostingMode = POST;

struct header {
    art_num article;
    char *artFile;
    char *newsgroups;
    char *subject;
    char *messageId;
    char *followupTo;
    char *references;
    char *from;
    char *replyTo;
    char *distribution;
    char *keywords;

    char *user;
    char *real_user;
    char *fullname;
    char *host;
    char *real_host;
    char *path;
    char *organization;

    char  *date; /* added to header structure....... */
};


/*
 * storage for the header information needed for building, checking,
 * and repairing the header.  Once this is created, the user can go
 * to another article or another group and still use the composition
 * window
 */

static struct header Header = {0, NIL(char), NIL(char), NIL(char), NIL(char), NIL(char),
                               NIL(char), NIL(char), NIL(char), NIL(char), NIL(char),
 NIL(char),NIL(char)};


#define HOST_NAME_SIZE 1024


static void
getHeader(article)
art_num article;
/*
 * get the header and other important information for an article
 *
 * if called with article equal to zero, will only set up the non-article
 * specific entries
 *
 */
{
    struct passwd *pwd;
    char host[HOST_NAME_SIZE], buffer[BUFFER_SIZE], *ptr;
    char real_host[HOST_NAME_SIZE];
    char path[HOST_NAME_SIZE];
#ifdef RESOLVER
    struct hostent *hent;
#endif
    char *getenv _ARGUMENTS((const char *));
    
    if (article > 0) {
	Header.article = article;
#ifdef XRN_PREFETCH
	/*
	 * handle the case where this is a request at the last
	 * article and the next newsgroup has been prefetched.
	 *
	 * must go back to the current newsgroup so the header information
	 * can be retrieved from the server
	 */
	if (!invalidatePrefetchedGroup(Newsrc[CurrentGroupNumber])) {
	    mesgPane(XRN_SERIOUS, "The newsgroup has been deleted out from underneath us");
	} else {
#endif /* XRN_PREFETCH */
	    xhdr(article, "newsgroups", &Header.newsgroups);
	    xhdr(article, "subject", &Header.subject);
	    xhdr(article, "message-id", &Header.messageId);
	    xhdr(article, "followup-to", &Header.followupTo);
	    xhdr(article, "references", &Header.references);
	    xhdr(article, "from", &Header.from);
	    xhdr(article, "reply-to", &Header.replyTo);
	    xhdr(article, "distribution", &Header.distribution);
	    xhdr(article, "keywords", &Header.keywords);
#ifdef XRN_PREFETCH
	}
#endif /* XRN_PREFETCH */
    } else {
	/* information for 'post' */
	if (CurrentGroupNumber != NO_GROUP) {
	    Header.newsgroups = XtNewString(Newsrc[CurrentGroupNumber]->name);
	} else {
	    Header.newsgroups = XtNewString("");
	}
    }

    /*
     * since I'm lazy down below, I'm going to replace NIL pointers with ""
     */
    if (Header.newsgroups == NIL(char)) {
	Header.newsgroups = XtNewString("");
    }
    if (Header.subject == NIL(char)) {
	Header.subject = XtNewString("");
    }
    if (Header.messageId == NIL(char)) {
	Header.messageId = XtNewString("");
    }
    if (Header.followupTo == NIL(char)) {
	Header.followupTo = XtNewString("");
    }
    if (Header.references == NIL(char)) {
	Header.references = XtNewString("");
    }
    if (Header.from == NIL(char)) {
	Header.from = XtNewString("");
    }
    if (Header.replyTo == NIL(char)) {
	Header.replyTo = XtNewString("");
    }
    if (Header.distribution == NIL(char)) {
	Header.distribution = XtNewString("");
    }
    if (Header.keywords == NIL(char)) {
	Header.keywords = XtNewString("");
    }

    real_host[0] = 0;
    host[0] = 0;
#ifdef	UUCPNAME
    {
        FILE * uup;
    
        if ((uup = fopen(UUCPNAME, "r")) != NULL) {
    		char   *p;
    		char    xbuf[BUFSIZ];
    
    		while (fgets(xbuf, sizeof(xbuf), uup) != NULL) {
    		    if (*xbuf <= ' ' || *xbuf == '#')
    			continue;
    		    break;
    		}
    		if (p = index(xbuf, '\n'))
    		    *p = 0;
    		(void) strncpy(real_host, xbuf, sizeof(real_host) - 1);
		strcpy(host, real_host);
    		(void) fclose(uup);
        }
    }
#endif
    if (real_host[0] == 0) {
	if ((ptr = getinfofromfile(HIDDEN_FILE)) != NULL) {
	    (void) strncpy(real_host, ptr, sizeof(real_host) - 1);
	} else {
#ifdef RETURN_HOST
	    (void) strcpy(real_host, RETURN_HOST);
#else /* Not RETURN_HOST */
#ifndef VMS
	    (void) gethostname(real_host, sizeof(real_host));
#ifdef RESOLVER
	    hent = gethostbyname(real_host);
	    if (hent != NULL) {
		(void) strcpy(real_host,hent->h_name);
	    }
#endif
#else
	    ptr = getenv("SYS$NODE");
	    (void) strcpy(real_host, ptr);
	    ptr = index(real_host, ':');
	    if (ptr != NIL(char)) {
		*ptr = '\0';
	    }
	    (void) strcat(real_host, DOMAIN_NAME);
	    XmuCopyISOLatin1Lowered(real_host, real_host);
#endif
#endif /* Not RETURN_HOST */
	}
        if ((ptr = getenv("HIDDENHOST")) != NIL(char)) {
	    (void) strncpy(host, ptr, sizeof(host)-1);
        }
	else
	    strcpy(host, real_host);
    }
    
    if ((ptr = getenv("HIDDENPATH")) != NIL(char)) {
	(void) strncpy(path, ptr, sizeof(path)-1);
    } else if ((ptr = getinfofromfile(PATH_FILE)) != NULL) {
	(void) strncpy(path, ptr, sizeof(path)-1);
    } else {
	(void) strncpy(path, host, sizeof(path)-1);
    }
    
    /* If the host name is not a full domain name, put in the domain */
    if (index (host, '.') == NIL(char)) {
        char   *domain;
    
        if ((domain = getenv ("DOMAIN")) != NIL (char)) {
	    (void) strcat(host, domain);
	} else if ((domain = getinfofromfile(DOMAIN_FILE)) != NULL) {
	    (void) strcat(host, domain);
        } else {
	    (void) strcat (host, DOMAIN_NAME);
	}
    }

    Header.host = XtNewString(host);
    Header.real_host = XtNewString(real_host);
    Header.path = XtNewString(path);

    if (app_resources.organization != NIL(char)) {
	Header.organization = XtNewString(app_resources.organization);
#ifndef apollo
    } else if ((ptr = getenv ("ORGANIZATION")) != NIL(char)) {
#else
    } else if ((ptr = getenv ("NEWSORG")) != NIL(char)) {
#endif
	Header.organization = XtNewString(ptr);
    } else if ((ptr = getinfofromfile(ORG_FILE)) != NULL) {
	Header.organization = XtNewString(ptr);
    } else {
	Header.organization = XtNewString(ORG_NAME);
    }

#ifndef VMS
    pwd = getpwuid(getuid());
    if (Header.user = getenv("USER")) {
	Header.user = XtNewString(Header.user);
    } else if (Header.user = pwd->pw_name) {
	Header.user = XtNewString(Header.user);
    } else {
	Header.user = XtNewString("");
    }

    if (Header.real_user = pwd->pw_name) {
	Header.real_user = XtNewString(Header.real_user);
    } else {
	Header.real_user = XtNewString("");
    }

    if (Header.fullname = getenv("FULLNAME")) {
	Header.fullname = XtNewString(Header.fullname);
    } else if (Header.fullname = pwd->pw_gecos) {
	Header.fullname = XtNewString(Header.fullname);
    } else {
	Header.fullname = XtNewString("");
    }
#else
    if (Header.user = getenv("USER")) {
	Header.user = XtNewString(Header.user);
    } else {
	Header.user = XtNewString("");
    }
    XmuCopyISOLatin1Lowered(Header.user, Header.user);

    if (Header.fullname = getenv("FULLNAME")) {
	Header.fullname = XtNewString(Header.fullname);
    } else {
	Header.fullname = XtNewString("");
    }

#endif
    ptr = index(Header.fullname, ',');
    if (ptr != NIL(char)) {
	*ptr = '\0';
    }
    
    /* & expansion */
    ptr = index(Header.fullname, '&');
    if (ptr != NIL(char)) {
	char *p = buffer + (ptr - Header.fullname);

	buffer[0] = '\0';
	*ptr = '\0';
	(void) strcpy(buffer, Header.fullname);
	(void) strcat(buffer, Header.user);
	if (isascii(*p)) {
	    *p = toupper(*p);
	}
	ptr++;
	(void) strcat(buffer, ptr);
	FREE(Header.fullname);
	Header.fullname = XtNewString(buffer);
    }
    
    return;
}


static int
fieldExists(fieldName)
char *fieldName;
/*
 * see if a field exists in the header that begins with `fieldName'
 */
{
    XawTextPosition pos, eoh;
    char buf[100]; /* XXX can you imagine any header fields larger than */
		   /* 48 characters? - let's make it 100 to be safe */
    
#ifndef MOTIF
    XawTextSetInsertionPoint(ComposeText, 0);
#else
    XmTextSetCursorPosition(ComposeText, 0);
#endif
    XAWSEARCH(ComposeText, "\n\n", XawsdRight, eoh);
    if (eoh == XawTextSearchError) {
	eoh = 300; /* XXX XawTextGetLastPosition(ComposeText); */
    }
#ifndef MOTIF
    XawTextSetInsertionPoint(ComposeText, eoh);
#else
    XmTextSetCursorPosition(ComposeText, eoh);
#endif

    /*
     * The field is found if it either has a newline right before it,
     * or if it's the first thing in the posting.  We do the search
     * with the newline first, because that's the more common case,
     * and then, if it fails, try the same search without the new line
     * and see if we end up at position 0.
     */

    strcpy(buf, "\n");
    strcat(buf, fieldName);
    
    XAWSEARCH(ComposeText, buf, XawsdLeft, pos);
    if ((pos < eoh) && (pos >= 0)) {
	 return pos + 1;
    }

#ifndef MOTIF
    XawTextSetInsertionPoint(ComposeText, strlen(fieldName));
#else
    XmTextSetCursorPosition(ComposeText, strlen(fieldName));
#endif

    XAWSEARCH(ComposeText, fieldName, XawsdLeft, pos);
    if (pos == 0) {
	 return pos;
    }

    return XawTextSearchError;
}


static void
addField(field)
char *field;
/*
 * add a header field to a message.
 * this is a destructive operation.
 */
{
    XAWTEXTINSERT(ComposeText, 0, field);
    return;
}    


static void
stripField(fieldName)
char *fieldName;
/*
 * remove all fields from a header that begin with `fieldName'.
 * this is a destructive operation.
 */
{
    XawTextPosition pos;
    XawTextPosition end;

    while ((pos = fieldExists(fieldName)) != XawTextSearchError) {
#ifndef MOTIF
	 XawTextBlock block;

	 XawTextSetInsertionPoint(ComposeText, pos);
	 block.firstPos = 0;
	 block.ptr = "\n";
	 block.length = 1;
	 block.format = FMT8BIT;
	 end = XawTextSearch(ComposeText, XawsdRight, &block);
#else
	 XmTextSetCursorPosition(ComposeText, pos);
	 end = MotifSearch(ComposeText, "\n", MOTIF_FORWARD);
#endif
	 if (end == XawTextSearchError) {
	      fprintf(stderr,
		      "ouch!  can't find newline in stripField\n");
	      return;
	 }
	 /* delete the line */
#ifndef MOTIF
	 block.ptr = NIL(char);
	 block.length = 0;
	 XawTextReplace(ComposeText, pos, end + 1, &block);
#else
	 XmTextReplace(ComposeText, pos, end + 1, "");
#endif
    }
    return;
}


static void
returnField(fieldName, removed)
char *fieldName;
char *removed;
/*
 * remove all fields from a header that begin with `fieldName'
 * and return the removed characters from any one of the fields
 * in `removed'.
 * this is a destructive operation.
 */
{
     int pos;
     char *ptr;
#ifndef MOTIF
     Arg args[1];
#endif

#ifndef MOTIF
     XtSetArg(args[0], XtNstring, &ptr);
     XtGetValues(ComposeText, args, 1);
#else
     ptr = XmTextGetString(ComposeText);
#endif

     pos = fieldExists(fieldName);
     if (pos == XawTextSearchError) {
#ifdef MOTIF
	  FREE(ptr);
#endif
	  *removed = '\0';
	  return;
     }

     while (ptr[pos] && ptr[pos] != '\n')
	  *removed++ = ptr[pos++];
     *removed++ = ptr[pos++];
     *removed++ = '\0';
     
     stripField(fieldName);

#ifdef MOTIF
     FREE(ptr);
#endif

     return;
}

static void
destroyCompositionTopLevel()
{
    if (app_resources.editorCommand == NIL(char)) {
	XtPopdown(ComposeTopLevel);
	XtDestroyWidget(ComposeTopLevel);
	ComposeTopLevel = (Widget) 0;
    }
    return;
}

static void
freeHeader()
{
    if (PostingMode != GRIPE) {
	FREE(Header.artFile);
	FREE(Header.newsgroups);
	FREE(Header.subject);
	FREE(Header.messageId);
	FREE(Header.followupTo);
	FREE(Header.references);
	FREE(Header.from);
	FREE(Header.replyTo);
	FREE(Header.distribution);
	FREE(Header.keywords);
	FREE(Header.user);
	FREE(Header.real_user);
	FREE(Header.fullname);
	FREE(Header.host);
	FREE(Header.real_host);
	FREE(Header.path);
	FREE(Header.organization);
    }
    return;
}


static void
buildSubject(message)
char *message;
/*
 * add a subject field to the header of a message.
 * deal with supressing multiple '[rR][eE]: ' strings
 */
{
    if (STREQN(Header.subject, "Re: ", 4) ||
	STREQN(Header.subject, "RE: ", 4) ||
	STREQN(Header.subject, "re: ", 4)) {
	(void) strcat(message, "Subject: ");
    } else {
	(void) strcat(message, "Subject: Re: ");
    }
    (void) strcat(message, Header.subject);
    (void) strcat(message, "\n");

    return;
}



/*ARGSUSED*/
static void
abortFunction(widget)
Widget widget;
{
    char *ptr;

    destroyCompositionTopLevel();
    freeHeader();

    switch (PostingMode) {
    case POST:
    case FOLLOWUP:
	 ptr = "Article";
	 break;
    default:
	 ptr = "Message";
	 break;
    }

    mesgPane(XRN_INFO, "%s aborted", ptr);

    return;
}

    

static void
saveMessage(fname, msg)
char *fname;
char *msg;
{
    FILE *fp;
    char *file = utTildeExpand(fname);

    if (file == NIL(char)) {
	mesgPane(XRN_SERIOUS, "Cannot save the article/message");
	return;
    }
    
    (void) sprintf(error_buffer, "Saving in %s", file);
    infoNow(error_buffer);
    
    if ((fp = fopen(file, "a")) != NULL) {
	(void) fwrite(msg, sizeof(char), utStrlen(msg), fp);
	(void) putc('\n', fp);
	(void) putc('\n', fp);
	(void) fclose(fp);
    }
    
    return;
}



/*ARGSUSED*/
static void
saveFunction(widget)
Widget widget;
{
    Arg args[1];
    char *ptr;
    
#ifndef MOTIF
    XtSetArg(args[0], XtNstring, &ptr);
    XtGetValues(ComposeText, args, XtNumber(args));
    saveMessage(app_resources.savePostings, ptr);
#else
    ptr = XmTextGetString(ComposeText);
    saveMessage(app_resources.savePostings, ptr);
    XtFree(ptr);
#endif
    return;
}


static void
saveDeadLetter(msg)
char *msg;
{
    saveMessage(app_resources.deadLetters, msg);
    return;
}


#ifdef VMS
static Widget mailErrorDialog = NULL;

static void
popDownMail(widget, client_data, call_data)
Widget widget;
caddr_t client_data;
caddr_t call_data;
{
    PopDownDialog(mailErrorDialog);
    mailErrorDialog = NULL;
    return;
}


static void
mailError(status)
int status;
{
#include <descrip.h>
    static struct DialogArg args[] = {
	{"Click to continue", popDownMail, (caddr_t) -1},
    };
    char	VMSmessage[255];
    char	message[255];
    struct dsc$descriptor_s messageText;

    destroyCompositionTopLevel();
    freeHeader();

    messageText.dsc$b_class = DSC$K_CLASS_S;
    messageText.dsc$b_dtype = DSC$K_DTYPE_T;
    messageText.dsc$w_length = 255;
    messageText.dsc$a_pointer = &VMSmessage;

    sys$getmsg(status, &messageText, &messageText, 0, 0);
    VMSmessage[messageText.dsc$w_length] = '\0';
    (void) strcpy(message,"Error sending mail:");
    (void) strcat(message,VMSmessage);
    mailErrorDialog = CreateDialog(TopLevel, message, DIALOG_NOTEXT,
				   args, XtNumber(args));
    PopUpDialog(mailErrorDialog);
    return;
}
#endif /* VMS */




/*ARGSUSED*/
static void
sendFunction(widget)
Widget widget;
{
    char buffer[BUFFER_SIZE];
    char *ErrMessage;
#ifdef VMS    
#include <maildef.h>
    struct _itemList {
	short item_size;
	short item_code;
	int   item_ptr;
	int   item_rtn_size;
	int   end_list;
    } itemList;
	 
    char toString[BUFFER_SIZE];
    char subjString[BUFFER_SIZE];
    int	context, status;
    char *textPtr, *nl;
#endif
    /*
     * I am loathe to use buffers that are 1024 bytes long for the old
     * from line, new from line and sender line, since they are almost
     * certainly going to be much shorter than this, but this seems to
     * be the way things are done elsewhere in this file, so I'll
     * stick with it.
     */
    char oldNgString[BUFFER_SIZE];
    char newNgString[BUFFER_SIZE];
    char oldFromString[BUFFER_SIZE];
    char newFromString[BUFFER_SIZE];
    char senderString[BUFFER_SIZE];
    Arg args[1];
    char *ptr;
    int mode, i, j, len, comma;
    extern char *strstr _ARGUMENTS((const char *, const char *));

    if ((PostingMode == POST) || (PostingMode == FOLLOWUP)) {
	mode = XRN_NEWS;
	if (fieldExists("Subject:") == XawTextSearchError) {
	    if (PostingMode == POST) {
		XBell(XtDisplay(TopLevel), 0);
		mesgPane(XRN_SERIOUS,
			 "The Subject field is missing in your message!");
		return;
	    }
	    buffer[0] = '\0';
	    buildSubject(buffer);
	    addField(buffer);
	}

	if (PostingMode == FOLLOWUP) {
	    if (fieldExists("References:") == XawTextSearchError) {
		(void) sprintf(buffer, "References: %s\n", Header.messageId);
		addField(buffer);
	    }
	}

	/*
	 * Strip any From: field currently in the message, and store
	 * it in oldFromString.
	 */
	returnField("From:", oldFromString);
	/*
	 * If there was no From: field in the message, create a
	 * template one.
	 */
	if (*oldFromString == '\0')
	     (void) sprintf(oldFromString, "From: %s@%s (%s)\n",
			    Header.user, Header.host, Header.fullname);
	/*
	 * Now add into the message either the From: field that was
	 * pulled out or the one that was just created.
	 */
	addField(oldFromString);
	/*
	 * Now figure out what the default From: field should look
	 * like, with the real username in it (in case the user has
	 * specified a different username in the USER environment
	 * variable).
	 */
	(void) sprintf(newFromString, "From: %s@%s (%s)\n",
		       Header.real_user, Header.real_host, Header.fullname);
	/*
	 * Get rid of any Sender: field currently in the message --
	 * the Sender: field should not ever be inserted by the user.
	 */
	returnField("Sender:", senderString);
	/*
	 * If the default From: field is different from what's
	 * actually in the message, then insert a Sender: field with
	 * the default information in it.
	 */
	if (strcmp(oldFromString, newFromString)) {
	     (void) sprintf(senderString, "Sender: %s@%s (%s)\n",
			    Header.real_user, Header.real_host,
			    Header.fullname);
	     addField(senderString);
	}

	if (fieldExists("Newsgroups:") == XawTextSearchError) {
	    (void) sprintf(buffer, "Newsgroups: %s\n", Header.newsgroups);
	    addField(buffer);
	} else {
	    /*
	     * fix up the Newsgroups: field - inews can not handle spaces
	     * between group names
	     */
	    returnField("Newsgroups:", oldNgString);
	    len = strlen(oldNgString);
	    j = 0;
	    comma = 0;
	    for (i = 0; i < len; i++) {
		if (comma && (oldNgString[i] == ' ')) continue;
		comma = 0;
		newNgString[j++] = oldNgString[i];
		if (oldNgString[i] == ',') {
		    comma = 1;
		}
	    }
	    newNgString[j] = '\0';
	    addField(newNgString);
	}

	if (fieldExists("Path:") == XawTextSearchError) {
#if defined(INEWS) || defined(HIDE_PATH)
	    (void) sprintf(buffer, "Path: %s\n", Header.user);
#else
	    (void) sprintf(buffer, "Path: %s!%s\n", Header.path, Header.user);
#endif
	    addField(buffer);
	}

/*	stripField("Message-ID:");  .............we need this !!! */
	stripField("Relay-Version:");
	stripField("Posting-Version:");
	
    } else {
	mode = XRN_MAIL;
    }

#ifndef MOTIF
    XtSetArg(args[0], XtNstring, &ptr);
    XtGetValues(ComposeText, args, XtNumber(args));
#else
    ptr = XmTextGetString(ComposeText);
#endif
	
    switch (postArticle(ptr, mode,&ErrMessage)) {
	case POST_FAILED:
	XBell(XtDisplay(TopLevel), 0);
	if(ErrMessage){
	   char *p;
	   char temp[512];

	   sprintf(temp,"Could not send, saving in %s",
		   app_resources.deadLetters);
	   p = XtMalloc(strlen(ErrMessage)+strlen(temp)+5);
	   sprintf(p,"%s\n%s",ErrMessage,temp);
	   mesgPane(XRN_SERIOUS,p);
	   FREE(p);
	   FREE(ErrMessage);
        }
	else
	    mesgPane(XRN_SERIOUS, "Could not send, saving in %s",
		     app_resources.deadLetters);
	saveDeadLetter(ptr);
	return;
	
	case POST_NOTALLOWED:
	mesgPane(XRN_SERIOUS, "Posting not allowed from this machine, saving in %s",
		 app_resources.deadLetters);
	saveDeadLetter(ptr);
	break;
	
	case POST_OKAY:
	switch (mode) {
		case XRN_NEWS:
		mesgPane(XRN_INFO, "Article Posted");
		break;

		case XRN_MAIL:
		mesgPane(XRN_INFO, "Mail Message Sent");
		break;
	}
	break;
    }
	
#ifdef MOTIF
    XtFree(ptr);
#endif

    destroyCompositionTopLevel();
    freeHeader();

#ifdef VMS
	// XXX this needs to be fixed
	returnField("To", toString);
	returnField("Subject", subjString);
	context = 0;

#ifndef MOTIF
	XtSetArg(args[0], XtNstring, &ptr);
	XtGetValues(ComposeText, args, XtNumber(args));
#else
        ptr = XmTextGetString(ComposeText);
#endif

	status = MAIL$SEND_BEGIN(&context, &0, &0);
	if ((status&1) != 1) {
	   mailError(status);
	   return;
	}
	itemList.item_code = MAIL$_SEND_TO_LINE;
	itemList.item_size = strlen(toString);
	itemList.item_ptr = &toString;
	itemList.item_rtn_size = 0;
	itemList.end_list = 0;
	status = MAIL$SEND_ADD_ATTRIBUTE(&context, &itemList, &0);
	if ((status&1) != 1) {
	   mailError(status);
	   return;
	}
	itemList.item_code = MAIL$_SEND_SUBJECT;
	itemList.item_size = strlen(subjString);
	itemList.item_ptr = &subjString;
	itemList.item_rtn_size = 0;
	itemList.end_list = 0;
	status = MAIL$SEND_ADD_ATTRIBUTE(&context, &itemList, &0);
	if ((status&1) != 1) {
	   mailError(status);
	   return;
	}
/*
 * Iterate over the composition string, extracting records
 * delimited by newlines and sending each as a record
 *
 */
	textPtr = ptr;
	if (*textPtr == '\n')
	    textPtr++;

	for (;;) {
/* Find the newline or end of string */
	    for (nl = textPtr; (*nl != '\0') && (*nl != '\n'); nl++);
	    itemList.item_code = MAIL$_SEND_RECORD;
	    itemList.item_size = nl - textPtr;
	    itemList.item_ptr = textPtr;
	    itemList.item_rtn_size = 0;
	    itemList.end_list = 0;
	    status = MAIL$SEND_ADD_BODYPART(&context, &itemList, &0);
	    if ((status&1) != 1) {
		mailError(status);
		return;
	    }
	
	    if (*nl == '\0')
		break;			/* end of string */
	    textPtr = ++nl;		/* skip the newline */
	}
	itemList.item_code = MAIL$_SEND_USERNAME;
	itemList.item_size = strlen(toString);
	itemList.item_ptr = &toString;
	itemList.item_rtn_size = 0;
	itemList.end_list = 0;
	status = MAIL$SEND_ADD_ADDRESS(&context, &itemList, &0);
	if ((status&1) != 1) {
	   mailError(status);
	   return;
	}
	status = MAIL$SEND_MESSAGE(&context, &0, &0);
	if ((status&1) != 1) {
	   mailError(status);
	   return;
	}
	mesgPane(XRN_INFO, "Message sent");

#ifdef MOTIF
        XtFree(ptr);
#endif

#endif

    return;
}


#define REALLOC_MAX (8 * BUFFER_SIZE)	/* somewhat arbitrary */
#ifndef MIN
#define MIN(a,b) ((a)<(b) ? (a) : (b))
#endif

static char *
getIncludedArticleText()
{
     char *text, *prefix, input[256];
     int size = BUFFER_SIZE, prefix_size, cur_size;
     FILE *infile;
     
     text = XtMalloc(size);

     if (PostingMode == REPLY) {
	  (void) sprintf(text, "In article %s, you write:\n",
			 Header.messageId );
     } else {
	  (void) sprintf(text, "In article %s, %s writes:\n",
			 Header.messageId, Header.from);
     }

     cur_size = strlen(text);

     if (app_resources.includeCommand) {
	  char cmdbuf[1024];

	  sprintf(cmdbuf, app_resources.includeCommand,
		  app_resources.includePrefix, Header.artFile);
	  infile = popen(cmdbuf, "r");
	  if (! infile) {
	       mesgPane(XRN_SERIOUS, "Cannot execute includeCommand.");
	       FREE(text);
	       return(0);
	  }

	  prefix = "";
	  prefix_size = 0;
     }
     else {
	  infile = fopen(Header.artFile, "r");
	  if (! infile) {
	       mesgPane(XRN_SERIOUS, "Cannot open article file %s.",
			Header.artFile);
	       FREE(text);
	       return(0);
	  }

	  if (app_resources.includeSep) {
	       prefix = app_resources.includePrefix;
	       prefix_size = strlen(prefix);
	  }
	  else {
	       prefix = "";
	       prefix_size = 0;
	  }
     }

     if (! app_resources.includeHeader) {
	  while (fgets(input, 256, infile)) {
	       if (*input == '\n')
		    break;
	  }
     }

     if (!feof(infile)) {
     while (fgets(input, 256, infile)) {
	  int line_size = strlen(input);
	  if (prefix_size + line_size > size - cur_size - 1) {
	       /* 
		* The point of doing the realloc'ing this way is that
		* messages that are bigger tend to be bigger (so to
		* speak), so as we read more lines in, we want to grow
		* the buffer faster to make more room for even more
		* lines.  However, we don't want to grow the buffer
		* *too* much at any one time, so the most we'll
		* realloc in one chunk is REALLOC_MAX.
		*/
	       size += MIN(size,REALLOC_MAX);
	       text = XtRealloc(text, size);
	  }
	  strcpy(&text[cur_size], prefix);
	  cur_size += prefix_size;
	  strcpy(&text[cur_size], input);
	  cur_size += line_size;
     }
     }

     if (app_resources.includeCommand)
	  (void) pclose(infile);
     else
	  (void) fclose(infile);

     return(text);
}


     
static void
includeArticleText()
{
    XawTextPosition point;
    char *message_text;

#ifndef MOTIF
    point = XawTextGetInsertionPoint(ComposeText);
    XawTextDisableRedisplay(ComposeText);
#else
    point = XmTextGetCursorPosition(ComposeText);
#endif

    message_text = getIncludedArticleText();

    if (message_text) {
	 XAWTEXTINSERT(ComposeText, point, message_text);
	 FREE(message_text);
    }

#ifndef MOTIF
    XawTextEnableRedisplay(ComposeText);
#endif

    return;
}


static void
includeArticleText2(fp)
FILE *fp;
{
     char *message_text;

     message_text = getIncludedArticleText();
     if (message_text) {
	  fwrite(message_text, sizeof(char), strlen(message_text), fp);
	  FREE(message_text);
     }

     return;
}


static void
includeArticleText3(message)
char **message;
{
     char *message_text;

     message_text = getIncludedArticleText();

     if (message_text) {
	  int oldsize = strlen(*message);

	  *message = XtRealloc(*message, oldsize + strlen(message_text) + 1);
	  strcpy(&(*message)[oldsize], message_text);
	  FREE(message_text);
     }

     return;
}


/*ARGSUSED*/
static void
includeArticleFunction(widget)
Widget widget;
{
    if (PostingMode == POST) {
	return;
    }
    includeArticleText();
    return;
}

#define XRNinclude_ABORT          0
#define XRNinclude_DOIT           1

static Widget IncludeBox = (Widget) 0;  /* box for typing in the name of a file */
static char *IncludeString = NULL; /* last input string */

/*ARGSUSED*/
static void
includeHandler(widget, client_data, call_data)
Widget widget;
caddr_t client_data;
caddr_t call_data;
/*
 * handler for the include box
 */
{
#ifndef MOTIF
    XawTextPosition point = XawTextGetInsertionPoint(ComposeText);
#else
    XawTextPosition point = XmTextGetCursorPosition(ComposeText);
#endif
    char *file;
    char line[256];
    FILE *filefp;

    if ((int) client_data != XRNinclude_ABORT) {
	/* copy the file */
	file = GetDialogValue(IncludeBox);
	if ((filefp = fopen(utTildeExpand(file), "r")) != NULL) {
#ifndef MOTIF
	    XawTextDisableRedisplay(ComposeText);
#endif
	    while (fgets(line, 256, filefp) != NULL) {
		XAWTEXTINSERT(ComposeText, point, line);
		point += strlen(line);
	    }
	    (void) fclose(filefp);

#ifndef MOTIF
	    XawTextEnableRedisplay(ComposeText);
#endif
	} else {
	    mesgPane(XRN_SERIOUS, "Cannot open `%s'", file);
	}
	XtFree(IncludeString);
	IncludeString = XtNewString(file);
    }

    PopDownDialog(IncludeBox);
    IncludeBox = 0;

#ifdef MOTIF
#ifndef MOTIF_FIXED
/* Motif text bug -- doesn't display right after insertion so force update */
    if (XtIsRealized(ComposeText)) {
      Arg args[2];
      Dimension width, height;

      XtSetArg(args[0], XmNwidth, &width);
      XtSetArg(args[1], XmNheight, &height);
      XtGetValues(ComposeText, args, 2);
      XClearArea(XtDisplay(ComposeText), XtWindow(ComposeText),
		 0, 0, width, height, True);
    }
#endif
#endif

    return;
}    


/*ARGSUSED*/
static void
includeFileFunction(widget)
Widget widget;
{
    static struct DialogArg args[] = {
	{"abort", includeHandler, (caddr_t) XRNinclude_ABORT},
	{"doit",  includeHandler, (caddr_t) XRNinclude_DOIT},
    };

    if (IncludeBox == (Widget) 0) {
	IncludeBox = CreateDialog(ComposeTopLevel, "    File Name?    ",
				  IncludeString == NULL ? DIALOG_TEXT
				   : IncludeString, args, XtNumber(args));
    }
    PopUpDialog(IncludeBox);

    return;
}


static struct stat originalBuf;
static char *FileName = 0;

typedef enum {
    NoEdit, InProgress, Completed
} EditStatus;

EditStatus editing_status = NoEdit;
extern int inchannel;
void
processMessage()
{
    FILE *filefp;
    struct stat buf;
    char buffer[10];
    char *ptr, *msg_type, *confirm1, *confirm2;
    int mode;
    char *ErrMessage;
    char *prompt;

    read(inchannel,buffer,1);
    if(editing_status != Completed || FileName == NIL(char)){
	return;
    }


    switch (PostingMode) {
    case POST:
    case FOLLOWUP:
	 msg_type = "article";
	 confirm1 = "Post the article?";
	 confirm2 = "Re-edit the article?";
	 break;
    default:
	 msg_type= "message";
	 confirm1 = "Send the message?";
	 confirm2 = "Re-edit the message?";
	 break;
    }

    if ((filefp = fopen(FileName, "r")) == NULL) {
	mesgPane(XRN_SERIOUS, "Cannot open the temp file");
	editing_status = NoEdit;
	return;
    }

    if (fstat(fileno(filefp), &buf) == -1) {
	mesgPane(XRN_SERIOUS, "Cannot stat the temp file");
	(void) fclose(filefp);
	(void) unlink(FileName);
	editing_status = NoEdit;
	return;
    }

    if (originalBuf.st_mtime == buf.st_mtime) {
	mesgPane(XRN_INFO, "No change, %s aborted.", msg_type);
	(void) fclose(filefp);
	(void) unlink(FileName);
	editing_status = NoEdit;
	return;
    }

    if (buf.st_size == 0) {
	mesgPane(XRN_INFO, "Zero size, %s aborted", msg_type);
	(void) fclose(filefp);
	(void) unlink(FileName);
	editing_status = NoEdit;
	return;
    }

    ptr = XtMalloc(buf.st_size + 1);
    (void) fread(ptr, sizeof(char), buf.st_size, filefp);
    ptr[buf.st_size] = '\0';
    (void) fclose(filefp);

    /* pop up a confirm box */

    if (ConfirmationBox(TopLevel, confirm1) == XRN_CB_ABORT) {
	if (ConfirmationBox(TopLevel, confirm2) == XRN_CB_ABORT) {
	    mesgPane(XRN_SERIOUS, "Aborting the %s, saving to %s",
		     msg_type, app_resources.deadLetters);
	    saveDeadLetter(ptr);
	    FREE(ptr);
	    (void) unlink(FileName);
	    editing_status = NoEdit;
	    return;
	} else {
	    Call_Editor(NIL(char));
	    FREE(ptr);
	    return;
	}
    }
	
    /* send to the proper processor */
    if ((PostingMode == GRIPE) ||
	(PostingMode == FORWARD) ||
	(PostingMode == REPLY)) {
	mode = XRN_MAIL;
    } else {
	mode = XRN_NEWS;
    }
    
    switch (postArticle(ptr, mode,&ErrMessage)) {
	case POST_FAILED:
	XBell(XtDisplay(TopLevel), 0);
	{
	    char *p;
	    char temp[512];

	    if(ErrMessage){
		sprintf(temp,"\n\nRe-Edit the file (if \"no\" saved in %s)\n",
		    app_resources.deadLetters);
		p = XtMalloc(strlen(ErrMessage) + strlen(temp)+10);
		sprintf(p,"%s/%s",ErrMessage,temp);
		FREE(ErrMessage);
	    }
	    else {
		p = XtMalloc(100);
		sprintf(p,"Re-Edit the file (if \"no\" saved in %s)\n",
		    app_resources.deadLetters);
	    }
	    if(ConfirmationBox(TopLevel, p) == XRN_CB_ABORT){
		saveDeadLetter(ptr);
		FREE(ptr);
		FREE(p);
		editing_status = NoEdit;
		return;
		}
	    else{
		FREE(ptr);
		FREE(p);
		Call_Editor(NIL(char));
		return;
	    }
	}

	case POST_NOTALLOWED:
	mesgPane(XRN_SERIOUS, "Posting not allowed from this machine, saving in %s",
		     app_resources.deadLetters);
	break;
	
	case POST_OKAY:
	switch (mode) {
		case XRN_NEWS:
		mesgPane(XRN_INFO, "Article Posted");
		break;

		case XRN_MAIL:
		mesgPane(XRN_INFO, "Mail Message Sent");
		break;
	}
	break;
    }
    (void) unlink(FileName);
    editing_status = NoEdit;
    FREE(ptr);
    return;
}


static int forkpid; 

extern int outchannel;

static int
catch_sigchld(signo)
int signo;
{

    if (signo != SIGCHLD) {
	/* whoops! */
	return 1;
    }
    (void) signal(SIGCHLD, SIG_DFL);
    if (forkpid != wait(0)) {
	/* whoops! */
	return 1;
    }
    editing_status = Completed;
    write(outchannel,"1",1);
    return 1;
}


/*
 * definition of the buttons
 */
extern int inCommand;

#undef lint
#ifdef lint
#define BUTTON(nm,lbl)
#else
#if defined(__STDC__) && !defined(UNIXCPP)
#define BUTTON(nm,lbl)				\
static void nm##Core(widget)			\
Widget widget;					\
{						\
    static void nm##Function();			\
    if (inCommand) {				\
	return;					\
    }						\
    inCommand = 1;				\
    busyCursor();				\
    nm##Function(widget);			\
    unbusyCursor();				\
    inCommand = 0;				\
    return;					\
}						\
static void nm##Action(widget, event, string, count) \
Widget widget;					\
XEvent *event;					\
String *string;					\
Cardinal count;					\
{						\
    nm##Core(widget);				\
    return;					\
}						\
static void nm##Button(widget, client_data, call_data)	\
Widget widget;					\
caddr_t client_data;				\
caddr_t call_data;				\
{						\
    nm##Core(widget);				\
    return;					\
}						\
static XtCallbackRec nm##Callbacks[] = {	\
    {nm##Button, NULL},				\
    {NULL, NULL}				\
};						\
static Arg nm##Args[] = {			\
    {XtNname, (XtArgVal) #nm},			\
    {XtNlabel, (XtArgVal) #lbl},		\
    {MyNcallback, (XtArgVal) nm##Callbacks}	\
};
#else
#define BUTTON(nm,lbl)				\
static void nm/**/Core(widget)			\
Widget widget;					\
{						\
    static void nm/**/Function();		\
    if (inCommand) {				\
	return;					\
    }						\
    inCommand = 1;				\
    busyCursor();				\
    nm/**/Function(widget);			\
    unbusyCursor();				\
    inCommand = 0;				\
    return;					\
}						\
static void nm/**/Action(widget, event, string, count) \
Widget widget;					\
XEvent *event;					\
String *string;					\
Cardinal count;					\
{						\
    nm/**/Core(widget);				\
    return;					\
}						\
static void nm/**/Button(widget, client_data, call_data)	\
Widget widget;					\
caddr_t client_data;				\
caddr_t call_data;				\
{						\
    nm/**/Core(widget);				\
    return;					\
}						\
static XtCallbackRec nm/**/Callbacks[] = {	\
    {nm/**/Button, NULL},			\
    {NULL, NULL}				\
};						\
static Arg nm/**/Args[] = {			\
    {XtNname, (XtArgVal) "nm"},			\
    {XtNlabel, (XtArgVal) "lbl"},		\
    {MyNcallback, (XtArgVal) nm/**/Callbacks}	\
};

#endif
#endif

BUTTON(abort,abort);
BUTTON(send,send);
BUTTON(save,save);
BUTTON(includeArticle,include article);
BUTTON(includeFile,include file);

char * signatureFile();


static int
Call_Editor(header)
char * header;
{
    char *signature;
    char buffer[1024];
    FILE *filefp;

    if (editing_status == InProgress) {
	mesgPane(XRN_SERIOUS, "Only one composition at a time");
	FREE(header);
	return (-1);
    }
    
    if (editing_status == NoEdit) {
	editing_status = InProgress;
	if(FileName != NIL(char))
	    FREE(FileName);
	FileName = XtNewString(utTempnam(app_resources.tmpDir, "xrn"));
	if ((filefp = fopen(FileName, "w")) == NULL) {
	    mesgPane(XRN_SERIOUS, "Cannot open the temp file");
	    FREE(header);
	    editing_status = NoEdit;
	    return (-1);
	}
	if(header){
	    (void) fwrite(header, sizeof(char), utStrlen(header), filefp);
	    FREE(header);
	}
	else{
	    mesgPane(XRN_SERIOUS, "No message header");
	    editing_status = NoEdit;
	    return (-1);
	}
	if ((PostingMode != POST) && (PostingMode != GRIPE)) {
	    includeArticleText2(filefp);
	}	

	if ((signature = signatureFile()) != NIL(char)) {
	    (void) fprintf(filefp, "\n");
	    (void) fprintf(filefp, signature);
	}

	(void) fclose(filefp);

	if (stat(FileName, &originalBuf) == -1) {
	    mesgPane(XRN_SERIOUS, "Cannot stat the temp file");
	    editing_status = NoEdit;
	    return (-1);
	}

    }

    /*
     * app_resources.editorCommand is a sprintf'able string with a %s where the
     * file name should be placed.  The result should be a command that
     * handles all editing and windowing.
     *
     * Examples are:
     *
     *   emacsclient %s
     *   xterm -e vi %s
     *   xterm -e microEmacs %s
     *
     */
    (void) sprintf(buffer, app_resources.editorCommand, FileName);

#ifndef VMS
#ifdef VFORK_SUPPORTED
    if ((forkpid = vfork()) == 0) 
#else
	if ((forkpid = fork()) == 0) 
#endif
       {
	    int i;
	    int maxdesc;

#ifdef hpux
	    maxdesc = _NFILE;
#else
#ifdef SVR4
#include <ulimit.h>
	    maxdesc = ulimit(UL_GDESLIM);
#else
	    maxdesc = getdtablesize();
#endif
#endif
	    for (i = 3; i < maxdesc; i++) {
		(void) close(i);
	    }
	    (void) execl("/bin/sh", "sh", "-c", buffer, 0);
	    (void) fprintf(stderr, "execl of %s failed\n", buffer);
	    (void) _exit(127);
	}
	if (forkpid < 0) {
	    sprintf(error_buffer, "Can not execute editor (%s)",
	    	errmsg(errno));
	    infoNow(error_buffer);
	    editing_status = NoEdit;
	    return (-1);
	}
	else {
	    signal(SIGCHLD, (SIG_PF0) catch_sigchld);
	}
#else
    system(buffer);
#endif

   return (0);
}

static int
composePane(titleString, header, point)
char *titleString;
char *header;
XawTextPosition point;
/*
 * brings up a new vertical pane, not moded, but maintains
 * enough state so that the current group and/or current
 * article can be changed
 *
 * only one compose pane at a time
 *
 * the pane consists of 3 parts: title bar, scrollable text window,
 * button box
 *
 * five functions:
 *    post article
 *    followup article
 *    reply to author
 *    include the text of the article (followup and reply)
 *    include the a file
 *    send a gripe
 *    forward a message
 */
{
    char *signature;
    int maxdesc;
    Widget pane, buttonBox, label;
#ifndef MOTIF
    static char titleStorage[LABEL_SIZE];
#else
    static XmString titleStorage;
    Position x, y;
    Dimension width, height;
#endif
    Arg fontArgs[1];
    Arg paneArgs[4];
#ifndef MOTIF
    static Arg labelArgs[] = {
	{XtNlabel, (XtArgVal) titleStorage},
	{XtNskipAdjust, (XtArgVal) True},
    };
#else
    static Arg labelArgs[] = {
	{XmNlabelString, (XtArgVal) NULL},
	{XmNrecomputeSize, (XtArgVal) False},
	{XmNskipAdjust, (XtArgVal) True},
    };
#endif
#ifndef MOTIF
    static Arg boxArgs[] = {
	{XtNskipAdjust, (XtArgVal) True},
    };
#else
    static Arg boxArgs[] = {
	{XmNskipAdjust, (XtArgVal) True},
    };
#endif
    static Arg shellArgs[] = {
	{XtNinput, (XtArgVal) True},
	{XtNsaveUnder, (XtArgVal) False},
    };
#ifndef MOTIF
    static Arg textArgs[] = {
	{XtNstring, (XtArgVal) NULL},
	{XtNtype,  (XtArgVal) XawAsciiString},
	{XtNeditType,  (XtArgVal) XawtextEdit},
    };
#else
    static Arg textArgs[] = {
      {XmNvalue, (XtArgVal) NULL},
    };
#endif

    if (ComposeTopLevel != (Widget) 0) {
	mesgPane(XRN_SERIOUS, "Only one composition pane at a time");
	FREE(header);
	return(-1);
    }

    if (app_resources.editorCommand != NIL(char)) {
	return(Call_Editor(header));
    }

    if (PostingMode == FORWARD) {
	includeArticleText3(&header);
    }

    if ((signature = signatureFile()) != NIL(char)) {
	/* current header + '\n' + signature + '\0' */
	header = XtRealloc(header, strlen(header) + strlen(signature) + 2);
	strcat(header, "\n");
	strcat(header, signature);
    }

    ComposeTopLevel = XtCreatePopupShell("Composition", topLevelShellWidgetClass,
					 TopLevel, shellArgs, XtNumber(shellArgs));
#ifndef MOTIF
    XtSetArg(paneArgs[0], XtNx, 0);
    XtSetArg(paneArgs[1], XtNy, 0);
    XtSetArg(paneArgs[2], XtNwidth, 0);
    XtSetArg(paneArgs[3], XtNheight, 0);
    XtGetValues(TopLevel, paneArgs, XtNumber(paneArgs));

    pane = XtCreateManagedWidget("pane", panedWidgetClass, ComposeTopLevel,
				 paneArgs, XtNumber(paneArgs));

    (void) strcpy(titleStorage, titleString);

    label = XtCreateManagedWidget("label", labelWidgetClass, pane,
				  labelArgs, XtNumber(labelArgs));

    XtSetArg(textArgs[0], XtNstring, header);
    ComposeText = XtCreateManagedWidget("text", asciiTextWidgetClass, pane,
					textArgs, XtNumber(textArgs));

    buttonBox = XtCreateManagedWidget("box", boxWidgetClass, pane,
				      boxArgs, XtNumber(boxArgs));

    XtCreateManagedWidget("abort", commandWidgetClass, buttonBox,
			  abortArgs, XtNumber(abortArgs));
    
    XtCreateManagedWidget("send", commandWidgetClass, buttonBox,
			  sendArgs, XtNumber(sendArgs));
    
    XtCreateManagedWidget("save", commandWidgetClass, buttonBox,
			  saveArgs, XtNumber(saveArgs));

    if ((PostingMode != POST) && 
	(PostingMode != GRIPE) &&
	(PostingMode != FORWARD)) {
      XtCreateManagedWidget("includeArticle", commandWidgetClass, buttonBox,
			    includeArticleArgs, XtNumber(includeArticleArgs));
    }
    XtCreateManagedWidget("includeFile", commandWidgetClass, buttonBox,
			  includeFileArgs, XtNumber(includeFileArgs));
#else
    pane = XmCreatePanedWindow(ComposeTopLevel, "pane",
			       NULL, 0);
    XtManageChild(pane);
    
    titleStorage = XmStringCreate(titleString, XmSTRING_DEFAULT_CHARSET);
    XtSetArg(labelArgs[0], XmNlabelString, titleStorage);
    label = XmCreateLabel(pane, "label", labelArgs, XtNumber(labelArgs));
    XtManageChild(label);
    
    XtSetArg(textArgs[0], XmNvalue, header);
    ComposeText = XmCreateScrolledText(pane, "text",
				       textArgs, XtNumber(textArgs));
    XtManageChild(ComposeText);
    
    buttonBox = XmCreateRowColumn(pane, "box",
				  boxArgs, XtNumber(boxArgs));
    XtManageChild(buttonBox);
    
    XtManageChild(XmCreatePushButton(buttonBox, "abort",
				     abortArgs, XtNumber(abortArgs)));
    
    XtManageChild(XmCreatePushButton(buttonBox, "send",
				     sendArgs, XtNumber(sendArgs)));
    
    XtManageChild(XmCreatePushButton(buttonBox, "save",
				     saveArgs, XtNumber(saveArgs)));

    if ((PostingMode != POST) && 
	(PostingMode != GRIPE) &&
	(PostingMode != FORWARD)) {
      XtManageChild(XmCreatePushButton(buttonBox, "includeArticle",
				       includeArticleArgs,
				       XtNumber(includeArticleArgs)));
    }
    XtManageChild(XmCreatePushButton(buttonBox, "includeFile",
				     includeFileArgs,
				     XtNumber(includeFileArgs)));
#endif

    XtRealizeWidget(ComposeTopLevel);
    XtSetKeyboardFocus(ComposeTopLevel, ComposeText);

    XtSetArg(fontArgs[0], XtNheight, 0);
    XtGetValues(label, fontArgs, XtNumber(fontArgs));

#ifndef MOTIF
    XawPanedSetMinMax(label, (int) fontArgs[0].value, (int) fontArgs[0].value);
    XawPanedAllowResize(ComposeText, True);
#else
    {
      Arg args[3];

      XtSetArg(args[0], XmNpaneMaximum, fontArgs[0].value);
      XtSetArg(args[1], XmNpaneMinimum, fontArgs[0].value);
      XtSetArg(args[2], XmNallowResize, True);
      XtSetValues(ComposeText, args, 2);
    }
#endif
    
    {
	static Cursor compCursor = (Cursor) 0;

	if (compCursor == (Cursor) 0) {
	    XColor colors[2];

	    colors[0].pixel = app_resources.pointer_foreground;
	    colors[1].pixel = app_resources.pointer_background;
	    XQueryColors(XtDisplay(TopLevel),
			 DefaultColormap(XtDisplay(TopLevel),
					 DefaultScreen(XtDisplay(TopLevel))),
			 colors, 2);
	    compCursor = XCreateFontCursor(XtDisplay(ComposeTopLevel),
					   XC_left_ptr);
	    XRecolorCursor(XtDisplay(TopLevel), compCursor,
			   &colors[0], &colors[1]);
	}
	XDefineCursor(XtDisplay(ComposeTopLevel), XtWindow(ComposeTopLevel),
		      compCursor);
    }

#ifndef MOTIF
    XawTextSetInsertionPoint(ComposeText, point);
#else
    XmTextSetCursorPosition(ComposeText, point);
#endif

    XtPopup(ComposeTopLevel, XtGrabNone);

    FREE(header);
    return(0);
}


static
char *
signatureFile()
/*
 * return a string containing the contents of the users signature file
 *   (in a static buffer)
 *
 * if the signature file is bigger than MAX_SIGNATURE_SIZE, return NIL(char).
 */
{
#if defined(INEWS_READS_SIG)
    return 0;
#else
    char *file;
    struct stat buf;
    FILE *infofp;
    long count;
    static char *info;
    static initialized = 0;

    if (!initialized) {

	info = NIL(char);
	
	if ((file = utTildeExpand(app_resources.signatureFile)) == NIL(char)) {
	    return(NIL(char));
	}
	if (stat(file, &buf) == -1) {
	    return(NIL(char));
	}
	if (buf.st_size > MAX_SIGNATURE_SIZE) {
	    return(NIL(char));
	}
	if ((infofp = fopen(file, "r")) == NULL) {
	    return(NIL(char));
	}
	info = XtMalloc(buf.st_size + 5); /* 5 for "-- \n" */
	(void) strcpy(info, "-- \n");
	count = fread(&info[4], sizeof(char), buf.st_size, infofp);
	info[count + 4] = '\0';

	(void) fclose(infofp);

	if (strncmp(info + 4, "--\n", 3) == 0 ||
	    strncmp(info + 4, "-- \n", 4) == 0) {
	    info += 4;
	}
	initialized = 1;
    }
    
    return(info);
#endif
}


/* public functions 'reply', 'gripe', 'forward', 'followup', and 'post' */

void
reply()
{
    struct newsgroup *newsgroup = Newsrc[CurrentGroupNumber];
    art_num current = newsgroup->current;
    char title[LABEL_SIZE];
    char *message;
    struct stat statbuf;
    int OldPostingMode = PostingMode;
    
    getHeader(current);
    Header.artFile = XtNewString(newsgroup->articles[INDEX(current)].filename);

    PostingMode = REPLY;
    message = XtMalloc(BUFFER_SIZE);
    
    (void) sprintf(title, "Reply to article %ld in %s",
		   current, newsgroup->name);
    
    if (app_resources.cc == True) {
	(void) sprintf(message, "To: %s\nCc: %s\n",
		   ((Header.replyTo != NIL(char)) && (*Header.replyTo != '\0'))
		   ? Header.replyTo : Header.from, Header.user);
    } else {
	(void) sprintf(message, "To: %s\n",
		   ((Header.replyTo != NIL(char)) && (*Header.replyTo != '\0'))
		   ? Header.replyTo : Header.from);
    }

    if (app_resources.replyTo != NIL(char)) {
	 (void) strcat(message, "Reply-To: ");
	 (void) strcat(message, app_resources.replyTo);
	 (void) strcat(message, "\n");
    }

    if (app_resources.extraMailHeaders) {
	 (void) sprintf(&message[strlen(message)],
			"X-Newsgroups: %s\nIn-reply-to: %s\n",
			Header.newsgroups, Header.messageId);
    }

    buildSubject(message);
    (void) strcat(message, "\n");
    (void) strcat(message, "\n");

    if (composePane(title, message, (XawTextPosition) strlen(message)))
	 PostingMode = OldPostingMode;

    return;
}

char *bugTemplate = 
"X VERSION, RELEASE, and PATCH LEVEL MACHINE:\n\
    [i.e., X Version 11, Release 4, Patch Level 18]\n\
\n\
CLIENT MACHINE and OPERATING SYSTEM:\n\
    [e.g., SparcStation II running SunOS 4.0.3, DECStation 5000 running Ultrix 4.2, ...]\n\
\n\
SYNOPSIS:\n\
    [brief description of the problem and where it is located]\n\
\n\
DESCRIPTION:\n\
    [detailed description]\n\
\n\
REPEAT BY:\n\
    [what you did to get the error]\n\
\n\
SAMPLE FIX:\n\
    [preferred, but not necessary.  Please send context diffs (diff -c -b)]";

void
gripe()
{
    char title[LABEL_SIZE];
    char *message;
    int OldPostingMode = PostingMode;
    XawTextPosition point;

    message = XtMalloc(BUFFER_SIZE);

    (void) strcpy(title, "Gripe");
    (void) sprintf(message, "To: %s\nSubject: GRIPE about XRN %s\n\n",
		   GRIPES, XRN_VERSION);

    if (app_resources.replyTo != NIL(char)) {
	 (void) strcat(message, "Reply-To: ");
	 (void) strcat(message, app_resources.replyTo);
	 (void) strcat(message, "\n");
    }

    point = (XawTextPosition) (strlen(message) +
			       (index(bugTemplate, '[') - bugTemplate));
    (void) strcat(message, bugTemplate);
    (void) strcat(message, "\n");

    PostingMode = GRIPE;

    if (composePane(title, message, point))
	 PostingMode = OldPostingMode;

    return;
}

void
forward()
{
    char title[LABEL_SIZE];
    char *message;
    struct newsgroup *newsgroup = Newsrc[CurrentGroupNumber];
    art_num current = newsgroup->current;
    int OldPostingMode = PostingMode;
    
    getHeader(current);
    Header.artFile = XtNewString(newsgroup->articles[INDEX(current)].filename);

    PostingMode = FORWARD;
    message = XtMalloc(BUFFER_SIZE);
    
    (void) sprintf(title, "Forward article %ld in %s to a user", current,
		   newsgroup->name);
    if (app_resources.ccForward == True) {
	(void) sprintf(message, "To: \nCc: %s\nSubject: %s - %s #%ld\n\n",
		       Header.user,
		       Header.subject, newsgroup->name, current);
    } else {
	(void) sprintf(message, "To: \nSubject: %s - %s #%ld\n\n",
		       Header.subject, newsgroup->name, current);
    }

    if (app_resources.replyTo != NIL(char)) {
	 (void) strcat(message, "Reply-To: ");
	 (void) strcat(message, app_resources.replyTo);
	 (void) strcat(message, "\n");
    }

    if (composePane(title, message, (XawTextPosition) 4))
	 PostingMode = OldPostingMode;

    return;
}

#ifdef GENERATE_EXTRA_FIELDS
/*
 *  generate a message id
 */
static char genid[132];

static char *gen_id()
{
    char *timestr, *cp;
    long cur_time;

    time(&cur_time);
    timestr = ctime(&cur_time);

    (void) sprintf(genid, "<%.4s%.3s%.2s.%.2s%.2s%.2s@%s>",
		    &timestr[20], &timestr[4], &timestr[8],
		    &timestr[11], &timestr[14], &timestr[17],
		    Header.host);
    cp = &genid[8];

    if (*cp == ' ') {
	do {
	    *cp = *(cp + 1); 
	} while (*cp++);
    }

    return(genid);
}
#endif


void
followup()
{
    struct newsgroup *newsgroup = Newsrc[CurrentGroupNumber];
    art_num current = newsgroup->current;
    char buffer[BUFFER_SIZE], title[LABEL_SIZE];
    char *message, *signature;
    int OldPostingMode = PostingMode;
    
#ifdef GENERATE_EXTRA_FIELDS
    long clock;
    char *ptr, timeString[30];
#endif

    if ((newsgroup->status & NG_UNPOSTABLE) == NG_UNPOSTABLE) {
	mesgPane(XRN_SERIOUS, "Cannot post articles to this group");
	return;
    }

    getHeader(current);
    Header.artFile = XtNewString(newsgroup->articles[INDEX(current)].filename);

    if (! strcmp(Header.followupTo, "poster")) {
	 freeHeader();
	 mesgPane(XRN_INFO, "Message says to followup to poster; composing reply instead of followup.");
	 reply();
	 return;
    }
    
    PostingMode = FOLLOWUP;
    message = XtMalloc(BUFFER_SIZE);
    
    (void) sprintf(title, "Followup to article %ld in %s",
		   current, newsgroup->name);
#if defined(INEWS) || defined(HIDE_PATH)
    (void) sprintf(message, "Path: %s\n", Header.user);
#else
    (void) sprintf(message, "Path: %s!%s\n", Header.path, Header.user);
#endif
    if ((Header.followupTo != NIL(char)) && (*Header.followupTo != '\0')) {
	Header.newsgroups = XtNewString(Header.followupTo);
    }
    (void) sprintf(buffer, "Newsgroups: %s\n", Header.newsgroups);
    (void) strcat(message, buffer);
    (void) strcat(message, "Distribution: ");
    if ((Header.distribution != NIL(char)) && (*Header.distribution != '\0')) {
	(void) strcat(message, Header.distribution);
    } else if (app_resources.distribution) {
	(void) strcat(message, app_resources.distribution);
    } else {	
	(void) strcat(message, DISTRIBUTION);
    }
    (void) strcat(message, "\n");
    (void) strcat(message, "Followup-To: \n");
    (void) sprintf(buffer, "References: %s %s\n",
		   Header.references, Header.messageId);
    (void) strcat(message, buffer);
    (void) sprintf(buffer, "From: %s@%s (%s)\n",
		   Header.user, Header.host, Header.fullname);
    (void) strcat(message, buffer);

#ifdef GENERATE_EXTRA_FIELDS
    /* stuff to generate Message-ID and Date... */
    (void) time(&clock);
    (void) strcpy(timeString, asctime(gmtime(&clock)));
    ptr = index(timeString, '\n');
    *ptr = '\0';
    (void) sprintf(buffer, "Date: %s GMT\n", timeString);
    (void) strcat(message, buffer);
    (void) sprintf(buffer, "Message-ID: %s\n", gen_id());
    (void) strcat(message, buffer);
#endif

    if (app_resources.replyTo != NIL(char)) {
	 (void) strcat(message, "Reply-To: ");
	 (void) strcat(message, app_resources.replyTo);
	 (void) strcat(message, "\n");
    }

    (void) sprintf(buffer, "Organization: %s\n", Header.organization);
    (void) strcat(message, buffer);

    buildSubject(message);
    (void) strcat(message, "Keywords: ");
    if ((Header.keywords != NIL(char)) && (*Header.keywords != '\0')) {
	(void) strcat(message, Header.keywords);
    }
    (void) strcat(message, "\n\n");

    if (composePane(title, message, (XawTextPosition) strlen(message)))
	 PostingMode = OldPostingMode;
    
    return;
}


void
post(ingroupp)
int ingroupp;
{
    struct newsgroup *newsgroup;
    char title[LABEL_SIZE], buffer[BUFFER_SIZE];
    char *message;
    int OldPostingMode = PostingMode;
    XawTextPosition point = 0;

#ifdef GENERATE_EXTRA_FIELDS
    long clock;
    char *ptr, timeString[30];
#endif
   
    if (CurrentGroupNumber != NO_GROUP) {
	newsgroup = Newsrc[CurrentGroupNumber];

	if ((newsgroup->status & NG_UNPOSTABLE) == NG_UNPOSTABLE) {
	    mesgPane(XRN_SERIOUS, "Cannot post articles to this group");
	    return;
	}
    }

    getHeader((art_num) 0);

    if (!ingroupp || (CurrentGroupNumber == NO_GROUP)) {
	FREE(Header.newsgroups);
	Header.newsgroups = XtNewString("");
	(void) sprintf(title, "Post article");
    } else {
	(void) sprintf(title, "Post article to `%s'", newsgroup->name);
    }

    message = XtMalloc(BUFFER_SIZE);

#if defined(INEWS) || defined(HIDE_PATH)
    (void) sprintf(message, "Path: %s\n", Header.user);
#else
    (void) sprintf(message, "Path: %s!%s\n", Header.path, Header.user);
#endif
    (void) sprintf(buffer, "Newsgroups: %s\n", Header.newsgroups);
    (void) strcat(message, buffer);
    if (! (*Header.newsgroups)) {
	 point = strlen(message) - 1;
    }
    (void) strcat(message, "Distribution: ");
    if (app_resources.distribution) {
	(void) strcat(message, app_resources.distribution);
    } else {	
	(void) strcat(message, DISTRIBUTION);
    }
    (void) strcat(message, "\n");

#ifdef GENERATE_EXTRA_FIELDS
    /* stuff to generate Message-ID and Date... */
    (void) time(&clock);
    (void) strcpy(timeString, asctime(gmtime(&clock)));
    ptr = index(timeString, '\n');
    *ptr = '\0';
    (void) sprintf(buffer, "Date: %s GMT\n", timeString);
    (void) strcat(message, buffer);
    (void) sprintf(buffer, "Message-ID: %s\n", gen_id());
    (void) strcat(message, buffer);
#endif

    (void) strcat(message, "Followup-To: \n");
    (void) sprintf(buffer, "From: %s@%s (%s)\n",
		   Header.user, Header.host, Header.fullname);
    (void) strcat(message, buffer);
    if (app_resources.replyTo != NIL(char)) {
	 (void) strcat(message, "Reply-To: ");
	 (void) strcat(message, app_resources.replyTo);
	 (void) strcat(message, "\n");
    }

    (void) sprintf(buffer, "Organization: %s\n", Header.organization);
    (void) strcat(message, buffer);
    (void) strcat(message, "Subject: \n");
    if (! point) {
	 point = strlen(message) - 1;
    }
    (void) strcat(message, "Keywords: \n");

    (void) strcat(message, "\n");

    PostingMode = POST;
    if (composePane(title, message, point))
	 PostingMode = OldPostingMode;
    
    return;
}


void
cancelArticle()
{
    struct newsgroup *newsgroup = Newsrc[CurrentGroupNumber];
    art_num current = newsgroup->current;
    char buffer[BUFFER_SIZE];
    char message[MAX_SIGNATURE_SIZE + 64 * BUFFER_SIZE];
    char *bufptr;
    char *ErrMessage;

    if ((newsgroup->status & NG_UNPOSTABLE) == NG_UNPOSTABLE) {
	mesgPane(XRN_SERIOUS, "Cannot post articles to this group");
	return;
    }

    getHeader(current);

    /* verify that the user can cancel the article */
    bufptr = index(Header.from, '@');
    if (bufptr != NIL(char)) {
	bufptr++;
	(void) strcpy(buffer, bufptr);
	if ((bufptr = index(buffer, ' ')) != NIL(char)) {
	    *bufptr = '\0';
	}
	if (strncmp(Header.host, buffer, utStrlen(Header.host))
	   || (strncmp(Header.user, Header.from, utStrlen(Header.user)) 
	      && strcmp(Header.user, "root"))) {
	    mesgPane(XRN_SERIOUS, "Not entitled to cancel the article");
	    freeHeader();
	    return;
        }
    }

#if defined(INEWS) || defined(HIDE_PATH)
    (void) sprintf(message, "Path: %s\n", Header.user);
#else
    (void) sprintf(message, "Path: %s!%s\n", Header.path, Header.user);
#endif
    (void) sprintf(buffer, "From: %s@%s (%s)\n",
		   Header.user, Header.host, Header.fullname);
    (void) strcat(message, buffer);
    (void) sprintf(buffer, "Subject: cancel %s\n", Header.messageId);
    (void) strcat(message, buffer);
    if ((Header.followupTo != NIL(char)) && (*Header.followupTo != '\0')) {
	Header.newsgroups = XtNewString(Header.followupTo);
    }
    (void) sprintf(buffer, "Newsgroups: %s\n", Header.newsgroups);
    (void) strcat(message, buffer);
    (void) sprintf(buffer, "References: %s %s\n",
		   Header.references, Header.messageId);
    (void) strcat(message, buffer);

    (void) strcat(message, "Distribution: ");
    if ((Header.distribution != NIL(char)) && (*Header.distribution != '\0')) {
	(void) strcat(message, Header.distribution);
    } else if (app_resources.distribution) {
	(void) strcat(message, app_resources.distribution);
    } else {	
	(void) strcat(message, DISTRIBUTION);
    }
    (void) strcat(message, "\n");
    (void) sprintf(buffer, "Control: cancel %s\n", Header.messageId);
    (void) strcat(message, buffer);

    freeHeader();

    switch (postArticle(message, XRN_NEWS,&ErrMessage)) {
	case POST_FAILED:
	if(ErrMessage){
	    char *p;
	    char *temp;

	    temp = "Could not cancel";
	    p = XtMalloc(strlen(ErrMessage) + strlen(temp)+10);
	    sprintf(p,"%s/%s",ErrMessage,temp);
	    mesgPane(XRN_SERIOUS, p);
	    FREE(ErrMessage);
	    FREE(p);
	}
	else
	    mesgPane(XRN_SERIOUS, "Could not cancel");
	break;

	case POST_NOTALLOWED:
	mesgPane(XRN_SERIOUS, "Posting not allowed from this machine");
	break;
	    
	case POST_OKAY:
	mesgPane(XRN_INFO, "Canceled the article");
	break;
    }

    return;

}

