/*
**++
**  FACILITY:
**      NNTP_DEC
**
**  ABSTRACT:
**      This module contains the QIO interface to DECnet for the NNTP
**      server module. This is used to support a single-threaded server.
**
**  AUTHOR:
**      Geoff Huston
**
**  COPYRIGHT:
**      Copyright © 1988,1989,1990
**
**  MODIFICATION HISTORY:
**      V5.5     7-Oct-1988     GIH
**        - Revised call interface to server added
**      V5.9C   13-Mar-1990     Leonard J. Peirce
**	  - Changed from C I/O to QIO interface so that DECnet stream socket
**          support could be added.
**      V6.0    ???
**      V6.1    16-Sep-1993     Mark.Martinec@ijs.si
**        - Rewritten getremhost() to obtain information from NCB
**          instead of from logical names SYS$REM*, which in some
**          circumstances are not updated for each connect when
**          the same network process is reused for another DECnet link;
**        - added error checking, establish condition handler;
**        - provided for more decent server rundown when link is closed
**          (now a call to log_to_file() will not be skipped);
**        - replaced illegal function code IO$_DELETE with IO$_DEACCESS.
**      V6.1    14-Dec-1993     Mark.Martinec@ijs.si
**        - handle SS$_LINKABORT gracefully in write_net as well
**	V6.1b9	17-Aug-1994     Mark Martinec   mark.martinec@ijs.si
**	  - code cleanup to make it compile under gcc 2.6.0 with full
**	    warnings reporting turned on - with no or very few harmless warnings
**	V6.1b9	17-Sep-1994     Mark Martinec   mark.martinec@ijs.si
**	  - code cleanup to preserve the read-only nature of string literals
**	    (strategically placed 'const' attribute to string parameters)
**--
**/

#ifdef vaxc
#module NNTP_DEC "V6.1"
#endif

#define _NNTP_SERVER_DRIVER_C
#define _NNTP_DEC_C
#define module_name "NNTP_DEC"

#include "nntpinclude.h"

#ifdef DECNETSTREAM
#define DECNETSTREAM	1
#endif

#define TIMEOUT		7200		/* read wait timeout  - 1 hour */
#define NNTP_STRLEN 	1024		/* should be in a header file */

struct iosb {
	unsigned short iostatus;
	unsigned short iosize;
	int netinfo;
	};

static unsigned long net_chan;	/* DECnet object I/O channel */

static int open_net1();

#ifdef DECNETSTREAM
static int strindex(char *, char *);
#endif

void (*next_function)(int);     /* next call back to server module */

extern char *news_getenv();

/******************************************************************
 * Exteral routines from NNTP_SERVER                              *
 ******************************************************************/

int server_init();
void server_init_unit();
void log_to_file();
int server_condition_handler();


int main(void)
{
  establish_handler(server_condition_handler);
  if (!server_init(1)) exit(1);
  if (!open_net1()) exit(1);
  server_init_unit(1);
  while (next_function) (*next_function)(1);
  server_shut();
  close_net();
  return(1);
}

/*
 *  cancel_net
 *
 *  If the read timer expires then cancel this server task (by exiting)
 */

void cancel_net(sig)
  int sig;
{
  int status;
  struct iosb del_iosb;

  write_net("400 service discontinued - read timeout\r\n",0);
  log_to_file(1);
  _c$cks(sys$cancel(net_chan));
  status = sys$qiow(0,net_chan,IO$_DEACCESS|IO$M_ABORT,&del_iosb,
                    0,0,0,0,0,0,0,0);
  if (status&1) status = del_iosb.iostatus;
  if (status&1) ;
  else if (status == SS$_FILNOTACC) ;  /* ignore */
  else _c$cks(status);
  _c$cks(sys$dassgn(net_chan));
  exit(1);
}

#ifdef DECNETSTREAM
/*
 *  strindex
 *
 *  Return the position of one string in another
 *
 *  Arguments:	char	*str;		string to search
 *		char	*key;		string to search str for
 *
 *  Description of Linkage:
 *
 *		int  strindex();
 *		rc = strindex(str,key);
 *
 *  Return codes:	-1		key not found in str or key is a null
 *					pointer or str is a null pointer
 *			offset		offset into str where key is located
 */

static int strindex(str,key)
  register char	*str,
  		*key;
{	/*** strindex ***/
  register int i,			/* primary pointer to str */
	       j,			/* secondary pointer to str for comparison */
	       k;			/* pointer to key for comparison */

  if (!str || !key || !*str || !*key) return(-1);
  i = 0;
  while (*(str+i)) {
    j = i; k = 0;			/* look for a match */
    while ((*(str+j) == *(key+k)) && *(str+j) && *(key+k)) { ++j; ++k; }
    if (!*(key+k)) return(i);		/* key was found */
    else if (!*(str+j)) return(-1);	/* key was not found */
    ++i;				/* not sure yet; keep trying... */
    }
  return(-1);			 	/* if we get to here, the key was not found */
}	/*** strindex ***/
#endif

/*
 *  read_net
 *
 *  read from the client process - if the read timer expires cancel this server
 */

int read_net(buffer,size,unit)
  char *buffer;
  int size, unit;
{
  int b_l,
      status;
  static struct iosb read_iosb;

#ifdef DECNETSTREAM
  static int temp;
  static short state,
	       buf_index,
	       end_index,
	       split_flag;
  static char temp_buffer[NNTP_STRLEN+1];

					/* resume the coroutine if necessary */
  if (state == 2) goto lab2;		/* most common resume point */
  else if (state == 1) goto lab1;

       /* when using DECnet stream sockets, we need to change the buffers to
	* records; thus, we become an inverted coroutine to handle the boundary
	* clash that can occur when a record spans buffer boundaries
	*/
  signal(SIGALRM,cancel_net);
  alarm(TIMEOUT);

  status = sys$qiow(0,net_chan,IO$_READVBLK|IO$M_MULTIPLE,&read_iosb,
                    0,0,temp_buffer,sizeof(temp_buffer) - 1,0,0,0,0);
  alarm(0);
  split_flag = 0;
  while ((status & 1) &&
         (read_iosb.iostatus == SS$_NORMAL ||
          read_iosb.iostatus == SS$_BUFFEROVF)) {
	       /* we read a full buffer; probably multiple records in it */
    temp_buffer[read_iosb.iosize] = '\0';	/* make it a big string */
    buf_index = 0;			/* start at beginning of buffer */
    if (split_flag) {			/* chance of a split \r\n? */
      if (temp_buffer[0] == '\n') {	/* maybe.... */
	       /* aha! we DO have a \r\n pair that was split between records;
		* put it together here and send it back; this probably isn't
		* the most elegant coding solution to this but it works....:^)
		*/
	temp = strlen(buffer);
	buffer[temp - 1] = '\n';	/* change \r\0 to \n\0 */
	state = 1;
	return(temp);
lab1:	;				/* resume point */
	buf_index = 1;	    		/* skip the \n */
	*buffer = '\0';			/* make sure it's clean */
	}
      split_flag = 0;
      }
    while (buf_index < read_iosb.iosize) {
					/* find end of record */
      end_index = strindex(&temp_buffer[buf_index],"\r\n");
      if (end_index >= 0) end_index += buf_index;
      if (end_index < 0) {
	       /* we have a record crossing a buffer boundary; copy the fragment
		* to the return buffer and force another read
		*/
	strcpy(buffer,&temp_buffer[buf_index]);
	buf_index = read_iosb.iosize + 1;
	       /* we have to check to see if a \r\n might be split between
		* buffers; if the last byte of temp_buffer is \r, we have to
		* check the first character of the next buffer to see if it's
		* \n
		*/
	if (temp_buffer[read_iosb.iosize - 1] == '\r') split_flag = 1;
	}
      else {
	end_index += 2;			/* skip the \r\n */
					/* change \r\n to \n\0 */
	if (temp_buffer[end_index - 2] == '\r' && temp_buffer[end_index - 1] == '\n')
	  temp_buffer[end_index - 2] = '\n';
	temp_buffer[end_index - 1] = '\0';
	       /* store it to the buffer, making sure to append if anything
		* is already there
		*/
	strcat(buffer,&temp_buffer[buf_index]);
	buf_index = end_index;
	state = 2;
	return(strlen(buffer));			/* ship the record back */
lab2:	;
	*buffer = '\0';			/* make sure it's clean */
	}
      }      
	       /* get another buffer; might be full, might not; if it isn't, we
		* STILL need to extract the individual records from it
		*/
    signal(SIGALRM,cancel_net);
    alarm(TIMEOUT);
    status = sys$qiow(0,net_chan,IO$_READVBLK|IO$M_MULTIPLE,&read_iosb,0,0,temp_buffer,sizeof(temp_buffer) - 1,0,0,0,0);
    alarm(0);
    }
	       /* if there is anything hanging around in buffer the return below will
		* send it back; first make sure that buffer doesn't have a \r\n that
		* might need to be changed to \n\0
		*/
  end_index = strindex(buffer,"\r\n");
  if (end_index >= 0) {
    buffer[end_index] = '\n';
    buffer[end_index + 1] = '\0';
    }
  state = 0;

#else

  signal(SIGALRM,cancel_net);
  alarm(TIMEOUT);
  status = sys$qiow(0,net_chan,IO$_READVBLK,&read_iosb,0,0,buffer,size,0,0,0,0);
  alarm(0);
  if (status & 1) status = read_iosb.iostatus;
  if (status & 1) ;
  else if (status == SS$_ABORT ||
           status == SS$_CANCEL ||
           status == SS$_LINKABORT ||
           status == SS$_LINKDISCON ||
           status == SS$_LINKEXIT ||
           status == SS$_PATHLOST ||
           status == SS$_THIRDPARTY) ;         /* no panic, expected errors */
  else _c$cks(status);
  if (status & 1) b_l = read_iosb.iosize; else b_l = 0;
  if (b_l >= 2) {  /* change the \r\n to \n\0 */
    if ((buffer[b_l - 2] == '\r') && (buffer[b_l - 1] == '\n'))
      buffer[--b_l - 1] = '\n';
    }
  buffer[b_l] = '\0';
  if (b_l <= 0) next_call(1,0,0);
#endif

  return(strlen(buffer));
}

/*
 *  write_net
 *
 *  write to the client process - catch i/o errors and abort
 */

void write_net(s,unit)
  const char *s;
  int unit;
{
  int status;
  struct iosb write_iosb;

  status = sys$qiow(0,net_chan,IO$_WRITEVBLK,&write_iosb,
                      0,0,s,strlen(s),0,0,0,0);
  if (status & 1) status = write_iosb.iostatus;
  if (status & 1) ;
  else if (status == SS$_ABORT ||
           status == SS$_CANCEL ||
           status == SS$_LINKABORT ||
           status == SS$_LINKDISCON ||
           status == SS$_LINKEXIT ||
           status == SS$_PATHLOST ||
           status == SS$_THIRDPARTY)             /* no panic, expected errors */
             next_call(1,0,0); 
  else if (!_c$cks(status)) next_call(1,0,0);
}

/*
 *  next_call
 *
 *  Set the next function to be invoked by the server
 */

void next_call(unit,func,type)
    int unit;
    void (*func)(int);
    int type;
{
    next_function = func;
}

/*
 *  open_net1
 *
 *  Open DECnet channel
 */

int open_net1(void)
{
  int status;
  $DESCRIPTOR_CONST(net_obj_d,"SYS$NET");

  status = sys$assign(&net_obj_d,&net_chan,0,0);
  if (status & 1) ;
  else if (status == SS$_LINKABORT ||
           status == SS$_PATHLOST ||
           status == SS$_THIRDPARTY) ;         /* no panic, expected errors */
  else _c$cks(status);
  return(status & 1);
}

/*
 *  close_net
 *
 *  Close the DECnet link
 */

void close_net()
{
  int status;
  struct iosb del_iosb;

  log_to_file(1);
/*_c$cks(sys$cancel(net_chan)); */
  _c$cks(sys$qiow(0,net_chan,IO$_DEACCESS|IO$M_SYNCH,&del_iosb,0,0,0,0,0,0,0,0));
  _c$cks(del_iosb.iostatus);
  _c$cks(sys$dassgn(net_chan));
}

/*
 *  getremhost
 *
 *  Return the remote host name and remote username
 */

void getremhost(remhost,remuser,unit)
  char *remhost, *remuser;
  int unit;    
{
  char *cp;

  /* defaults */
  if (remhost) strcpy(remhost,"DECnet");
  if (remuser) strcpy(remuser,"nntp");

  /* Using NCB is more reliable and more general than
     using logical names SYS$REM_NODE and SYS$REM_ID */

  if ( (cp = news_getenv("SYS$NET",0)) ) {   /* parse network connect block (NCB) */
    char *dcolon, *uname_b, *uname_e;
    int uname_l;

    /* NCB has the form:   node::"task=username/options"  */
    if (!(dcolon = strstr(cp,"::\""))) return;
    if (remhost) { strncpy(remhost,cp,dcolon-cp); remhost[dcolon-cp] = '\0'; }
    if (!(uname_b = strchr(dcolon,'='))) return;
    uname_b++;
    if (!(uname_e = strchr(uname_b,'/'))) uname_l = strlen(uname_b);
    else { uname_e--;  uname_l = uname_e - uname_b + 1; }
    if (remuser) { strncpy(remuser,uname_b,uname_l); remuser[uname_l] = '\0'; }
    }
}
