/*
 * SC_VERSION: 0.0.1
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include <linux/limits.h>
#include <errno.h>

#include <signal.h>

#include <termios.h>
#include <poll.h>

#include <netdb.h>

#include <libssh2_config.h>
#include <libssh2.h>
#include <libssh2_sftp.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/socket.h>

#include <netinet/in.h>
#include <arpa/inet.h>

#include <fcntl.h>

#include "list.h"
#include "smtp.h"
#include "conffile.h"

#include "ui_common.h"
#include "ui_user.h"
#include "ui_imodem.h"
#include "ui_port.h"


void killed( int  );
void Dummy( int );
void Deadly( int );

#define SFTP_PORT	22	/* TBD: Move to .h file */

/*
 * TBD: Replace w/ common definitions.
 */
#define MAX_PATH	256
#define MAX_FILE	64


/*
 * SFTP file tranmission states.
 */
enum sftp_state {
SFTP_IDLE = 0,
SFTP_DNS,
SFTP_CONNECT,
SFTP_NEGOTIATE,
SFTP_AUTHENTICATE,
SFTP_SESSION,
SFTP_VERIFY,
SFTP_CREATE,
SFTP_UP,
SFTP_DISCONNECT
};

/*
 * SFTP operation outcome codes.
 */
enum sftp_status {
OP_OK = 0,
OP_PROGRESS,
OP_FAILURE
};

/*
 * File tranmission modes.
 *
 * CM_SAFE: Don't overwrite.
 * CM_FORCE: Force overwrite.
 * CM_AUTO: Automatically select name and Don't overwrite.
 */
UCHAR *Mode_list[] = {
#define CM_SAFE	0	
(UCHAR *) "SAFE",
#define CM_FORCE	1
(UCHAR *) "FORCE",
#define CM_AUTO	2
(UCHAR *) "AUTO",
(UCHAR *) NULL
};

/*
 * Data for peforming a file transfer to
 * an SFTP server. 
 *
 * TBD: Move to a .h file.
 */
typedef struct put_data {
/*
 * SFTP server file creation mode.
 */
int	create_mode;
/*
 * iMODEM SFTP passphrase.
 *
 * TBD: Fix libssh2 so this really works.
 */
#define MAX_PASS_PHRASE	128
UCHAR	passphrase[MAX_PASS_PHRASE];
/*
 * SFTP server Unix/Linux account and name.
 * Specifies the server name--which is normally suffixed
 * by a URL, and the iMODEM Unix/Linux account--which is
 * assumed to be both the name of the iMODEM home directory
 * and iMODEM Unix/Linux Login ID. Example: boss@server1.acme.com
 */
UCHAR	account[(MAX_FILE * 2)];
/*
 * Alternate server specifier.
 *
 * This field is only used if server name specified by
 * account can not be resolved via DNS or
 * differs from the Internet server name.
 * It may be an IP address or a server name.
 */
UCHAR	alt_server[MAX_FILE];
/*
 * The name of the file to be created on the SFTP server.
 * This field must be specified except in CM_AUTO in
 * which case it must be NULL.
 */
UCHAR	target_file[MAX_FILE];
/*
 * The name of the directory in which the file
 * is to be created. The directory is under /home/ and
 * must not be prefixed by a '/'. 
 * If a subdirectory is specified, it must be prefixed
 * by the iMODEM home directory name.
 * Example:  user/custom
 */
UCHAR	target_dir[MAX_PATH];
/*
 * The file to be tranferred from the iMODM to the SMTP server.
 * A complete path must be specified.
 * Example: /etc/imodem/sftp/file.txt
 */
UCHAR source_file[MAX_FILE];
} PUT_DATA;

/*
 * Callback function for transfer completion.
 * TBD: Add Channel desriptor linkage.
 * TBD: Should probably to to a .h file.
 */
typedef void (*SFTP_CB_VOID) (PUT_DATA *, BOOL, int );

/*
 * Data to manage SFTP put transaction.
 */
typedef struct sftp_data {
enum sftp_state		state;
/*
 * TRUE when the first phase of an operation
 * with two phases is complete.
 */
BOOL			op_started;
/*
 * File transfer outcome and error code. 
 */
BOOL			outcome;
int			final_error;
/*
 * TCP Connection data.
 * hostdata is NULL unless DNS resolution has
 * been performed.
 */
int			sock_fd;
struct hostent		*hostdata;
/*
 * PUT transaction data and callback.
 */
PUT_DATA		*put_data;
SFTP_CB_VOID		put_done;
/*
 * Libssh2 handles.
 */
LIBSSH2_SESSION		*ssh_session;
LIBSSH2_SFTP		*sftp_session;
LIBSSH2_SFTP_HANDLE	*sftp_handle;
/*
 * Local file descriptor.
 */
int			local_fd;
/*
 * Only used in CM_AUTO mode.
 * Maximum attempts to find an unused file
 * name on the remote SFTP server.
 */
#define SFTP_MAX_DUP_FILES	24
int			n_dup_files;
/*
 * The number of bytes left to send in data_buf
 * and a pointer to the next byte to transfer.
 */
int			n_to_send;
char			*data_ptr;
/*
 * Complete path to target file.
 */
char			target_file[MAX_PATH];
/*
 * Data buffer.
 */
#define	BUF_SIZE	256
char			data_buf[BUF_SIZE];
} SFTP_DATA;



void sftp_events();
BOOL sftp_put(PUT_DATA *, SFTP_CB_VOID put_done, int *);


/*
 * Just examples.
 */
BOOL Put_Done;
void my_put_done(PUT_DATA *, BOOL , int );


BOOL start_ppp();
void do_test(char *, char *);

char w_buf[256];
char account[128];
char secret[128];

int main(int argc, char *argv[])
{
	/*
	 * Set up signals for initialization.
	 */
	{
		int k;
		for(k = 0; k < NSIG; k++)
			(void) signal(k, Dummy);

	}
	(void) signal(SIGABRT, SIG_DFL);
	(void) signal(SIGSEGV, Deadly);
	(void) signal(SIGFPE, Deadly);
	(void) signal(SIGPIPE, Deadly);
	(void) signal(SIGTERM, Deadly);

/* #define ROBUST */
#ifdef ROBUST
	(void) signal(SIGINT, SIG_IGN);
#else /* ROBUST */
	(void) signal(SIGINT, killed);
#endif /* ROBUST */

#ifdef UC_LINUX
	/*
	 * Protect against spurious SIGILL signals.
	 * This KLUDGE is necessary for the MC 5249
	 * implementation of UC Linux.
	 */
	signal(SIGILL, SIG_IGN);
#endif /* UC_LINUX */

	printf("SFTP Test Program\n");

	printf("Enter your Account: ");
	while(gets(w_buf) != w_buf);
	strcpy(account, w_buf);
	printf("\n");

	printf("Enter Your PassPhrase: ");
	while(gets(w_buf) != w_buf);
	strcpy(secret, w_buf);
	printf("\n");


	printf("Press Any Key to Connect\n");
	(void) getchar();

	if(start_ppp())
	  {
		sleep(30);
		printf("Connected ?\n");
	  }
	else
	  {
		printf("Connect Failed\n");
		abort();
	  }

	printf("Press Any Key to Start Test\n");
	(void) getchar();

#ifdef UC_LINUX
	sprintf(w_buf, "cp /etc/ppp/resolv.conf /etc/resolv.conf");
#else /* UC_LINUX */
	sprintf(w_buf, "cp -f  /etc/ppp/resolv.conf /etc/resolv.conf\n");
#endif /* UC_LINUX */
	system(w_buf);

	sprintf(w_buf, "route add default ppp0\n");
	system(w_buf);

	sleep(5);


	for(;;)
	  {
		do_test(account, secret);
		sleep(10);
	  }

	return(0);
}


ROUTINE void do_test(char *account, char *secret)
{
	PUT_DATA put;	/* TBD: Allocate dynamically */
	int error;

	bzero((void *) &put, sizeof(put));

	strcpy((char *) put.account, account);
	strcpy((char *) put.passphrase, secret);
	strcpy((char *) put.source_file, "/etc/imodem/sftp/test");
	strcpy((char *) put.target_dir, "paulr/test");
	strcpy((char *) put.target_file, "imodem_test");

	put.create_mode = CM_FORCE;

	Put_Done = FALSE;
	if(sftp_put(&put, my_put_done, &error))
	  {
		while(Put_Done != TRUE)
			sftp_events();
	  }
	else
		printf("Can't Start. %d\n", error);

	return;
}


void Dummy( int sig )
{
	fprintf(stderr, "UI_MAIN: *** Unexpected Signal %d ***", sig);
	sleep(3);
	return;
}

void my_put_done(PUT_DATA *p_put, BOOL outcome, int error_code)
{
	Put_Done = TRUE;

	if(outcome)
		fprintf(stderr, "***** SUCCESS: %s Sent *****\n", p_put->source_file);
	else
		fprintf(stderr, "***** FAILURE %d: %s Not Sent ****\n",
			error_code, p_put->source_file);

	return;
}


/*
 * TBD: Replace w/ channel descriptor linkage.
 */
SFTP_DATA *Sftp_Test_Data;

enum sftp_status sftp_start_dns(SFTP_DATA *, int *);
enum sftp_status sftp_start_connect(SFTP_DATA * , int *);
enum sftp_status sftp_connect(SFTP_DATA *, int *);
enum sftp_status sftp_start_negotiate(SFTP_DATA *, int *);
enum sftp_status sftp_negotiate(SFTP_DATA *, int *);
enum sftp_status sftp_authenticate(SFTP_DATA *, int *);
enum sftp_status sftp_session(SFTP_DATA *, int *);
enum sftp_status sftp_verify_remote(SFTP_DATA *, int *);
enum sftp_status sftp_create_remote(SFTP_DATA *, int *);
enum sftp_status sftp_transmit(SFTP_DATA *, int *);
enum sftp_status sftp_disconnect(SFTP_DATA *, int *);

int sftp_map_error(int);
void sftp_init(SFTP_DATA *, PUT_DATA *, SFTP_CB_VOID );
void sftp_cleanup(SFTP_DATA *);

/*
 * Handle SFTP  file transfer events.
 */
ROUTINE void sftp_events()
{
	enum sftp_status status;
	SFTP_DATA *p_sftp;
	BOOL done;
	int error;

	/*
	 * TBD: Replace w/ Channel Descriptor Linkage.
	 */
	p_sftp = Sftp_Test_Data;

	done = FALSE;
	error = 0;
	
#ifdef DEBUG
	fprintf(stderr, "SFTP: State = %d\n", p_sftp->state);
#endif /* DEBUG */
	
	switch(p_sftp->state)
	{
	case SFTP_IDLE:
		break;
	case SFTP_DNS:
		if( !p_sftp->op_started )
		  {
			p_sftp->op_started = TRUE;
			status = sftp_start_dns(p_sftp, &error);
		  }

		switch(status)
		{
		case OP_PROGRESS:
			p_sftp->op_started = FALSE;
			break;
		case OP_OK:
			p_sftp->state = SFTP_CONNECT;
			p_sftp->op_started = FALSE;
			break;
		case OP_FAILURE:
			p_sftp->state = SFTP_IDLE;
			p_sftp->op_started = FALSE;
			p_sftp->final_error = error;
			done = TRUE;
			break;
		}
		break;
	case SFTP_CONNECT:
		if( !p_sftp->op_started)
		  {
			p_sftp->op_started = TRUE;
			status = sftp_start_connect(p_sftp, &error);
		  }
		else
			status = sftp_connect(p_sftp, &error);

		switch(status)
		{
		case OP_PROGRESS:
			break;
		case OP_OK:
			p_sftp->state = SFTP_NEGOTIATE;
			p_sftp->op_started = FALSE;
			break;
		case OP_FAILURE:
			p_sftp->state = SFTP_IDLE;
			p_sftp->op_started = FALSE;
			p_sftp->final_error = error;
			done = TRUE;
			break;
		}
		break;
	case SFTP_NEGOTIATE:
		if( !p_sftp->op_started )
		  {
			p_sftp->op_started = TRUE;
			status = sftp_start_negotiate(p_sftp, &error);
		  }
		else
			status = sftp_negotiate(p_sftp, &error);

		switch(status)
		{
		case OP_PROGRESS:
			break;
		case OP_OK:
			p_sftp->state = SFTP_AUTHENTICATE;
			p_sftp->op_started = FALSE;
			break;
		case OP_FAILURE:
			p_sftp->state = SFTP_DISCONNECT;
			p_sftp->op_started = FALSE;
			p_sftp->final_error = error;
			break;
		}
		break;
	case SFTP_AUTHENTICATE:
		status = sftp_authenticate(p_sftp, &error);
		switch(status)
		{
		case OP_PROGRESS:
			break;
		case OP_OK:
			p_sftp->state = SFTP_SESSION;
			break;
		case OP_FAILURE:
			p_sftp->state = SFTP_DISCONNECT;
			p_sftp->final_error = error;
			break;
		}
		break;
	case SFTP_SESSION:
		status = sftp_session(p_sftp, &error);
		switch(status)
		{
		case OP_PROGRESS:
			break;
		case OP_OK:
			if(p_sftp->put_data->create_mode != CM_FORCE)
				p_sftp->state = SFTP_VERIFY;
			else
				p_sftp->state = SFTP_CREATE;
			break;
		case OP_FAILURE:
			p_sftp->state = SFTP_DISCONNECT;
			p_sftp->final_error = error;
			break;
		}
		break;
	case SFTP_VERIFY:
		status = sftp_verify_remote(p_sftp, &error);
		switch(status)
		{
		case OP_PROGRESS:
			break;
		case OP_OK:
			p_sftp->state = SFTP_CREATE;
			break;
		case OP_FAILURE:
			p_sftp->state = SFTP_DISCONNECT;
			p_sftp->final_error = error;
			break;
		}
		break;
	case SFTP_CREATE:
		status = sftp_create_remote(p_sftp, &error);
		switch(status)
		{
		case OP_PROGRESS:
			break;
		case OP_OK:
			p_sftp->state = SFTP_UP;
			break;
		case OP_FAILURE:
			p_sftp->state = SFTP_DISCONNECT;
			p_sftp->final_error = error;
			break;
		}
		break;
	case SFTP_UP:
		status = sftp_transmit(p_sftp, &error);
		switch(status)
		{
		case OP_PROGRESS:
			break;
		case OP_OK:
			p_sftp->state = SFTP_DISCONNECT;
			p_sftp->outcome = TRUE;
			p_sftp->final_error = error;
			break;
		case OP_FAILURE:
			p_sftp->state = SFTP_DISCONNECT;
			p_sftp->final_error = error;
			break;
		}
		break;
	case SFTP_DISCONNECT:
		status = sftp_disconnect(p_sftp, &error);
		switch(status)
		{
		case OP_PROGRESS:
			break;
		case OP_OK:
			p_sftp->state = SFTP_IDLE;
			done = TRUE;
			break;
		case OP_FAILURE:
			p_sftp->state = SFTP_IDLE;
			done = TRUE;
			break;
		}
		break;
	default:
		/*
		 * TBD: Replace.
		 */
		fprintf(stderr, "sftp_events: BUG Bad State %d\n", p_sftp->state);
		abort();
	}


#ifdef DEBUG
	fprintf(stderr, "SFTP: Status = %d\n", status);
#endif /* DEBUG */

	if(done)
	  {
		/*
		 * Invoke the configured callback function here.
		 */
		(* p_sftp->put_done) (p_sftp->put_data, p_sftp->outcome,
		                      p_sftp->final_error);

		/*
		 * The order of operations is critical in
		 * the following segment.
		 */
		sftp_cleanup(p_sftp);
		if(p_sftp->sock_fd != -1)
			close(p_sftp->sock_fd);
		if(p_sftp->ssh_session)
			libssh2_session_free(p_sftp->ssh_session);

		p_sftp->sock_fd = -1;
		p_sftp->ssh_session = NULL;

		free(p_sftp);
		Sftp_Test_Data = NULL;
		/*
		 * TBD: Maybe deinitialize libssh2
		 */
	  }

	return;
}



/*
 * If the mode is AUTO, the target file is automatically
 * selected.
 *
 * TBD: Add channel descriptor linkage.
 */
ROUTINE BOOL sftp_put(PUT_DATA *p_put, SFTP_CB_VOID put_done, int *perror)
{
	SFTP_DATA *p_sftp;
	BOOL rval = FALSE;

	/*
	 * Allocate the SFTP descriptor.
	 */
	p_sftp = (SFTP_DATA *) malloc(sizeof(SFTP_DATA));
	if(p_sftp)
	  {
		Sftp_Test_Data = p_sftp;
		sftp_init(p_sftp, p_put, put_done);
	  }
	else
	  {
		*perror = UI_ERR_NO_MEMORY;
		return(FALSE);
	  }

	if((p_sftp->local_fd = open((char *) p_put->source_file,
	                             (O_RDONLY | O_NONBLOCK))) < 0)
	  {
		*perror = UI_ERR_FILE_OP;
		free((void *) p_sftp);
		/*
		 * TBD: Replace w/ Channel descriptor linkage
		 * and real diagnostic.
		 */
		Sftp_Test_Data = NULL;
fprintf(stderr, "sftp_put: Open %s Failed. Errno = %d\n",
p_put->source_file, errno);
	  }
	else
	  {
		char *server;
		char *p, n;

		rval = TRUE;
		if(*p_put->alt_server)
			server = (char *) p_put->alt_server;
		else
		  {
			/*
			 * Strip the user off the server.
			 * TBD: Maybe strip URL suffix in future.
			 */
			server = (char *) p_put->account;
			while(*server && *server != '@') server++;
			if(*server)
				server++;
		  }
fprintf(stderr, "sftp_put: Server = %s\n", server);

		/*
		 * TBD: Try to replace w/ general routine.
		 *
		 * If the server name begins with digits and a '.',
		 * assume it is an IP address.
		 */
		n = 0;
		p = server;
		while(*p && isdigit(*p)) { p++, n++; }
		if(n && *p == '.')
		  {
fprintf(stderr, "Skip DNS\n");
			p_sftp->state = SFTP_CONNECT;
			strcpy((char *) p_put->alt_server, server);
		  }
		else
			p_sftp->state = SFTP_DNS;
	  }

	return(rval);
}

/*
 * TBD: Replace gethostbyname() with a non-blocking
 * version.
 */
ROUTINE enum sftp_status sftp_start_dns(SFTP_DATA *p_sftp, int *perror)
{
	PUT_DATA *p_put;
	char *server;
	struct hostent *phost;
	enum sftp_status rval;

fprintf(stderr, "sftp_start_dns: Performing DNS\n");

	p_put = p_sftp->put_data;

	if(*p_put->alt_server)
		server = (char *) p_put->alt_server;
	else
	  {
		/*
		 * Strip the user off the server.
		 * TBD: Maybe strip URL suffix in future.
		 */
		server = (char *) p_put->account;
		while(*server && *server != '@') server++;
		if(*server)
			server++;

	  }

fprintf(stderr, "sftp_start_dns: Server = %s\n", server);

	phost = gethostbyname(server);
	if(phost)
	  {
		rval = OP_OK;
		p_sftp->hostdata = phost;
fprintf(stderr, "sftp_start_dns: DNS Succeeded\n");
	  }
	else
	if(h_errno == TRY_AGAIN)
		rval = OP_PROGRESS;
	else
	  {
		rval = OP_FAILURE;
		*perror = UI_ERR_BAD_SERVER;
	  }

	return(rval);
}

ROUTINE enum sftp_status sftp_start_connect(SFTP_DATA *p_sftp, int *perror)
{
	PUT_DATA *p_put;
	struct sockaddr_in sin;
	enum sftp_status rval = OP_FAILURE;

fprintf(stderr, "sftp_start_connect: Starting TCP Connection\n");

	p_put = p_sftp->put_data;
	/*
	 * Allocate the TCP socket and put in in non-blocking mode.
	 */
	p_sftp->sock_fd = socket(PF_INET, SOCK_STREAM, 0);
	if(p_sftp->sock_fd < 0)
		*perror = UI_ERR_SOCKET;
	else
	if(fcntl(p_sftp->sock_fd, F_SETFL, O_NONBLOCK))
	  {
		/*
		 * TBD: Add diagnostic.
		 */
		rval = OP_FAILURE;
		*perror = UI_ERR_OS_OP;
	  }
	else
	  {
		/*
		 * Retrieve the IP address if DNS
		 * resolution was performed.
		 */
		sin.sin_family = AF_INET;
		sin.sin_port = htons(SFTP_PORT);
		if(p_sftp->hostdata)
		  {
fprintf(stderr, "sftp_start_connect: Resolved IP\n");
    			memcpy((void *) &sin.sin_addr,
			(void *) p_sftp->hostdata->h_addr_list[0],
                        (size_t) p_sftp->hostdata->h_length);
		  }
		else
		  {
fprintf(stderr, "sftp_start_connect: Alternate IP = %s\n", p_put->alt_server);
			sin.sin_addr.s_addr = inet_addr((char *) p_put->alt_server);
		  }
		/*
		 * Try to connect to the SFTP server.
		 */
		if(connect(p_sftp->sock_fd, (struct sockaddr *) &sin, sizeof(sin)) == 0)
		  {
fprintf(stderr, "sftp_start_connect: Connect Succeeded Early\n");
			rval = OP_OK;
		  }
		else
		if(errno == EINPROGRESS || errno == EALREADY)
			rval = OP_PROGRESS;
		else
			*perror = UI_ERR_TCP_CONN;
	  }

	return(rval);
}

/*
 * Determine if there is poll event indicating the TCP
 * connection attempt completed.
 *
 * TBD: Perhaps cap the no. of invocations.
 */
ROUTINE enum sftp_status sftp_connect(SFTP_DATA *p_sftp, int *perror)
{
	struct pollfd poll_fd;
	int r;
	enum sftp_status rval = OP_FAILURE;

	poll_fd.fd = p_sftp->sock_fd;
	poll_fd.revents = 0;
	poll_fd.events = (POLLIN | POLLPRI | POLLHUP);

	r = poll(&poll_fd, 1, 5);
	if(r > 0)
	  {
		socklen_t len;
		int error;

fprintf(stderr, "sftp_connect: Poll Event %d\n", poll_fd.events);
		/*
		 * Received a poll event.
		 * Determine if the connection succeeded.
		 */
		error = 0;
		len = sizeof(error);
		if(getsockopt(p_sftp->sock_fd, SOL_SOCKET, SO_ERROR,
		   (void *) &error, &len) != 0)
		  {
fprintf(stderr, "sftp_connect: System Failure %d\n", error);
			/*
			 * TBD: Add diagnostic.
			 */
			*perror = UI_ERR_OS_OP;
		  }
		else
		if(error)
		  {
fprintf(stderr, "sftp_connect: Operation Failure %d\n", error);
			/*
			 * TBD: Add diagnostic ?
			 */
			*perror = UI_ERR_TCP_CONN;
		  }
		else
		  {
			rval = OP_OK;
fprintf(stderr, "sftp_connect: Connect Succeeded\n");
		  }
	  }
	else
	if(r < 0)
	  {
		/*
		 * TBD: Add diagnostic.
		 */
fprintf(stderr, "TECH FAILURE %d\n", errno);
		*perror = UI_ERR_OS_OP;
	  }
	else
		rval = OP_PROGRESS;

	return(rval);
}

ROUTINE enum sftp_status sftp_start_negotiate(SFTP_DATA *p_sftp, int *perror)
{
	int rc;
	enum sftp_status rval = OP_FAILURE;

fprintf(stderr, "sftp_start_negotiate: Start KEX Operations\n");

	rc = 0;
	p_sftp->ssh_session = libssh2_session_init();
	if(p_sftp->ssh_session)
	  {
fprintf(stderr, "sftp_start_negotiate: SSH Session Initialized\n");

		libssh2_session_set_blocking(p_sftp->ssh_session, 0);
		rc = libssh2_session_startup(p_sftp->ssh_session, p_sftp->sock_fd);
		if(rc == LIBSSH2_ERROR_EAGAIN)
			rval = OP_PROGRESS;
		else
		if( !rc )
		  {
			rval = OP_OK;
fprintf(stderr, "sftp_start_negotiate: SSH Session Started Early\n");
		  }
	  }

	*perror = sftp_map_error(rc);
	return(rval);
}

ROUTINE enum sftp_status sftp_negotiate(SFTP_DATA *p_sftp, int *perror)
{
	int rc;
	enum sftp_status rval;

	rc = libssh2_session_startup(p_sftp->ssh_session, p_sftp->sock_fd);
	if(rc == LIBSSH2_ERROR_EAGAIN)
		rval = OP_PROGRESS;
	else
	if(rc)
		rval = OP_FAILURE;
	else
	  {
		rval = OP_OK;
fprintf(stderr, "sftp_negotiate: SSH Session Started\n");
	  }

	*perror = sftp_map_error(rc);
	return(rval);
}

ROUTINE enum sftp_status sftp_authenticate(SFTP_DATA *p_sftp, int *perror)
{
	char *name;
	enum sftp_status rval = OP_FAILURE;

	if((name = (char *) malloc(256)))
	  {
		PUT_DATA *p_put;
		char *p;
		int rc, n;

		bzero((void *) name, 256);
	
		p_put = p_sftp->put_data;
		/*
		 * Extract the SFTP system user name from
		 * the account.
		 */
		p = (char *) p_put->account;
		n = 0;
		while(*p && *p != '@') { p++; n++; }
		strncpy((char *) name, (char *) p_put->account, n);

#ifdef NOT_DEFINED
fprintf(stderr,
"sftp_authenticate: User %s Passphrase %s Public Key %s Private Key %s\n",
name, p_put->passphrase, RSA_PUBLIC_KEY, RSA_PRIVATE_KEY);
#endif
	
		rc = libssh2_userauth_publickey_fromfile(p_sftp->ssh_session, name,
		                               RSA_PUBLIC_KEY, RSA_PRIVATE_KEY,
		                               (char *) p_put->passphrase);
		if(rc == LIBSSH2_ERROR_EAGAIN)
			rval = OP_PROGRESS;
		else
		if( !rc )
		  {
fprintf(stderr, "sftp_authenticate: Auth. Suceeded\n");
			rval = OP_OK;
		  }

		free(name);
		*perror = sftp_map_error(rc);
	  }
	else
		*perror = UI_ERR_NO_MEMORY;

	return(rval);
}

ROUTINE enum sftp_status sftp_session(SFTP_DATA *p_sftp, int *perror)
{
	enum sftp_status rval;
	int rc;

	rc = 0;
	p_sftp->sftp_session = libssh2_sftp_init(p_sftp->ssh_session);
	if(p_sftp->sftp_session)
	  {
fprintf(stderr, "sftp_session: SFTP Session Established\n");
		rval = OP_OK;
	  }
	else
	  {
		rc = libssh2_session_last_errno(p_sftp->ssh_session);
		if(rc == LIBSSH2_ERROR_EAGAIN)
			rval = OP_PROGRESS;
		else
			rval = OP_FAILURE;
	   }

	*perror = sftp_map_error(rc);
	return(rval);
}

ROUTINE enum sftp_status sftp_verify_remote(SFTP_DATA *p_sftp, int *perror)
{
	LIBSSH2_SFTP_ATTRIBUTES attr_data;
	PUT_DATA *p_put;
	char *file;
	int rc;
	enum sftp_status rval = OP_FAILURE;


	p_put = p_sftp->put_data;
	if(p_put->create_mode == CM_AUTO)
	  {
		char *p;
		/*
		 * Select a file name that might not exist
		 * on the SFTP server.
		 *
		 * TBD: Replace w/ cleaner function ?
		 */
		file = tmpnam(NULL);
		p = file + (strlen(file) - 1);
		while(*p && *p != '/') p--;
		if(*p)
			file = (p + 1);
	  }
	else
		file = (char *) p_put->target_file;

/*
fprintf(stderr, "sftp_verify_remote: Checking Existence of %s\n", file);
*/
		
	sprintf(p_sftp->target_file, "/home/%s/%s", p_put->target_dir, file);
	bzero((void *) &attr_data, sizeof(attr_data));

	rc = libssh2_sftp_stat_ex(p_sftp->sftp_session,
	     p_sftp->target_file, strlen(p_sftp->target_file),
	     LIBSSH2_SFTP_STAT, &attr_data);

	if(rc == LIBSSH2_ERROR_EAGAIN)
		rval = OP_PROGRESS;
	else
	if(rc)
	  {
		rc = libssh2_sftp_last_error(p_sftp->sftp_session);
		if(rc == LIBSSH2_FX_NO_SUCH_FILE)
		  {
			/*
			 * SUCCESS: * The file does not exist on the SFTP server.
			 */
fprintf(stderr, "sftp_verify_remote: Succeeded Definitively\n");
			rval = OP_OK;
		  }
		else
		  {
fprintf(stderr, "Dead  !!!!! %d\n", rc);
			*perror = sftp_map_error(rc);
		  }
	  }
	else
	if(p_put->create_mode == CM_AUTO)
	  {
		/*
		 * The file exists on the SFTP server.
		 * Try another file name if there are
		 * not too many previous failures.
		 */
		p_sftp->n_dup_files++;
		if(p_sftp->n_dup_files < SFTP_MAX_DUP_FILES)
		  {
fprintf(stderr, "sftp_verify_remote: Retry %d\n", p_sftp->n_dup_files);
			rval = OP_PROGRESS;
		  }
		else
			*perror = UI_ERR_FILE_EXISTS;
	  }
	else
	  {
		/*
		 * FAILURE: The file exists on the SFTP server.
		 */
fprintf(stderr, "Found it\n");
		*perror = UI_ERR_FILE_EXISTS;
	  }

	return(rval);
}

ROUTINE enum sftp_status sftp_create_remote(SFTP_DATA *p_sftp, int *perror)
{
	PUT_DATA *p_put;
	ULONG flags;
	enum sftp_status rval;
	int rc = 0;
	
fprintf(stderr, "sftp_create_remote: Creating Remote File\n");

	p_put = p_sftp->put_data;
        flags = LIBSSH2_FXF_WRITE | LIBSSH2_FXF_CREAT| LIBSSH2_FXF_TRUNC;
	switch(p_put->create_mode)
	{
	case CM_SAFE:
	case CM_AUTO:
		/*
		 * Inhibit overwriting on the SFTP server.
		 */
fprintf(stderr, "sftp_create_remote: No Overwrite\n");
		flags |= LIBSSH2_FXF_EXCL;
		break;
	case CM_FORCE:
		sprintf(p_sftp->target_file,
	        	"/home/%s/%s", p_put->target_dir, p_put->target_file);
		break;
	}

fprintf(stderr, "sftp_create_remote: File = %s\n", p_sftp->target_file);

	p_sftp->sftp_handle = libssh2_sftp_open(p_sftp->sftp_session,
	                      p_sftp->target_file, flags,
                              (LIBSSH2_SFTP_S_IRUSR | LIBSSH2_SFTP_S_IWUSR |
                               LIBSSH2_SFTP_S_IRGRP | LIBSSH2_SFTP_S_IROTH) );

	if(p_sftp->sftp_handle)
	  {
fprintf(stderr, "sftp_create_remote: File Creation Succeeded\n");
		rval = OP_OK;
	  }
	else
	  {
		rc = libssh2_session_last_errno(p_sftp->ssh_session);
		if(rc ==  LIBSSH2_ERROR_EAGAIN)
			rval = OP_PROGRESS;
		else
			rval = OP_FAILURE;
	  }

	*perror = sftp_map_error(rc);
	return(rval);
}

ROUTINE enum sftp_status sftp_transmit(SFTP_DATA *p_sftp, int *perror)
{
	int rc, n;
	enum sftp_status rval = OP_FAILURE;

	/*
	 * Read some data from the local file if necessary.
	 */
	rc = 0;
	if(p_sftp->n_to_send > 0)
		n = p_sftp->n_to_send;
	else
	  {
		p_sftp->data_ptr = p_sftp->data_buf;
		n = read(p_sftp->local_fd, p_sftp->data_buf, BUF_SIZE);
		p_sftp->n_to_send = n;
	  }

	if(n > 0)
	  {
		/*
		 * Send data.
		 */
		rc = libssh2_sftp_write(p_sftp->sftp_handle, p_sftp->data_ptr, n);
		if(rc == LIBSSH2_ERROR_EAGAIN)
			rval = OP_PROGRESS;
		else
		if(rc > 0)
		  {
			rval = OP_PROGRESS;

			p_sftp->n_to_send -= rc;
			p_sftp->data_ptr += rc;
		  }
		else
		if(rc)
			*perror = sftp_map_error(rc);
		else
			rval = OP_PROGRESS;
	  }
	else
	if(n < 0)
	  {
		/*
		 * TBD: Add diagnostic.
		 */
		if(errno == EINTR || errno == EAGAIN)
			rval = OP_PROGRESS;
		else
			*perror = UI_ERR_FILE_OP;
	  }
	else
	  {
		/*
		 * SUCCESS: All data sent.
		 */
fprintf(stderr, "sftp_transmit: No More Data. SUCCESS\n");
		rval = OP_OK;
	  }

	return(rval);
}

ROUTINE enum sftp_status sftp_disconnect(SFTP_DATA *p_sftp, int *perror)
{
	enum sftp_status rval;
	int rc;

fprintf(stderr, "sftp_disconnect: Disconnect Libssh2\n");

	rc = libssh2_session_disconnect(p_sftp->ssh_session, "Bye !!!");
	if(rc == LIBSSH2_ERROR_EAGAIN)
		rval = OP_PROGRESS;
	else
	if(rc)
		rval = OP_FAILURE;
	else
	  {
		rval = OP_OK;
fprintf(stderr, "sftp_disconnect: Succeeed LIBSSH2 Disconnected\n");
		/*
		 * The order of operations is critical
		 * in the following segment.
		 */
		sftp_cleanup(p_sftp);

		libssh2_session_free(p_sftp->ssh_session);
		p_sftp->ssh_session = NULL;

		close(p_sftp->sock_fd);
		p_sftp->sock_fd = -1;
	  }

	*perror = sftp_map_error(rc);;
	return(rval);
}

ROUTINE int sftp_map_error(int ssh_error)
{
	int rval = 0;

	switch(ssh_error)
	{
	case 0:
	case LIBSSH2_ERROR_EAGAIN:
		break;
	/*
	 * Connection problems.
	 */
	case LIBSSH2_FX_NO_CONNECTION:
	case LIBSSH2_FX_CONNECTION_LOST:
	case LIBSSH2_ERROR_SOCKET_DISCONNECT:
		fprintf(stderr, "SFTP: Connection Error %d\n", ssh_error);
		rval = UI_ERR_TCP_REMOTE_DISC;
		break;
	/*
	 * Socket Operations.
	 */
	case LIBSSH2_ERROR_SOCKET_NONE:
	case LIBSSH2_ERROR_SOCKET_SEND:
		fprintf(stderr, "SFTP: Socket Error %d\n", ssh_error);
		rval = UI_ERR_SOCKET;
		break;
	/*
	 * Authentication Operations.
	 */
	case LIBSSH2_ERROR_INVALID_MAC:
	case LIBSSH2_ERROR_KEX_FAILURE:
	case LIBSSH2_ERROR_KEY_EXCHANGE_FAILURE:
	case LIBSSH2_ERROR_HOSTKEY_INIT:
	case LIBSSH2_ERROR_HOSTKEY_SIGN:
	case LIBSSH2_ERROR_DECRYPT:
	case LIBSSH2_ERROR_AUTHENTICATION_FAILED:
	case LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED:
	case LIBSSH2_ERROR_PUBLICKEY_PROTOCOL:
		fprintf(stderr, "SFTP: Authentication Error %d\n", ssh_error);
		rval = UI_ERR_AUTH;
		break;
	/*
	 * Resource shortage.
	 */
	case LIBSSH2_ERROR_ALLOC:
	case LIBSSH2_FX_NO_SPACE_ON_FILESYSTEM:
		fprintf(stderr, "SFTP: Resource Error %d\n", ssh_error);
		rval = UI_ERR_NO_MEMORY;
		break;
	/*
	 * Timeouts.
	 */
	case LIBSSH2_ERROR_TIMEOUT:
	case LIBSSH2_ERROR_SOCKET_TIMEOUT:
		fprintf(stderr, "SFTP: Timeout Err %d\n", ssh_error);
		rval = UI_ERR_SOCK_TIMEOUT;
		break;
	/*
	 * Protocol errs.
	 */
	case LIBSSH2_ERROR_PROTO:
	case LIBSSH2_ERROR_SFTP_PROTOCOL:
	case LIBSSH2_ERROR_AGENT_PROTOCOL:
		fprintf(stderr, "SFTP: Protocol Error %d\n", ssh_error);
		rval = UI_ERR_PROTOCOL;
		break;
	/*
	 * File problems
	 */
	case LIBSSH2_FX_FILE_ALREADY_EXISTS:
		rval = UI_ERR_FILE_EXISTS;
		break;
	case LIBSSH2_ERROR_FILE:
	case LIBSSH2_FX_NO_SUCH_FILE:
	case LIBSSH2_FX_PERMISSION_DENIED:
	case LIBSSH2_FX_NO_SUCH_PATH:
	case LIBSSH2_FX_WRITE_PROTECT:
	case LIBSSH2_FX_DIR_NOT_EMPTY:
	case LIBSSH2_FX_NOT_A_DIRECTORY:
	case LIBSSH2_FX_INVALID_FILENAME:
		fprintf(stderr, "SFTP: File Error %d\n", ssh_error);
		rval = UI_ERR_FILE_OP;
		break;
	default:
		fprintf(stderr, "SFTP: Unexpected Error %d\n", ssh_error);
		rval = UI_ERR_UNKNOWN;
	}

	return(rval);
}

ROUTINE void sftp_init(SFTP_DATA *p_sftp, PUT_DATA *p_put, SFTP_CB_VOID put_done)
{

fprintf(stderr, "sftp_init: Initializing SFTP\n");

	bzero((void *) p_sftp, sizeof(SFTP_DATA));

	p_sftp->state = SFTP_IDLE;
	p_sftp->data_ptr = &p_sftp->data_buf[0];

	p_sftp->put_data = p_put;
	p_sftp->put_done = put_done;

	p_sftp->sock_fd = -1;
	p_sftp->local_fd = -1;

	return;
}

ROUTINE void sftp_cleanup(SFTP_DATA *p_sftp)
{

fprintf(stderr, "sftp_cleanup: Closing Everything\n");

	if(p_sftp->local_fd != -1)
		close(p_sftp->local_fd);
	if(p_sftp->sftp_handle)
		libssh2_sftp_close(p_sftp->sftp_handle);
	if(p_sftp->sftp_session)
		libssh2_sftp_shutdown(p_sftp->sftp_session);

	p_sftp->local_fd = -1;
	p_sftp->sftp_handle = NULL;
	p_sftp->sftp_session = NULL;

	return;
}

void Deadly( int sig )
{
	fprintf(stderr, "UI_MAIN: *** Illegal Signal %d *****\n", sig);
	sleep(3);
	_exit(0);
	return;
}

void killed( int sig )
{
	fprintf(stderr, "UI_MAIN: Killed\n");
	_exit(0);
	return;
}

ROUTINE BOOL start_ppp()
{
	char *argv[4];
	pid_t pid;
	BOOL rval;


	argv[0] = PPPD_X;
	argv[1] = "call";
	argv[2] = "isp.test";
	argv[3] = (char *) NULL;

	pid = vfork();
	if(pid == 0)
	  {
		/*
		 * Child Process: This process is no executing
		 * separately from the parent. Calling execv()
		 * transforms this process into the child
		 * process specified, and puts it into execution.
		 */
		if(execv(PPPD_X, argv) < 0)
		  {
			fprintf(stderr,"start_ppp: CHILD EXEC ERROR = %d\n", errno);
		  }
		fprintf(stderr,"start_ppp: Exec Failure %d\n", errno);
		abort();
	  }
	else
	if(pid > 0)
	  {
		int r, status;
		/*
		 * Parent: PID is valid in all cases.
		 * R = 0: Means the child is executing.
		 * R > 0: Means the child exited.
		 */
		r = waitpid(pid, &status,  WNOHANG);
		if(r < 0)
		  {
			pid = -1;
			fprintf(stderr,"start_ppp: Error Starting Child R = %d Err = %d !\n",
			r,  errno);

			if(status != (int) NULL)
			  {
				if(WIFEXITED(status))
				  {
					fprintf(stderr,"start_ppp: Exit Status = %d\n",
					       WEXITSTATUS(status));
				  }
				else
				if(WIFSIGNALED(status))
				  {
					fprintf(stderr,"start_ppp: Signal Status = %d\n",
					      WTERMSIG(status));
				  }
			  }
		  }
	  }
	else
	  {
		fprintf(stderr,"start_ppp: CHILD FORK ERROR = %d  PID = %d\n",
		 errno, pid);
		pid = -1;
		abort();
	  }

	/*
	 * Save the real Process ID: This one belongs to a Zombie.
	 */
	if(pid > 0)
	  {
		fprintf(stderr, "start_ppp: PPP Starting Execution\n");
		rval = TRUE;
	  }
	else
	  {
		rval = FALSE;
	  }

	fprintf(stderr, "start_ppp: FORK DONE %d\n", rval);
	return(rval);
}
