/* Virtual File System: FTP file system.
   This has nothing to do with cryptography.
   Copyright (C) 1995 The Free Software Foundation
   
   Written by: 1995 Ching Hui
               1995 Jakub Jelinek
               1995, 1996, 1997 Miguel de Icaza
	       1997 Norbert Warmuth
   
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.
   
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
   
/* FTPfs TODO:

- make it more robust - all the connects etc. should handle EADDRINUSE and
  ERETRY (have I spelled these names correctly?)
- make the user able to flush a connection - all the caches will get empty
  etc., (tarfs as well), we should give there a user selectable timeout
  and assign a key sequence.  
- use hash table instead of linklist to cache ftpfs directory.
- complete rename operation.
 */



#include <config.h>
#include "net-includes.h"
#if 0
#include "diffie/diffie-socket.h"
#include "diffie/z-socket.h"
#endif
#include "mem.h"
#define WANT_PARSE_LS_LGA
#include "tcputil.h"
#include "util.h"
#include "vfs.h"
/* #include "../src/dialog.h" */
#include "container.h"
#include "ftpfs.h"
#ifndef MAXHOSTNAMELEN
#    define MAXHOSTNAMELEN 64
#endif
#include "diffie/compat.h"
#include "mad.h"

#define UPLOAD_ZERO_LENGTH_FILE

/* #define PATH_SEP '/' */
/* #define PATH_SEP_STR "/" */

static int ftpfserrno;
static int code;

/* if on, does not prepend a - to the anonymous ftp password */
int ftpfs_literal_password = 0;

/* Delay to retry a connection */
int ftpfs_retry_seconds = 30;

/* Method to use to connect to ftp sites */
int ftpfs_use_passive_connections = 1;

/* Use the ~/.netrc */
int use_netrc = 1;

char *home_dir = 0;

extern int verbose;

char *ftpfs_temp_name_dir = 0;

/* Anonymous setup */
char *ftpfs_anonymous_passwd = 0;
int ftpfs_directory_timeout;

/* Proxy host */
char *ftpfs_proxy_host = 0;

/* wether we have to use proxy by default? */
int ftpfs_always_use_proxy = 1;

/* source routing host */
int source_route = 0;

/* If set, then log dialog between server and client */
static int ftpfs_debug_server_dialog = 0;

/* Where we store the transactions */
static FILE *ftpfs_logfile;

/* If true, the directory cache is forced to reload */
static int force_expiration = 0;

struct linklist *ftpfs_connections_list;

extern char *last_current_dir;

/* command wait_flag: */
#define NONE        0x00
#define WAIT_REPLY  0x01
#define WANT_STRING 0x02
char reply_str [80];

static char *ftpfs_get_current_directory(struct ftpfs_connection *bucket);
static int __ftpfs_chdir (struct ftpfs_connection *bucket ,char *remote_path);

/* Extract the hostname and username from the path */
/* path is in the form: [user@]hostname:port/remote-dir, e.g.:
 * ftp://sunsite.unc.edu/pub/linux
 * ftp://miguel@sphinx.nuclecu.unam.mx/c/nc
 * ftp://tsx-11.mit.edu:8192/
 * ftp://joe@foo.edu:11321/private
 * If the user is empty, e.g. ftp://@roxanne/private, then your login name
 * is supplied.
 * */

#ifdef HAVE_MAD
#define ftpfs_get_host_and_username(a,b,c,d,e) mad_ftpfs_get_host_and_username(a,b,c,d,e,__FILE__,__LINE__)
char *mad_ftpfs_get_host_and_username (char *path, char **host, char **user, int *port, char **pass, char *file, int line)
{
    return mad_get_host_and_username (path, host, user, port, 21, 1, pass, file, line);
}
#else
char *ftpfs_get_host_and_username (char *path, char **host, char **user, int *port, char **pass)
{
    return get_host_and_username (path, host, user, port, 21, 1, pass);
}
#endif

static int
select_on_two (int fd1, int fd2)
{
    fd_set set;
    struct timeval timeout;
    int v;
    int maxfd = (fd1 > fd2 ? fd1 : fd2) + 1;

    timeout.tv_sec  = 1;
    timeout.tv_usec = 0;
    FD_ZERO(&set);
    FD_SET(fd1, &set);
    FD_SET(fd2, &set);
    v = select (maxfd, &set, 0, 0, &timeout);
    if (v <= 0)
	return v;
    if (FD_ISSET (fd1, &set))
	return 1;
    if (FD_ISSET (fd2, &set))
	return 2;
    return -1;
}

static int
get_line (int sock, char *buf, int buf_len)
{
    int i, status;
    char c;

    for (i = 0; i < buf_len; i++, buf++) {
	if (recv (sock, buf, sizeof(char), 0) <= 0) {
	    return 0;
	}
	if (ftpfs_debug_server_dialog){
	    fwrite (buf, 1, 1, ftpfs_logfile);
	    fflush (ftpfs_logfile);
	}
	if (*buf == '\n') {
	    *buf = 0;
	    return 1;
	}
    }
    *buf = 0;
    while ((status = recv (sock, &c, sizeof(c), 0)) > 0){
	if (ftpfs_debug_server_dialog){
	    fwrite (&c, 1, 1, ftpfs_logfile);
	    fflush (ftpfs_logfile);
	}
	if (c == '\n')
	    return 1;
    }
    return 0;
}

/* Returns a reply code, check /usr/include/arpa/ftp.h for possible values */
int getreply (int sock, char *string_buf, int string_len)
{
    char answer[4096];
    int i;
    
    for (;;) {
        if (!get_line(sock, answer, sizeof(answer))) {
	    if (string_buf)
		*string_buf = 0;
	    code = 421;
	    return 4;
	}
	if (verbose >= 3) {
	    char m[128];
	    sprintf (m, "ftpfs: server: %.80s\n", answer);
	    print_vfs_message (m);
	}
	switch(sscanf(answer, "%d", &code)) {
	    case 0:
	        if (string_buf) {
		    strncpy(string_buf, answer, string_len - 1);
		    *(string_buf + string_len - 1) = 0;
		}
	        code = 500;
	        return 5;
	    case 1:
 		if (answer[3] == '-') {
		    while (1) {
			if (!get_line(sock, answer, sizeof(answer))) {
			    if (string_buf)
				*string_buf = 0;
			    code = 421;
			    return 4;
			}
			if ((sscanf(answer, "%d", &i) > 0) && 
			    (code == i) && (answer[3] == ' '))
			    break;
		    }
		}
	        if (string_buf) {
		    strncpy(string_buf, answer, string_len - 1);
		    *(string_buf + string_len - 1) = 0;
		}
		return code / 100;
	}
    }
}

static void
ftpentry_destructor(void *data)
{
    struct ftpentry *fe = data;
    if (!fe)
	return;
    fe->count--;
    if (fe->count > 0)
        return;
    free(fe->name);
    if (fe->linkname)
	free(fe->linkname);
    if (fe->local_filename) {
        if (fe->local_is_temp) {
            if (!fe->local_stat.st_mtime) {
	        unlink(fe->local_filename);
	    } else {
	        struct stat sb;
	        
	        if (stat (fe->local_filename, &sb) >=0 && 
	            fe->local_stat.st_mtime == sb.st_mtime) {
	            unlink (fe->local_filename); /* Delete only if it hasn't changed */
		}
	    }
	}
	free(fe->local_filename);
    }
    if (fe->remote_filename)
	free(fe->remote_filename);
    if (fe->l_stat)
	free(fe->l_stat);
    memset (fe, 0, sizeof (struct ftpentry));
    free(fe);
}

static void
ftpfs_dir_destructor(void *data)
{
    struct ftpfs_dir *fd = data;

    fd->count--;
    if (fd->count > 0) 
	return;
    memset (fd->remote_path, 0, strlen (fd->remote_path));
    free(fd->remote_path);
    linklist_destroy(fd->file_list, ftpentry_destructor);
    memset (fd, 0, sizeof (struct ftpfs_dir));
    free(fd);
}

static int command (struct ftpfs_connection *bucket, int wait_reply,
		    char *fmt, ...)
{
    va_list ap;
    char buf[4096]; /* FIXME: buffer exceed ?? */
    int n, status;
    int sock = qsock (bucket);
    
    va_start (ap, fmt);
    vsprintf (buf, fmt, ap);
    va_end (ap);

#ifdef WIN32
    char_translate (buf, '\\', '/');
#endif 

    n = strlen(buf);
    buf[n++] = '\r';
    buf[n++] = '\n';
    buf[n] = 0;

    if (ftpfs_debug_server_dialog){
        if (strncmp (buf, "PASS ", 5) == 0) {
            char *tmp = "PASS <Password not logged>\r\n";
            fwrite (tmp, strlen (tmp), 1, ftpfs_logfile);
        } else
	    fwrite (buf, strlen (buf), 1, ftpfs_logfile);
	fflush (ftpfs_logfile);
    }
    got_sigpipe = 0;
    enable_interrupt_key();
    status = send(sock, buf, strlen(buf), 0);
    if (verbose >= 4) {
	char m[128];
	sprintf (m, "ftpfs:   send: %.80s\n", buf);
	print_vfs_message (m);
    }
    if (status < 0){
	code = 421;
	if (errno == EPIPE){
	    got_sigpipe = 1;
	}
	disable_interrupt_key();
	return TRANSIENT;
    }
    disable_interrupt_key();
    
    if (wait_reply)
	return getreply (sock, (wait_reply & WANT_STRING) ? reply_str : NULL, sizeof (reply_str)-1);
    return COMPLETE;
}

static void
ftpfs_connection_close (void *data)
{
    struct ftpfs_connection *bucket = data;

    if (qsock (bucket) != -1){
	print_vfs_message ("ftpfs: Disconnecting from %s", qhost(bucket));
	command(bucket, WAIT_REPLY, "QUIT");
	shutdown (qsock(bucket), 2);
    }
}

static void
ftpfs_free_bucket (void *data)
{
    struct ftpfs_connection *bucket = data;

    free(qhost(bucket));
    free(quser(bucket));
    if (qcdir(bucket))
	free(qcdir(bucket));
    if (qhome(bucket))
    	free(qhome(bucket));
    if (qupdir(bucket))
        free(qupdir(bucket));
    if (bucket->password)
	wipe_password (bucket->password);
    linklist_destroy(qdcache(bucket), ftpfs_dir_destructor);
    memset (bucket, 0, sizeof (struct ftpfs_connection));
    free(bucket);
}

static void
ftpfs_connection_destructor(void *data)
{
    ftpfs_connection_close (data);
    ftpfs_free_bucket (data);
}

static int
changetype (struct ftpfs_connection *bucket, int binary)
{
    if (binary || binary != bucket->isbinary) {
        if (command (bucket, WAIT_REPLY, "TYPE %c", binary ? 'I' : 'A') != COMPLETE) {
	    ftpfserrno = EIO;
            return -1;
	}
        bucket->isbinary = binary;
    }
    return binary;
}

inline void
flush_all_directory(struct ftpfs_connection *bucket)
{
    linklist_delete_all(qdcache(bucket), ftpfs_dir_destructor);

}

/* This routine logs the user in */
static int 
login_server (struct ftpfs_connection *bucket, char *netrcpass)
{
#if defined(HSC_PROXY)
    char *proxypass, *proxyname;
#endif
    char *pass;
    char *op;
    char *name;			/* login user name */
    int  anon = 0;

    if (netrcpass)
        op = (char *) strdup (netrcpass);
    else {
        if (!strcmp (quser(bucket), "anonymous") || 
            !strcmp (quser(bucket), "ftp")) {
	    /* check if a specific anonymous password exists */
	    op = load_anon_passwd (qhost(bucket)); /*EK*/
	    /* if not, then use our default */
	    if (!op) op = (char *) strdup(ftpfs_anonymous_passwd);
	    anon = 1;
         } else {
            char *p;

	    if (!bucket->password){
		p = copy_strings (" FTP: Password required for ", quser(bucket), 
				  " ", NULL);
		op = input_dialog (p, "Password:", "", 
				       qhost(bucket), quser(bucket)); /*EK*/
		free (p);
		if (op == NULL) {
		    ftpfserrno = EPERM;
		    return 0;
		}
		bucket->password = (char *) strdup (op);
	    } else
		op = (char *) strdup (bucket->password);
        }
    }

    if (!anon || ftpfs_debug_server_dialog || ftpfs_literal_password)
	pass = (char *) strdup (op);
    else
	pass = copy_strings ("-", op, 0);
    wipe_password (op);

    /* Proxy server accepts: username@host-we-want-to-connect*/
    if (qproxy (bucket)){
#if defined(HSC_PROXY)
	char *p, *host;
	int port;
	p = ftpfs_get_host_and_username(ftpfs_proxy_host, &host, &proxyname,
					&port, &proxypass);
	if (p)
	    free (p);
	
	free(host);
	if (proxypass)
	    wipe_password (proxypass);
	p = copy_strings(" Proxy: Password required for ", proxyname, " ",
			 NULL);
	proxypass = input_dialog (p, "Password:", "",
					  NULL, NULL); /*EK*/
	free(p);
	if (proxypass == NULL) {
	    ftpfserrno = EPERM;
	    wipe_password (pass);
	    free (proxyname);
	    return 0;
	}
	name = (char *) strdup(quser (bucket));
#else
	name = copy_strings (quser(bucket), "@", 
		qhost(bucket)[0] == '!' ? qhost(bucket)+1 : qhost(bucket), 0);
#endif
    } else 
	name = (char *) strdup (quser (bucket));

    if (getreply (qsock(bucket), NULL, 0) == COMPLETE) {
#if defined(HSC_PROXY)
	if (qproxy(bucket)) {
	    print_vfs_message("ftpfs: sending proxy login name");
	    if (command (bucket, 1, "USER %s", proxyname) == CONTINUE) {
		print_vfs_message("ftpfs: sending proxy user password");
		if (command (bucket, 1, "PASS %s", proxypass) == COMPLETE)
		    {
			print_vfs_message("ftpfs: proxy authentication succeeded");
			if (command (bucket, 1, "SITE %s", qhost(bucket)+1) ==
			    COMPLETE) {
			    print_vfs_message("ftpfs: connected to %s", qhost(bucket)+1);
			}
			else {
			    bucket->failed_on_login = 1;
			    /* ftpfserrno = E; */
			    if (proxypass)
				wipe_password (proxypass);
    			    wipe_password (pass);
			    free (proxyname);
			    free (name);
			    return 0;
			}
		    }
		else {
		    bucket->failed_on_login = 1;
		    /* ftpfserrno = E; */
		    if (proxypass)
			wipe_password (proxypass);
		    wipe_password (pass);
		    free (proxyname);
		    free (name);
		    return 0;
		}
	    }
	    else {
		bucket->failed_on_login = 1;
		/* ftpfserrno = E; */
		if (proxypass)
		    wipe_password (proxypass);
		wipe_password (pass);
		free (proxyname);
		free (name);
		return 0;
	    }
	    if (proxypass)
		wipe_password (proxypass);
	    free (proxyname);
	}
#endif
	print_vfs_message("ftpfs: sending login name");
	code = command (bucket, WAIT_REPLY, "USER %s", name);

	switch (code){
	case CONTINUE:
	    print_vfs_message("ftpfs: sending user password");
            if (command (bucket, WAIT_REPLY, "PASS %s", pass) != COMPLETE)
		break;

	case COMPLETE:
	    print_vfs_message("ftpfs: logged in");
	    wipe_password (pass);
	    free (name);
	    return 1;

	default:
	    bucket->failed_on_login = 1;
	    /* ftpfserrno = E; */
	    if (bucket->password)
		wipe_password (bucket->password);
	    bucket->password = 0;
	    
	    /* This matches the end of the code below, just to make it
	     * obvious to the optimizer
	     */
	    wipe_password (pass);
	    free (name);
	    return 0;
	}
    }
    print_vfs_message ("ftpfs: Login incorrect for user %s ", quser(bucket));
    ftpfserrno = EPERM;
    wipe_password (pass);
    free (name);
    return 0;
}

#ifdef HAVE_SETSOCKOPT
static void
setup_source_route (int socket, int dest)
{
    char buffer [20];
    char *ptr = buffer;

    if (!source_route)
	return;
    bzero (buffer, sizeof (buffer));
    *ptr++ = IPOPT_LSRR;
    *ptr++ = 3 + 8;
    *ptr++ = 4;			/* pointer */

    /* First hop */
    bcopy ((char *) &source_route, ptr, sizeof (int));
    ptr += 4;

    /* Second hop (ie, final destination) */
    bcopy ((char *) &dest, ptr, sizeof (int));
    ptr += 4;
    while ((ptr - buffer) & 3)
	ptr++;
    if (setsockopt (socket, IPPROTO_IP, IP_OPTIONS,
		    buffer, ptr - buffer) < 0)
	message_2s (1, " Error ", " Could not set source routing (%s)", unix_error_string (errno));
}
#else
#define setup_source_route(x,y)
#endif
    
static struct no_proxy_entry {
    char  *domain;
    void  *next;
} *no_proxy;

static void
load_no_proxy_list (void)
{
    /* FixMe: shouldn't be hardcoded!!! */
    char		s[258]; /* provide for 256 characters and nl */
    struct no_proxy_entry *np, *current = 0;
    FILE	*npf;
    int		c;
    char	*p, *l;
    static int	loaded;

    if (loaded)
	return;

    l = copy_strings (home_dir, PATH_SEP_STR, ".mc.no_proxy", 0);
    if (exist_file (l) &&
	(npf = fopen (l, "r"))) {
	while (fgets (s, 258, npf) || !(feof (npf) || ferror (npf))) {
	    if (!(p = strchr (s, '\n'))) {	/* skip bogus entries */ 
		while ((c = getc (npf)) != EOF && c != '\n')
		    ;
		continue;
	    }

	    if (p == s)
		continue;

	    *p = '\0';
	    p = malloc (strlen (s));
	    np = malloc (sizeof (*np));
	    strcpy (p, s);
	    np->domain = p;
	    np->next   = 0;
	    if (no_proxy)
		current->next = np;
	    else
		no_proxy = np;
	    current = np;
	}

	fclose (npf);
	loaded = 1;
    }
    free (l);
}

static int
ftpfs_check_proxy (char *host)
{
    struct no_proxy_entry	*npe;

    if (!ftpfs_proxy_host || !*ftpfs_proxy_host || !host || !*host)
	return 0;		/* sanity check */

    if (*host == '!')
	return 1;
    
    if (!ftpfs_always_use_proxy)
	return 0;

    if (!strchr (host, '.'))
	return 0;

    load_no_proxy_list ();
    for (npe = no_proxy; npe; npe=npe->next) {
	char	*domain = npe->domain;

	if (domain[0] == '.') {
	    int		ld = strlen (domain);
	    int		lh = strlen (host);

	    while (ld && lh && host[lh - 1] == domain[ld - 1]) {
		ld--;
		lh--;
	    }

	    if (!ld)
		return 0;
	} else
	    if (!strcasecmp (host, domain))
		return 0;
    }

    return 1;
}

static void
ftpfs_get_proxy_host_and_port (char *proxy, char **host, int *port)
{
    char *user, *pass, *dir;

#if defined(HSC_PROXY)
#define HSC_DEFAULT_PORT 9875
    dir = get_host_and_username(proxy, host, &user, port, HSC_DEFAULT_PORT, 1,
				 &pass);
#else
    dir = 
    get_host_and_username(proxy, host, &user, port, 21, 1,
				 &pass);
#endif
    free(user);
    if (pass)
	wipe_password (pass);
    if (dir)
	free(dir);
}

static int
ftpfs_open_socket(struct ftpfs_connection *bucket)
{
    struct   sockaddr_in server_address;
    struct   hostent *hp;
    int      my_socket;
    char     *host;
    int      port;
    int      free_host = 0;
    
    /* Use a proxy host? */
    host = qhost(bucket);

    if (!host || !*host){
	print_vfs_message ("ftpfs: Invalid host name.");
        ftpfserrno = EINVAL;
	return -1;
    }

    /* Hosts to connect to that start with a ! should use proxy */
    if (qproxy(bucket)) {
	ftpfs_get_proxy_host_and_port (ftpfs_proxy_host, &host, &port);
	free_host = 1;
    }

    /* Get host address */
    bzero ((char *) &server_address, sizeof (server_address));
    server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = inet_addr (host);
    if ((int) server_address.sin_addr.s_addr != -1)
	server_address.sin_family = AF_INET;
    else {
	hp = gethostbyname(host);
	if (hp == NULL){
	    print_vfs_message("ftpfs: Invalid host address.");
	    ftpfserrno = EINVAL;
	    if (free_host)
		free (host);
	    return -1;
	}
	server_address.sin_family = hp->h_addrtype;
	bcopy ((char *) hp->h_addr, (char *) &server_address.sin_addr,
	       hp->h_length);
    }

#define THE_PORT qproxy(bucket) ? port : qport (bucket)

    server_address.sin_port = htons (THE_PORT);

    /* Connect */
    if ((my_socket = socket (AF_INET, SOCK_STREAM, 0)) < 0) {
	ftpfserrno = errno;
        if (free_host)
	    free (host);
	return -1;
    }
    setup_source_route (my_socket, server_address.sin_addr.s_addr);
    
    print_vfs_message("ftpfs: making connection to %s", host);
    if (free_host)
	free (host);

    enable_interrupt_key(); /* clear the interrupt flag */

#ifdef SCRIPT_ACCEPT
    diffie_errno = 0;
#endif
    if (connect (my_socket, (struct sockaddr *) &server_address,
	     sizeof (server_address)) < 0){
	ftpfserrno = errno;
	if (errno == EINTR && got_interrupt())
	    print_vfs_message("ftpfs: connection interrupted by user");
	else
	    print_vfs_message("ftpfs: connection to server failed: %s",
			      unix_error_string(errno));
#ifdef SCRIPT_ACCEPT
	if (diffie_errno)
	    print_vfs_message ("ftpfs: Security says: %s ",
		arc_socket_error_string (diffie_errno));
#endif
	disable_interrupt_key();
	shutdown (my_socket, 2);
	return -1;
    }
    disable_interrupt_key();
    return my_socket;
}

static struct ftpfs_connection *
open_command_connection (char *host, char *user, int port, char *netrcpass)
{
    struct ftpfs_connection *bucket;
    int retry_seconds, count_down;

    bucket = malloc(sizeof(struct ftpfs_connection));
    
    if (bucket == NULL) {
	ftpfserrno = ENOMEM;
	return NULL;
    }
#ifdef HAVE_MAD
    {
	extern void *watch_free_pointer;

	if (!watch_free_pointer)
	    watch_free_pointer = host;
    }
#endif
    qhost(bucket) = (char *) strdup (host);
    quser(bucket) = (char *) strdup (user);
    qcdir(bucket) = NULL;
    qport(bucket) = port;
    qlock(bucket) = 0;
    qhome(bucket) = NULL;
    qproxy(bucket)= 0;
    qupdir(bucket)= 0;
    qdcache(bucket)=0;
    bucket->__inode_counter = 0;
    bucket->lock = 0;
    bucket->use_proxy = ftpfs_check_proxy (host);
    bucket->password = 0;
    bucket->use_passive_connection = ftpfs_use_passive_connections | source_route;
    bucket->use_source_route = source_route;
    bucket->isbinary = -1;

    /* We do not want to use the passive if we are using proxies */
    if (bucket->use_proxy)
	bucket->use_passive_connection = 0;

    if ((qdcache(bucket) = linklist_init()) == NULL) {
	ftpfserrno = ENOMEM;
	free (qhost(bucket));
	free (quser(bucket));
	free (bucket);
	return NULL;
    }
    
    retry_seconds = 0;
    do { 
	bucket->failed_on_login = 0;

	qsock(bucket) = ftpfs_open_socket(bucket);
	if (qsock(bucket) == -1)  {
	    ftpfs_free_bucket (bucket);
	    return NULL;
	}

	if (login_server(bucket, netrcpass)) {
	    /* Logged in, no need to retry the connection */
	    break;
	} else {
	    if (bucket->failed_on_login){
		/* Close only the socket descriptor */
		shutdown (qsock (bucket), 2);
	    } else {
		ftpfs_connection_destructor (bucket);
		return NULL;
	    } 
	    if (ftpfs_retry_seconds){
		retry_seconds = ftpfs_retry_seconds;
		enable_interrupt_key ();
		for (count_down = retry_seconds; count_down; count_down--){
		    print_vfs_message ("Waiting to retry... %d (Control-C to cancel)\n", count_down);
		    sleep (1);
		    if (got_interrupt ()){
			/* ftpfserrno = E; */
			disable_interrupt_key ();
			ftpfs_free_bucket (bucket);
			return NULL;
		    }
		}
		disable_interrupt_key ();
	    }
	}
    } while (retry_seconds);
    
    qhome(bucket) = ftpfs_get_current_directory (bucket);
    if (!qhome(bucket))
        qhome(bucket) = (char *) strdup (PATH_SEP_STR);
    if (last_current_dir) {
        if (last_current_dir [strlen (last_current_dir) - 1] == PATH_SEP)
            qupdir(bucket) = (char *) strdup (last_current_dir);
        else
            qupdir(bucket) = copy_strings (last_current_dir, PATH_SEP_STR, NULL);
    } else
        qupdir(bucket) = (char *) strdup (PATH_SEP_STR);
    return bucket;
}

static int
is_connection_closed(struct ftpfs_connection *bucket)
{
    fd_set rset;
    struct timeval t;

    if (got_sigpipe){
	return 1;
    }
    t.tv_sec = 0;
    t.tv_usec = 0;
    FD_ZERO(&rset);
    FD_SET(qsock(bucket), &rset);
    while (1) {
	if (select(qsock(bucket) + 1, &rset, NULL, NULL, &t) < 0)
	    if (errno != EINTR)
		return 1;
	break;
#if 0
	if (FD_ISSET(qsock(bucket), &rset)) {
	    n = recv(qsock(bucket), &read_ahead, sizeof(read_ahead), 0);
	    if (n <= 0) 
		return 1;
	} 	else
#endif
    }
    return 0; /* prevents warning */
}

/* This routine keeps track of open connections */
/* Returns a connected socket to host */
static struct ftpfs_connection *
ftpfs_open_link (char *host, char *user, int port, char *netrcpass)
{
    int sock;
    struct ftpfs_connection *bucket;
    struct linklist *lptr;
    
    for (lptr = ftpfs_connections_list->next; 
	 lptr != ftpfs_connections_list; lptr = lptr->next) {
	bucket = lptr->data;
	if ((strcmp (host, qhost(bucket)) == 0) &&
	    (strcmp (user, quser(bucket)) == 0) &&
	    (port == qport(bucket))) {
	    
	    /* check the connection is closed or not, just hack */
	    if (is_connection_closed(bucket)) {
		flush_all_directory(bucket);
		sock = ftpfs_open_socket(bucket);
		if (sock != -1) {
		    shutdown (qsock(bucket), 2);
		    qsock(bucket) = sock;
		    if (login_server(bucket, netrcpass))
			return bucket;
		} 
		
		/* connection refused */
		lptr->prev->next = lptr->next;
		lptr->next->prev = lptr->prev;
		ftpfs_connection_destructor(bucket);
		return NULL;
	    }
	    return bucket;
	}
    }
    bucket = open_command_connection(host, user, port, netrcpass);
    if (bucket == NULL)
	return NULL;
    if (!linklist_insert(ftpfs_connections_list, bucket)) {
	ftpfserrno = ENOMEM;
	ftpfs_connection_destructor(bucket);
	return NULL;
    }
    return bucket;
}

/* The returned directory should always contain a trailing slash */
static char *ftpfs_get_current_directory(struct ftpfs_connection *bucket)
{
    char buf[4096], *bufp, *bufq;

    if (command(bucket, NONE, "PWD") == COMPLETE &&
        getreply(qsock(bucket), buf, sizeof(buf)) == COMPLETE) {
    	bufp = NULL;
	for (bufq = buf; *bufq; bufq++)
	    if (*bufq == '"') {
	        if (!bufp) {
		    bufp = bufq + 1;
	        } else {
		    *bufq = 0;
		    if (*bufp) {
		        if (*(bufq - 1) != PATH_SEP) {
		            *bufq++ = PATH_SEP;
		            *bufq = 0;
		        }
		        return (char *) strdup (bufp);
		    } else {
		        ftpfserrno = EIO;
		        return NULL;
		    }
		}
	    }
    }
    ftpfserrno = EIO;
    return NULL;
}

void ftpfs_fill_names (void (*func)(char *))
{
    struct linklist *lptr;
    char   *path_name;
    struct ftpfs_connection *bucket;
    
    if (!ftpfs_connections_list)
	return;
    lptr = ftpfs_connections_list;
    do {
	if ((bucket = lptr->data) != 0){

	    path_name = copy_strings ("ftp:" PATH_SEP_STR PATH_SEP_STR, quser (bucket),
				      "@",      qhost (bucket), 
				      qcdir(bucket), 0);
	    (*func)(path_name);
	    free (path_name);
	}
	lptr = lptr->next;
    } while (lptr != ftpfs_connections_list);
}

    
/* Setup Passive ftp connection, we use it for source routed connections */
static int
setup_passive (int my_socket, struct ftpfs_connection *bucket, struct sockaddr_in *sa)
{
    int xa, xb, xc, xd, xe, xf;
    char n [6];
    char *c = reply_str;
    
    if (command (bucket, WAIT_REPLY | WANT_STRING, "PASV") != COMPLETE)
	return 0;

    /* Parse remote parameters */
    for (c = reply_str + 4; (*c) && (!isdigit (*c)); c++)
	;
    if (!*c)
	return 0;
    if (!isdigit (*c))
	return 0;
    if (sscanf (c, "%d,%d,%d,%d,%d,%d", &xa, &xb, &xc, &xd, &xe, &xf) != 6)
	return 0;
    n [0] = (unsigned char) xa;
    n [1] = (unsigned char) xb;
    n [2] = (unsigned char) xc;
    n [3] = (unsigned char) xd;
    n [4] = (unsigned char) xe;
    n [5] = (unsigned char) xf;

    bcopy ((void *)n,     &(sa->sin_addr.s_addr), 4);
    bcopy ((void *)&n[4], &(sa->sin_port), 2);
    setup_source_route (my_socket, sa->sin_addr.s_addr);
    if (connect (my_socket, (struct sockaddr *) sa, sizeof (struct sockaddr_in)) < 0) {
	char m[80];
	sprintf (m, "ftpfs: connect() failed: %s\n", strerror (errno));
	print_vfs_message(m);
	return 0;
    }
    return 1;
}

static int
initconn (struct ftpfs_connection *bucket)
{
    struct sockaddr_in data_addr;
    int data, len = sizeof(data_addr);
    struct protoent *pe;

    getsockname(qsock(bucket), (struct sockaddr *) &data_addr, (unsigned int *) &len);
    data_addr.sin_port = 0;
    
    pe = getprotobyname("tcp");
    if (pe == NULL) {
	ftpfserrno = EIO;
	return -1;
    }
    data = socket (AF_INET, SOCK_STREAM, pe->p_proto);
    if (data < 0) {
	ftpfserrno = EIO;
        return -1;
    }

#ifdef ORIGINAL_CONNECT_CODE
    if (bucket->use_source_route){
	int c;
	
	if ((c = setup_passive (data, bucket, &data_addr)))
	    return data;
	print_vfs_message("ftpfs: could not setup passive mode for source routing");
	bucket->use_source_route = 0;
    }
#else
    if (bucket->use_passive_connection){
	if ((bucket->use_passive_connection = setup_passive (data, bucket, &data_addr)))
	    return data;

	bucket->use_source_route = 0;
	bucket->use_passive_connection = 0;
	print_vfs_message ("ftpfs: could not setup passive mode");
    }
#endif
    /* If passive setup fails, fallback to active connections */
    /* Active FTP connection */
    if (bind (data, (struct sockaddr *)&data_addr, len) < 0)
	goto error_return;
    getsockname(data, (struct sockaddr *) &data_addr, (unsigned int *) &len);
    if (listen (data, 1) < 0)
	goto error_return;
    {
	unsigned char *a = (unsigned char *)&data_addr.sin_addr;
	unsigned char *p = (unsigned char *)&data_addr.sin_port;
	
	if (command (bucket, WAIT_REPLY, "PORT %d,%d,%d,%d,%d,%d", a[0], a[1], 
		     a[2], a[3], p[0], p[1]) != COMPLETE)
	    goto error_return;
    }
    return data;
error_return:
    shutdown (data, 2);
    ftpfserrno = EIO;
    return -1;
}

static int
open_data_connection (struct ftpfs_connection *bucket, char *cmd, char *remote, 
		int isbinary)
{
    struct sockaddr_in from;
    int s, j, data, fromlen = sizeof(from);
    
    if ((s = initconn (bucket)) == -1)
        return -1;
    if (changetype (bucket, isbinary) == -1)
        return -1;
    if (remote)
        j = command (bucket, WAIT_REPLY, "%s %s", cmd, remote);
    else
    	j = command (bucket, WAIT_REPLY, "%s", cmd);
    if (j != PRELIM) {
	shutdown (s, 2); /* FIXED */
	ftpfserrno = EPERM;
        return -1;
    }
    enable_interrupt_key();
    if (bucket->use_passive_connection)
	data = s;
    else {
	data = accept (s, (struct sockaddr *)&from, (unsigned int *) &fromlen);
	if (data < 0) {
	    ftpfserrno = errno;
	    shutdown (s, 2);
	    return -1;
	}
	shutdown (s, 2);
    } 
    disable_interrupt_key();
    return data;
}

/* ftpfs_get_path:
 * makes BUCKET point to the connection bucket descriptor for PATH
 * returns a malloced string with the pathname relative to BUCKET.
 */
#ifdef HAVE_MAD
#define ftpfs_get_path(a,b) mad_ftpfs_get_path(a,b,__FILE__,__LINE__)
static char *mad_ftpfs_get_path (struct ftpfs_connection **bucket, char *path, char *file, int line)
#else
static char *ftpfs_get_path (struct ftpfs_connection **bucket, char *path)
#endif
{
    char *user, *host = 0, *remote_path, *pass;
    int port;

    /* An absolute path name, try to determine connection socket */
    if (strncmp (path, "ftp:" PATH_SEP_STR PATH_SEP_STR, 6) == 0) {
	path += 6;
#ifdef HAVE_MAD
	if (!(remote_path = mad_ftpfs_get_host_and_username (path, &host, &user, &port, &pass, file, line))) {
#else
	if (!(remote_path = ftpfs_get_host_and_username (path, &host, &user, &port, &pass))) {
#endif
	    ftpfserrno = ENOENT;
	    free (host);
	    free (user);
	    if (pass)
		wipe_password (pass);
	    return NULL;
	}
	if ((*bucket = ftpfs_open_link (host, user, port, pass)) == NULL) {
	    free (remote_path);
	    free (host);
	    free (user);
	    if (pass)
		wipe_password (pass);
	    return NULL;
	}
	free (host);
	free (user);
	if (pass)
	    wipe_password (pass);
	return remote_path;
    }
    /* never get here !!! */
    message_1s (1, " Error ", " Oops, you just hit a bug in the code ");
    return NULL;
}

static void
ftpfs_abort (struct ftpfs_connection *bucket, int dsock)
{
    static unsigned char ipbuf[3] = { IAC, IP, IAC };
    fd_set mask;
    char buf[4096];

    print_vfs_message("ftpfs: aborting transfer.");
    if (send(qsock(bucket), ipbuf, sizeof(ipbuf), MSG_OOB) != sizeof(ipbuf)) {
	print_vfs_message("ftpfs: abort error: %s", unix_error_string(errno));
	return;
    }
    
    if (command(bucket, NONE, "%cABOR", DM) != COMPLETE){
	print_vfs_message ("ftpfs: abort failed");
	return;
    }
    if (dsock != -1) {
	FD_ZERO(&mask);
	FD_SET(dsock, &mask);
	if (select(dsock + 1, &mask, NULL, NULL, NULL) > 0)
	    while (recv (dsock, buf, sizeof(buf), 0) > 0);
    }
    if ((getreply(qsock(bucket), NULL, 0) == TRANSIENT) && (code == 426))
	getreply(qsock(bucket), NULL, 0);
}

static void
resolve_symlink(struct ftpfs_connection *bucket, struct ftpfs_dir *dir)
{
    char  buffer[4096] = "", *filename;
    int sock;
    FILE *fp;
    struct stat s;
    struct linklist *flist;
    struct ftpentry *fe;
    
    print_vfs_message("Resolving symlink...");

    if (strchr (dir->remote_path, ' ')) {
        if (__ftpfs_chdir(bucket, dir->remote_path) != COMPLETE) {
            print_vfs_message("ftpfs: CWD failed.");
	    return;
        }
        sock = open_data_connection (bucket, "LIST -lLa", ".", 0);
    }
    else
        sock = open_data_connection (bucket, "LIST -lLa", 
                                     dir->remote_path, 0);

    if (sock == -1) {
	print_vfs_message("ftpfs: couldn't resolve symlink");
	return;
    }
    
    fp = (FILE *) fdopen((int) sock, (const char *) "r");
    if (fp == NULL) {
	shutdown (sock, 2);
	print_vfs_message("ftpfs: couldn't resolve symlink");
	return;
    }
    enable_interrupt_key();
    flist = dir->file_list->next;
    while (1) {
	do {
	    if (flist == dir->file_list)
		goto done;
	    fe = flist->data;
	    flist = flist->next;
	} while (!S_ISLNK(fe->s.st_mode));
	while (1) {
	    if (fgets (buffer, sizeof (buffer), fp) == NULL)
		goto done;
	    if (parse_ls_lga (buffer, &s, &filename, NULL)) {
		int r = strcmp(fe->name, filename);
		free(filename);
		if (r == 0) {
		    fe->l_stat = malloc(sizeof(struct stat));
		    if (fe->l_stat == NULL)
			goto done;
		    *fe->l_stat = s;
                    (*fe->l_stat).st_ino = bucket->__inode_counter++;
		    break;
		}
		if (r < 0)
		    break;
	    }
	}
    }
done:
#ifdef WIN32
    s.st_mode |= (s.st_mode >> 16) & (S_IRWXU | S_IFREG | S_IFDIR);
#endif
    while (fgets(buffer, sizeof(buffer), fp) != NULL);
    disable_interrupt_key();
    fclose(fp);
    getreply(qsock(bucket), NULL, 0);
    return;
}

static int
get_line_interruptible (char *buffer, int size, int fd)
{
    int n;
    int i = 0;

    for (i = 0; i < size-1; i++) {
	n = recv (fd, buffer+i, 1, 0);
	if (n == -1 && errno == EINTR){
	    buffer [i] = 0;
	    return EINTR;
	}
	if (n == 0){
	    buffer [i] = 0;
	    return 0;
	}
	if (buffer [i] == '\n'){
	    buffer [i] = 0;
	    return 1;
	}
    }
    buffer [size-1] = 0;
    return 0;
}

/* Return true if path is the same directoy as the one we are on now */
static int
ftpfs_same_dir (char *path, struct ftpfs_connection *bucket)
{
    if (!qcdir (bucket))
	return 0;
    if (strcmp (path, qcdir (bucket)) == 0)
	return 1;
    return 0;
}


static int parse_buffer (struct linklist **file_list, char *buffer, struct ftpfs_connection *bucket, int has_symlinks)
{
    struct ftpentry *fe;
    fe = malloc (sizeof (struct ftpentry));
    memset (fe, 0, sizeof (struct ftpentry));
    if (!fe)
	return ENOMEM;
    if (parse_ls_lga (buffer, &fe->s, &fe->name, &fe->linkname)) {
#ifdef WIN32
	fe->s.st_mode |= (fe->s.st_mode >> 16) & (S_IRWXU | S_IFREG | S_IFDIR);
#endif
	fe->count = 1;
	fe->local_filename = fe->remote_filename = NULL;
	fe->l_stat = NULL;
	fe->bucket = bucket;
	(fe->s).st_ino = bucket->__inode_counter++;
	if (S_ISLNK (fe->s.st_mode))
	    has_symlinks = 1;

	if (!linklist_insert (*file_list, fe)) {
	    free (fe);
	    return ENOMEM;
	}
    } else {
	free (fe);
    }
    return 0;
}

int ftpfs_option_allow_empty_directories = 1;

static struct ftpfs_dir *
 retrieve_dir (struct ftpfs_connection *bucket, char *remote_path)
{
    int err;
    int sock, has_symlinks;
    struct linklist *file_list, *p;
    char buffer[8192];
    struct ftpfs_dir *dcache;
    int got_intr = 0;

    for (p = qdcache (bucket)->next; p != qdcache (bucket);
	 p = p->next) {
	dcache = p->data;
	if (strcmp (dcache->remote_path, remote_path) == 0) {
	    struct timeval tim;

	    gettimeofday (&tim, NULL);
	    if ((tim.tv_sec < dcache->timestamp.tv_sec) && !force_expiration)
		return dcache;
	    else {
		force_expiration = 0;
		p->next->prev = p->prev;
		p->prev->next = p->next;
		ftpfs_dir_destructor (dcache);
		free (p);
		break;
	    }
	}
    }

    has_symlinks = 0;
    print_vfs_message ("ftpfs: Reading FTP directory...");
/* FIXED: always does a CWD */
    if (__ftpfs_chdir (bucket, remote_path) != COMPLETE) {
	ftpfserrno = ENOENT;
	print_vfs_message ("ftpfs: CWD failed.");
	return NULL;
    }
    file_list = linklist_init ();
    if (file_list == NULL) {
	ftpfserrno = ENOMEM;
	print_vfs_message ("ftpfs: couldn't get a file listing");
	return NULL;
    }
    dcache = malloc (sizeof (struct ftpfs_dir));
    if (dcache == NULL) {
	ftpfserrno = ENOMEM;
	linklist_destroy (file_list, NULL);
	print_vfs_message ("ftpfs: FAIL");
	return NULL;
    }
    gettimeofday (&dcache->timestamp, NULL);
    dcache->timestamp.tv_sec += ftpfs_directory_timeout;
    dcache->file_list = file_list;
    dcache->remote_path = (char *) strdup (remote_path);
    dcache->count = 1;

    sock = open_data_connection (bucket, "LIST -la", remote_path, 0);

    if (sock == -1)
	goto error_3;

    /* Clear the interrupt flag */
    enable_interrupt_key ();

    errno = 0;
    while ((got_intr = get_line_interruptible (buffer, sizeof (buffer), sock)) != EINTR) {
	int eof = got_intr == 0;
	if (verbose >= 4) {
	    char m[128];
	    sprintf (m, "ftpfs: server: %.80s\n", buffer);
	    print_vfs_message (m);
	}
	    

	if (ftpfs_debug_server_dialog) {
	    fputs (buffer, ftpfs_logfile);
	    fflush (ftpfs_logfile);
	}
	if (buffer[0] == 0 && eof)
	    break;
	err = parse_buffer (&file_list, buffer, bucket, has_symlinks);
	if (err == ENOMEM) {
	    ftpfserrno = err;
	    goto error_1;
	}
	if (eof)
	    break;
    }
    if (got_intr) {
	disable_interrupt_key ();
	print_vfs_message ("ftpfs: reading FTP directory interrupt by user");
	ftpfs_abort (bucket, sock);
	shutdown (sock, 2);
	ftpfserrno = EINTR;
	goto error_3;
    }
    shutdown (sock, 2);
    disable_interrupt_key ();
    if (getreply (qsock (bucket), NULL, 0) != COMPLETE) {
	ftpfserrno = EIO;
	goto error_3;
    }
    if (file_list->next == file_list) {
	if (!ftpfs_option_allow_empty_directories) {
	    ftpfserrno = EACCES;
	    goto error_3;
	} else {
	    strcpy (buffer, "drwxrwxrwx   2 root     root         1024 Apr 17 12:38 .");
	    err = parse_buffer (&file_list, buffer, bucket, has_symlinks);
	    if (err == ENOMEM) {
		ftpfserrno = err;
		goto error_1;
	    }
	    strcpy (buffer, "drwxrwxrwx   2 root     root         1024 Apr 17 12:38 ..");
	    err = parse_buffer (&file_list, buffer, bucket, has_symlinks);
	    if (err == ENOMEM) {
		ftpfserrno = err;
		goto error_1;
	    }
	}
    }
    if (!linklist_insert (qdcache (bucket), dcache)) {
	ftpfserrno = ENOMEM;
	goto error_3;
    }
    if (has_symlinks)
	resolve_symlink (bucket, dcache);
    print_vfs_message ("ftpfs: got listing");
    return dcache;
  error_1:
    disable_interrupt_key ();
    shutdown (sock, 2);
    getreply (qsock (bucket), NULL, 0);
  error_3:
    free (dcache->remote_path);
    free (dcache);
    linklist_destroy (file_list, ftpentry_destructor);
    print_vfs_message ("ftpfs: failed");
    return NULL;
}

static int
store_file(struct ftpentry *fe)
{
    int local_handle, sock, n, total;
    char *local_filename = 0;
/* FIXME: configure does not detect */
#ifdef HAVE_STRUCT_LINGER
    struct linger li;
#else
    int flag_one = 1;
#endif
    char buffer[8192];
    struct stat s;

    local_handle = open(fe->local_filename, O_RDONLY);
    local_filename = (char *) strdup (fe->local_filename);
    if (local_handle == -1) {
	ftpfserrno = EIO;
	goto error_return1;
    }
    fstat(local_handle, &s);
    sock = open_data_connection(fe->bucket, "STOR", fe->remote_filename, 1);
    if (sock < 0) {
	close(local_handle);
	goto error_return1;
    }
#ifdef HAVE_STRUCT_LINGER
    li.l_onoff = 1;
    li.l_linger = 120;
    setsockopt(sock, SOL_SOCKET, SO_LINGER, (char *) &li, sizeof(li));
#else
    setsockopt(sock, SOL_SOCKET, SO_LINGER, (char *) &flag_one, sizeof (flag_one));
#endif
    total = 0;
    
    enable_interrupt_key();
    while (1) {
	while ((n = read(local_handle, buffer, sizeof(buffer))) < 0) {
	    if (errno == EINTR) {
		if (got_interrupt()) {
		    ftpfserrno = EINTR;
		    goto error_return;
		}
		else
		    continue;
	    }
	    ftpfserrno = errno;
	    goto error_return;
	}
	if (n == 0)
	    break;
    	while (send(sock, buffer, n, 0) < 0) {
	    if (errno == EINTR) {
		if (got_interrupt()) {
		    ftpfserrno = EINTR;
		    goto error_return;
		}
		else 
		    continue;
	    }
	    ftpfserrno = errno;
	    goto error_return;
	}
	total += n;
	print_vfs_message("ftpfs: storing file %d (%d)", 
			  total, s.st_size);
    }
    disable_interrupt_key();
    shutdown (sock, 2);
    close(local_handle);
    if (getreply (qsock (fe->bucket), NULL, 0) != COMPLETE) {
	ftpfserrno = EIO;
	goto error_return1;
    }
    unlink (local_filename);
    free (local_filename);
    return 1;
error_return:
    disable_interrupt_key();
    shutdown (sock, 2);
    close(local_handle);
    getreply(qsock(fe->bucket), NULL, 0);
error_return1:
    unlink (local_filename);
    free (local_filename);
    return 0;
}

/* These variables are for the _ctl routine */
static char *localname = NULL;
static struct ftpentry *remoteent;
static int remotetotal = 0;
static int transfer_started = 0;
static char *remotebuffer;
static int isremotecopy = 0;
static int remotelocal_handle, remotesock, remoten, remotestat_size;

int retrieve_file_start(struct ftpentry *fe)
{
    if (fe->local_filename == NULL) {
	ftpfserrno = ENOMEM;
	return 0;
    }
    remotesock = open_data_connection(fe->bucket, "RETR", fe->remote_filename, 1);
    if (remotesock == -1) {
	ftpfserrno = EACCES;
	free (fe->local_filename);
	fe->local_filename = NULL;
	return 0;
    }
    shutdown (remotesock, 2);
    remotetotal = 0;
    remoteent = fe;
    return 1;
}

int retrieve_file_start2(struct ftpentry *fe)
{
    remotelocal_handle = open(fe->local_filename, O_RDWR | O_CREAT | O_TRUNC, S_IWUSR | S_IRUSR);
    if (remotelocal_handle == -1) {
	ftpfserrno = EIO;
	free(fe->local_filename);
	fe->local_filename = NULL;
	fe->local_is_temp = 1;
	shutdown (remotesock, 2);
	return 0;
    }
    remotestat_size = fe->s.st_size;
    remotebuffer = malloc (8192);
    return 1;
}

void ftpfs_flushdir (void)
{
	    force_expiration = 1;
}

int ftpfs_ctl (void *data, int ctlop, int arg)
{
    int n = 0;

    switch (ctlop) {
        case MCCTL_ISREMOTECOPY:
            return isremotecopy;
	    
        case MCCTL_REMOTECOPYCHUNK:
            if (!transfer_started) {
                if (!retrieve_file_start2 (remoteent)){
                    return MCERR_TARGETOPEN;
		} else
		    transfer_started = 1;
	    }
	    enable_interrupt_key ();
            if (!remoten) {
		int v = select_on_two (remotesock, 0);
		
		if (((v < 0) && (errno == EINTR)) || v == 0){
		    disable_interrupt_key ();
		    return MCERR_DATA_ON_STDIN;
		}
		    
    	        if ((n = recv (remotesock, remotebuffer, 8192, 0)) < 0){
		    disable_interrupt_key ();
		    if (errno == EINTR)
			return MCERR_DATA_ON_STDIN;
		    else
			return MCERR_READ;
		}
	        if (n == 0) {
    	    	    shutdown (remotesock, 2);
    	    	    close(remotelocal_handle);
                    remotesock = -1;
                    remotelocal_handle = -1;
    	    	    if (getreply (qsock (remoteent->bucket), NULL, 0) != COMPLETE) {
	        	ftpfserrno = EIO;
	    	    }
		    if (localname){
			free (localname);
			localname = NULL;
		    }
		    disable_interrupt_key ();
		    transfer_started = 0;
	            return MCERR_FINISH;
	        }
		disable_interrupt_key ();
	        remotetotal += n;
	        remoten = n;
            } else
                n = remoten;
            if (write(remotelocal_handle, remotebuffer, remoten) < 0)
                return MCERR_WRITE;
            remoten = 0;
            return n;

	    /* We get this message if the transfer was aborted */
        case MCCTL_FINISHREMOTE:
	    if (localname) {
	        free (localname);
	        localname = NULL;
	    }
	    if (!arg) { /* OK */
	        if (stat (remoteent->local_filename, &remoteent->local_stat) < 0) {
	            remoteent->local_stat.st_mtime = 0;
		}
	    } else {
	        remoteent->local_stat.st_mtime = 0;
	    }
	    transfer_started = 0;
	    ftpfs_abort (remoteent->bucket, remotesock);
	    ftpfserrno = EINTR;
	    return 0;
    }
    return 0;
}

static int flush_dir (char *remote_path);
static int remove_temp_file (char *file_name);

int ftpfs_setctl (char *path, int ctlop, char *arg)
{
    switch (ctlop) {
	case MCCTL_SETDIRTIMEOUT:
	    ftpfs_directory_timeout = (time_t) arg;
	    return 0;

	case MCCTL_FLUSHDIR:
	    return flush_dir (path);

	case MCCTL_REMOVELOCALCOPY:
	    return remove_temp_file (path);

        case MCCTL_SETREMOTECOPY: if (localname) free (localname);
            localname = (char *) strdup (vfs_canon (arg));
            return 1;

        case MCCTL_SETTMPDIR:
	    if (ftpfs_temp_name_dir)
		free (ftpfs_temp_name_dir);
	    if (arg) {
		ftpfs_temp_name_dir = (char *) strdup (arg);
	    } else
		ftpfs_temp_name_dir = 0;
	    return 1;

        default:
            return 0;
    }
}

int retrieve_file(struct ftpentry *fe)
{
    int total;
    char buffer[8192];
    int local_handle, sock, n;
    
    if (fe->local_filename)
        return 1;
    fe->local_stat.st_mtime = 0;
    fe->local_filename = (char *) tempnam(ftpfs_temp_name_dir, "ftpfs");
    fe->local_is_temp = 1;
    if (fe->local_filename == NULL) {
	ftpfserrno = ENOMEM;
	return 0;
    }
    local_handle = open(fe->local_filename, O_RDWR | O_CREAT | O_TRUNC, S_IWUSR | S_IRUSR);
    if (local_handle == -1) {
	ftpfserrno = EIO;
	free(fe->local_filename);
	fe->local_filename = NULL;
	return 0;
    }
    sock = open_data_connection(fe->bucket, "RETR", fe->remote_filename, 1);
    if (sock == -1) {
	ftpfserrno = EACCES;
	goto error_3;
    }

    /* Clear the interrupt status */
    enable_interrupt_key ();
    total = 0;
    while (1) {
	int stat_size = fe->s.st_size;
	while ((n = recv (sock, buffer, sizeof(buffer), 0)) < 0) {
	    if (errno == EINTR) {
		if (got_interrupt ()) {
		    disable_interrupt_key();
		    ftpfs_abort(fe->bucket, sock);
		    ftpfserrno = EINTR;
		    goto error_2;
		}
		else 
		    continue;
	    }
	    ftpfserrno = errno;
	    disable_interrupt_key();
	    goto error_1;
	}
	if (n == 0)
	    break;
	total += n;
	if (stat_size == 0)
	    print_vfs_message ("ftpfs: Getting file: %ld bytes transfered", 
			       total);
	else
	    print_vfs_message ("ftpfs: Getting file: %3d%% (%ld bytes transfered)",
			       (int) ((double) total*100.0/stat_size), total);
        while (write(local_handle, buffer, n) < 0) {
	    if (errno == EINTR) {
		if (got_interrupt()) {
		    ftpfs_abort(fe->bucket, sock);
		    ftpfserrno = EINTR;
		    goto error_2;
		}
		else
		    continue;
	    }
	    ftpfserrno = errno;
	    goto error_1;
	}
    }
    close(local_handle);
    shutdown (sock, 2);
    if (getreply (qsock (fe->bucket), NULL, 0) != COMPLETE) {
	ftpfserrno = EIO;
	goto error_3;
    }
    if (stat (fe->local_filename, &fe->local_stat) < 0) {
        fe->local_stat.st_mtime = 0;
    }
    return 1;
error_1:
    getreply(qsock(fe->bucket), NULL, 0);
error_2:
    shutdown (sock, 2);
error_3:
    disable_interrupt_key ();
    close(local_handle);
    unlink(fe->local_filename);
    free(fe->local_filename);
    fe->local_filename = NULL;
    return 0;
}

static struct ftpentry *
_get_file_entry(struct ftpfs_connection *bucket, char *file_name, 
		int op, int flags)
{
    char *p, q;
    struct ftpentry *ent;
    struct linklist *file_list, *lptr;
    struct ftpfs_dir *dcache;
    struct stat sb;

    p = strrchr(file_name, PATH_SEP);
    q = *p;
    *p = '\0';
    dcache = retrieve_dir(bucket, *file_name ? file_name : PATH_SEP_STR);
    if (dcache == NULL)
        return NULL;
    file_list = dcache->file_list;
    *p++ = q;
    if (!*p) 
        p = ".";
    for (lptr = file_list->next; lptr != file_list; lptr = lptr->next) {
	ent = lptr->data;
        if (strcmp(p, ent->name) == 0) {
	    if (S_ISLNK(ent->s.st_mode) && (op & FTPFS_RESOLVE_SYMLINK)) {
		if (ent->l_stat == NULL) {
		    ftpfserrno = ENOENT;
		    return NULL;
		}
		if (S_ISLNK(ent->l_stat->st_mode)) {
		    ftpfserrno = ELOOP;
		    return NULL;
		}
	    }
	    if (ent && (op & FTPFS_OPEN)) {
		mode_t fmode;

		fmode = S_ISLNK(ent->s.st_mode)
		    ? ent->l_stat->st_mode
		    : ent->s.st_mode;
		if (S_ISDIR(fmode)) {
		    ftpfserrno = EISDIR;
		    return NULL;
		}
		if (!S_ISREG(fmode)) {
		    ftpfserrno = EPERM;
		    return NULL;
		}
		if ((flags & O_EXCL) && (flags & O_CREAT)) {
		    ftpfserrno = EEXIST;
		    return NULL;
		}
		if (ent->remote_filename == NULL) {
		    ent->remote_filename = (char *) strdup(file_name);
		    if (ent->remote_filename == NULL) {
			ftpfserrno = ENOMEM;
			return NULL;
		    }
		}
		if (ent->local_filename == NULL || 
		    !ent->local_stat.st_mtime || 
		    stat (ent->local_filename, &sb) < 0 || 
		    sb.st_mtime != ent->local_stat.st_mtime) {
		    int handle;
		    
		    if (ent->local_filename){
		        free (ent->local_filename);
			ent->local_filename = 0;
		    }
		    if (flags & O_TRUNC) {
			ent->local_filename = (char *) tempnam (ftpfs_temp_name_dir, "ftpfs");
			if (ent->local_filename == NULL) {
			    ftpfserrno = ENOMEM;
			    return NULL;
			}
			handle = open(ent->local_filename, O_CREAT | O_TRUNC | 
				 O_RDWR, S_IWUSR | S_IRUSR);
			if (handle < 0) {
			    ftpfserrno = EIO;
			    return NULL;
			}
			close(handle);
			if (stat (ent->local_filename, &ent->local_stat) < 0)
			    ent->local_stat.st_mtime = 0;
		    }
		    else {
		        if (localname != NULL) {
		            isremotecopy = 1;
		            ent->local_is_temp = 0;
		            ent->local_stat.st_mtime = 0;
		            ent->local_filename = (char *) strdup (localname);
		            if (!retrieve_file_start (ent)) {
		                isremotecopy = 0;
		                return NULL;
		            }
		            return ent;
		        }
		        if (!retrieve_file(ent))
			    return NULL;
		    }
		}
		else if (flags & O_TRUNC) {
#ifdef HAVE_TRUNCATE
			truncate(ent->local_filename, 0);
#else
			close (open (ent->local_filename, O_CREAT | O_WRONLY | O_TRUNC, S_IWUSR));
#endif
		}
	    }
	    return ent;
	}
    }
    if ((op & FTPFS_OPEN) && (flags & O_CREAT)) {
	int handle;

	ent = malloc(sizeof(struct ftpentry));
	if (ent == NULL) {
	    ftpfserrno = ENOMEM;
	    return NULL;
	}
	bzero (ent, sizeof (struct ftpentry));
	ent->count = 1;
	ent->linkname = NULL;
	ent->l_stat = NULL;
	ent->bucket = bucket;
	ent->name = (char *) strdup(p);
	ent->remote_filename = (char *) strdup(file_name);
	ent->local_filename = (char *) tempnam (ftpfs_temp_name_dir, "ftpfs");
	if (!ent->name && !ent->remote_filename && !ent->local_filename) {
	    ftpentry_destructor(ent);
	    ent = 0;
	    ftpfserrno = ENOMEM;
	    return NULL;
	}
        handle = creat(ent->local_filename, S_IRUSR | S_IWUSR);
	if (handle == -1) {
	    ftpfserrno = EIO;
	    ftpentry_destructor(ent);
	    return NULL;
	}
	fstat(handle, &ent->s);
	close(handle);
#if 0
/* This is very wrong - like this a zero length file will be always created
   and usually preclude uploading anything more desirable */	
#if defined(UPLOAD_ZERO_LENGTH_FILE)
	if (!store_file(ent)) {
	    ftpentry_destructor(ent);
	    return NULL;
	}
#endif
#endif
	if (!linklist_insert(file_list, ent)) {
	    ftpfserrno = ENOMEM;
	    ftpentry_destructor(ent);
	    return NULL;
	}
	ent->freshly_created = 1;
	return ent;
    }
    else {
	ftpfserrno = ENOENT;
	return NULL;
    }
}

/* this just free's the local temp file. I don't know if the
   remote file can be used after this without crashing - paul
   psheer@obsidian.co.za psheer@icon.co.za */
static int remove_temp_file (char *file_name)
{
    char *p, q;
    struct ftpfs_connection *bucket;
    struct ftpentry *ent;
    struct linklist *file_list, *lptr;
    struct ftpfs_dir *dcache;

    if (!(file_name = ftpfs_get_path (&bucket, file_name)))
	return -1;
    p = strrchr (file_name, PATH_SEP);
    q = *p;
    *p = '\0';
    dcache = retrieve_dir (bucket, *file_name ? file_name : PATH_SEP_STR);
    if (dcache == NULL) {
	free (file_name);
	return -1;
    }
    file_list = dcache->file_list;
    *p++ = q;
    if (!*p)
	p = ".";

    for (lptr = file_list->next; lptr != file_list; lptr = lptr->next) {
	ent = lptr->data;
	if (strcmp (p, ent->name) == 0) {
	    if (ent->local_filename) {
		unlink (ent->local_filename);
		free (ent->local_filename);
		ent->local_filename = 0;
		free (file_name);
		return 0;
	    }
	}
    }
    free (file_name);
    return -1;
}

static int flush_dir (char *remote_path)
{
    struct ftpfs_connection *bucket;
    struct linklist *p;
    struct ftpfs_dir *dcache;

    if (!(remote_path = ftpfs_get_path (&bucket, remote_path)))
	return -1;

    for (p = qdcache (bucket)->next; p != qdcache (bucket);
	 p = p->next) {
	dcache = p->data;
	if (strcmp (dcache->remote_path, remote_path) == 0) {
	    force_expiration = 0;
	    p->next->prev = p->prev;
	    p->prev->next = p->next;
	    ftpfs_dir_destructor (dcache);
	    free (p);
	    free (remote_path);
	    return 0;
	}
    }
    free (remote_path);
    return -1;
}

static struct ftpentry *
get_file_entry(char *path, int op, int flags)
{
    struct ftpfs_connection *bucket;
    struct ftpentry *fe;
    char *remote_path;

    if (!(remote_path = ftpfs_get_path (&bucket, path)))
	return NULL;
    isremotecopy = 0;
    fe = _get_file_entry(bucket, remote_path, op,
			 flags);
    free(remote_path);
#if 0
    if (op & FTPFS_FREE_RESOURCE)
	vfs_add_noncurrent_stamps (&ftpfs_vfs_ops, (vfsid) bucket, NULL);
#endif
    return fe;
}

#define OPT_FLUSH        1
#define OPT_IGNORE_ERROR 2

static int ftpfs_normal_flush = 1;

void ftpfs_hint_reread(int reread)
{
    if (reread)
	ftpfs_normal_flush++;
    else
	ftpfs_normal_flush--;
}

static int
send_ftp_command(char *filename, char *cmd, int flags)
{
    char *remote_path;
    struct ftpfs_connection *bucket;
    int r;
    int flush_directory_cache = (flags & OPT_FLUSH) && (ftpfs_normal_flush > 0);

    if (!(remote_path = ftpfs_get_path(&bucket, filename)))
	return -1;
    r = command (bucket, WAIT_REPLY, cmd, remote_path);
    free(remote_path);
    vfs_add_noncurrent_stamps (&ftpfs_vfs_ops, (vfsid) bucket, NULL);
    if (flags & OPT_IGNORE_ERROR)
	r = COMPLETE;
    if (r != COMPLETE) {
	ftpfserrno = EPERM;
	return -1;
    }
    if (flush_directory_cache)
	flush_all_directory(bucket);
    return 0;
}

static char *free_passwd = 0;

struct passwd *cached_getpwuid (pid_t pid);
struct group *cached_getgrgid (gid_t gid);

/* This routine is called as the last step in load_setup */
void
ftpfs_init_passwd(void)
{
    struct passwd *passwd_info;
    char *p, hostname[MAXHOSTNAMELEN];
    struct hostent *hp;

    /* establish if there is an anonymous password */
    ftpfs_anonymous_passwd = load_anon_passwd ("*"); /*EK*/
    if (ftpfs_anonymous_passwd)
	return;

    /* if there isn't, then make one */
    if ((passwd_info = cached_getpwuid (geteuid ())) == NULL)
	p = "guest";
    else
	p = passwd_info->pw_name;
    gethostname(hostname, sizeof(hostname));
    hp = gethostbyname(hostname);
    if (hp != NULL)
	ftpfs_anonymous_passwd = copy_strings (p, "@", hp->h_name, NULL);
    else
	ftpfs_anonymous_passwd = copy_strings (p, "@", hostname, NULL);
    free_passwd = ftpfs_anonymous_passwd;
    endpwent ();
}

void
ftpfs_init ()
{
    home_dir = getenv ("HOME");
    if (!home_dir)
	home_dir = (cached_getpwuid (getuid ()))->pw_dir;
    home_dir = (char *) strdup (home_dir);

    ftpfs_connections_list = linklist_init();
    ftpfs_directory_timeout = FTPFS_DIRECTORY_TIMEOUT;
}

void
ftpfs_done(void)
{
    linklist_destroy(ftpfs_connections_list, ftpfs_connection_destructor);
    if (ftpfs_debug_server_dialog)
	fclose (ftpfs_logfile);
    if (free_passwd)
	wipe_password (free_passwd);
    if (ftpfs_temp_name_dir)
	free (ftpfs_temp_name_dir);
    if (home_dir)
	free (home_dir);
}

/* The callbacks */

struct ftpfs_filp {
    unsigned int has_changed:1;
    struct ftpentry *fe;
    int local_handle;
};

static void *ftpfs_open (char *file, int flags, int mode)
{
    struct ftpfs_filp *fp;
    struct ftpentry *fe;

    fp = malloc(sizeof(struct ftpfs_filp));
    if (fp == NULL) {
	ftpfserrno = ENOMEM;
        return NULL;
    }
    fe = get_file_entry(file, FTPFS_OPEN | FTPFS_RESOLVE_SYMLINK, flags);
    if (fe == NULL) {
	free(fp);
        return NULL;
    }
    if (!isremotecopy) {
        fp->local_handle = open(fe->local_filename, flags, mode);
        if (fp->local_handle < 0) {
	    ftpfserrno = errno;
	    free(fp);
	    return NULL;
        }
    } else
        fp->local_handle = -1;
#ifdef UPLOAD_ZERO_LENGTH_FILE        
    fp->has_changed = fe->freshly_created;
#else
    fp->has_changed = 0;
#endif    
    fp->fe = fe;
    qlock(fe->bucket)++;
    fe->count++;
    return fp;
}

static int ftpfs_read (void *data, char *buffer, int count)
{
    struct ftpfs_filp *fp;
    int n;
    
    fp = data;
    n = read(fp->local_handle, buffer, count);
    if (n < 0)
      ftpfserrno = errno;
    return n;
}

int ftpfs_write (void *data, char *buf, int nbyte)
{
    struct ftpfs_filp *fp;
    int n;
    
    fp = data;
    n = write(fp->local_handle, buf, nbyte);
    if (n < 0)
      ftpfserrno = errno;
    fp->has_changed = 1;
    return n;
}

static int ftpfs_close (void *data)
{
    struct ftpfs_filp *fp = data;
    int result = 0;

    if (fp->local_handle >= 0)
        close(fp->local_handle);
    if (fp->has_changed) {
	if (!store_file(fp->fe))
	    result = -1;
	if (ftpfs_normal_flush)
	    flush_all_directory(fp->fe->bucket);
    }
    qlock(fp->fe->bucket)--;
    ftpentry_destructor(fp->fe);
    free(fp);
    return result;
}

static int ftpfs_errno (void)
{
    return ftpfserrno;
}


/* Explanation:
 * On some operating systems (Slowaris 2 for example)
 * the d_name member is just a char long (Nice trick that break everything,
 * so we need to set up some space for the filename.
 */
struct ftpfs_dirent {
    struct dirent dent;
    char extra_buffer [MC_MAXPATHLEN - sizeof (struct dirent)];
    struct linklist *pos;
    struct ftpfs_dir *dcache;
};

char *ftpfs_gethome (char *servername)
{
    struct ftpfs_connection *bucket;
    char *remote_path;

    if (!(remote_path = ftpfs_get_path (&bucket, servername)))
        return NULL;
    free (remote_path);
    return qhome(bucket);
}

char *ftpfs_getupdir (char *servername)
{
    struct ftpfs_connection *bucket;
    char *remote_path;

    if (!(remote_path = ftpfs_get_path (&bucket, servername)))
        return NULL;
    free (remote_path);
    return qupdir(bucket);
}

static void *ftpfs_opendir (char *dirname)
{
    struct ftpfs_connection *bucket;
    char *remote_path;
    struct ftpfs_dirent *dirp;

    if (!(remote_path = ftpfs_get_path (&bucket, dirname)))
        return NULL;
    dirp = malloc(sizeof(struct ftpfs_dirent));
    if (dirp == NULL) {
	ftpfserrno = ENOMEM;
	goto error_return;
    }
    dirp->dcache = retrieve_dir(bucket, remote_path);
    if (dirp->dcache == NULL)
        goto error_return;
    dirp->pos = dirp->dcache->file_list->next;
    free(remote_path);
    dirp->dcache->count++;
    return (void *)dirp;
error_return:
    vfs_add_noncurrent_stamps (&ftpfs_vfs_ops, (vfsid) bucket, NULL);
    free(remote_path);
    free(dirp);
    return NULL;
}

static void *ftpfs_readdir (void *data)
{
    struct ftpentry *fe;
    struct ftpfs_dirent *dirp = data;
    
    if (dirp->pos == dirp->dcache->file_list)
	return NULL;
    fe = dirp->pos->data;
#ifdef WIN32
    dirp->dent.d_name = dirp->extra_buffer;
    strcpy (dirp->extra_buffer, fe->name);
#else
    strcpy (&(dirp->dent.d_name [0]), fe->name);
#endif
#ifdef HAVE_DIR_D_NAMELEN
    dirp->dent.d_namlen = strlen (dirp->dent.d_name);
#endif
    dirp->pos = dirp->pos->next;
    return (void *) &dirp->dent;
}

static int ftpfs_closedir (void *info)
{
    struct ftpfs_dirent *dirp = info;
    ftpfs_dir_destructor(dirp->dcache);
    free(dirp);
    return 0;
}

static int ftpfs_lstat (char *path, struct stat *buf)
{
    struct ftpentry *fe;
    
    fe = get_file_entry(path, FTPFS_FREE_RESOURCE, 0);
    if (fe) {
	*buf = fe->s;
	return 0;
    }
    else
        return -1;
}

static int ftpfs_stat (char *path, struct stat *buf)
{
    struct ftpentry *fe;

    fe = get_file_entry(path, FTPFS_RESOLVE_SYMLINK | FTPFS_FREE_RESOURCE, 0);
    if (fe) {
	if (!S_ISLNK(fe->s.st_mode))
	    *buf = fe->s;
	else
	    *buf = *fe->l_stat;
	return 0;
    }
    else
        return -1;
}

int ftpfs_fstat (void *data, struct stat *buf)
{
    struct ftpfs_filp *fp = data;

    if (!S_ISLNK(fp->fe->s.st_mode))
	*buf = fp->fe->s;
    else
	*buf = *fp->fe->l_stat;
    return 0;
}

int ftpfs_chmod (char *path, unsigned long mode)
{
    char buf[40];
#ifdef WIN32
    if ((mode >> 16))
	mode >>= 16;
#endif
    sprintf (buf, "SITE CHMOD %4.4o %%s", (unsigned int) mode & 07777);
    return send_ftp_command (path, buf, OPT_IGNORE_ERROR | OPT_FLUSH);
}

int ftpfs_chown (char *path, int owner, int group)
{
#if 0
    ftpfserrno = EPERM;
    return -1;
#else
/* Everyone knows it is not possible to chown remotely, so why bother them.
   If someone's root, then copy/move will always try to chown it... */
    return 0;
#endif    
}

static int ftpfs_readlink (char *path, char *buf, int size)
{
    struct ftpentry *fe;

    fe = get_file_entry(path, FTPFS_FREE_RESOURCE, 0);
    if (!fe)
	return -1;
    if (!S_ISLNK(fe->s.st_mode)) {
	ftpfserrno = EINVAL;
	return -1;
    }
    if (fe->linkname == NULL) {
	ftpfserrno = EACCES;
	return -1;
    }
    if (strlen(fe->linkname) >= size) {
	ftpfserrno = ERANGE;
	return -1;
    }
    strncpy(buf, fe->linkname, size);
    return strlen(fe->linkname);
}

static int ftpfs_unlink (char *path)
{
    return send_ftp_command(path, "DELE %s", 1);
}

static int ftpfs_symlink (char *n1, char *n2)
{
    ftpfserrno = EPERM;
    return -1;
}

static int ftpfs_rename (char *path1, char *path2)
{
    ftpfserrno = EPERM;
    return -1;
}

static int
__ftpfs_chdir (struct ftpfs_connection *bucket ,char *remote_path)
{
    int r;

    if (ftpfs_same_dir (remote_path, bucket))
	return COMPLETE;

    r = command (bucket, WAIT_REPLY, "CWD %s", remote_path);
    if (r != COMPLETE) {
	ftpfserrno = EIO;
    } else {
	if (qcdir(bucket))
	    free(qcdir(bucket));
	qcdir(bucket) = (char *) strdup (remote_path);
    }
    return r;
}

static int ftpfs_chdir (char *path)
{
    char *remote_path;
    struct ftpfs_connection *bucket;
    int r;

 retry:
    if (!(remote_path = ftpfs_get_path(&bucket, path)))
	return -1;
    got_sigpipe = 0;
    r = __ftpfs_chdir (bucket, remote_path);
    free (remote_path);
    if (got_sigpipe)
	goto retry;
    
    vfs_add_noncurrent_stamps (&ftpfs_vfs_ops, (vfsid) bucket, NULL);
    return r == COMPLETE ? 0 : -1;
}

static int ftpfs_lseek (void *data, off_t offset, int whence)
{
    struct ftpfs_filp *fp = data;

    return lseek(fp->local_handle, offset, whence);
}

static int ftpfs_mknod (char *path, int mode, dev_t dev)
{
    ftpfserrno = EPERM;
    return -1;
}

static int ftpfs_mkdir (char *path, unsigned long mode)
{
    return send_ftp_command(path, "MKD %s", 1);
}

static int ftpfs_rmdir (char *path)
{
    return send_ftp_command(path, "RMD %s", 1);
}

static int ftpfs_link (char *p1, char *p2)
{
    ftpfserrno = EPERM;
    return -1;
}

static vfsid ftpfs_getid (char *p, struct vfs_stamping **parent)
{
    struct ftpfs_connection *bucket;
    char *remote_path;

    *parent = NULL; /* We are not enclosed in any other fs */
    
    if (!(remote_path = ftpfs_get_path (&bucket, p)))
        return (vfsid) -1;
    else {
	free(remote_path);
    	return (vfsid) bucket;
    }
}

static int ftpfs_nothingisopen (vfsid id)
{
    return qlock((struct ftpfs_connection *)id) == 0;
}

static void ftpfs_free (vfsid id)
{
    struct ftpfs_connection *bucket = (struct ftpfs_connection *) id;
 
    ftpfs_connection_destructor(bucket);
    linklist_delete(ftpfs_connections_list, bucket);
}

static char *ftpfs_getlocalcopy (char *path)
{
    struct ftpfs_filp *fp = (struct ftpfs_filp *) ftpfs_open (path, O_RDONLY, 0);
    char *p;
    
    if (fp == NULL)
        return NULL;
    if (fp->fe->local_filename == NULL) {
        ftpfs_close ((void *) fp);
        return NULL;
    }
    p = (char *) strdup (fp->fe->local_filename);
    qlock(fp->fe->bucket)++;
    fp->fe->count++;
    ftpfs_close ((void *) fp);
    return p;
}

static void ftpfs_ungetlocalcopy (char *path, char *local, int has_changed)
{
    struct ftpfs_filp *fp = (struct ftpfs_filp *) ftpfs_open (path, O_WRONLY, 0);
    
    if (fp == NULL)
        return;
    if (!strcmp (fp->fe->local_filename, local)) {
        fp->has_changed = has_changed;
        qlock(fp->fe->bucket)--;
        ftpentry_destructor(fp->fe);
        ftpfs_close ((void *) fp);
    } else {
        /* Should not happen */
        ftpfs_close ((void *) fp);
        mc_def_ungetlocalcopy (path, local, has_changed);
    }
}

void ftpfs_set_debug (char *file)
{
    if ((ftpfs_logfile = fopen (file, "w+")) != NULL)
	ftpfs_debug_server_dialog = 1;
}

void ftpfs_forget (char *file)
{
    struct linklist *l;
    char *host, *user, *pass, *rp;
    int port;

    if (strncmp (file, "ftp:" PATH_SEP_STR PATH_SEP_STR, 6) != 0)
	return;

    file += 6;
    if (!(rp = ftpfs_get_host_and_username (file, &host, &user, &port, &pass))) {
        free (host);
	free (user);
	if (pass)
	    wipe_password (pass);
	return;
    }

    /* we do not care about the path actually */
    free (rp);
    
    for (l = ftpfs_connections_list->next; l != ftpfs_connections_list; l = l->next){
	struct ftpfs_connection *bucket = l->data;
	
	if ((strcmp (host, qhost (bucket)) == 0) &&
	    (strcmp (user, quser (bucket)) == 0) &&
	    (port == qport (bucket))){
	    
	    /* close socket: the child owns it now */
	    shutdown (bucket->sock, 2);
	    bucket->sock = -1;

	    /* reopen the connection */
	    bucket->sock = ftpfs_open_socket (bucket);
	    if (bucket->sock != -1)
		login_server (bucket, pass);
	    break;
	}
    }
    free (host);
    free (user);
    if (pass)
        wipe_password (pass);
}

#ifdef HAVE_MMAP
caddr_t ftpfs_mmap (caddr_t addr, size_t len, int prot, int flags, void *data, off_t offset)
{
    return (caddr_t)-1; /* We do not mmap to far away */
}

int ftpfs_munmap (caddr_t addr, size_t len, void *data)
{
    return -1;
}
#endif

vfs ftpfs_vfs_ops = {
    ftpfs_open,
    ftpfs_close,
    ftpfs_read,
    ftpfs_write,
    
    ftpfs_opendir,
    ftpfs_readdir,
    ftpfs_closedir,

    ftpfs_stat,
    ftpfs_lstat,
    ftpfs_fstat,

    ftpfs_chmod,
    ftpfs_chown,
    NULL,

    ftpfs_readlink,
    ftpfs_symlink,
    ftpfs_link,
    ftpfs_unlink,

    ftpfs_rename,
    ftpfs_chdir,
    ftpfs_errno,
    ftpfs_lseek,
    ftpfs_mknod,
    
    ftpfs_getid,
    ftpfs_nothingisopen,
    ftpfs_free,
    
    ftpfs_getlocalcopy,
    ftpfs_ungetlocalcopy,

    ftpfs_mkdir,
    ftpfs_rmdir,
    ftpfs_ctl,
    ftpfs_setctl,
    ftpfs_forget
#ifdef HAVE_MMAP
    , ftpfs_mmap,
    ftpfs_munmap
#endif
};

#ifdef USE_NETRC
static char buffer[100];
static char *netrc, *netrcp;

static int netrc_next (void)
{
    char *p;
    int i;
    static char *keywords [] = { "default", "machine", 
        "login", "password", "passwd",
        "account", "macdef" };

    while (1) {
        netrcp = skip_separators (netrcp);
        if (*netrcp != '\n')
            break;
        netrcp++;
    }
    if (!*netrcp)
	return 0;
    p = buffer;
    if (*netrcp == '"') {
	for (;*netrcp != '"' && *netrcp; netrcp++) {
	    if (*netrcp == '\\')
		netrcp++;
	    *p++ = *netrcp;
	}
    } else {
	for (;*netrcp != '\n' && *netrcp != '\t' && *netrcp != ' ' &&
	    *netrcp != ',' && *netrcp; netrcp++) {
	    if (*netrcp == '\\')
		netrcp++;
	    *p++ = *netrcp;
	}
    }
    *p = 0;
    if (!*buffer)
	return 0;
    for (i = 0; i < sizeof (keywords) / sizeof (keywords [0]); i++)
	if (!strcmp (keywords [i], buffer))
	    break;
    return i + 1;
}

int lookup_netrc (char *host, char **login, char **pass)
{
    char *netrcname, *tmp;
    char hostname[MAXHOSTNAMELEN], *domain;
    int keyword;
    struct stat mystat;
    static int be_angry = 1;
    static struct rupcache {
        struct rupcache *next;
        char *host;
        char *login;
        char *pass;
    } *rup_cache = NULL, *rupp;

    for (rupp = rup_cache; rupp != NULL; rupp = rupp->next)
        if (!strcmp (host, rupp->host)) {
            if (rupp->login != NULL)
                *login = (char *) strdup (rupp->login);
            if (rupp->pass != NULL)
                *pass = (char *) strdup (rupp->pass);
            return 0;
        }
    netrcname = malloc (strlen (home_dir) + strlen (PATH_SEP_STR ".netrc") + 1);
    strcpy (netrcname, home_dir);
    strcat (netrcname, PATH_SEP_STR ".netrc");
    netrcp = netrc = load_file (netrcname);
    if (netrc == NULL) {
        free (netrcname);
	return 0;
    }
    if (gethostname (hostname, sizeof (hostname)) < 0)
	*hostname = 0;
    if (!(domain = strchr (hostname, '.')))
	domain = "";

    while ((keyword = netrc_next ())) {
        if (keyword == 2) {
	    if (netrc_next () != 8)
		continue;
	    if (strcasecmp (host, buffer) &&
	        ((tmp = strchr (host, '.')) == NULL ||
		strcasecmp (tmp, domain) ||
		strncasecmp (host, buffer, tmp - host) ||
		buffer [tmp - host]))
		continue;
	} else if (keyword != 1)
	    continue;
	while ((keyword = netrc_next ()) > 2) {
	    switch (keyword) {
		case 3:
		    if (netrc_next ())
			if (*login == NULL)
			    *login = (char *) strdup (buffer);
			else if (strcmp (*login, buffer))
			    keyword = 20;
		    break;
		case 4:
		case 5:
		    if (strcmp (*login, "anonymous") && strcmp (*login, "ftp") &&
			stat (netrcname, &mystat) >= 0 &&
			(mystat.st_mode & 077)) {
			if (be_angry) {
			    message_1s (1, "Error", "~/.netrc file has not correct mode.\n"
					         "Remove password or correct mode.");
			    be_angry = 0;
			}
			free (netrc);
			free (netrcname);
			return -1;
		    }
		    if (netrc_next () && *pass == NULL)
			*pass = (char *) strdup (buffer);
		    break;
		case 6:
		    if (stat (netrcname, &mystat) >= 0 && 
		        (mystat.st_mode & 077)) {
			if (be_angry) {
			    message_1s (1, "Error", "~/.netrc file has not correct mode.\n"
					            "Remove password or correct mode.");
			    be_angry = 0;
			}
			free (netrc);
			free (netrcname);
			return -1;
		    }
		    netrc_next ();
		    break;
		case 7:
		    for (;;) {
		        while (*netrcp != '\n' && *netrcp);
		        if (*netrcp != '\n')
		            break;
		        netrcp++;
		        if (*netrcp == '\n' || !*netrcp)
		            break;
		    }
		    break;
	    }
	    if (keyword == 20)
	        break;
	}
	if (keyword == 20)
	    continue;
	else
	    break;
    }
    rupp = (struct rupcache *) malloc (sizeof (struct rupcache));
    rupp->host = (char *) strdup (host);
    rupp->login = rupp->pass = 0;
    
    if (*login != NULL)
        rupp->login = (char *) strdup (*login);
    if (*pass != NULL)
        rupp->pass = (char *) strdup (*pass);
    rupp->next = rup_cache;
    rup_cache = rupp;
    
    free (netrc);
    free (netrcname);
    return 0;
}

#ifndef HAVE_STRNCASECMP
int strncasecmp (char *s, char *d, int l)
{
    int result;
    
    while (l--){
	if (result = (0x20 | *s) - (0x20 | *d))
	    break;
	if (!*s)
	    return 0;
	s++;
	d++;
    }
}
#endif
#endif /* USE_NETRC */
