/* $Id: fish.c,v 1.58 1999/06/25 07:13:48 levitte Exp $
**++
**  FACILITY:	FISH main procedures
**
**  ABSTRACT:	SSH client
**
**  MODULE DESCRIPTION:
**
**  	This was a test client for NETLIB, that has been slammed together
**	together with an SSH client for Palm Pilot, resulting in FISH,
**	a SSH client for VMS.
**
**  AUTHOR OF TEST CLIENT:
**	 	    M. Madison
**  	    	    COPYRIGHT © 1994, MADGOAT SOFTWARE.  ALL RIGHTS RESERVED.
**
**  AUTHORS OF FISH:
**		    Richard Levitte (RL) & Christer Weinigel (CW)
**
**  OTHER HACKERS:
**		    Wolfgang J. Moeller (WJM)
**
**  CREATION DATE:  
**
**  MODIFICATION HISTORY:
**
**  	1998-MAY-??	V0.0	CW	Initial coding.
**	1998-MAY-06	V0.1	RL	Quickened things up by moving some
**					things out of the AST routines.
** wjm 27-may-1998: (silently) fixed a few bugs relating to event flags.
** wjm 28-may-1998: netlib_read() for more than 1 byte does terminate
**		    at end of current packet (it *seems*).
**		    So raise read count (in AST only) to 64 = WJM_read_size
**		    (after CMU-IP TELNET).
**--
*/

#include "gnu_extras.h"

#include <netlib_dir/netlibdef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <ctype.h>
#include <descrip.h>
#include <climsgdef.h>
#include <clidef.h>
#include <stsdef.h>
#include <ssdef.h>
#include <iodef.h>
#include <libdef.h>

/* XXX: GNU C kludge! */
#if defined(__GNUC__) && defined(__ALPHA)
#define LIB$ESTABLISH DummyFoo
#endif

#include <lib$routines.h>

/* XXX: GNU C kludge! */
#if defined(__GNUC__) && defined(__ALPHA)
#undef LIB$ESTABLISH
#define lib$establish LIB$ESTABLISH
#endif

#include <ots$routines.h>
#include <str$routines.h>
#include <starlet.h>
#if defined(__DECC) || defined(__GNUC__)
#include <unistd.h>
#endif
#include <errno.h>
#ifdef __DECC
#include <builtins.h>
#else
#pragma builtins
#endif
#ifdef __GNUC__
#define __DECC
#endif
#include <openssl/rand.h>
#ifdef __GNUC__
#undef __DECC
#endif

#include "fish.h"

#include "ssh.h"
#include "ui.h"
#include "erf.h"
#include "vms.h"
#include "util.h"
#include "fishmsg.h"
#include "fish-keygen.h"

/* XXX: ugly cast! */
#define INIT_SDESC(dsc, len, ptr) {(dsc).dsc$b_dtype = DSC$K_DTYPE_T;\
    (dsc).dsc$b_class = DSC$K_CLASS_S; (dsc).dsc$w_length = (len);\
    (dsc).dsc$a_pointer = (char *)(ptr);}

/*
**  Other statics we need because we have to pass these values by
**  reference
*/
static unsigned int sinsize = sizeof(struct SINDEF);
static unsigned int adrsize = sizeof(struct INADDRDEF);
static unsigned int which_dns = NETLIB_K_LOOKUP_DNS;
static unsigned int which_ht  = NETLIB_K_LOOKUP_HOST_TABLE;

/*
**  Queue
*/
typedef struct Queue {void *head, *tail;} Queue;

/*
**  I/O request
*/
#define IORBUFSize 16384
typedef struct IORequest {
    struct Queue Queue;
    struct NETLIBIOSBDEF iosb;
    struct dsc$descriptor bufdsc;
    unsigned char buf[1];
} IORequest;

#define WJM_read_size 64	/* taken from CMUIP TELNET */
#if WJM_read_size > IORBUFSize
	You lose ... !
#endif

/*
**  Forward declarations
*/
int main(int argc, char *argv[]);
void Terminal_Read_AST(IORequest *ior);
void Network_Read_AST(IORequest *ior);
void Write_AST(IORequest *ior);
IORequest *Allocate_IOR(int buflen);
IORequest *Get_IOR(int buflen);

/*
**  OWN storage
*/
static Queue Free_Queue = { 0, 0 };
char *input_device = "SYS$INPUT";
char *output_device = "SYS$OUTPUT";
static void *network_context;
static unsigned long network_ef;
static unsigned long network_ef_mask;
static unsigned char network_buffer[IORBUFSize];
static int network_buffer_n = 0;
static unsigned long input_ef;
static unsigned long input_ef_mask;
static unsigned char input_buffer[IORBUFSize];
static int input_buffer_n = -1;
unsigned long done_ef;
static unsigned long done_ef_mask;
static unsigned long ef_bits;
static unsigned int completion_status = SS$_NORMAL;
static const unsigned int terminator_mask[2] = {0, 0};
static unsigned char timeout[8];
static unsigned char *timeout_ptr = 0;
static char randfilename[256] = "";
unsigned char verbose = 0;

/*
 * We're gonna change some of the characteristics of the terminal so I'll
 * declare a buffer for the terminal's characteristics before the changes
 * and another for after the changes. When the program exits the terminal's
 * characteristics will be reset the way they were before they were changed.
 */

tty_s *input_tty = 0, *output_tty = 0;


static ssh_prefs gsshprefs;
static void *sshstate = NULL;

int sockfd;

static int gerrp;
Erf erf, erfp;


int lib_insqti(void *entry, Queue *header, int retry)
{
    int status;

    status = lib$insqti(entry, header, retry);
    ssh_debugf("INSQTI(%p, %p, %d) --> %%X%X", entry, header, retry, status);

    return status;
}
int lib_remqhi(Queue *header, void **entry, int retry)
{
    int status;

    status = lib$remqhi(header, entry, retry);
    ssh_debugf("REMQHI(%p, %p, %d) --> %%X%X", entry, header, retry, status);

    return status;
}

void connected(ssh_state *state, Erf erf, void *erfp)
{
    IORequest *ior;
    int status;
    tty_s *ttyi = 0, *ttyo = 0;

    on_info(ssh_info("Entering interactive mode"));
    on_state(lib$signal(FISH_M_INFD, state->infd);
	     lib$signal(FISH_M_OUTFD, state->outfd));

    input_buffer_n = 0;

    if (state->infd == 0) {
	ttyi = tty_open(input_device,&status);
	if (!tty_sensemode(ttyi,&status))
	    exit(status);
	input_tty = tty_dup(ttyi);
    }

    if (state->outfd == 0) {
	ttyo = tty_open(output_device,&status);
	if (!tty_sensemode(ttyo,&status))
	    exit(status);
	output_tty = tty_dup(ttyo);
    }

    if (ttyi) {
	tty_setflags(ttyi, /*TT$M_WRAP|*/ TT$M_NOECHO|TT$M_HOSTSYNC,TT2$M_PASTHRU,&status);
	tty_clearflags(ttyi,0 /*TT$M_TTSYNC*/ ,TT2$M_LOCALECHO,&status);
	if (!tty_setmode(ttyi,&status))
	    exit(status);
	tty_release(ttyi,&status);
    }

    if (ttyo) {
	tty_setflags(ttyo, /*TT$M_WRAP|*/ TT$M_NOECHO|TT$M_HOSTSYNC,TT2$M_PASTHRU,&status);
	tty_clearflags(ttyo,0 /*TT$M_TTSYNC*/ ,TT2$M_LOCALECHO,&status);
	if (!tty_setmode(ttyo,&status))
	    exit(status);
	tty_release(ttyo,&status);
    }

/*
**  Start a terminal read
*/
    if (state->infd == 0) {
	ssh_debug("connected(): starting a terminal read");
	ior = Get_IOR(1);
	if (ior == 0) exit(SS$_INSFMEM);
	status = sys$qio(0, input_tty->channel,
			 IO$_READVBLK|IO$M_NOECHO|IO$M_NOFILTR,
			 &ior->iosb, Terminal_Read_AST, ior,
			 ior->buf, ior->bufdsc.dsc$w_length, 0,
			 terminator_mask, 0, 0);
	if (!$VMS_STATUS_SUCCESS(status)) exit(status);
    }
}

long sock_write(unsigned char *data, long len, void *state_,
		Erf erf, void *erfp)
{
    IORequest *ior;
    long totret = 0;
    int status;

    ssh_debug("sock_write()");

    if (len < 0) {
	sys$setef(done_ef);

	totret = -1;
	goto sockwrite_out;
    }

    totret = len;

    while (len > 0) {
	int this_len = min(len, IORBUFSize);

/*
**  Write the data to the server.
*/
	ior = Get_IOR(this_len);
	if (ior == 0) status = SS$_INSFMEM;
	else
	{
	    ssh_debugf("sock_write(): writing to network, %d bytes (should be %d)",
		       ior->bufdsc.dsc$w_length, len);
	    memcpy(ior->buf, data, this_len);
	    status = netlib_write(&network_context, &ior->bufdsc,
				  0, 0, &ior->iosb, Write_AST, ior);
	}

	len -= this_len;

	if (!$VMS_STATUS_SUCCESS(status)) {
	    unsigned long ef_bits_to_check;
	    sys$readef(input_ef, &ef_bits_to_check);
	    if (!(ef_bits_to_check & done_ef_mask)) completion_status = status;
	    sys$setef(done_ef);
	    totret = -1;
	    goto sockwrite_out;
	}
    }

sockwrite_out:
    return totret;
}

/* This function is called when data arrives */
void ui_dispatch(unsigned char *data, long len, void *state_, Erf erf,
		 void *erfp)
{
    IORequest *ior;
    int status;

    if (len < 0) {
	sys$setef(done_ef);

	/* The connection was closed; pass on the info */
	goto ui_dispatch_out;
    }

    while (len > 0) {
	int this_len = min(len, IORBUFSize);

	ior = Get_IOR(this_len);
	if (ior == 0)
	    status = SS$_INSFMEM;
	else 
	{
	    ssh_debug("ui_dispatch(): writing to terminal");

	    memcpy(ior->buf, data, this_len);
	    
	    ior->bufdsc.dsc$w_length = this_len;
	    status = sys$qio(0, output_tty->channel, IO$_WRITEVBLK,
			     &ior->iosb, Write_AST, ior,
			     ior->buf, ior->bufdsc.dsc$w_length, 0, 0, 0, 0);
	}

	len -= this_len;

	if (!$VMS_STATUS_SUCCESS(status)) {
	    unsigned long ef_bits_to_check;
	    sys$readef(input_ef, &ef_bits_to_check);
	    if (!(ef_bits_to_check & done_ef_mask))
		completion_status = status;
	    sys$setef(done_ef);
	}
    }
	    
ui_dispatch_out:
    ;
}

void uf_dispatch(unsigned char *data, long len, void *state_, Erf erf,
		 void *erfp)
{
    int wr_status;

    if (len < 0) {
	sys$setef(done_ef);

	/* The connection was closed; pass on the info */
	goto uf_dispatch_out;
    }

    while(len > 0) {
	wr_status = write(((ssh_state *)sshstate)->outfd, data, len);
	if (wr_status < 0) {
	    close(((ssh_state *)sshstate)->outfd);
	    ((ssh_state *)sshstate)->outfd = -1;
	    sys$setef(done_ef);
	    goto uf_dispatch_out;
	}
	len -= wr_status;
	data += wr_status;
    }
	    
uf_dispatch_out:
    ;
}

/*
 *      e x i t _ h a n d l e r
 */
static void exit_handler ()
{
    int status;

    sys$setast (0);
    
    if (input_tty) {
	tty_io_cancel(input_tty, &status);
	tty_setmode(input_tty, &status);
	tty_close(input_tty, &status);
	input_tty = 0;
    }
    if (output_tty) {
	tty_io_cancel(output_tty, &status);
	tty_setmode(output_tty, &status);
	tty_close(output_tty, &status);
	output_tty = 0;
    }

    if (*randfilename != '\0')
	RAND_write_file(randfilename);
}

void sighand(int signo)
{
    signal(SIGINT, SIG_DFL);

    ssh_W_interrupt();

    exit(0);
}


/* Parsing section */

#if defined(__DECC) || (defined(__GNUC__) && defined(__ALPHA))
#pragma extern_model save
#pragma extern_model strict_refdef
extern void *fish_internal;
#pragma extern_model restore
#else
GLOBALREF(void *,fish_internal);
#endif

extern unsigned long cli$dcl_parse ();
extern unsigned long cli$dispatch ();
extern unsigned long cli$present ();
extern unsigned long cli$get_value ();

static void usage()
{
    fprintf(stderr,
"FISH [qualifier ...] (host|user@host) [commands ...]\n"
"\n"
"Available qualifiers:\n"
"\n"
"/HELP         gives this message        /VERSION      gives version number\n"
"/VERBOSE      writes some extra info    /DEBUG        writes debug messages\n"
"/INPUT=file   input redirection         /OUTPUT=file  output redirection\n"
"/MODE=modes   redirection file modes\n"
"/TTY          force tty allocation on remote server\n"
"/PORT=n       set remote port on remote server to connect to\n"
"/USERNAME=u   the username you want to use on the remote host\n"
"/PASSWORD=p   the password you want to use on the remote host.  UNSECURE!\n"
"/COMPRESS=n   sets the compression level.  Default is no compression.\n"
"/CIPHERS=c    One or several ciphers you want to use:\n"
"              All, IDEA, 3DES, DES, RC4, Blowfish\n"
"\n"
"/AUTHENTICATION_METHODS=a\n"
"              One or several authentication methods to try:\n"
"              All, RHOSTS, RSA, RHRSA, PASSWORD, TIS, Kerberos\n"
"              (only RHOSTS, PASSWORD and TIS are currently implemented)\n"
"\n"    
"/IDENTITY_FILE=f\n"
"              For RSA authentication, designates the file where your key\n"
"	      pair is stored.\n"
"\n"
"/KEY/GENERATE=keys\n"
"              Generate a RSA key pair for authentication.  Available keywords:\n"
"              BITS=n, CIPHER=c, COMMENT=m, IDENTITYFILE=f.\n"
"\n");
}

static unsigned long
check_cli (struct dsc$descriptor_s *qual)
{
/*
**  Routine:	check_cli
**
**  Function:	Check to see if a CLD was used to invoke the program.
**
*/
    lib$establish(lib$sig_to_ret);	/* Establish condition handler */
    return (cli$present(qual));		/* Just see if something was given */
}

static void unquote(char *s)
{
    char *p1,*p2;
    for (p1=p2=s; *p2 != '\0'; ) {
	if (*p2 == '"') {
	    p2++;
	    if (*p2 == '"') {
		*p1++ = '"';
		p2++;
	    }
	} else {
	    *p1++ = *p2++;
	}
    }
    *p1 = *p2;
}

/*
**  "Macro" to initialize a dynamic string descriptor.
*/
#define init_dyndesc(dsc) {\
	dsc.dsc$w_length = 0;\
	dsc.dsc$b_dtype = DSC$K_DTYPE_T;\
	dsc.dsc$b_class = DSC$K_CLASS_D;\
	dsc.dsc$a_pointer = 0;}

$DESCRIPTOR(cli_help,			"HELP");
$DESCRIPTOR(cli_version,		"VERSION");
$DESCRIPTOR(cli_host,			"HOST");
$DESCRIPTOR(cli_port,			"PORT");
$DESCRIPTOR(cli_password,		"PASSWORD");
$DESCRIPTOR(cli_username,		"USERNAME");
$DESCRIPTOR(cli_input,			"INPUT");
$DESCRIPTOR(cli_output,			"OUTPUT");
$DESCRIPTOR(cli_mode_input_text,	"MODE.INPUT.TEXT");
$DESCRIPTOR(cli_mode_input_binary,	"MODE.INPUT.BINARY");
$DESCRIPTOR(cli_mode_output_text,	"MODE.OUTPUT.TEXT");
$DESCRIPTOR(cli_mode_output_binary,	"MODE.OUTPUT.BINARY");
$DESCRIPTOR(cli_commands,		"COMMANDS");
$DESCRIPTOR(cli_tty,			"TTY");
$DESCRIPTOR(cli_compress,		"COMPRESS");
$DESCRIPTOR(cli_ciphers,		"CIPHERS");
$DESCRIPTOR(cli_ciphers_none,		"CIPHERS.NONE");
$DESCRIPTOR(cli_ciphers_idea,		"CIPHERS.IDEA");
$DESCRIPTOR(cli_ciphers_3des,		"CIPHERS.3DES");
$DESCRIPTOR(cli_ciphers_des,		"CIPHERS.DES");
#if 0 /* Not supported */
$DESCRIPTOR(cli_ciphers_tss,		"CIPHERS.TSS");
#endif
$DESCRIPTOR(cli_ciphers_rc4,		"CIPHERS.RC4");
$DESCRIPTOR(cli_ciphers_blowfish,	"CIPHERS.BLOWFISH");
$DESCRIPTOR(cli_ciphers_all,		"CIPHERS.ALL");
$DESCRIPTOR(cli_auths,			"AUTHENTICATION_METHODS");
$DESCRIPTOR(cli_auths_all,		"AUTHENTICATION_METHODS.ALL");
$DESCRIPTOR(cli_auths_none,		"AUTHENTICATION_METHODS.NONE");
$DESCRIPTOR(cli_auths_rhosts,	       	"AUTHENTICATION_METHODS.RHOSTS");
$DESCRIPTOR(cli_auths_rsa,		"AUTHENTICATION_METHODS.RSA");
$DESCRIPTOR(cli_auths_password,		"AUTHENTICATION_METHODS.PASSWORD");
$DESCRIPTOR(cli_auths_rhosts_rsa,	"AUTHENTICATION_METHODS.RHRSA");
$DESCRIPTOR(cli_auths_tis,		"AUTHENTICATION_METHODS.TIS");
$DESCRIPTOR(cli_auths_kerberos,		"AUTHENTICATION_METHODS.KERBEROS");
$DESCRIPTOR(cli_identity_file,		"IDENTITY_FILE");
$DESCRIPTOR(cli_verbose,		"VERBOSE");
$DESCRIPTOR(cli_verbose_info,		"VERBOSE.INFO");
$DESCRIPTOR(cli_verbose_state,		"VERBOSE.STATE");
$DESCRIPTOR(cli_verbose_io,		"VERBOSE.IO");
$DESCRIPTOR(cli_verbose_longinfo,	"VERBOSE.LONGINFO");
$DESCRIPTOR(cli_verbose_packet,	"VERBOSE.PACKET");
$DESCRIPTOR(cli_verbose_password,	"VERBOSE.PASSWORD");
$DESCRIPTOR(cli_verbose_brief,		"VERBOSE.BRIEF");
$DESCRIPTOR(cli_verbose_full,		"VERBOSE.FULL");
$DESCRIPTOR(cli_debug,			"DEBUG");

unsigned int fish_args(ssh_prefs *sshprefs)
{
    int status, h_status = 0, v_status = 0;
    int got_user = 0;
    struct dsc$descriptor_d work_str;

    init_dyndesc(work_str);

    status = cli$present(&cli_verbose);
    if (status == CLI$_PRESENT) {
	verbose = VERBOSE_M_BRIEF;
    }
    if (status == CLI$_NEGATED) {
	verbose = 0;
    }

    if ($VMS_STATUS_SUCCESS(status))
    {
	status = cli$present(&cli_verbose_info);
	if ($VMS_STATUS_SUCCESS(status))
	    verbose |= VERBOSE_M_INFO;
	status = cli$present(&cli_verbose_state);
	if ($VMS_STATUS_SUCCESS(status))
	    verbose |= VERBOSE_M_STATE;
	status = cli$present(&cli_verbose_io);
	if ($VMS_STATUS_SUCCESS(status))
	    verbose |= VERBOSE_M_IO;
	status = cli$present(&cli_verbose_longinfo);
	if ($VMS_STATUS_SUCCESS(status))
	    verbose |= VERBOSE_M_LONGINFO;
	status = cli$present(&cli_verbose_packet);
	if ($VMS_STATUS_SUCCESS(status))
	    verbose |= VERBOSE_M_PACKET;
	status = cli$present(&cli_verbose_password);
	if ($VMS_STATUS_SUCCESS(status))
	    verbose |= VERBOSE_M_PASSWORD;
	status = cli$present(&cli_verbose_brief);
	if ($VMS_STATUS_SUCCESS(status))
	    verbose |= VERBOSE_M_BRIEF;
	status = cli$present(&cli_verbose_full);
	if ($VMS_STATUS_SUCCESS(status))
	    verbose |= VERBOSE_M_FULL;
    }

    status = cli$present(&cli_debug);
    if (status == CLI$_PRESENT) {
	sshprefs->debug = 1;
    }
    if (status == CLI$_NEGATED) {
	sshprefs->debug = 0;
    }

    h_status = cli$present(&cli_help);
    v_status = cli$present(&cli_version);
    if ($VMS_STATUS_SUCCESS(h_status))
	if ($VMS_STATUS_SUCCESS(v_status))
	    lib$signal(FISH_M_HVQUAL);
	else
	    lib$signal(FISH_M_HQUAL);
    else if ($VMS_STATUS_SUCCESS(v_status))
	lib$signal(FISH_M_VQUAL);

    if ($VMS_STATUS_SUCCESS(h_status))
	usage();

    if ($VMS_STATUS_SUCCESS(v_status))
	lib$signal(FISH_M_VERSION,1,FISH_VERSION);

    if (($VMS_STATUS_SUCCESS(h_status)) || ($VMS_STATUS_SUCCESS(v_status)))
	return FISH_M_STOP;

    status = cli$present(&cli_host);
    if ($VMS_STATUS_SUCCESS(status)) {
	char *p;

	status = cli$get_value(&cli_host, &work_str);
	sshprefs->host = xstrndup(work_str.dsc$a_pointer,
				  work_str.dsc$w_length);
	if (*sshprefs->host == '"')
	    unquote(sshprefs->host);
	else
	    for (p = sshprefs->host; *p; p++)
		*p = tolower(*p);
	    
	if ((p = strchr(sshprefs->host,'@')) != 0) {
	    *p++ = '\0';
	    strcpy(sshprefs->username,sshprefs->host);
	    strcpy(sshprefs->host,p);
	    got_user = 1;
	}
    }

    status = cli$present(&cli_port);
    if ($VMS_STATUS_SUCCESS(status)) {
	status = cli$get_value(&cli_port, &work_str);
	status = ots$cvt_tu_l (&work_str, &sshprefs->port,
			       sizeof (sshprefs->port), 0x11);
    }

    status = cli$present(&cli_compress);
    if (status == CLI$_PRESENT) {
	status = cli$get_value(&cli_compress, &work_str);
	status = ots$cvt_tu_l (&work_str, &sshprefs->compress_p,
			       sizeof (sshprefs->compress_p), 0x11);
	if (sshprefs->compress_p < 1 || sshprefs->compress_p > 9)
	    return FISH_M_INVCFACT;
    }

    status = cli$present(&cli_username);
    if ($VMS_STATUS_SUCCESS(status)) {
	register char *cp;

	if (got_user)
	    return FISH_M_TWOUSERNM;

	status = cli$get_value(&cli_username, &work_str);
	sshprefs->username = xstrndup(work_str.dsc$a_pointer,
				  work_str.dsc$w_length);
	if (*sshprefs->username == '"')
	    unquote(sshprefs->username);
	else
	    for (cp = sshprefs->username; *cp; cp++)
		*cp = tolower(*cp);
    }

    status = cli$present(&cli_password);
    if ($VMS_STATUS_SUCCESS(status)) {
	register unsigned char *cp;

	status = cli$get_value(&cli_password, &work_str);
	sshprefs->password = xstrndup(work_str.dsc$a_pointer,
				  work_str.dsc$w_length);
	unquote(sshprefs->password);
    }

    status = cli$present(&cli_ciphers);
    if ($VMS_STATUS_SUCCESS(status)) {
	int all_ciphers = cli$present(&cli_ciphers_all) != CLI$_NEGATED;
	int wanted_ciphers = 1 << SSH_CIPHER_NONE;

	status = cli$present(&cli_ciphers_des);
	if (status == CLI$_PRESENT || all_ciphers)
	    wanted_ciphers |= 1 << SSH_CIPHER_DES;
	if (status == CLI$_NEGATED)
	    wanted_ciphers &= ~(1 << SSH_CIPHER_DES);

	status = cli$present(&cli_ciphers_3des);
	if (status == CLI$_PRESENT || all_ciphers)
	    wanted_ciphers |= 1 << SSH_CIPHER_3DES;
	if (status == CLI$_NEGATED)
	    wanted_ciphers &= ~(1 << SSH_CIPHER_3DES);

	status = cli$present(&cli_ciphers_idea);
	if (status == CLI$_PRESENT || all_ciphers)
	    wanted_ciphers |= 1 << SSH_CIPHER_IDEA;
	if (status == CLI$_NEGATED)
	    wanted_ciphers &= ~(1 << SSH_CIPHER_IDEA);

	status = cli$present(&cli_ciphers_rc4);
	if (status == CLI$_PRESENT || all_ciphers)
	    wanted_ciphers |= 1 << SSH_CIPHER_RC4;
	if (status == CLI$_NEGATED)
	    wanted_ciphers &= ~(1 << SSH_CIPHER_RC4);

#if 0 /* Not currently supported */
	status = cli$present(&cli_ciphers_tss);
	if (status == CLI$_PRESENT || all_ciphers)
	    wanted_ciphers |= 1 << SSH_CIPHER_TSS;
	if (status == CLI$_NEGATED)
	    wanted_ciphers &= ~(1 << SSH_CIPHER_TSS);
#endif

	status = cli$present(&cli_ciphers_blowfish);
	if (status == CLI$_PRESENT || all_ciphers)
	    wanted_ciphers |= 1 << SSH_CIPHER_BLOWFISH;
	if (status == CLI$_NEGATED)
	    wanted_ciphers &= ~(1 << SSH_CIPHER_BLOWFISH);

	sshprefs->ciphers = wanted_ciphers;
    } else {
	sshprefs->ciphers = 1 << SSH_CIPHER_NONE;
	sshprefs->ciphers |= 1 << SSH_CIPHER_DES;
	sshprefs->ciphers |= 1 << SSH_CIPHER_3DES;
	sshprefs->ciphers |= 1 << SSH_CIPHER_IDEA;
	sshprefs->ciphers |= 1 << SSH_CIPHER_RC4;
#if 0 /* not currently supported */
	sshprefs->ciphers |= 1 << SSH_CIPHER_TSS;
#endif
	sshprefs->ciphers |= 1 << SSH_CIPHER_BLOWFISH;
    }

    on_state(ssh_infof("default cipher mask is %%0x%X", sshprefs->ciphers));

    status = cli$present(&cli_auths);
    if ($VMS_STATUS_SUCCESS(status)) {
	int all_auths = cli$present(&cli_auths_all) != CLI$_NEGATED;
	int wanted_auths = 1 << SSH_AUTH_NONE;

	status = cli$present(&cli_auths_rhosts);
	if (status == CLI$_PRESENT || all_auths)
	    wanted_auths |= 1 << SSH_AUTH_RHOSTS;
	if (status == CLI$_NEGATED)
	    wanted_auths &= ~(1 << SSH_AUTH_RHOSTS);

	status = cli$present(&cli_auths_rsa);
	if (status == CLI$_PRESENT || all_auths)
	    wanted_auths |= 1 << SSH_AUTH_RSA;
	if (status == CLI$_NEGATED)
	    wanted_auths &= ~(1 << SSH_AUTH_RSA);

#if 0
	status = cli$present(&cli_auths_rhosts_rsa);
	if (status == CLI$_PRESENT || all_auths)
	    wanted_auths |= 1 << SSH_AUTH_RHOSTS_RSA;
	if (status == CLI$_NEGATED)
	    wanted_auths &= ~(1 << SSH_AUTH_RHOSTS_RSA);
#endif

	status = cli$present(&cli_auths_password);
	if (status == CLI$_PRESENT || all_auths)
	    wanted_auths |= 1 << SSH_AUTH_PASSWORD;
	if (status == CLI$_NEGATED)
	    wanted_auths &= ~(1 << SSH_AUTH_PASSWORD);

	status = cli$present(&cli_auths_tis);
	if (status == CLI$_PRESENT || all_auths)
	    wanted_auths |= 1 << SSH_AUTH_TIS;
	if (status == CLI$_NEGATED)
	    wanted_auths &= ~(1 << SSH_AUTH_TIS);

#if 0
	status = cli$present(&cli_auths_kerberos);
	if (status == CLI$_PRESENT || all_auths)
	    wanted_auths |= 1 << SSH_AUTH_KERBEROS;
	if (status == CLI$_NEGATED)
	    wanted_auths &= ~(1 << SSH_AUTH_KERBEROS);
#endif

	sshprefs->auths = wanted_auths;
    } else {
	sshprefs->auths = 1 << SSH_AUTH_NONE;
	sshprefs->auths |= 1 << SSH_AUTH_RHOSTS;
	sshprefs->auths |= 1 << SSH_AUTH_RSA;
	sshprefs->auths |= 1 << SSH_AUTH_RHOSTS_RSA;
	sshprefs->auths |= 1 << SSH_AUTH_PASSWORD;
	sshprefs->auths |= 1 << SSH_AUTH_TIS;
	sshprefs->auths |= 1 << SSH_AUTH_KERBEROS;
    }

    sshprefs->auths &=
	(1 << SSH_AUTH_NONE) | (1 << SSH_AUTH_RHOSTS) | (1 << SSH_AUTH_RSA)
	    | (1 << SSH_AUTH_PASSWORD) | (1 << SSH_AUTH_TIS);

    status = cli$present(&cli_identity_file);
    if ($VMS_STATUS_SUCCESS(status)) {
	status = cli$get_value(&cli_identity_file, &work_str);
	sshprefs->identity_file = xstrndup(work_str.dsc$a_pointer,
					   work_str.dsc$w_length);
    } else {
	char keyfile[256];

	strcpy(keyfile, sshprefs->default_directory);
	strcat(keyfile, "IDENTITY.DAT");

	sshprefs->identity_file = xstrndup(keyfile, strlen(keyfile));
    }

    on_state(ssh_infof("default auth mask is %%0x%X", sshprefs->auths));

    status = cli$present(&cli_commands);
    if ($VMS_STATUS_SUCCESS(status)) {
	status = cli$get_value(&cli_commands, &work_str);
	sshprefs->commands = xstrndup(work_str.dsc$a_pointer,
				      work_str.dsc$w_length);
	unquote(sshprefs->commands);
    }

    status = cli$present(&cli_input);
    if ($VMS_STATUS_SUCCESS(status)) {
	char *p1, *p2;
	status = cli$get_value(&cli_input, &work_str);
	sshprefs->infile = xstrndup(work_str.dsc$a_pointer,
				    work_str.dsc$w_length);
    }

    status = cli$present(&cli_output);
    if ($VMS_STATUS_SUCCESS(status)) {
	char *p1, *p2;
	status = cli$get_value(&cli_output, &work_str);
	sshprefs->outfile = xstrndup(work_str.dsc$a_pointer,
				     work_str.dsc$w_length);
    }

    status = cli$present(&cli_mode_input_text);
    if ($VMS_STATUS_SUCCESS(status))
	sshprefs->inmode = 0;
    status = cli$present(&cli_mode_input_binary);
    if ($VMS_STATUS_SUCCESS(status))
	sshprefs->inmode = 1;
    status = cli$present(&cli_mode_output_text);
    if ($VMS_STATUS_SUCCESS(status))
	sshprefs->outmode = 0;
    status = cli$present(&cli_mode_output_binary);
    if ($VMS_STATUS_SUCCESS(status))
	sshprefs->outmode = 1;

    status = cli$present(&cli_tty);
    if ($VMS_STATUS_SUCCESS(status))
	sshprefs->force_tty = 1;

    return SS$_NORMAL;
}

$DESCRIPTOR(cli_key,			"KEY");
$DESCRIPTOR(cli_key_generate,		"GENERATE");
$DESCRIPTOR(cli_key_change,		"CHANGE");
$DESCRIPTOR(cli_key_comment,		".COMMENT");
$DESCRIPTOR(cli_key_id_file,		".IDENTITY_FILE");
$DESCRIPTOR(cli_key_bits,		".BITS");
$DESCRIPTOR(cli_key_cipher,		".CIPHER");
$DESCRIPTOR(cli_key_ciph_des,		".CIPHER.DES");
$DESCRIPTOR(cli_key_ciph_3des,		".CIPHER.3DES");
$DESCRIPTOR(cli_key_ciph_idea,		".CIPHER.IDEA");
$DESCRIPTOR(cli_key_ciph_rc4,		".CIPHER.RC4");
#if 0 /* not currently supported */
$DESCRIPTOR(cli_key_ciph_tss,		".CIPHER.TSS");
#endif
$DESCRIPTOR(cli_key_ciph_bf,		".CIPHER.BLOWFISH");
$DESCRIPTOR(cli_key_password,		".PASSWORD");


unsigned int fish_key_args(ssh_prefs *sshprefs)
{
    int status;
    struct dsc$descriptor_d work_str;
    struct dsc$descriptor_s *key_str;

    init_dyndesc(work_str);

    sshprefs->todo_flags = 0;

    status = cli$present(&cli_key_generate);
    if ($VMS_STATUS_SUCCESS(status)) {
	sshprefs->whattodo = KEYGEN;
	key_str = &cli_key_generate;
    }

    status = cli$present(&cli_key_change);
    if ($VMS_STATUS_SUCCESS(status)) {
	sshprefs->whattodo = KEYCHANGE;
	key_str = &cli_key_change;
    }

    status = str$concat(&work_str, key_str, &cli_key_comment);
    if (!$VMS_STATUS_SUCCESS(status))
	lib$signal(status);
    status = cli$present(&work_str);
    if ($VMS_STATUS_SUCCESS(status)) {
	char *p;

	status = cli$get_value(&work_str, &work_str);
	sshprefs->comment = xstrndup(work_str.dsc$a_pointer,
				     work_str.dsc$w_length);
	unquote(sshprefs->comment);
	sshprefs->todo_flags |= CH_COMMENT;
    } else {
	struct dsc$descriptor_s h_dsc;
	char h[256];
	unsigned short retlen = 0;

	INIT_SDESC(h_dsc,sizeof(h),h);

	netlib_get_hostname(&h_dsc,&retlen);
	h[retlen] = '\0';
	sshprefs->comment = xmalloc(strlen(sshprefs->username)+retlen+2);
	strcpy(sshprefs->comment,sshprefs->username);
	strcat(sshprefs->comment,"@");
	strcat(sshprefs->comment,h);
    }

    status = str$concat(&work_str, key_str, &cli_key_id_file);
    if (!$VMS_STATUS_SUCCESS(status))
	lib$signal(status);
    status = cli$present(&work_str);
    if ($VMS_STATUS_SUCCESS(status)) {
	status = cli$get_value(&work_str, &work_str);
	sshprefs->identity_file = xstrndup(work_str.dsc$a_pointer,
					 work_str.dsc$w_length);
    } else {
	char keyfile[256];

	strcpy(keyfile, sshprefs->default_directory);
	strcat(keyfile, "IDENTITY.DAT");

	sshprefs->identity_file = xstrndup(keyfile, strlen(keyfile));
    }

    status = str$concat(&work_str, key_str, &cli_key_bits);
    if (!$VMS_STATUS_SUCCESS(status))
	lib$signal(status);
    status = cli$present(&work_str);
    if ($VMS_STATUS_SUCCESS(status)) {
	status = cli$get_value(&work_str, &work_str);
	status = ots$cvt_tu_l (&work_str, &sshprefs->bits,
			       sizeof (sshprefs->bits), 0x11);
	sshprefs->todo_flags |= CH_BITS;
    }

    status = str$concat(&work_str, key_str, &cli_key_cipher);
    if (!$VMS_STATUS_SUCCESS(status))
	lib$signal(status);
    status = cli$present(&work_str);
    if ($VMS_STATUS_SUCCESS(status)) {
	status = str$concat(&work_str, key_str, &cli_key_ciph_des);
	if (!$VMS_STATUS_SUCCESS(status))
	    lib$signal(status);
	status = cli$present(&work_str);
	if ($VMS_STATUS_SUCCESS(status)) {
	}
	status = str$concat(&work_str, key_str, &cli_key_ciph_3des);
	if (!$VMS_STATUS_SUCCESS(status))
	    lib$signal(status);
	status = cli$present(&work_str);
	if ($VMS_STATUS_SUCCESS(status))
	    sshprefs->cipher_type = SSH_CIPHER_DES;
	status = str$concat(&work_str, key_str, &cli_key_ciph_idea);
	if (!$VMS_STATUS_SUCCESS(status))
	    lib$signal(status);
	status = cli$present(&work_str);
	if ($VMS_STATUS_SUCCESS(status))
	    sshprefs->cipher_type = SSH_CIPHER_3DES;
	status = str$concat(&work_str, key_str, &cli_key_ciph_rc4);
	if (!$VMS_STATUS_SUCCESS(status))
	    lib$signal(status);
	status = cli$present(&work_str);
	if ($VMS_STATUS_SUCCESS(status))
	    sshprefs->cipher_type = SSH_CIPHER_RC4;
#if 0 /* not currently supported */
	status = str$concat(&work_str, key_str, &cli_key_ciph_tss);
	if (!$VMS_STATUS_SUCCESS(status))
	    lib$signal(status);
	status = cli$present(&work_str);
	if ($VMS_STATUS_SUCCESS(status))
	    sshprefs->cipher_type = SSH_CIPHER_TSS;
#endif
	status = str$concat(&work_str, key_str, &cli_key_ciph_bf);
	if (!$VMS_STATUS_SUCCESS(status))
	    lib$signal(status);
	status = cli$present(&work_str);
	if ($VMS_STATUS_SUCCESS(status))
	    sshprefs->cipher_type = SSH_CIPHER_BLOWFISH;
	sshprefs->todo_flags |= CH_CIPHER_TYPE;
    } else
	sshprefs->cipher_type = SSH_CIPHER_3DES;

    if (sshprefs->whattodo == KEYCHANGE) {
	status = str$concat(&work_str, key_str, &cli_key_password);
	if (!$VMS_STATUS_SUCCESS(status))
	    lib$signal(status);
	status = cli$present(&work_str);
	if ($VMS_STATUS_SUCCESS(status)) {
	    sshprefs->todo_flags |= CH_PASSPHRASE;
	}
    }

    return SS$_NORMAL;
}

$DESCRIPTOR(fish_command,		"FISH ");

unsigned int parse_args(ssh_prefs *sshprefs)
{
    int status;
    struct dsc$descriptor_d work_str;

    init_dyndesc(work_str);

    lib$get_foreign (&work_str);
    str$concat (&work_str, &fish_command, &work_str);
    status = cli$dcl_parse(&work_str, &fish_internal, lib$get_input,
			   lib$get_input, 0);
    if (!$VMS_STATUS_SUCCESS(status))
	return status | 0x10000000; /* Because the error has already
				       been reported */
    status = cli$dispatch(sshprefs);
    if (status == CLI$_INVREQTYP || status == CLI$_INVROUT)
	return status | 0x10000000; /* Because the error has already
				       been reported */
    return status;
}


#define BAIL_OUT(__status) do { completion_status = __status; goto bail_out; } while (0)
int main(int argc, char *argv[])
{
    IORequest *ior;
    struct SINDEF remsin;
    struct dsc$descriptor dsc;
#ifndef WJM_read_size
    struct dsc$descriptor dsc_timeout;
    char net_time[] = "0 00:00:00.20";
#endif
    int status;
    unsigned int remlen;
    unsigned short port, namelen;
    char name[1024], buf[1024];
    int i;
    static int excode;
    struct NETLIBIOSBDEF iosb;
    struct exit_handler_block 
    {
        struct exit_handler_block *flink;
        void (*exit_routine)();
        int arg_count;
        int *status_address;
        int exit_status;
    } exhblk = {0, exit_handler, 1, &excode, 0};

    ssh_initprefs(&gsshprefs);

    /* Load up with some randomness; use the saved file and the memopad */
    strcpy(randfilename, gsshprefs.default_directory);
    strcat(randfilename, "RANDOM_SEED.");

    if (RAND_load_file(randfilename, 0x7fffffffL) == 0) {
	lib$signal(FISH_M_CRERANDOM, 1, randfilename);
	RAND_write_file(randfilename);
    }

/*
** Set up exit handler
*/
    status = sys$dclexh (&exhblk);
    if (!$VMS_STATUS_SUCCESS(status)) return(status);

    if (gsshprefs.infile != 0)
	signal(SIGINT, sighand);

/*
** Parse the arguments, qualifiers, ...
*/
    status = parse_args(&gsshprefs);
    if (status == FISH_M_STOP)
	BAIL_OUT(SS$_NORMAL);
    if (!$VMS_STATUS_SUCCESS(status))
	BAIL_OUT(status);

    /* So, what does the user want to do? */
    switch(gsshprefs.whattodo) {
    case KEYGEN:
	BAIL_OUT(create_keyfile(&gsshprefs, 0, 0, 0, 0));
    case KEYCHANGE:
	BAIL_OUT(change_keyfile(&gsshprefs));
    case CONNECT:
	break;			/* happens below */
    default:
	BAIL_OUT(FISH_M_WEIRDNESS);
    }

/*
**  Allocate a network socket and connect to the server
*/
    status = netlib_socket(&network_context);
    if (!$VMS_STATUS_SUCCESS(status))
	BAIL_OUT(status);

    INIT_SDESC(dsc, strlen(gsshprefs.host), gsshprefs.host);
    port = gsshprefs.port;
    status = netlib_connect_by_name(&network_context, &dsc, &port, &iosb);
    if (status == 0x870
	|| iosb.iosb_w_status == 0x870) /* SYSTEM-W-ENDOFFILE */
    {
	BAIL_OUT(FISH_M_HOSTNOTFOUND);
    }
    if (!$VMS_STATUS_SUCCESS(iosb.iosb_w_status))
	BAIL_OUT(iosb.iosb_w_status);
    if (!$VMS_STATUS_SUCCESS(status))
	BAIL_OUT(status);

/*
**  Allocate event flags for signaling to the main loop.
*/
    status = lib$get_ef(&input_ef);
    if (!$VMS_STATUS_SUCCESS(status))
	BAIL_OUT(status);
    input_ef_mask = 1<<(input_ef & 31);
    status = sys$clref(input_ef);
    if (!$VMS_STATUS_SUCCESS(status))
	BAIL_OUT(status);

    status = lib$get_ef(&network_ef);
    if (!$VMS_STATUS_SUCCESS(status))
	BAIL_OUT(status);
    network_ef_mask = 1<<(network_ef & 31);
    status = sys$clref(network_ef);
    if (!$VMS_STATUS_SUCCESS(status))
	BAIL_OUT(status);

    status = lib$get_ef(&done_ef);
    if (!$VMS_STATUS_SUCCESS(status))
	BAIL_OUT(status);
    done_ef_mask = 1<<(done_ef & 31);
    status = sys$clref(done_ef);
    if (!$VMS_STATUS_SUCCESS(status))
	BAIL_OUT(status);

    ef_bits = input_ef_mask | network_ef_mask | done_ef_mask;

#ifndef WJM_read_size
/* Setup timeout value for network reads */
 
    INIT_SDESC(dsc_timeout, strlen(net_time), net_time);
    status = sys$bintim(&dsc_timeout, &timeout);
    if (!$VMS_STATUS_SUCCESS(status))
	BAIL_OUT(status);
#endif

/*
**  Let the user know who we're connected to
*/
    status = netlib_getpeername(&network_context, &remsin, &sinsize, &remlen);
    if ($VMS_STATUS_SUCCESS(status)) {
    	INIT_SDESC(dsc, sizeof(name), name);
    	status = netlib_address_to_name(&network_context, &which_dns,
					&remsin.sin_x_addr, &adrsize,
					&dsc, &namelen);
    	if (!$VMS_STATUS_SUCCESS(status))
    	    status = netlib_address_to_name(&network_context, &which_ht,
					    &remsin.sin_x_addr, &adrsize,
					    &dsc, &namelen);
    	if (!$VMS_STATUS_SUCCESS(status))
    	    status = netlib_addrtostr(&remsin.sin_x_addr, &dsc, &namelen);
	if ($VMS_STATUS_SUCCESS(status)) {
	    on_info(ssh_infof("Connected to host %.*s, port %d.", namelen,
			      name, netlib_ntoh_word(&remsin.sin_w_port)));
	}
    }

/*
**  Pre-load the free queue
*/
    for (i = 0; i < 16; i++) {
    	ior = Allocate_IOR(-1);
    	if (ior == 0) break;
    	status = lib_insqti(ior, &Free_Queue, 0);
	if (!$VMS_STATUS_SUCCESS(status)) {
	    lib$signal(FISH_M_PREALLOC, 0, status);
	    BAIL_OUT(status);
	}
    }

    /* Set up an SSH connection */
    sshstate = ssh_init(sock_write, NULL, erf, erfp);
    if (!sshstate)
	BAIL_OUT(FISH_M_WEIRDNESS);

    /* Set the preferences */
    ssh_set_prefs(&gsshprefs, sshstate, erf, erfp);

    do {
	char *termname = getenv("TERM");

	if (termname)
	{
	    char *t2 = xstrdup(termname);
	    char *p;

	    if ((p = strchr(t2, '-')) != NULL)
		*p = '\0';

	    input_tty = tty_open(input_device, &status);
	    if (!tty_sensemode(input_tty,&status))
		BAIL_OUT(status);
	    ssh_setterm(t2, (input_tty->tty_char.basic_chars >> 24),
			input_tty->tty_char.buffer_size,
			0, 0, sshstate, erf, erfp);
	    tty_close(input_tty,&status);
	    input_tty = 0;

	    xfree(t2);
	}
    } while(0);

    /* Set the dispatch path */
    if (((ssh_state *)sshstate)->outfd == 0)
	ssh_set_dispatch(ui_dispatch, NULL, sshstate, erf, erfp);
    else
	ssh_set_dispatch(uf_dispatch, NULL, sshstate, erf, erfp);

#ifndef WJM_read_size
    ior->bufdsc.dsc$w_length = 1; /* Initial read is for one byte, untimed */
#endif

/*
**  Start a network read
*/
    ior = Get_IOR(-1);
    if (ior == 0)
	ssh_F_memfull();
    ssh_debug("main(): starting network read");
    status = netlib_read(&network_context, &ior->bufdsc,
			 0, 0, 0, 0, &ior->iosb,
			 Network_Read_AST, ior);
    if (!$VMS_STATUS_SUCCESS(status)) {
    	sys$cancel(input_tty->channel);
    	ssh_F_memfull();
    }

/*
**  Let the ASTs do most of the work.  They will set appropriate event
**  flags when it's time for this loop to do some external work, or when
**  it's time to shut down.
*/
    while (1) {
	unsigned long ef_bits_to_check;
	if (((ssh_state *)sshstate)->infd <= 0)
	    status = sys$wflor(input_ef,ef_bits);
	status = sys$readef(input_ef,&ef_bits_to_check);
	if (((ssh_state *)sshstate)->infd == 0) {
	    if (ef_bits_to_check & input_ef_mask) {
		sys$setast(0);
/*
**  Send data downlink through SSH layer
*/
		ssh_write(input_buffer, input_buffer_n, sshstate, erf, &gerrp);
		input_buffer_n = 0;
		sys$clref(input_ef);
		sys$setast(1);
	    }
	} else if (((ssh_state *)sshstate)->infd > 0 && input_buffer_n >= 0) {
	    input_buffer_n = read(((ssh_state *)sshstate)->infd,
				  input_buffer, IORBUFSize);
	    on_state(ssh_infof("%%FISH-I-FILEINPUT, [%d/%d] \"%-*.*s\"",
			       input_buffer_n, IORBUFSize,
			       input_buffer_n, input_buffer_n, input_buffer));
	    if (input_buffer_n == 0) {
		close(((ssh_state *)sshstate)->infd);
		((ssh_state *)sshstate)->infd = -1;
		ssh_write_type(SSH_CMSG_EOF, NULL, (ssh_state *)sshstate,
			       erf, erfp);
	    } else if (input_buffer_n < 0) {
#ifndef EAGAIN
#define EAGAIN -1
#endif
#ifndef EWOULDBLOCK
#define EWOULDBLOCK -1
#endif
		if (errno != EAGAIN && errno != EWOULDBLOCK) {
		    close(((ssh_state *)sshstate)->infd);
		    ((ssh_state *)sshstate)->infd = -1;
		}
	    } else
		ssh_write(input_buffer, input_buffer_n, sshstate, erf, &gerrp);
	}
	if (ef_bits_to_check & done_ef_mask) {
	    break;
	}
    }

/*
**  Close down the network socket and terminal channels.
*/
    netlib_shutdown(&network_context);
    netlib_close(&network_context);

 bail_out:
    exit_handler();
    *randfilename = '\0';

    ssh_clearprefs(&gsshprefs);

    return completion_status;
}



/*
**++
**  ROUTINE:	Terminal_Read_AST
**
**  FUNCTIONAL DESCRIPTION:
**
**  	Read completion AST for terminal input.
**
**  RETURNS:	void
**
**  PROTOTYPE:
**
**  	Terminal_Read_AST(ior)
**
**  IMPLICIT INPUTS:	None.
**
**  IMPLICIT OUTPUTS:	None.
**
**  COMPLETION CODES:	None.
**
**  SIDE EFFECTS:   	None.
**
**--
*/
void Terminal_Read_AST (IORequest *ior) {

    int status;
    static enum { NOTHING, CR_SEEN, TILDE_SEEN } keystate = NOTHING;
    register unsigned int c = -1;
    register unsigned int i;
    unsigned long ef_bits_to_check;

    ssh_debugf("Terminal_Read_AST(%p)", ior);
    ssh_debugf("ior = { iosb_w_status = %%X%X, iosb_w_count = %d }",
	       ior->iosb.iosb_w_status, ior->iosb.iosb_w_count);

    if (ior->iosb.iosb_w_count != 0) {
/*
**  Exit on error
*/
	if (!$VMS_STATUS_SUCCESS(ior->iosb.iosb_w_status)) {
	    ssh_debugf("Terminal_Read_AST(): Exiting on condition %%X%X",
		       ior->iosb.iosb_w_status);
	    sys$readef(input_ef, &ef_bits_to_check);
	    if (!(ef_bits_to_check & done_ef_mask))
		completion_status = ior->iosb.iosb_w_status;
	    sys$setef(done_ef);
	    return;
	}
    }

    for (i = 0; i < ior->iosb.iosb_w_count; i++) {
	c = ior->buf[i];

	if (c >= 0) {
	    switch (keystate)
	    {
	    case NOTHING:
		if (c == '\r' || c == '\n')
		    keystate = CR_SEEN;
		break;
	    case CR_SEEN:
		if (c == '~')
		    keystate = TILDE_SEEN;
		else
		    keystate = NOTHING;
		break;
	    case TILDE_SEEN:
		if (c == '.')
		{
		    lib$signal(FISH_M_CLOSED);
		    sys$setef(done_ef);
		}
		else
		    keystate = NOTHING;
		break;
	    }
	    input_buffer[input_buffer_n++] = c;
	}
    }

/*
**  Start a new read
*/
    status = SS$_NORMAL;
    sys$readef(input_ef, &ef_bits_to_check);
    if (!(ef_bits_to_check & done_ef_mask)) {
	ssh_debug("Terminal_Read_AST(): starting another terminal read");
        status = sys$qio(0, input_tty->channel,
			 IO$_READVBLK|IO$M_NOECHO|IO$M_NOFILTR,
			 &ior->iosb, Terminal_Read_AST, ior,
			 ior->buf, ior->bufdsc.dsc$w_length, 0,
			 terminator_mask, 0, 0);
    }
    if (!$VMS_STATUS_SUCCESS(status)) {
	unsigned long ef_bits_to_check;
	ssh_debugf("Terminal_Read_AST(): Exiting on condition %%X%X", status);
	sys$readef(input_ef, &ef_bits_to_check);
	if (!(ef_bits_to_check & done_ef_mask))
	    completion_status = ior->iosb.iosb_w_status;
	sys$setef(done_ef);
    	return;
    }
    sys$setef(input_ef);
} /* Terminal_Read_AST */

/*
**++
**  ROUTINE:	Network_Read_AST
**
**  FUNCTIONAL DESCRIPTION:
**
**  	Completion AST for network read.
**
**  RETURNS:	void
**
**  PROTOTYPE:
**
**  	Network_Read_AST(ior)
**
**  IMPLICIT INPUTS:	None.
**
**  IMPLICIT OUTPUTS:	None.
**
**  COMPLETION CODES:	None.
**
**  SIDE EFFECTS:   	None.
**
**--
*/
void Network_Read_AST (IORequest *ior) {

    int status;
    int len;

    ssh_debugf("Network_Read_AST(%p)", ior);
    ssh_debugf("ior = { iosb_w_status = %%X%X, iosb_w_count = %d }",
	       ior->iosb.iosb_w_status, ior->iosb.iosb_w_count);

/*
**  Exit on error
*/
    if (!$VMS_STATUS_SUCCESS(ior->iosb.iosb_w_status)
#ifndef WJM_read_size
	&& ior->iosb.iosb_w_status != SS$_TIMEOUT
#endif
	) {
	unsigned long ef_bits_to_check;
	ssh_debugf("Network_Read_AST(): Exiting on condition %%X%X",
		   ior->iosb.iosb_w_status);
	if (ior->iosb.iosb_w_count)
	    dump_buffer (stderr, "There was perhaps still some data",
			 ior->buf, ior->iosb.iosb_w_count);
	sys$readef(input_ef, &ef_bits_to_check);
    	if (!(ef_bits_to_check & done_ef_mask))
	    completion_status = ior->iosb.iosb_w_status;
	sys$setef(done_ef);
    	return;
    }

/* 
**  Send data up through ssh layer 
*/  
#ifdef WJM_read_size
    ssh_dispatch(ior->buf, ior->iosb.iosb_w_count, sshstate, erf, erfp);
#else	/* WJM_read_size */
    len = ior->iosb.iosb_w_count;
    if (len > 0)
	ssh_dispatch(ior->buf, len, sshstate, erf, erfp);
#endif	/* WJM_read_size */
    
/*
**  Start a new read from the network
*/
#ifdef WJM_read_size
    ior->bufdsc.dsc$w_length = WJM_read_size;
#else /* WJM_read_size */
/*
** If the previous read timed out, we start an untimed read for 1 byte.
** Otherwise, start a timed read for the full buffer.
*/
    if (status == SS$_TIMEOUT) {
	ior->bufdsc.dsc$w_length = 1;
	timeout_ptr = 0;
    } else {
	ior->bufdsc.dsc$w_length = IORBUFSize;
	timeout_ptr = &timeout;
    }
#endif	/* WJM_read_size */
    ssh_debugf("Network_Read_AST(): starting another network read");
    status = netlib_read(&network_context, &ior->bufdsc,
			 0, 0, 0, timeout_ptr, &ior->iosb,
			 Network_Read_AST, ior);
    if (!$VMS_STATUS_SUCCESS(status)) {
	unsigned long ef_bits_to_check;
	ssh_debugf("Network_Read_AST(): Exiting on condition %%X%X", status);
	sys$readef(input_ef, &ef_bits_to_check);
    	if (!(ef_bits_to_check & done_ef_mask))
	    completion_status = ior->iosb.iosb_w_status;
	sys$setef(done_ef);
    	return;
    }
} /* Network_Read_AST */

unsigned long ior_allocation_count = 0;	/* Statistics */
unsigned long ior_use_count = 0;	/* Statistics */

/*
**++
**  ROUTINE:	Write_AST
**
**  FUNCTIONAL DESCRIPTION:
**
**  	Completion AST for terminal and network writes.  It checks
**  for successful completion and frees the I/O request block.
**
**  RETURNS:	void
**
**  PROTOTYPE:
**
**  	Write_AST(ior)
**
**  IMPLICIT INPUTS:	None.
**
**  IMPLICIT OUTPUTS:	None.
**
**  COMPLETION CODES:	None.
**
**  SIDE EFFECTS:   	None.
**
**--
*/
void Write_AST (IORequest *ior) {
    int status;

    ssh_debugf("Write_AST(%p)", ior);
    ssh_debugf("ior = { iosb_w_status = %%X%X, iosb_w_count = %d }",
	       ior->iosb.iosb_w_status, ior->iosb.iosb_w_count);

    if (!$VMS_STATUS_SUCCESS(ior->iosb.iosb_w_status)) {
	unsigned long ef_bits_to_check;
	ssh_debugf("Write_AST(): Exiting on condition %%X%X",
		   ior->iosb.iosb_w_status);
	sys$readef(input_ef, &ef_bits_to_check);
    	if (!(ef_bits_to_check & done_ef_mask))
	    completion_status = ior->iosb.iosb_w_status;
	sys$setef(done_ef);
    	return;
    }

    status = lib_insqti(ior, &Free_Queue, 0);
    if (!$VMS_STATUS_SUCCESS(status)) {
	lib$signal(FISH_M_BUFALLOC,0,status);
	completion_status = status;
	sys$setef(done_ef);
    	return;
    }

    ior_use_count--;

} /* Write_AST */

/*
**++
**  ROUTINE:	Allocate_IOR
**
**  FUNCTIONAL DESCRIPTION:
**
**  	Allocates an IORequest.
**
**  RETURNS:    IORequest *
**
**  PROTOTYPE:
**
**  	Allocate_IOR()
**
**  IMPLICIT INPUTS:	None.
**
**  IMPLICIT OUTPUTS:	None.
**
**  COMPLETION CODES:	0 = allocation failure
**
**  SIDE EFFECTS:   	None.
**
**--
*/
IORequest *Allocate_IOR (int buflen) {

    IORequest *ior;
    int status;
    const unsigned int iorsize = sizeof(IORequest) + IORBUFSize - 1;

    buflen = (buflen > 0 ? buflen : IORBUFSize);

    status = lib$get_vm((long *)&iorsize, &ior);
    if (!$VMS_STATUS_SUCCESS(status)) return 0;

    memset(ior, 0, iorsize);
    INIT_SDESC(ior->bufdsc, buflen, ior->buf);

    ior_allocation_count++;

    return ior;

} /* Allocate_IOR */

/*
**++
**  ROUTINE:	Get_IOR
**
**  FUNCTIONAL DESCRIPTION:
**
**  	Gets a free IORequest, either off the free queue or by
**  allocating one.
**
**  RETURNS:	IORequest *
**
**  PROTOTYPE:
**
**  	Get_IOR()
**
**  IMPLICIT INPUTS:	None.
**
**  IMPLICIT OUTPUTS:	None.
**
**  COMPLETION CODES:	0 = couldn't get one
**
**  SIDE EFFECTS:   	None.
**
**--
*/
IORequest *Get_IOR (int buflen) {
    IORequest *ior;
    int status = SS$_NORMAL;

    buflen = (buflen > 0 ? buflen : IORBUFSize);

    /* Let's watch out, it looks like things get a little overwritten if
       we don't do the extra check on Free_Queue.head... */
    if ((status = lib_remqhi(&Free_Queue, (void **)&ior, 0)) == LIB$_QUEWASEMP)
	ior = Allocate_IOR(buflen);
    else if (!$VMS_STATUS_SUCCESS(status)) {
	lib$signal(FISH_M_BUFDEALLOC, 0, status);
    }

    if (ior)
	ior->bufdsc.dsc$w_length = buflen;

    ior_use_count++;

    return ior;
} /* Get_IOR */

/* Emacs local variables

Local variables:
eval: (set-c-style "BSD")
end:

*/
