ftp.nice.ch/pub/next/unix/network/news/slrn0.9.0.0.s.tar.gz#/slrn/src/nntp.c

This is nntp.c in view mode; [Download] [Up]

/*  Copyright (c) 1995 John E. Davis (davis@space.mit.edu)
 *  All rights reserved.
 */
#include "config.h"
#include "features.h"

#include <stdio.h>

#ifdef HAVE_STDLIB_H
# include <stdlib.h>
#endif

#include <string.h>
#include <errno.h>


#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif

#include <slang.h>
#include "jdmacros.h"

#include "slrn.h"
#include "server.h"
#include "misc.h"

#if SLRN_USE_SLTCP
static int client_put_server (char *);
static int client_get_server (char *, unsigned int);
static void client_close_server (void);
static int client_server_init (char *, int, char *, unsigned int);

static SLTCP_Type NNTP_SLtcp;
#endif

static Slrn_Server_Obj_Type NNTP_Server_Obj;
static Slrn_Post_Obj_Type NNTP_Post_Obj;

char *Slrn_NNTP_Server_Name;

int Slrn_Query_Reconnect = 1;
static int NNTP_Port = -1;

static int nntp_select_group (char *, int *, int *);
static char *nntp_read_line (char *, unsigned int);
static void nntp_close_server (void);
static int nntp_initialize_server (void);
static int nntp_select_article (int, char *);
static char *nntp_head_from_msgid (char *, char *, unsigned int);
static char *nntp_read_xover (char *, unsigned int);
static int nntp_open_xover (int, int);
static void nntp_close_xover (void);
static int nntp_xpat_cmd (char *, int, int, char *);
static int nntp_printf (char *, ...);
static int nntp_puts (char *);
static int nntp_xhdr_command (char *, int, char *, unsigned int);
static char *nntp_get_extra_xover_header (char *);
static void nntp_close_suspend_xover (void);
static void nntp_open_suspend_xover (void);
static int nntp_xgtitle_cmd (char *);
static int nntp_has_cmd (char *);
static int nntp_list_newsgroups (void);
static int nntp_list_active (void);


static int NNTP_Has_XPat = -1;	       /* -1 means probe needs performed */
static int NNTP_Has_Xhdr = -1;
static int NNTP_Has_Xgtitle = -1;

static char *nntp_read_line (char *, unsigned int);

static int NNTP_Reconnect_Ok;
static char NNTP_Last_Reconnect_Command[256];

static int slrn_get_server (char *, unsigned int, int);

/* This function does not attempt a reconnection.  A reconnection can be
 * achieved only in a definite context.  
 */
static int slrn_put_server_cmd (char *line, char *buf, unsigned int len)
{
   NNTP_Reconnect_Ok = 0;
   if (-1 == client_put_server (line))
     {
	slrn_exit_error ("Server closed connection on %s command.", line);
     }
   *buf = 0;
   slrn_get_server (buf, len, 1);
   return atoi (buf);
}


static int slrn_get_server (char *line, unsigned int len, int fatal)
{
   int ret;
   *line = 0;
   while (1)
     {
	errno = 0;
	ret = client_get_server (line, (int) len);
	if (ret == -1)
	  {
#ifdef EAGAIN
	     if (errno == EAGAIN)
	       {
		  sleep (1);
		  continue;
	       }
#endif
#ifdef EINTR
	     if (errno == EINTR)
	       {
		  slrn_error ("read aborted.");
		  return -1;
	       }
#endif
	     if (fatal) slrn_exit_error ("Read failed from server. (%d)", errno);
	     return -1;
	  }
	else return ret;
     }
}

static int nntp_attempt_reconnect (void)
{
   char buf[256];
   int max_tries_without_query = 1;
   
   while (1)
     {
	if ((Slrn_Query_Reconnect || (max_tries_without_query == 0))
	    && (slrn_get_yesno (1, "NNTP connection dropped -- reconnect") <= 0))
	  return -1;
	
	if (max_tries_without_query != 0) max_tries_without_query--;
	
	if (nntp_initialize_server () != 0)
	  {
	     continue;
	  }
	
	
	if ((NULL != Slrn_Current_Group_Name) && ('\0' != *Slrn_Current_Group_Name))
	  {
	     sprintf (buf, "GROUP %s", Slrn_Current_Group_Name);
	     if ((-1 == client_put_server (buf))
		 || (-1 == slrn_get_server (buf, sizeof (buf) - 1, 0)))
	       continue;
	  }
	break;
     }
   
   return 0;
}

static int put_server_with_reconnect (char *cmd, char *buf, unsigned int len)
{
   int status;
   
   NNTP_Reconnect_Ok = 1;
   strcpy (NNTP_Last_Reconnect_Command, cmd);
   
   while (1)
     {
	*buf = 0;
	
	if ((-1 == client_put_server (NNTP_Last_Reconnect_Command))
	    || (-1 == slrn_get_server (buf, len - 1, 0)))
	  status = -1;
	else
	  status = atoi (buf);
	
	if ((status == ERR_FAULT)
	    && !slrn_case_strncmp ((unsigned char *) "503 Timeout",
				   (unsigned char *) buf, 11))
	  status = -1;
	
	if ((status != -1)
	    && (status != ERR_GOODBYE))
	  return status;
	
	if (-1 == nntp_attempt_reconnect ())
	  {
	     slrn_exit_error ("Server connection closed. (%s)", NNTP_Last_Reconnect_Command);
	  }
     }
}

static void nntp_discard_output (char *line, unsigned int len)
{
   while (NULL != nntp_read_line (line, len))
     {
	;
     }
}


int nntp_select_group (char *name, int *min, int *max)
{
   char group[256];
   int estim;
   int code;
   
   sprintf (group, "GROUP %s", name);
   
   code = put_server_with_reconnect (group, group, sizeof (group));
   
   /* responses 211 and 411 as well as 502 */
   if (code != OK_GROUP)
     {
	if (code == ERR_ACCESS)
	  {
	     *min = *max = 0;
	     return 0;
	  }
	return -1;
     }
   
   if (3 != sscanf(group + 4, "%d %d %d", &estim, min, max)) return -1;
   
   return 0;
}

static int NNTP_Server_Inited = 0;

void nntp_close_server (void)
{
   client_close_server ();
   NNTP_Server_Inited = 0;
}

static int nntp_authorization (void)
{
   char line[NNTP_STRLEN];
   
   sprintf (line, "authinfo user %s", Slrn_User_Info.nnrpname);
   
   if (NEED_AUTHDATA == slrn_put_server_cmd (line, line, sizeof (line)))
     {
	sprintf (line, "authinfo pass %s", Slrn_User_Info.nnrppass);
	
	switch (slrn_put_server_cmd (line, line, sizeof (line)))
	  {
	   case ERR_ACCESS:
	     return -1;
	   case OK_AUTH:
	     Slrn_Post_Obj->po_can_post = 1;
	     return 0;
	  }
     }
   return 0;
}

static int probe_server (char *cmd)
{
   char line[256];
   
   if (ERR_COMMAND == put_server_with_reconnect (cmd, line, sizeof (line)))
     return 0;
   return 1;
}

#define PROBE_XCMD(var, cmd) (((var) != -1) ? (var) : ((var) = probe_server(cmd)))

int nntp_has_cmd (char *cmd)
{
   if (!strcmp (cmd, "XHDR"))
     return PROBE_XCMD(NNTP_Has_Xhdr, cmd);
   
   if (!strcmp (cmd, "XPAT"))
     return PROBE_XCMD(NNTP_Has_XPat, cmd);
   
   if (!strcmp (cmd, "XGTITLE"))
     return PROBE_XCMD(NNTP_Has_Xgtitle, cmd);
   
   return probe_server (cmd);
}


static int nntp_initialize_server (void)
{
   char line[256];
   int code;
   char *str;
   char msg1[] = "Connecting to server at %s ...";
   char msg2[] = "Unable to initialize server: %s";
   char msg4[] = "Authenticating %s...";
   int tt_inited = (Slrn_TT_Initialized & 1);
   char *host = Slrn_NNTP_Server_Name;
   
   if (NNTP_Server_Inited) nntp_close_server ();
   
   if (tt_inited == 0)
     slrn_tty_message (0, msg1, host);
   else
     {
	slrn_message ("Connecting to server at %s ...", host);
	slrn_smg_refresh ();
     }
   
   if ((code = client_server_init (host, NNTP_Port, line, sizeof(line))) == -1)
     {
	if (tt_inited == 0)
	  slrn_tty_message (2, "failed!");
	else
	  {
	     slrn_message ("Connecting to server at %s ... failed!", host);
	     slrn_smg_refresh ();
	  }
	return -1;
     }
   NNTP_Server_Inited = 1;
   
   switch (code)
     {
      case OK_CANPOST:
	Slrn_Post_Obj->po_can_post = 1;
	str = "Posting ok.";
	break;
      case OK_NOPOST:
	Slrn_Post_Obj->po_can_post = 0;
	str = "Posting NOT ok.";
	break;
      default:
	if (tt_inited)
	  {
	     slrn_message (msg2, line);
	     slrn_smg_refresh ();
	  }
	else slrn_tty_message (1 | 2, msg2, line);
	return -1;
     }
   
   if (tt_inited)
     {
	slrn_message (str);
	slrn_smg_refresh ();
     }
   else slrn_tty_message (1 | 2, str);
   
   /* Apparantly mode reader should occur before the authorization code.  The
    * reason is that after receiving mode reader, the server will fork a
    * process that actually performs the role of server.  It is what needs
    * authorization.
    */
   
   if (ERR_ACCESS == slrn_put_server_cmd ("mode reader", line, sizeof (line)))
     {
	char msg3[] = "NNTP: 'mode reader' failed: %s";
	if (tt_inited)
	  {
	     slrn_message (msg3, line);
	     slrn_smg_refresh ();
	  }
	else slrn_tty_message (2, msg3);
 	return (-1);
     }
   
   if ((Slrn_User_Info.nnrpname != NULL)
       && (*Slrn_User_Info.nnrpname != 0))
     {
	if (tt_inited == 0)
	  slrn_tty_message (0, msg4, Slrn_User_Info.nnrpname);
	else
	  {
	     slrn_message (msg4, host);
	     slrn_smg_refresh ();
	  }
	
	/* Authenticate ourselves. If we fail the server will disconnect us
	 so we need to reconnect if we were disconnected */
	if (nntp_authorization() < 0)
	  {
	     /* The server probably hung up on us.  Check and see.
 	      * If it did, then we will login again as a default user.
 	      * Send the date command to see if the server is still alive.
	      */
	     if ((-1 == client_put_server("date"))
		 || (-1 == slrn_get_server(line,sizeof(line),0)))
 	       {
		  /* Yup it hung up on us. */
		  if (tt_inited)
		    {
		       slrn_message ("Failed!! Using Default.");
		       slrn_smg_refresh ();
		    }
		  else
		    {
		       slrn_tty_message (0, "Failed!! Using Default.\r\n");
		    }
		  line[0] = '\0';
		  if (-1 == client_server_init (host, NNTP_Port, line, sizeof(line) ))
		    {
		       return -1;
		    }
 	       }
	  }
	else
	  {
	     if (tt_inited)
	       {
		  slrn_message ("Succeeded");
		  slrn_smg_refresh ();
	       }
	     else slrn_tty_message (0, "Succeeded");
	  }
     }
   
   if (ERR_COMMAND == slrn_put_server_cmd ("XOVER", line, sizeof (line)))
     {
	char msg5[] = "Server %s does not implement the XOVER command.";
	NNTP_Server_Obj.sv_has_xover = 0;
	if (tt_inited)
	  {
	     slrn_message (msg5, host);
	     slrn_smg_refresh();
	  }
	else slrn_tty_message (2, msg5, host);
     }
   else NNTP_Server_Obj.sv_has_xover = 1;
   return 0;
}

static char *nntp_read_line (char *line, unsigned int len)
{
   /* This needs to be modified if Reconnection_Ok is non-zero.  Somehow,
    * the calling routine needs to be told to re-issue the NNTP command 
    * and restart.
    */
   slrn_get_server (line, len, 1);
   
   if ((*line == '.') && (*(line + 1) == 0)) return NULL;
   return line;
}

static int nntp_xpat_cmd (char *hdr, int rmin, int rmax, char *pat)
{
   char buf[512];
   
   if (0 == PROBE_XCMD(NNTP_Has_XPat, "XPAT"))
     return -1;
   
   sprintf (buf, "XPAT %s %d-%d *%s*", hdr, rmin, rmax, pat);
   if (OK_HEAD != put_server_with_reconnect (buf, buf, sizeof (buf)))
     return -1;
   return 0;
}

static int nntp_xgtitle_cmd (char *pattern)
{
   char buf[512];
   
   /* XGTITLE appears broken on some servers.  So, do not probe for it. */
   if (NNTP_Has_Xgtitle == 0) return -1;
   
   if (NNTP_Has_Xgtitle == -1)
     {
	slrn_message ("Sending Query to Server...");
	slrn_smg_refresh ();
	Slrn_Message_Present = 0;
     }
   
   sprintf (buf, "XGTITLE %s", pattern);
   
   if (OK_XGTITLE != put_server_with_reconnect (buf, buf, sizeof (buf)))
     {
	slrn_message ("Server does not support XGTITLE command.");
	NNTP_Has_Xgtitle = 0;
	return -1;
     }
   
   NNTP_Has_Xgtitle = 1;
   return 0;
}

static int nntp_select_article (int n, char *msgid)
{
   char cmd[256];
   
   if (n != -1)
     sprintf (cmd, "ARTICLE %d", n);
   else
     sprintf (cmd, "ARTICLE %s", msgid);
   
   if (OK_ARTICLE == put_server_with_reconnect (cmd, cmd, sizeof (cmd)))
     return 0;
   else return -1;
}

static int NNTP_XOver_Done;
static int NNTP_XOver_Min;
static int NNTP_XOver_Max;
static int NNTP_XOver_Next;

static int NNTP_Suspend_XOver_For_Kill = 0;

/* We cannot reconnect on this because of the context may be too complex. */
static int nntp_open_xover (int min, int max)
{
   char buf[512];
   if (NNTP_Server_Obj.sv_has_xover && !NNTP_Suspend_XOver_For_Kill)
     {
	sprintf (buf, "XOVER %d-%d", min, max);
	
	if (OK_XOVER != slrn_put_server_cmd (buf, buf, sizeof (buf)))
	  {
	     NNTP_XOver_Done = 1;
	     slrn_error ("XOVER failed.");
	     return -1;
	  }
     }
   NNTP_XOver_Next = NNTP_XOver_Min = min;
   NNTP_XOver_Max = max;
   NNTP_XOver_Done = 0;
   return 0;
}

/* RFC850: The  required  headers  are
 * Relay-Version,  Posting-Version,  From,  Date, Newsgroups,
 * Subject,  Message-ID,  Path. */

#define NNTP_MAX_XOVER_SIZE 1024
typedef struct
{
   char name[25];
   unsigned int colon;
   char buf[NNTP_MAX_XOVER_SIZE + 16];
}
NNTP_Header_Xover_Type;

/* These MUST be arranged in XOver format:
 * id|subj|from|date|msgid|refs|bytes|line|misc stuff
 *
 * !!!DO NOT REARRANGE OR ADD ANYTHING WITHOUT UPDATING THE ARRAY REFERENCES!!!
 */

#define NUM_XOVER_HEADERS 7
static NNTP_Header_Xover_Type NNTP_Xover_Headers[NUM_XOVER_HEADERS] =
{
     {"Subject:", 8, ""},
     {"From:", 5, ""},
     {"Date:", 5, ""},
     {"Message-ID:", 11, ""},
     {"References:", 11, ""},
     /* {"Bytes:", 6, ""}, */
     {"Lines:", 6, ""},
     {"Xref:", 5, ""}
};

/* The rest of these are for scoring/killing */
#define NNTP_MAX_EXTRA_XOVER_HEADERS 20
static NNTP_Header_Xover_Type *NNTP_Extra_Xover_Headers[NNTP_MAX_EXTRA_XOVER_HEADERS];
static unsigned int NNTP_Next_Extra_Xover_Header;

static void nntp_open_suspend_xover (void)
{
   NNTP_Suspend_XOver_For_Kill = 1;
   NNTP_Next_Extra_Xover_Header = 0;
}

static void nntp_close_suspend_xover (void)
{
   unsigned int i;
   
   if (NNTP_Suspend_XOver_For_Kill == 0) return;
   NNTP_Suspend_XOver_For_Kill = 0;
   for (i = 0; i < NNTP_MAX_EXTRA_XOVER_HEADERS; i++)
     {
	if (NNTP_Extra_Xover_Headers[i] != NULL)
	  SLFREE (NNTP_Extra_Xover_Headers[i]);
	NNTP_Extra_Xover_Headers[i] = NULL;
     }
   NNTP_Next_Extra_Xover_Header = 0;
}

static char *nntp_get_extra_xover_header (char *hdr)
{
   unsigned int i;
   unsigned int colon = strlen (hdr) + 1;
   for (i = 0; i < NNTP_Next_Extra_Xover_Header; i++)
     {
	NNTP_Header_Xover_Type *xov = NNTP_Extra_Xover_Headers[i];
	if ((colon == xov->colon)
	    && (!slrn_case_strcmp ((unsigned char *)xov->name,
				   (unsigned char *)hdr)))
	  return xov->buf;
     }
   return NULL;
}



static char *read_create_xover_line (int id, char *the_buf)
{
   char *hbuf, *b;
   char buf[8 * NNTP_MAX_XOVER_SIZE];
   unsigned int colon = 0;
   char *p, ch;
   unsigned int group_len;
   int i, headers_not_found;
   NNTP_Header_Xover_Type *xov = NULL;
   
   for (i = 0; i < NUM_XOVER_HEADERS; i++)
     {
	*NNTP_Xover_Headers[i].buf = 0;
     }
   NNTP_Next_Extra_Xover_Header = 0;
   
   headers_not_found = NUM_XOVER_HEADERS;
   
   hbuf = NULL;
   
   while (NULL != nntp_read_line (buf, sizeof(buf)))
     {
	unsigned int maxlen;
	ch = *buf;
	
	if ((ch == ' ') || (ch == '\t'))
	  {
	     if (hbuf == NULL) continue;
	     if (hbuf - xov->buf >= NNTP_MAX_XOVER_SIZE - 2)
	       {
		  hbuf = NULL;
		  continue;
	       }
	     
	     *hbuf++ = ' ';	       /* one blank to separate */
	     *hbuf = 0;
	     colon = 0;
	     /* Drop to add continuation */
	  }
	else
	  {
	     if (headers_not_found) i = 0;
	     else i = NUM_XOVER_HEADERS;
	     
	     while (i < NUM_XOVER_HEADERS)
	       {
		  if ((ch == *NNTP_Xover_Headers[i].name)
		      && !slrn_case_strncmp ((unsigned char *)NNTP_Xover_Headers[i].name,
					     (unsigned char *)buf, NNTP_Xover_Headers[i].colon))
		    {
		       headers_not_found--;
		       
		       xov = NNTP_Xover_Headers + i;
		       colon = xov->colon;
		       hbuf = xov->buf;
		       break;
		    }
		  i++;
	       }
	     
	     if (i == NUM_XOVER_HEADERS)
	       {
		  if ((!NNTP_Suspend_XOver_For_Kill)
		      || (NNTP_Next_Extra_Xover_Header == NNTP_MAX_EXTRA_XOVER_HEADERS))
		    {
		       hbuf = NULL;
		       continue;
		    }
		  
		  xov = NNTP_Extra_Xover_Headers[NNTP_Next_Extra_Xover_Header];
		  if (xov == NULL)
		    {
		       xov = (NNTP_Header_Xover_Type *) SLMALLOC (sizeof(NNTP_Header_Xover_Type));
		       if (xov == NULL)
			 {
			    hbuf = NULL;
			    continue;
			 }
		       NNTP_Extra_Xover_Headers[NNTP_Next_Extra_Xover_Header] = xov;
		    }
		  
		  b = buf;
		  while (*b && (*b != ':')) b++;
		  if (*b != ':')
		    {
		       hbuf = NULL;
		       continue;
		    }
		  *b++ = 0;
		  colon = (b - buf);
		  
		  strncpy (xov->name, buf, sizeof (xov->name) - 1);
		  xov->name[sizeof(xov->name) - 1] = 0;
		  xov->colon = colon;
		  
		  NNTP_Next_Extra_Xover_Header += 1;
		  hbuf = xov->buf;
	       }
	  }
	
	
	
	/* trim trailing whitespace */
	(void) slrn_trim_string (buf);
	
	/* trim leading whitespace */
	b = slrn_skip_whitespace (buf + colon);
	
	maxlen = (NNTP_MAX_XOVER_SIZE - (unsigned int)(hbuf - xov->buf)) - 1;
	strncpy (hbuf, b, maxlen);
	hbuf[maxlen] = 0;
	
	while (*hbuf)
	  {
	     if (*hbuf == '\t') *hbuf = ' ';
	     hbuf++;
	  }
     }
   
   /* we should try to extract the id from the Xref header if possible */
   if (id == -1)
     {
	char *xref = NNTP_Xover_Headers[6].buf;
	if (*xref != 0)
	  {
	     group_len = strlen (Slrn_Current_Group_Name);
	     p = xref;
	     while ((ch = *p) != 0)
	       {
		  if (ch == ' ')
		    {
		       p++;
		       continue;
		    }
		  if (!strncmp (p, Slrn_Current_Group_Name, group_len))
		    {
		       p += group_len;
		       if (*p == ':')
			 {
			    p++;
			    id = atoi (p);
			    break;
			 }
		    }
		  /* skip to next space */
		  while (((ch = *p) != 0) && (ch != ' ')) p++;
	       }
	  }
	
	if ((id == -1) && PROBE_XCMD(NNTP_Has_XPat, "XPAT"))
	  {
	     char xpatbuf[256];
	     char *msgid = NNTP_Xover_Headers[3].buf;
	     if (-1 != nntp_xpat_cmd ("Message-Id", Slrn_Server_Min, Slrn_Server_Max, msgid))
	       {
		  /* This should only loop once. */
		  while (nntp_read_line (xpatbuf, sizeof (xpatbuf) - 1) != NULL)
		    {
		       id = atoi (xpatbuf);
		    }
	       }
	  }
     }
   
   
   /* put in standard XOVER format:
    * id|subj|from|date|msgid|refs|bytes|line|misc stuff
    */
   
   sprintf (the_buf, "%d\t%s\t%s\t%s\t%s\t%s\t%s\t%s\tXref: %s",
	    id,
	    NNTP_Xover_Headers[0].buf,      /* subject */
	    NNTP_Xover_Headers[1].buf,      /* from */
	    NNTP_Xover_Headers[2].buf,      /* date */
	    NNTP_Xover_Headers[3].buf,      /* msgid */
	    NNTP_Xover_Headers[4].buf,      /* references */
	    "",			       /* bytes */
	    NNTP_Xover_Headers[5].buf,      /* lines */
	    NNTP_Xover_Headers[6].buf       /* xref */
	    );
   return the_buf;
}

static char *nntp_read_xover (char *the_buf, unsigned int len)
{
   char buf[512];
   int id;
   int already_tried = 0;
   
   if (NNTP_Server_Obj.sv_has_xover && !NNTP_Suspend_XOver_For_Kill)
     {
	char *ret;
	
	while (NULL != (ret = nntp_read_line (the_buf, len)))
	  {
	     id = atoi (ret);
	     if ((id >= NNTP_XOver_Min) && (id <= NNTP_XOver_Max))
	       break;
	     /* Server screwed up and gave bad response.  Ignore it. */
	  }
	return ret;
     }
   
   if (NNTP_XOver_Next > NNTP_XOver_Max) return NULL;
   
   while (NNTP_XOver_Done == 0)
     {
	sprintf (buf, "HEAD %d", NNTP_XOver_Next);
	if (OK_HEAD != slrn_put_server_cmd (buf, buf, sizeof(buf)))
	  {
	     already_tried = 0;
	     /* This is ugly */
	     
	     /* If a head fails even though server says it has article, jump
	      * back and go on.
	      */
	     server_is_messed_up_head_failed:
	     
	     do
	       {
		  switch (slrn_put_server_cmd ("NEXT", buf, sizeof (buf)))
		    {
		     case ERR_NONEXT:
		       NNTP_XOver_Done = 1;
		       return NULL;
		       
		     case OK_NOTEXT:
		       /* Ok, goto next article */
		       break;
		       
		     default:
		       slrn_exit_error ("Server failed NEXT request.");
		    }
		  
		  id = atoi (buf + 4);
		  /* Try going back 5 articles */
		  if ((already_tried == 0) && (id < NNTP_XOver_Next - 5))
		    {
		       
		       sprintf (buf, "HEAD %d", NNTP_XOver_Next - 5);
		       
		       if (OK_HEAD == slrn_put_server_cmd (buf, buf, sizeof(buf)))
			 {
			    /* read all header */
			    nntp_discard_output (buf, sizeof (buf));
			 }
		       already_tried = 1;
		    }
	       }
	     while (id < NNTP_XOver_Next);
	     
	     
	     if (OK_HEAD != slrn_put_server_cmd ("HEAD", buf, sizeof(buf)))
	       {
		  sprintf (buf, "HEAD %d", id);
		  
		  if (OK_HEAD != slrn_put_server_cmd (buf, buf, sizeof(buf)))
		    {
		       already_tried++;
		       if (already_tried > 10)
			 {
			    slrn_exit_error ("Server Error: Head failed.");
			 }
		       
		       goto server_is_messed_up_head_failed;
		    }
	       }
	  }
	/* extract article number */
	id = atoi(buf + 4);
	if (id >= NNTP_XOver_Min) break;
	
	/* we just did a head.  Read it and do next until range is ok */
	nntp_discard_output (buf, sizeof (buf));
	
	while (id < NNTP_XOver_Min)
	  {
	     if (OK_NOTEXT == slrn_put_server_cmd ("NEXT", buf, sizeof (buf)))
	       {
		  id = atoi(buf + 4);
		  continue;
	       }
	     NNTP_XOver_Done = 1;
	     break;
	  }
     }
   
   if (NNTP_XOver_Done) return NULL;
   if (id > NNTP_XOver_Max)
     {
	/* clear out header text */
	NNTP_XOver_Done = 1;
	while (nntp_read_line (buf, sizeof(buf)) != NULL)  ;
	return NULL;
     }
   
   read_create_xover_line (id, the_buf);
   
   if (id > NNTP_XOver_Max)
     {
	NNTP_XOver_Done = 1;
	return NULL;
     }
   
   
   if (OK_NOTEXT == slrn_put_server_cmd ("NEXT", buf, sizeof (buf)))
     {
	NNTP_XOver_Next = atoi(buf + 4);
	if (NNTP_XOver_Next > NNTP_XOver_Max)
	  {
	     NNTP_XOver_Done = 1;
	  }
     }
   else NNTP_XOver_Done = 1;
   return the_buf;
}

static void nntp_close_xover ()
{
   NNTP_XOver_Done = 1;
}


static char *nntp_head_from_msgid (char *msgid, char *buf, unsigned int len)
{
   int id, status;
   
   if ((msgid == NULL) || (*msgid == 0)) return NULL;
   
   sprintf (buf, "HEAD %s", msgid);
   status = put_server_with_reconnect (buf, buf, len);
   if (OK_HEAD != status)
     {
	if (ERR_FAULT == status)
	  {
	     slrn_error ("Server does not provide this capability.");
	  }
	return NULL;
     }
   
   id = atoi(buf + 4);
   if (id == 0) id = -1;
   return read_create_xover_line (id, buf);
}

static int nntp_start_post (void)
{
   char line[256];
   /* Make sure we're connected to a server -- won't be the case the first
    * time we post if we're reading news from a local spool.  In this case
    * a late connect is better as we don't bother the server until we need
    * to.
    */
   if (!NNTP_Server_Inited)
     {
	/* nntp_initialize_server gives an error if it fails, so we don't */
	if (nntp_initialize_server () != 0)
	  return -1;
     }
   
   if (CONT_POST != put_server_with_reconnect ("POST", line, sizeof (line)))
     {
	slrn_error ("Posting not allowed.");
	return -1;
     }
   return 0;
}

static int nntp_end_post (void)
{
   char line[256];
   
#ifdef VMS
   netwrite (SERVER_WRITE_FP, "\r\n.\r\n", 5);
#else
# if SLRN_USE_SLTCP
   sltcp_fputs (&NNTP_SLtcp, "\r\n.\r\n");
   sltcp_fflush (&NNTP_SLtcp);
# else
   fputs("\r\n.\r\n", SERVER_WRITE_FP);
   fflush (SERVER_WRITE_FP);
# endif
#endif
   slrn_get_server (line, 255, 1);
   if (atoi(line) != 240)
     {
	slrn_error ("Article rejected: %s", line);
	return -1;
     }
   return 0;
}

static int nntp_printf (char *fmt, ...)
{
   va_list ap;
#if SLRN_USE_SLTCP
   va_start (ap, fmt);
   sltcp_vfprintf (&NNTP_SLtcp, fmt, ap);
   va_end (ap);
#else
# ifdef VMS
   char buf[512];
   va_start (ap, fmt);
   vsprintf (buf, fmt, ap);
   netwrite (SERVER_WRITE_FP, buf, strlen (buf));
# else
   va_start (ap, fmt);
   vfprintf (SERVER_WRITE_FP, fmt, ap);
   va_end (ap);
# endif
#endif
   return 0;
}

static int nntp_puts (char *s)
{
#if SLRN_USE_SLTCP
   (void) sltcp_fputs (&NNTP_SLtcp, s);
#else
# ifndef VMS
   fputs (s, SERVER_WRITE_FP);
# else
   netwrite (SERVER_WRITE_FP, s, strlen (s));
# endif
#endif
   return 0;
}

static int nntp_xhdr_command (char *hdr, int num, char *buf, unsigned int buflen)
{
   char cmd[256], tmpbuf[1024];
   int found;
   unsigned int colon;
   
   if (PROBE_XCMD(NNTP_Has_Xhdr, "XHDR"))
     {
	char *b, ch;
	sprintf (cmd, "XHDR %s %d", hdr, num);
	if (OK_HEAD != put_server_with_reconnect (cmd, buf, buflen))
	  return -1;
	
	if (NULL == nntp_read_line (tmpbuf, sizeof(tmpbuf))) return -1;
	
	/* skip past article number */
	b = tmpbuf;
	while (((ch = *b++) >= '0') && (ch <= '9'))
	  ;
	strncpy (buf, b, buflen - 1);
	buf[buflen - 1] = 0;
	
	/* I should handle multi-line returns but I doubt that there will be
	 * any for our use of xhdr
	 */
	nntp_discard_output (tmpbuf, sizeof (tmpbuf));
	return 0;
     }
   
   sprintf (cmd, "HEAD %d", num);
   if (OK_HEAD != put_server_with_reconnect (cmd, buf, buflen))
     return -1;
   
   found = 0;
   colon = strlen (hdr);
   
   while (NULL != nntp_read_line (tmpbuf, sizeof (tmpbuf)))
     {
	char *b;
	if (found
	    || slrn_case_strncmp ((unsigned char *) tmpbuf, (unsigned char *) hdr, colon)
	    || (tmpbuf[colon] != ':'))
	  continue;
	
	found = 1;
	
	b = tmpbuf + colon;
	if (*b == ' ') b++;
	strncpy (buf, b, buflen - 1);
	buf[buflen - 1] = 0;
     }
   return 0;
}


static int nntp_list_newsgroups (void)
{
   char buf[256];
   
   if (OK_GROUPS != slrn_put_server_cmd ("LIST NEWSGROUPS", buf, sizeof (buf)))
     {
	slrn_exit_error ("Server failed to return proper response to LIST NEWGROUPS");
     }
   return 0;
}

static int nntp_list_active (void)
{
   char buf[256];
   
   if (OK_GROUPS != slrn_put_server_cmd ("LIST", buf, sizeof (buf)))
     {
	slrn_exit_error("List failed: %s", buf);
     }
   return 0;
}


static char *nntp_getserverbyfile (char *file)
{
   static char buf[256];
   register FILE *fp;
   register char *cp;
   
   if (NULL != (cp = getenv("NNTPSERVER")))
     {
	strcpy (buf, cp);
	return buf;
     }
   
   if (file == NULL)
     {
#ifdef NNTPSERVER_NAME
	strcpy (buf, NNTPSERVER_NAME);
	return buf;
#else
	return NULL;
#endif
     }
   
   fp = fopen(file, "r");
   if (fp == NULL)
     return NULL;
   
   while (fgets(buf, sizeof (buf), fp) != NULL)
     {
	if (*buf == '\n' || *buf == '#')
	  continue;
	cp = slrn_strchr (buf, '\n');
	if (cp)
	  *cp = '\0';
	(void) fclose(fp);
	return buf;
     }
   
   (void) fclose(fp);
   return NULL;    /* No entry */
}

static int nntp_init_objects (void)
{
   NNTP_Post_Obj.po_start = nntp_start_post;
   NNTP_Post_Obj.po_end = nntp_end_post;
   NNTP_Post_Obj.po_printf = nntp_printf;
   NNTP_Post_Obj.po_puts = nntp_puts;
   NNTP_Post_Obj.po_can_post = 1;
   
   NNTP_Server_Obj.sv_select_group = nntp_select_group;
   NNTP_Server_Obj.sv_read_line = nntp_read_line;
   NNTP_Server_Obj.sv_close = nntp_close_server;
   NNTP_Server_Obj.sv_initialize = nntp_initialize_server;
   NNTP_Server_Obj.sv_select_article = nntp_select_article;
   NNTP_Server_Obj.sv_head_from_msgid = nntp_head_from_msgid;
   NNTP_Server_Obj.sv_read_xover = nntp_read_xover;
   NNTP_Server_Obj.sv_open_xover = nntp_open_xover;
   NNTP_Server_Obj.sv_close_xover = nntp_close_xover;
   NNTP_Server_Obj.sv_put_server_cmd = slrn_put_server_cmd;
   NNTP_Server_Obj.sv_xpat_cmd = nntp_xpat_cmd;
   NNTP_Server_Obj.sv_xhdr_command = nntp_xhdr_command;
   NNTP_Server_Obj.sv_get_extra_xover_header = nntp_get_extra_xover_header;
   NNTP_Server_Obj.sv_close_suspend_xover = nntp_close_suspend_xover;
   NNTP_Server_Obj.sv_open_suspend_xover = nntp_open_suspend_xover;
   NNTP_Server_Obj.sv_xgtitle_cmd = nntp_xgtitle_cmd;
   NNTP_Server_Obj.sv_has_xover = 0;
   NNTP_Server_Obj.sv_has_cmd = nntp_has_cmd;
   NNTP_Server_Obj.sv_list_newsgroups = nntp_list_newsgroups;
   NNTP_Server_Obj.sv_list_active = nntp_list_active;
   
   return 0;
}


static char *nntp_port_change (char *hostname)
{
   char *colon;
   char *h;
   
   colon = slrn_strchr (hostname, ':');
   if (colon == NULL) return hostname;
   
   h = SLmake_string (hostname);
   if (h == NULL)
     slrn_exit_error ("Out of memory.");
   
   colon = h + (colon - hostname);
   *colon++ = 0;
   NNTP_Port = atoi (colon);
   return h;
}

#ifndef NNTPSERVER_FILE
# define NNTPSERVER_FILE NULL
#endif

static int nntp_get_servername (void)
{
   char *host = Slrn_NNTP_Server_Name;
   
   if ((host == NULL)
       && (NULL == (host = nntp_getserverbyfile(NNTPSERVER_FILE))))
     {
	fprintf (stderr, "You need to set the NNTPSERVER environment variable to your server name.\r\n");
#ifdef VMS
	fprintf (stderr, "Example: $ define/job NNTPSERVER my.news.server\r\n");
#else
# ifdef __os2__
	fprintf (stderr, "Example: set NNTPSERVER=my.news.server\r\n");
# else
	fprintf (stderr, "Example (csh): setenv NNTPSERVER my.news.server\r\n");
# endif
#endif
	return -1;
     }
   
   Slrn_NNTP_Server_Name = nntp_port_change (host);
   
   if (NNTP_Server_Obj.sv_name != NULL)
     SLFREE (NNTP_Server_Obj.sv_name);
   
   NNTP_Server_Obj.sv_name = slrn_make_startup_string (host);
   return 0;
}



static int nntp_select_server_object (void)
{
   Slrn_Server_Obj = &NNTP_Server_Obj;
   
   if (NNTP_Server_Obj.sv_name == NULL)
     return nntp_get_servername ();
   
   return 0;
}

static int nntp_select_post_object (void)
{
   Slrn_Post_Obj = &NNTP_Post_Obj;
   
   if (NNTP_Server_Obj.sv_name == NULL)
     return nntp_get_servername ();
   
   return 0;
}

static void nntp_usage (void)
{
   fputs ("--nntp options:\n\
-h nntp-host    Host name to connect to.  This overrides NNTPSERVER variable.\n\
-p NNTP-PORT    Set the NNTP port to NNTP-PORT. The default value is 119.\n\
                 Note: This option has no effect on some systems.\n\
",
	  stdout);
   exit (0);
}
/* returns number parsed */
static int nntp_parse_args (char **argv, int argc)
{
   int i;
   
   for (i = 0; i < argc; i++)
     {
	if (!strcmp (argv[i], "--help"))
	  nntp_usage ();
	else if (i + 1 < argc)
	  {
	     if (!strcmp ("-p", argv[i]))
	       {
		  NNTP_Port = atoi (argv[++i]);
	       }
	     else if (!strcmp ("-h", argv[i]))
	       {
		  Slrn_NNTP_Server_Name = argv[++i];
	       }
	     else break;
	  }
	else break;
     }
   
   return i;
}


#if SLRN_USE_SLTCP
static int Server_Has_Been_Closed = 1;

static int client_put_server (char *s)
{
   if (Server_Has_Been_Closed) return -1;
   if ((EOF == sltcp_fputs (&NNTP_SLtcp, s))
       || (EOF == sltcp_fputs (&NNTP_SLtcp, "\r\n"))
       || (EOF == sltcp_fflush (&NNTP_SLtcp)))
     return -1;
   
   return 0;
}

static int client_get_server (char *buf, unsigned int len)
{
   if (Server_Has_Been_Closed) return -1;
   
   if (NULL == sltcp_fgets (&NNTP_SLtcp, buf, len))
     return -1;
   
   len = strlen (buf);
   if (len && (buf[len - 1] == '\n'))
     {
	len--;
	buf[len] = 0;
	if (len && (buf[len - 1] == '\r'))
	  buf[len - 1] = 0;
     }
   
   return 0;
}

static void client_close_server (void)
{
   char buf[256];
   
   if (Server_Has_Been_Closed) return;
   
   if (-1 != client_put_server("QUIT"))
     (void) client_get_server(buf, sizeof(buf));
   
   sltcp_close (&NNTP_SLtcp);
   Server_Has_Been_Closed = 1;
}



static int client_server_init (char *host, int port, char *line, unsigned int len)
{
   if (Server_Has_Been_Closed == 0)
     client_close_server ();
   
   if (port < 0)
     {
	port = sltcp_map_service_to_port ("nntp");
	if (port == -1)
	  port = 119;
     }
   
   if (-1 == sltcp_open_connection (&NNTP_SLtcp, host, port))
     return -1;
   
   Server_Has_Been_Closed = 0;

   if (-1 == client_get_server (line, len))
     return -1;
   
   return atoi (line);
}


#endif

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.