/***************************************************************************/
/***************************************************************************/
/*                                                                         */
/*   (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>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <errno.h>
#include <netdb.h>
#include <stdio.h>
#include <ctype.h>
#include <X11/Intrinsic.h>
#ifdef KERBEROS
#include <krb5/krb5.h>
#include <krb5/asn1.h>
#endif
#include "xdir.h"
#include "str.h"

#ifndef FNDELAY
#define FNDELAY O_NDELAY
#endif

extern struct st_host_info hinfo[];
extern int diagnostics;
extern int maxt;


/*
 * ftp_open - Open a control connection with the ftp server specified
 *            by "hostname" and "port".  "host" is index host structure array.
 *            If successful, the function value is 0 and hinfo[host].ctrl_fd
 *            is set to the file descriptor used for the control connection
 *            socket; returns -6 for stop button pressed; else -1 is returned.
 *            If successful, updates hinfo[host].system and hinfo[host].server.
 */
ftp_open(host, hostname, port)
int host;
char *hostname;
int port;
{
	int ctrl_fd;
	struct sockaddr_in remote;
	int code;
	char reply[MAXFTPREPLY];
	struct hostent *hostptr;
	char msg[50];
	int scode;
	unsigned long temp;

	/* Form destination address */
	bzero(&remote, (int)sizeof(remote));
	temp = inet_addr(hostname);
	if (temp == ~0L || (sizeof(int) >= 4 && (unsigned int)temp == ~0)) {
		if ((hostptr = gethostbyname(hostname)) == NULL)
			return -1;
		if (hostptr->h_addrtype != AF_INET)
			return -1;
		remote.sin_addr.s_addr =
	           	((struct in_addr*)hostptr->h_addr_list[0])->s_addr;
	} else
		remote.sin_addr.s_addr = temp;
	remote.sin_family = AF_INET;
	remote.sin_port = htons((short)port);
	if (diagnostics == DEBUG) {
		sprintf(msg, "*** dest_addr = %lx\n",
		        (unsigned long)remote.sin_addr.s_addr);
		write_log(msg);
	}

	/* Create socket */
	if ((ctrl_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		report_perror("Opening socket stream");
		return -1;
	}

	/* Make socket nonblocking */
#if !defined(UMAXV) && !defined(SOCKS)
	if (fcntl(ctrl_fd, F_SETFL, FNDELAY) == -1) {
		report_perror("Making socket stream nonblocking");
		close(ctrl_fd);
		return -1;
	}
#endif

	/* Connect socket */
	if (diagnostics == DEBUG)
		write_log("*** Will try to connect to remote\n");
	if (iconnect(ctrl_fd, (struct sockaddr *)&remote,(int)sizeof(remote)) < 0) {
		if (errno != EINPROGRESS) {
			report_perror("Connecting socket stream");
			close(ctrl_fd);
			return -1;
		}
		switch (select_on_write(ctrl_fd, maxt)) {
		case -6:
			close(ctrl_fd);
			return -6;
		case 0:
			write_log("Timed out while trying to establish connection\n");
		case -1:
			close(ctrl_fd);
			return -1;
		}
	}
	hinfo[host].ctrl_fd = ctrl_fd;


	/* Get response from FTP server */
	if (diagnostics == DEBUG)
		write_log("*** Will try to receive welcome message from remote host\n");
	scode = get_ftp_reply(host, reply, MAXFTPREPLY, &code, maxt);
	switch (scode) {
	case -6:
		close(hinfo[host].ctrl_fd);
		return -6;
	case 2:
		determine_server_from_open(host, reply);
		return 0;
	case -2:
		report_client_timeout(host);
	default:
		close(hinfo[host].ctrl_fd);
		if (scode > 0 && diagnostics < VERBOSE)
			report_ftp_reply(host, reply);
		return -1;
	}
}


/*
 * ftp_user - Sends USER command to FTP server.  "host" is the host id.
 *            Returns 0 if successful, 1 if successful but a password is
 *            required, 2 if successful but a password or a Kerberos
 *            ticket is required, -3 for broken connection, -6 for stop
 *            button pressed, and -1 for other errors.
 */
ftp_user(host, username)
int host;
char *username;
{
	char cmd[100];
	char reply[MAXFTPREPLY];
	int code;
	int retval;

	/* Send USER command to FTP server */
	sprintf(cmd, "USER %s", username);
	if ((retval = send_ftp_cmd(host, cmd)) == -2) {
		report_client_timeout(host);
		return -1;
	} else if (retval < 0)
		return retval;

	/* Process reply */
	retval = get_ftp_reply(host, reply, MAXFTPREPLY, &code, maxt);
	switch (retval) {
	case 2:
		return 0;
	case 3:
#ifdef KERBEROS
		if (code == 332)
			return 2;
#endif
		return 1;
	case -2:
		report_client_timeout(host);
		return -1;
	case -6:
		abort_ftp_cmd(host);
		return -6;
	default:
		if (retval > 0 && diagnostics < VERBOSE)
			report_ftp_reply(host, reply);
		return retval;
	}
}


/*
 * ftp_password - Sends PASS command to FTP server.  "host" is the host
 *                id.  Returns 0 if successful, 1 if successful but an
 *                account is required, -3 for broken connection, -6 for
 *                stop button pressed, and -1 for other errors.
 */
ftp_password(host, password)
int host;
char *password;
{
	char cmd[100];
	char reply[MAXFTPREPLY];
	int code;
	int retval;
	int scode;

	/* Send PASS command to FTP server */
	sprintf(cmd, "PASS %s", password);
	retval = send_ftp_cmd(host, cmd);
	bzero(cmd, (int)strlen(cmd));
	if (retval == -2) {
		report_client_timeout(host);
		return -1;
	} else if (retval < 0)
		return retval;

	/* Process reply */
	scode = get_ftp_reply(host, reply, MAXFTPREPLY, &code, maxt);
	switch (scode) {
	case 2:
		return 0;
	case 3:
		return 1;
	case -3:
		return -3;
	case -6:
		abort_ftp_cmd(host);
		return -6;
	case -2:
		report_client_timeout(host);
	default:
		if (scode > 0 && diagnostics < VERBOSE)
			report_ftp_reply(host, reply);
		return -1;
	}
}


/*
 * ftp_account - Sends ACCT command to FTP server.  "host" is the host id.
 *               Returns 0 if successful, -3 for broken connection, -6 for
 *               stop button pressed, and -1 for other errors.
 */
ftp_account(host, account)
int host;
char *account;
{
	char cmd[100];
	char reply[MAXFTPREPLY];
	int code;
	int retval;
	int scode;

	/* Send ACCT command to FTP server */
	sprintf(cmd, "ACCT %s", account);
	if ((retval = send_ftp_cmd(host, cmd)) == -2) {
		report_client_timeout(host);
		return -1;
	} else if (retval < 0)
		return retval;

	/* Process reply */
	scode = get_ftp_reply(host, reply, MAXFTPREPLY, &code, maxt);
	switch (scode) {
	case 2:
		return 0;
	case -3:
		return -3;
	case -6:
		abort_ftp_cmd(host);
		return -6;
	case -2:
		report_client_timeout(host);
	default:
		if (scode > 0 && diagnostics < VERBOSE)
			report_ftp_reply(host, reply);
		return -1;
	}
}


/*
 * ftp_passive - Tell server to enter passive mode.  If successful,
 *               returns pointer to string containing port address
 *               returned by server in "port", else "port" is undefined.
 *               "host" is the host id.  Use XtFree() to release
 *               memory pointed to by "port".  Returns 0 if successful,
 *               -3 for broken connection, -6 for stop button pushed,
 *               and -1 for other errors.
 */
ftp_passive(host, port)
int host;
char **port;
{
	char reply[MAXFTPREPLY];
	int code;
	int nbytes;
	char *start;
	char *end;
	int retval;

	/* Send PASV command to FTP server */
	if ((retval = send_ftp_cmd(host, "PASV")) == -2) {
		report_client_timeout(host);
		return -1;
	} else if (retval < 0)
		return retval;

	/* Get response from FTP server */
    retval = get_ftp_reply(host, reply, MAXFTPREPLY, &code, maxt);
    switch (retval) {
    case 2:
        break;
    case -2:
		report_client_timeout(host);
        return -1;
	case -3:
		return -3;
	case -6:
		abort_ftp_cmd(host);
		return -6;
    default:
        return -1;
    }

	/* Extract port address */
	if ((start = strchr(reply, '(')) == NULL)
		return -1;
	start++;
	if ((end = strrchr(start, ')')) == NULL)
		return -1;
	if ((nbytes = end-start) <= 0)
		return -1;
	*port = XtMalloc(nbytes+1);
	bcopy(start, *port, nbytes);
	(*port)[nbytes] = '\0';
	return 0;
}


/*
 * ftp_type - Set file tranfer mode.  "host" is the host id.  Valid values
 *            for "mode" are ASCII and BINARY.  Returns 0 for success,
 *            -3 for broken connection, -6 for stop button pressed, and -1
 *            for other errors.
 */
ftp_type(host, mode)
int host;
int mode;
{
	char reply[MAXFTPREPLY];
	char cmd[7];
	int code;
	int retval;
	char type;
	int scode;

	/* Send TYPE command to FTP server */
	if (mode == ASCII)
		type = 'A';
	else if (mode == BINARY)
		type = 'I';
	else
		return -1;
	sprintf(cmd, "TYPE %c", (char)type);
	if ((retval = send_ftp_cmd(host, cmd)) == -2) {
		report_client_timeout(host);
		return -1;
	} else if (retval < 0)
		return retval;

	/* Get response from FTP server */
	scode = get_ftp_reply(host, reply, MAXFTPREPLY, &code, maxt);
	switch (scode) {
	case 2:
		return 0;
	case -3:
		return -3;
	case -6:
		abort_ftp_cmd(host);
		return -6;
	case -2:
		report_client_timeout(host);
	default:
		if (scode > 0 && diagnostics < VERBOSE)
			report_ftp_reply(host, reply);
		return -1;
	}
}


/*
 * ftp_port - Set the data port to be used in data connection to the
 *            specified value.  "hostport" is a string of the form
 *            h1,h2,h3,h4,p1,p2  where the hi's are the decimal-
 *            valued bytes of the host's Internet address, and the pi's
 *            are the decimal valued bytes of the port's address.
 *            "host" is the host id.  Returns 0 if successful, -3 for
 *            broken connection, -6 for stop button pressed, and -1 for
 *            other errors.
 */
ftp_port(host, hostport)
int host;
char *hostport;
{
	char reply[MAXFTPREPLY];
	char cmd[37];
	int code;
	int retval;
	int scode;

	/* Send PORT command to FTP server */
	sprintf(cmd, "PORT %s", hostport);
	if ((retval = send_ftp_cmd(host, cmd)) == -2) {
		report_client_timeout(host);
		return -1;
	} else if (retval < 0)
		return retval;

	/* Get response from FTP server */
	scode = get_ftp_reply(host, reply, MAXFTPREPLY, &code, maxt);
	switch (scode) {
	case 2:
		return 0;
	case -3:
		return -3;
	case -6:
		abort_ftp_cmd(host);
		return -6;
	case -2:
		report_client_timeout(host);
	default:
		if (scode > 0 && diagnostics < VERBOSE)
			report_ftp_reply(host, reply);
		return -1;
	}
}


/*
 * ftp_system - Tries to determine the type of system and server the
 *              remote host has by using the low-level SYST command.
 *              Set "print_error_msg" to True if FTP-generated error
 *              messages are to be written to the log when not in verbose
 *              mode.  Returns 0 for success, -3 for broken connection,
 *              -6 for stop button pressed, and -1 for other errors.
 *              If successful, and if enough information is returned,
 *              hinfo[host].system and hinfo[host].server are set.
 */
ftp_system(host, print_error_msg)
int host;
int print_error_msg;
{
	char reply[MAXFTPREPLY];
	int code;
	int retval;
	int scode;

	/* Send SYST command to FTP server */
	if ((retval = send_ftp_cmd(host, "SYST")) == -2) {
		report_client_timeout(host);
		return -1;
	} else if (retval < 0)
		return retval;

	/* Get response from FTP server */
	scode = get_ftp_reply(host, reply, MAXFTPREPLY, &code, maxt);
	switch (scode) {
	case 2:
		determine_server_from_syst(host, reply);
		return 0;
	case -3:
		return -3;
	case -6:
		abort_ftp_cmd(host);
		return -6;
	case -2:
		report_client_timeout(host);
	default:
		if (scode > 0 && diagnostics < VERBOSE && print_error_msg)
			report_ftp_reply(host, reply);
		return -1;
	}
}


/*
 * ftp_allocate - Tell the server to reserve "size" bytes of space for a
 *                subsequent store command.  Returns 0 if successful,
 *                -3 for broken connection, -6 for stop button pushed,
 *                and -1 for other errors.  "host" is the host id.
 */
ftp_allocate(host, size)
int host;
long size;
{
	char cmd[100];
	char reply[MAXFTPREPLY];
	int code;
	int scode;
	int retval;

	/* Send ALLO command to FTP server */
	if (hinfo[host].server == SERVER_UNIX_HPSS)
		sprintf(cmd, "ALLO64 %ld", size);
	else
		sprintf(cmd, "ALLO %ld", size);
	if ((retval = send_ftp_cmd(host, cmd)) == -2) {
		report_client_timeout(host);
		return -1;
	} else if (retval < 0)
		return retval;

	/* Get response from FTP server */
	scode = get_ftp_reply(host, reply, MAXFTPREPLY, &code, maxt);
	switch (scode) {
	case 2:
		return 0;
	case -3:
		return -3;
	case -6:
		abort_ftp_cmd(host);
		return -6;
	case -2:
		report_client_timeout(host);
	default:
		if (scode > 0 && diagnostics < VERBOSE)
			report_ftp_reply(host, reply);
		return -1;
	}
}


/*
 * ftp_kerberos_ticket - Sends TKT command to FTP server.  "host" is the
 *                       host id.  Returns 0 if successful, 1 if successful
 *                       but an account is required, -3 for broken
 *                       connection, -6 for stop button pressed, and -1 for
 *                       other errors.  Assumes hinfo[host].hostname has
 *                       been set.
 */
#ifdef KERBEROS
ftp_kerberos_ticket(host)
int host;
{
	char reply[MAXFTPREPLY];
	int code;
	int retval;
	int scode;
	int port_part;
	char *host_part;
	krb5_error_code status;
	krb5_principal server;
	krb5_principal client;
	krb5_ccache ccdef;
	krb5_ap_rep_enc_part *rep_ret;

	/* Send TKT command to FTP server */
	if ((retval = send_ftp_cmd(host, "TKT")) == -2) {
		report_client_timeout(host);
		return -1;
	} else if (retval < 0)
		return retval;

	/* Process reply */
	scode = get_ftp_reply(host, reply, MAXFTPREPLY, &code, maxt);
	switch (scode) {
	case 2:
		return 0;
	case 3:
		if (code == 333)
			break;
		else
			return -1;
	case -3:
		return -3;
	case -6:
		abort_ftp_cmd(host);
		return -6;
	case -2:
		report_client_timeout(host);
	default:
		if (scode > 0 && diagnostics < VERBOSE)
			report_ftp_reply(host, reply);
		return -1;
	}

	/* Send authorization request to Kerberos */
	krb5_init_ets();
	if (parse_hostname(hinfo[host].hostname, &host_part, &port_part))
		fatal_error("Bug in ftp_kerberos_ticket()");
	krb5_sname_to_principal(host_part, "ftp", KRB5_NT_SRV_HST,
		&server);
	XtFree(host_part);
	if ((status = krb5_cc_default(&ccdef))) {
		if (diagnostics == DEBUG) {
			write_log("*** Cannot open credential cache: ");
			write_log(error_message(status));
			write_log("\n");
		}
		return -1;
	}
	if ((status = krb5_cc_get_principal( ccdef, &client))){
		if (diagnostics == DEBUG) {
			write_log("*** Cannot get client principal: ");
			write_log(error_message(status));
			write_log("\n");
		}
		return -1;
	}
	status = krb5_sendauth((krb5_pointer)&hinfo[host].ctrl_fd, "FTP_V5",
		client, server, AP_OPTS_MUTUAL_REQUIRED, 0, 0, ccdef, 0, 0, 0,
		&rep_ret);
	if (status) {
		if (diagnostics == DEBUG) {
			write_log("*** Kerberos authentication failure: ");
			write_log(error_message(status));
			write_log("\n");
		}
		return -1;
	}
	krb5_free_ap_rep_enc_part(rep_ret);

	/* Process reply */
	scode = get_ftp_reply(host, reply, MAXFTPREPLY, &code, maxt);
	switch (scode) {
	case 2:
		return 0;
	case 3:
		return 1;
	case -3:
		return -3;
	case -6:
		abort_ftp_cmd(host);
		return -6;
	case -2:
		report_client_timeout(host);
	default:
		if (scode > 0 && diagnostics < VERBOSE)
			report_ftp_reply(host, reply);
		return -1;
	}
}
#endif



syntax highlighted by Code2HTML, v. 0.9.1