/***************************************************************************/
/***************************************************************************/
/*                                                                         */
/*   (c) 1995-1999.  The Regents of the University of California.  All     */
/*   rights reserved.                                                      */
/*                                                                         */
/*   This work was produced at the University of California, Lawrence      */
/*   Livermore National Laboratory (UC LLNL) under contract no.            */
/*   W-7405-ENG-48 (Contract 48) between the U.S. Department of Energy     */
/*   (DOE) and The Regents of the University of California (University)    */
/*   for the operation of UC LLNL.  Copyright is reserved to the           */
/*   University for purposes of controlled dissemination,                  */
/*   commercialization through formal licensing, or other disposition      */
/*   under terms of Contract 48; DOE policies, regulations and orders;     */
/*   and U.S. statutes.  The rights of the Federal Government are          */
/*   reserved under Contract 48 subject to the restrictions agreed upon    */
/*   by the DOE and University.                                            */
/*                                                                         */
/*                                                                         */
/*                              DISCLAIMER                                 */
/*                                                                         */
/*   This software was prepared as an account of work sponsored by an      */
/*   agency of the United States Government.  Neither the United States    */
/*   Government nor the University of California nor any of their          */
/*   employees, makes any warranty, express or implied, or assumes any     */
/*   liability or responsibility for the accuracy, completeness, or        */
/*   usefulness of any information, apparatus, product, or process         */
/*   disclosed, or represents that its specific commercial products,       */
/*   process, or service by trade name, trademark, manufacturer, or        */
/*   otherwise, does not necessarily constitute or imply its               */
/*   endorsement, recommendation, or favoring by the United States         */
/*   Government or the University of California. The views and opinions    */
/*   of the authors expressed herein do not necessarily state or reflect   */
/*   those of the United States Government or the University of            */
/*   California, and shall not be used for advertising or product          */
/*   endorsement purposes.                                                 */
/*                                                                         */
/*   Permission to use, copy, modify and distribute this software and its  */
/*   documentation for any non-commercial purpose, without fee, is         */
/*   hereby granted, provided that the above copyright notice and this     */
/*   permission notice appear in all copies of the software and            */
/*   supporting documentation, and that all UC LLNL identification in      */
/*   the user interface remain unchanged.  The title to copyright LLNL     */
/*   XDIR shall at all times remain with The Regents of the University     */
/*   of California and users agree to preserve same. Users seeking the     */
/*   right to make derivative works with LLNL XDIR for commercial          */
/*   purposes may obtain a license from the Lawrence Livermore National    */
/*   Laboratory's Technology Transfer Office, P.O. Box 808, L-795,         */
/*   Livermore, CA 94550.                                                  */
/*                                                                         */
/***************************************************************************/
/***************************************************************************/

#include <sys/types.h>
#if defined(_AIX) || defined __QNX__
#include <sys/select.h>
#endif
#include <sys/socket.h>
#include <sys/time.h>
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <Xm/Xm.h>
#include "xdir.h"
#include "str.h"

#define DM   242   /* Telnet commands */
#define IP   244
#define WILL 251
#define WONT 252
#define DO   253
#define DONT 254
#define IAC  255

#define MICRODELTA 100000

int sig_pipe_invoked = False;

extern struct st_host_info hinfo[];
extern int diagnostics;
extern XtAppContext app;
extern Display *display;
extern int maxt;


/*
 * write_tcp - Writes "n" bytes from "buf" to the TCP connection on
 *             socket "fd" to "host".  If unable to send data during
 *             time interval, "maxtime", -2 is returned.  If connection
 *             is lost, -3 is returned.  If stop button is pressed, -6
 *             is returned.  Returns -1 for other errors.
 */
write_tcp(host, fd, buf, n, maxtime)
int host;
int fd;
char *buf;
int n;
int maxtime;
{

	int total_nbytes = 0;
	int nbytes;
	int saved_errno;

	while (total_nbytes < n)
		switch (select_on_write(fd, maxtime)) {
		case 1:
			if ((nbytes = iwrite(fd, &buf[total_nbytes], n-total_nbytes)) < 0) {
				saved_errno = errno;
				report_perror("Writing connection");
				if (saved_errno == EPIPE)
					return -3;
				else
					return -1;
			} else
				total_nbytes += nbytes;
			break;
		case 0:
			if (total_nbytes)
				return total_nbytes;
			else
				return -1;   /* Maybe should change to -2 */
		case -1:
			return -1;
		case -6:
			return -6;
		}

	return total_nbytes;
}


/*
 * select_on_write - Waits for file descriptor fd to be writeable.  maxtime
 *                   is the number of seconds to wait.  Returns values are 1
 *                   for fd is readable, 0 for timeout, -6 for stop button
 *                   pressed, and -1 for error.
 */
select_on_write(fd, maxtime)
int fd;
int maxtime;
{
	fd_set writefds;
	fd_set exceptfds;
	fd_set readfds;
	struct timeval timeout;
	int nfds;
	int xfd;
	struct timeval finish_time;
	struct timeval current_time;
	struct timeval delta;

	/* Determine timeout time */
	gettimeofday(&current_time, NULL);
	delta.tv_sec = maxtime;
	delta.tv_usec = 0;
	add_timevals(&current_time, &delta, &finish_time);

	/* Get file descriptor for display connection socket */
	xfd = ConnectionNumber(display);

	while (1) {
		if (stop())
			return -6;
		gettimeofday(&current_time, NULL);
		if (cmp_timevals(&current_time, &finish_time) > 0)
			return 0;   /* Timeout */
		FD_ZERO(&writefds);
		FD_SET(fd, &writefds);
		FD_ZERO(&readfds);
		FD_SET(xfd, &readfds);
		FD_ZERO(&exceptfds);
		FD_SET(fd, &exceptfds);
		FD_SET(xfd, &exceptfds);
		timeout.tv_sec = 0;
		timeout.tv_usec = MICRODELTA;
		nfds = iselect(max_fds(), &readfds, &writefds, &exceptfds, &timeout);
		if (nfds > 0) {
			if (FD_ISSET(xfd, &readfds) || FD_ISSET(xfd, &exceptfds))
				XtAppProcessEvent(app, (XtInputMask)XtIMAll);
			else
				return 1;
		} else if (nfds < 0)
			return -1;
	}
}


/*
 * read_tcp - Reads "n" bytes to "buf" from the TCP connection on socket
 *            "fd" to "host".  Returns number of bytes read if no error,
 *            socket "fd".  If no data is received during time interval,
 *            -2 is returned.  If connection is lost, -3 is returned.  If
 *            stop button is pressed, -6 is returned.  Returns -1 for other
 *            errors.
 */
read_tcp(host, fd, buf, n, maxtime)
int host;
int fd;
char *buf;
int n;
int maxtime;
{
	int total_nbytes = 0;
	int nbytes;
	int saved_errno;

	while (total_nbytes < n)
		switch (select_on_read(fd, maxtime)) {
		case 1:
		    if ((nbytes = iread(fd, &buf[total_nbytes], n-total_nbytes)) < 0) {
				saved_errno = errno;
		    	report_perror("Reading connection");
				if (saved_errno == ECONNRESET)
					return -3;
				else
		    		return -1;
		    } else if (nbytes == 0)
		    	return total_nbytes;
			else
				total_nbytes += nbytes;
		case 0:
			if (total_nbytes)
				return total_nbytes;
			else
				return -2;
		case -1:
			return -1;
		case -6:
			return -6;
		}
	return total_nbytes;
}


/*
 * select_on_read - Waits for file descriptor fd to be readable.  maxtime
 *                  is the number of seconds to wait.  Returns values are
 *                  1 for fd is readable, 0 for timeout, -6 for stop button
 *                  pressed, and -1 for error.
 */
select_on_read(fd, maxtime)
int fd;
int maxtime;
{
	fd_set readfds;
	fd_set exceptfds;
	struct timeval timeout;
	int nfds;
	int xfd;
	struct timeval finish_time;
	struct timeval current_time;
	struct timeval delta;

	/* Determine timeout time */
	gettimeofday(&current_time, NULL);
	delta.tv_sec = maxtime;
	delta.tv_usec = 0;
	add_timevals(&current_time, &delta, &finish_time);

	/* Get file descriptor for display connection socket */
	xfd = ConnectionNumber(display);

	while (1) {
		if (stop())
			return -6;
		gettimeofday(&current_time, NULL);
		if (cmp_timevals(&current_time, &finish_time) > 0)
			return 0;   /* Timeout */
		FD_ZERO(&readfds);
		FD_SET(fd, &readfds);
		FD_SET(xfd, &readfds);
		FD_ZERO(&exceptfds);
		FD_SET(fd, &exceptfds);
		FD_SET(xfd, &exceptfds);
		timeout.tv_sec = 0;
		timeout.tv_usec = MICRODELTA;
		nfds = iselect(max_fds(), &readfds, NULL, &exceptfds, &timeout);
		if (nfds > 0) {
			if (FD_ISSET(xfd, &readfds) || FD_ISSET(xfd, &exceptfds))
				XtAppProcessEvent(app, (XtInputMask)XtIMAll);
			else
				return 1;
		} else if (nfds < 0)
			return -1;
	}
}


/*
 * get_ftp_reply - Returns in "reply" a string containing the next reply
 *                 from the FTP daemon.  The function return value is the
 *                 first digit of the reply code.  "host" is the host id.
 *                 Input argument "maxreply" is the maximum length string
 *                 to be returned.  "code" is the full reply code.
 *                 The input argument "maxtime" is the maximum allowable
 *                 interval in seconds in which no data is received.
 *                 Other return values are -2 for timeout, -3 for
 *                 broken connection, -6 for stop button pressed,  and 
 *                 -1 for other failures.
 */
get_ftp_reply(host, reply, maxreply, code, maxtime)
int host;
char *reply;
int maxreply;
int *code;
int maxtime;
{
	int fd = hinfo[host].ctrl_fd;
	int firstline = True;
	int origcode;
	int replycode;
	int nbytes;
	int lenline;
	int lenreply;
	char line[MAXFTPLINE];
	static char *vms_timeout_pat = "500 FTP connection terminated";
	int len_vms_timeout_pat = strlen(vms_timeout_pat);
	struct timeval current_time;

	reply[0] = '\0';

	while (1) {
		if ((replycode = get_ftp_line(host, fd, line, MAXFTPLINE, maxtime)) < 0)
			return replycode;
		lenline = strlen(line);
		lenreply = strlen(reply);
		nbytes = MIN(maxreply-lenreply-1, lenline);
		strncat(reply, line, nbytes);
		if (firstline) {
			if (replycode == 0)
				return -1;
			if (line[3] == '-') {
				origcode = replycode;
				firstline = False;
			} else
				break;
		} else if (replycode == origcode && line[3] == ' ')
			break;
	}

	if (diagnostics >= VERBOSE)
		report_ftp_reply(host, reply);

	/* Check for server timeout */
	if (hinfo[host].system == SYS_VMS) {
		if ((replycode == 421) ||
				!strncmp(line, vms_timeout_pat, len_vms_timeout_pat)) {
			write_log("*** FTP server on remote host timed out.\n");
			return -3;
		}
	} else if (replycode == 421) {
		write_log("*** FTP server on remote host timed out.\n");
		return -3;
	}

	/* Remember when last reply from server was received */
	gettimeofday(&current_time, NULL);
	hinfo[host].last_reply_time = current_time.tv_sec;

	/* Return reply code */
	*code = replycode;
	return replycode/100;
}


/*
 * get_ftp_line - Returns in "line" a string containing the next line
 *                from the FTP daemon.  The function return value is the
 *                reply code.  "fd" is the file descriptor of the socket
 *                used for the control connection.  The input argument
 *                "maxline" is the maximum length string to be returned.
 *                The input argument "maxtime" is the maximum allowable
 *                interval in seconds in which no data is received.
 *                "host" is the host to get the FTP response from.
 *                The return value is for lines containing no reply code.
 *                Other return values are -2 for timeout, -3 for broken
 *                connection, -6 for stop button pressed, and -1 for other
 *                failures.
 */
get_ftp_line(host, fd, line, maxline, maxtime)
int host;
int fd;
char *line;
int maxline;
int maxtime;
{
	int cnt;
	unsigned char ch;
	char buf[3];
	int code = 0;
	int indx = 0;
	int retval;

	while (1) {
		if ((cnt = read_tcp(host, fd, (char *)&ch, 1, maxtime)) < 0)
			return cnt;
		else if (cnt == 0)
			return -3;
		if (ch == IAC) {
			if ((cnt = read_tcp(host, fd, (char *)&ch, 1, maxtime)) < 0)
				return cnt;
			if (cnt == 0)
				return -3;
			switch (ch) {
			case DO:
			case DONT:
				if ((cnt = read_tcp(host, fd, (char *)&ch, 1, maxtime)) < 0)
					return cnt;
				if (cnt == 0)
					return -3;
				buf[0] = IAC;
				buf[1] = DONT;
				buf[2] = ch;
				if ((retval = write_tcp(host, fd, buf, 3, maxtime)) != 3)
					return retval;
				break;
			case WILL:
			case WONT:
				if ((cnt = read_tcp(host, fd, (char *)&ch, 1, maxtime)) < 0)
					return cnt;
				if (cnt == 0)
					return -3;
				buf[0] = IAC;
				buf[1] = WONT;
				buf[2] = ch;
				if ((retval = write_tcp(host, fd, buf, 3, maxtime)) != 3)
					return retval;
			}
			continue;
		}
		if (ch != '\r' && indx < maxline-1)
			line[indx++] = ch;
		if (ch == '\n') {
			if (indx >= 4 && (line[3] == ' ' || line[3] == '-')
						   && isdigit(line[0]) && isdigit(line[1])
						   && isdigit(line[2]))
				code = (line[0]-'0')*100+(line[1]-'0')*10+(line[2]-'0');
			line[indx] = '\0';
			return code;
		}
	}
}


/*
 * send_ftp_cmd - Sends command "cmd" to the FTP server.  "host" is the
 *                host id.  "\r\n" is appended to cmd before it is sent.
 *                "maxtime" is the maximum allowable interval in seconds
 *                in which some data must be sent.  Returns 0 for success,
 *                -2 for timeout, -3 for broken connection, -6 for stop
 *                button pressed, and -1 for other errors.
 */
send_ftp_cmd(host, cmd)
int host;
char *cmd;
{
	int fd = hinfo[host].ctrl_fd;
	int len = strlen(cmd);
	int retval;
	char *buf;

	if (diagnostics == DEBUG)
		report_ftp_cmd(host,cmd);

	buf = XtMalloc(len+3);
	strcpy(buf, cmd);
	strcat(buf, "\r\n");
	retval = write_tcp(host, fd, buf, len+2, maxt);
	bzero(buf, (int)strlen(buf));
	XtFree(buf);
	if (retval != len+2)
		return retval;
	else
		return 0;
}


/*
 * sig_pipe - Will be invoked if trying to write on a broken connection.
 */
void
sig_pipe()
{
	sig_pipe_invoked = True;
}


/*
 * urgent_abort - Sends an urgent abort request to the FTP server
 *                connected via the TCP connection for "host".
 *                Returns 0 if successful, -3 for broken connection,
 *                -6 for stop button pressed, and -1 for other errors.
 *                (If connection is lost and stop button is pushed,
 *                -6 is returned.)  If stop button was pressed, this
 *                routine does not return prematurely.   This routine
 *                does not try to receive a reply from the FTP server.
 */
urgent_abort(host)
int host;
{
	int fd = hinfo[host].ctrl_fd;
	char buf[2];
	int retval;
	int stop_was_pressed = False;

	if (diagnostics == DEBUG)
		report_ftp_cmd(host, "Urgent Abort");

	/* Send IAC, IP */
	buf[0] = IAC;
	buf[1] = IP;
	retval = write_tcp(host, fd, buf, 2, 5);
	if (retval == -6) {
		stop_was_pressed = True;
		retval = write_tcp(host, fd, buf, 2, 5);
	}
	if (retval != 2) {
		if (stop_was_pressed)
			return -6;
		else
			return retval;
	}

	/* Send IAC, DM in urgent mode */
	buf[0] = IAC;
	if (isend(fd, buf, 1, MSG_OOB) != 1)
		return -1;
	buf[0] = DM;

	/* Send ABOR command */
	retval = write_tcp(host, fd, buf, 1, 5);
	if (retval == -6) {
		stop_was_pressed = True;
		retval = write_tcp(host, fd, buf, 1, 5);
	}
	if (retval != 1) {
		if (stop_was_pressed)
			return -6;
		else
			return retval;
	}
	retval = write_tcp(host, fd, "ABOR\r\n", 6, 5);
	if (retval == -6) {
		stop_was_pressed = True;
		retval = write_tcp(host, fd, "ABOR\r\n", 6, 5);
	}
	if (retval != 6) {
		if (stop_was_pressed)
			return -6;
		else
			return retval;
	}

	if (stop_was_pressed)
		return -6;
	else
		return 0;
}


/*
 * abort_ftp_cmd - Send urgent abort to specified remote host, and absorb
 *                 any responses.
 */
void
abort_ftp_cmd(host)
int host;
{
	char reply[MAXFTPREPLY];
	int code;
	int scode;

	/* Pop up abort dialog */
	show_abort_dialog();

	/* Send urgent abort */
	urgent_abort(host);

	/* Absorb first reply */
	scode = get_ftp_reply(host, reply, MAXFTPREPLY, &code, 15);

	/* If no response, send NOOP command to try to jostle server */
	if (scode < 0) {
		send_ftp_cmd(host, "NOOP");
		scode = get_ftp_reply(host, reply, MAXFTPREPLY, &code, 15);
	}

	/* Absorb remaining replies */
	while (scode > 0)
		scode = get_ftp_reply(host, reply, MAXFTPREPLY, &code, 5);

	/* Want to check for lost host before issuing next command */
	hinfo[host].last_reply_time = 0;

	/* Get rid of abort dialog */
	hide_abort_dialog();
}


/*
 * rest_stop - A replacement for sleep() that returns in "seconds" seconds.
 *             Returns values are 0 for time has elapsed, -6 for stop
 *             button pressed, and -1 for error.  X events are processed.
 *             Call rest_stop() instead of sleep() when the stop button
 *             is enabled.
 */
rest_stop(seconds)
int seconds;
{
	fd_set readfds;
	fd_set exceptfds;
	struct timeval timeout;
	int nfds;
	int xfd;
	struct timeval finish_time;
	struct timeval current_time;
	struct timeval delta;

	/* Determine timeout time */
	gettimeofday(&current_time, NULL);
	delta.tv_sec = seconds;
	delta.tv_usec = 0;
	add_timevals(&current_time, &delta, &finish_time);

	/* Get file descriptor for display connection socket */
	xfd = ConnectionNumber(display);

	while (1) {
		if (stop())
			return -6;
		gettimeofday(&current_time, NULL);
		if (cmp_timevals(&current_time, &finish_time) > 0)
			return 0;   /* Time is up */
		FD_ZERO(&readfds);
		FD_SET(xfd, &readfds);
		FD_ZERO(&exceptfds);
		FD_SET(xfd, &exceptfds);
		timeout.tv_sec = 0;
		timeout.tv_usec = MICRODELTA;
		nfds = iselect(max_fds(), &readfds, NULL, &exceptfds, &timeout);
		if (nfds > 0 && (FD_ISSET(xfd, &readfds) || FD_ISSET(xfd, &exceptfds)))
			XtAppProcessEvent(app, (XtInputMask)XtIMAll);
		else
			return -1;
	}
}



syntax highlighted by Code2HTML, v. 0.9.1