/* Declarations for wput.
   Copyright (C) 2003
   This file is part of wput.

   The wput 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.1 of the License, or (at your option) any later version.

   The wput 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.

   You should have received a copy of the GNU General Public
   License along with the wput; if not, write to the Free
   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
   02111-1307 USA.  */

/* This file contains procedures for interacting with the FTP-Server */

#define ISDIGIT(x) ((x) >= '0' && (x) <= '9')

#include "ftp.h"
#include "utils.h"
#include "progress.h"
#include "windows.h"
#include "wpsocket.h"
#include "_queue.h"


directory_list * add_directory(directory_list * A, struct fileinfo * K) {
    if(A == NULL) {
        A = (directory_list *) malloc(sizeof(directory_list));
        A->list = K;
        A->name = cpy(cc.current_directory);
        A->next = NULL;
    } else
        A->next = add_directory(A->next, K);
    return A;
}
void fileinfo_free(void) {
    directory_list  * K = cc.directorylist;
    directory_list  * L;
    struct fileinfo * M;
    struct fileinfo * N;
    while(K != NULL) {
        L = K->next;
        free(K->name);
        M = K->list;
        while(M != NULL) {
            N = M->next;
            if(M->name) free(M->name);
            free(M);
            M = N;
        }
        free(K);
        K = L;
    }
}
    
struct fileinfo * fileinfo_find_file(struct fileinfo * F, char * name) {
    while(F != NULL) {
        if( !strcmp(F->name, name) ) return F;
        F = F->next;
    }
    return NULL;
}
struct fileinfo * find_directory(_fsession * F) {
    directory_list * K = cc.directorylist;
    while(K != NULL) {
        if( !strcmp(K->name, F->target_dname) ) return K->list;
        K = K->next;
    }
    return NULL;
}


/* send the QUIT command and close the sockets */
void do_quit(){
	/* if a connection failed we might write on a closed pipe, so check
	 * whether writing is possible */
	if(isDataWriteable(cc.csock, 1)) {
		issue_cmd("QUIT", 0);
		/* no further error-checking required. we close the connection anyway.
		* so this would not be even necessary. it's just for the ftp-servers to
		* recognize us leaving */
		get_msg();
	}
	#ifdef WIN32
	shutdown(cc.s_socket, SD_BOTH);
	shutdown(cc.data_socket, SD_BOTH);
	shutdown(cc.csock, SD_BOTH);
	#endif
	if(cc.s_socket != -1) {
		closesocket(cc.s_socket);
		cc.s_socket = -1;
	}
	if(cc.data_socket != -1) {
		closesocket(cc.data_socket);
		cc.data_socket = -1;
	}
	if(cc.csock != -1) {
		closesocket(cc.csock);
		cc.csock = -1;
	}
	if(cc.current_directory)  free(cc.current_directory);
	if(cc.hostname)           free(cc.hostname);
	if(cc.r.reply)            free(cc.r.reply);
	cc.current_directory = cc.hostname = cc.r.reply = NULL;
	fileinfo_free();
}

/* send an ABOR command and clear the data-socket */
int do_abor(){
	int res;
	printout(vMORE, "==> ABOR ... ");
	issue_cmd("ABOR", 0);
	res = get_msg();
	/* if we have to do an abor, we usually have a failed connection, 
	* so the control connection is certainly failed as well... */
	if(SOCKET_ERROR(res)) {
		printout(vMORE, "failed (1).\n");
		return ERR_RECONNECT;
	}
	printout(vMORE, "done.\n");
	if(cc.r.code == 426) {
		printout(vNORMAL, "Connection cancelled (%s)\n", cc.r.message);
		res = get_msg();
	}
	if(cc.data_socket != -1) {
		closesocket(cc.data_socket);
		cc.data_socket = -1;
	}
	return res;
}
/* performs the initial login */
/* error-levels: ERR_FAILED, get_msg() */
int do_login(_fsession * fsession){
	int res = 0;
	
	issue_cmd("USER", fsession->user);
	res = get_msg();
	if(SOCKET_ERROR(res)) return res;
	
	/* rfc states: 331 need password
	 *             332 need account TODO USS (do we need support for this?)
	 *             230 logged in */
	if(cc.r.code >= 300) {
	/* if we have no password (e.g. ftp://guest@host) the PASS command is
	 * left out unless the server explicitly requires it.
	 * if we have a "" one (e.g. ftp://guest:@host) we'll send it */

		if(cc.r.code == 331) {
        	if(!fsession->pass) {
          		printout(vMORE, "Warning: remote server requires a password, but none set. Using an empty one\n");
			}
	    	issue_cmd("PASS", fsession->pass ? fsession->pass : "");
	    	res = get_msg();
			if(SOCKET_ERROR(res)) return res;
		
		} else if(cc.r.code == 332) {
			printout(vLESS, "Error: Server requires account login, which is not supported\n");
			return ERR_FAILED;
		}
	}
	if(cc.r.code != 230) {
		printout(vLESS, "Error: Login-Sequence failed (%d %s)\n", cc.r.code, cc.r.message);
		return ERR_FAILED;
	}
	cc.loggedin = 1;
	return 0;
}
/* tries to find out which system is running remotely.
 * mainly of interest for parsing the LIST reply */
int do_syst(void) {
	int res;

	if(cc.OS != ST_UNDEFINED) return 0;
	
	issue_cmd("SYST", 0);
	res = get_msg();
	if(res == ERR_PERMANENT) {
		cc.OS = ST_OTHER;
		return 0;
	} else if(res < 0) return res;

	cc.OS = ST_OTHER;

#define OS_CMP( mess, os) strncasecmp( mess, os, strlen( os))

	if( (cc.r.code/ 100) == 2 ) {
		if (!OS_CMP( cc.r.message, "VMS"))
			cc.OS = ST_VMS;
		else if (!OS_CMP( cc.r.message, "UNIX"))
			cc.OS = ST_UNIX;
		else if (!OS_CMP( cc.r.message, "WINDOWS_NT"))
			cc.OS = ST_WINNT;
		else if (!OS_CMP( cc.r.message, "MACOS"))
			cc.OS = ST_MACOS;
		else if (!OS_CMP( cc.r.message, "OS/400"))
			cc.OS = ST_OS400;
	}
	printout(vDEBUG, "Operating System (enum): %d\n", cc.OS);
	return 0;
}
/* add a skip entry for the particular host and location */
void makeskip(_fsession * fsession, char * tmp) {
	char * ptr = strtok(0x0, "/");
	int    len = ((ptr != 0x0) ? ptr-tmp : strlen(fsession->target_dname));
	char * skipdname = malloc(len+1);
	printout(vDEBUG, "makeskip: %s (%s)\n", tmp, ptr);
	strncpy(skipdname, fsession->target_dname, len);
	skipdname[len] = 0;
	printout(vDEBUG, "len: %d -> path: %s (ptr: %s) => skipd: %s\n", ptr-tmp, tmp, ptr, skipdname);
	/* tmp is a malloced buffer. free it */
	free(tmp);

	opt.skipdlist = skiplist_add_entry(opt.skipdlist, fsession->ip,
		fsession->hostname ? cpy(fsession->hostname) : NULL,  fsession->port,
		cpy(fsession->user), fsession->pass ? cpy(fsession->pass) : NULL, skipdname);
}

/* if direct cwding fails for some reason, try the long way.
 * directiories that do not exist yet are being created if possible */
/* error-levels: ERR_FAILED, ERR_RECONNECT */
int long_do_cwd(_fsession * fsession){
	int res = 0;
	
	char * unescaped = cpy(fsession->target_dname);
	char * tmpbuf = unescaped;
	char * ptr;
	
	clear_path(unescaped);
	if(cc.current_directory) {
		unescaped = get_relative_path(cc.current_directory, unescaped);
		free(tmpbuf);
		tmpbuf = unescaped;
	}
	
	/* unescape up to the '/' char. target_dname is "dir1/dir2/dir3"
	 * ftp-urls like ftp://server/<dir1>/<dir2>/<dirN>/ will be executed as
		CWD <dir1>
		CWD <dir2>
		CWD <dirN>
	 */
	tmpbuf = strtok(tmpbuf, "/");
	while(tmpbuf) {
		ptr = unescape(cpy(tmpbuf));
			
		res = try_do_cwd(fsession, tmpbuf, res);
		free(ptr);
		if(res == ERR_FAILED) {
			ptr = strtok(unescaped, tmpbuf);
			makeskip(fsession, ptr);
		}
		if(res == ERR_RECONNECT || res == ERR_FAILED) {
			free(unescaped);
			return res;
		}
		tmpbuf = strtok(NULL, "/");
	}
	free(unescaped);
	return 0;
}
/* first try to change to the directory. if it fails, try to change to the
 * root-directory if path begins with '/'. is this successful, try to MKDIR
 * path and change there again */
/* error-levels: ERR_RECONNECT, ERR_FAILED, 1 */
int try_do_cwd(_fsession * fsession, char * path, int mkd) {
	int res;
	if(!strcmp(path, "."))
		return mkd;
	if(!strcmp(path, ".."))
		mkd = 0;
	/* don't even try to CWD if we know that we had to create the 
	 * prior directories... */
	if(!mkd) {
		printout(vMORE, "==> CWD %s", path);
		issue_cmd("CWD", path);
	
		res = get_msg();
		if(SOCKET_ERROR(res))
			return ERR_RECONNECT;
	}
	
    if(cc.r.code != 250 || mkd) {
		printout(vMORE, " failed (%s).\n", cc.r.message);
		if(path[0] == '/') {
			printout(vMORE, "==> CWD /");	
			issue_cmd("CWD", 0);
			
			res = get_msg();
			if(SOCKET_ERROR(res))
				return ERR_RECONNECT;
		
			if(cc.r.code != 250) {
				/* cannot even change dir to root dir */
				printout(vMORE, " failed (%s).\n", cc.r.message);
				makeskip(fsession, "/");
				return ERR_FAILED;
			}
			printout(vMORE, "\n");
			path++;
		}
		
		/* this is vNORMAL because the user should get a notice
		 * when a remote directory is created... */
		printout(vNORMAL, "==> MKD %s", path);
		issue_cmd("MKD", path);
		res = get_msg();
		if(SOCKET_ERROR(res))
			return ERR_RECONNECT;
			
		if( cc.r.code != 257 ) {
			printout(vNORMAL, " failed (%s).\n", cc.r.message);
			return ERR_FAILED;
		}
		printout(vNORMAL, "\n");
		mkd = 1; 

		printout(vMORE, "==> CWD %s", path);
		issue_cmd("CWD", path);
		res = get_msg();
		if(SOCKET_ERROR(res))
			return ERR_RECONNECT;
			
		if(cc.r.code != 250) {
			printout(vMORE, " failed (%s)\n", cc.r.message);
			return ERR_FAILED;
		}
	}
	printout(vMORE, "\n");
	/* return 1 if we successfully created a directory, 0 if everything went allright */
	return mkd;
/*
  if(opt.current_directory && opt.relative_cwds) 
    tmpbuf = get_relative_path(opt.current_directory, fsession->target_dname);
  else {
    tmpbuf = malloc(strlen(fsession->target_dname)+1);
    strcpy(tmpbuf, fsession->target_dname);
  }*/
}

/* try direct cwding. this is not rfc-conform, but works in
 * most cases and usually safes time. if it fails we fall back
 * on the safe method */
/* error-levels: ERR_FAILED, ERR_RECONNECT */
int do_cwd(_fsession * fsession){
	int res;
	/* by unescaping, we get in trouble with certain characters (such as /)
	* thats why this method might fail sometimes. it also fails if the
	* directory does not exist or is a file */
	char * unescaped = unescape(cpy(fsession->target_dname));
	char * relative;
	clear_path(unescaped);
	if(cc.current_directory) clear_path(cc.current_directory);
	
	printout(vDEBUG, "previous directory: %s\ttarget: %s\n", cc.current_directory, unescaped);
	if(cc.current_directory) {
		relative = get_relative_path(cc.current_directory, unescaped);
		free(unescaped);
		unescaped = relative;
	}
	
	printout(vMORE, "==> CWD %s", unescaped);
	issue_cmd("CWD", unescaped);
	free(unescaped);
	
	res = get_msg();
	if(SOCKET_ERROR(res))
		return ERR_RECONNECT;
		
	if(cc.r.code != 250) {
		printout(vMORE, " failed (%s).\n", cc.r.message);
		return ERR_FAILED;
	}
	printout(vMORE, " done.\n");
	return 0;
}
/* i allways have to search hours until i find the document. save time:
 * http://www.ietf.org/internet-drafts/draft-ietf-ftpext-mlst-16.txt */
/* error-levels: ERR_FAILED, get_msg */
int do_mdtm(_fsession * fsession) {
	int res;
	struct fileinfo * finfo;
	struct tm ts;
	/* if we already have a directory-listing, we can obtain the modification
	 * time from there, otherwise it is better to use the MDTM command if avail-
	 * able */
	/* TODO NRV this is less efficient if the modification time has to be checked
	 * TODO NRV for a huge amount of files of the same directory */
	if(!fsession->directory) {
		printout(vMORE, "==> MDTM %s ... ", fsession->target_fname);
		issue_cmd("MDTM", fsession->target_fname);
		res = get_msg();
		if(SOCKET_ERROR(res)) return res;
		/* if the file does not exist remotely, this is ok for us */
		if(cc.r.code == 213) {
			//JJJJMMDDHHMMSS
#define MDTM_AT(x) atoi(cc.r.message+x); cc.r.message[x] = 0
			ts.tm_sec   = MDTM_AT(12);
			ts.tm_min   = MDTM_AT(10);
			ts.tm_hour  = MDTM_AT( 8);
			ts.tm_mday  = MDTM_AT( 6);
			ts.tm_mon   = -1+MDTM_AT( 4);
			ts.tm_year  = atoi(cc.r.message) - 1900;
			ts.tm_wday  = 0;
			ts.tm_yday  = 0;
			ts.tm_isdst = -1;
			fsession->target_ftime = mktime(&ts);
			printout(vMORE, "done (modified on %d.%d.%d at %d:%d:%d)\n", ts.tm_mday, 
				ts.tm_mon, ts.tm_year+1900, ts.tm_hour, ts.tm_min, ts.tm_sec);
			return 0;
		}
		printout(vMORE, "failed (2).\n");
		if(cc.r.code == 550)
			return 0;
		res = get_list(fsession);
		if(SOCKET_ERROR(res) || res == ERR_FAILED) return res;
	}
	finfo = fileinfo_find_file(fsession->directory, fsession->target_fname);
	if(!finfo) return 0;
	fsession->target_ftime = finfo->tstamp;
	return 0;
}

/* retrieve the filesize. if the SIZE command is not available retrieve
 * a directory-listing and get the size it from there */
/* error-levels: ERR_FAILED, ERR_TIMEOUT, ERR_RECONNECT */
int do_size(_fsession * fsession){
	int res;
	struct fileinfo * finfo;

	/* in certain situations we don't need to SIZE, because we starting from
	* the beginning anyway */
	if(opt.resume_table.large_small == RESUME_TABLE_UPLOAD &&
		opt.resume_table.large_large == RESUME_TABLE_UPLOAD &&
		opt.resume_table.small_large == RESUME_TABLE_UPLOAD) {
		printout(vDEBUG, "Skipping SIZE\n");
		fsession->target_fsize = 0;
		return 0;
	}
	
	if(!fsession->directory) {
		printout(vMORE, "==> SIZE %s ...", fsession->target_fname);
		issue_cmd("SIZE", fsession->target_fname);
		res = get_msg();
		if(SOCKET_ERROR(res)) return res;
		/* TODO USS there might be other codes for 'file not found' */
		if(cc.r.code == 213) {
			printout(vMORE, "done (%s bytes)\n", cc.r.message);
			fsession->target_fsize = strtoll(cc.r.message, NULL, 10);
			return 0;
		}
		printout(vMORE, "failed (3).\n");
		if(cc.r.code == 550)
			return 0;
		
		/* otherwise try LIST method */
		res = get_list(fsession);
		if(SOCKET_ERROR(res) || res == ERR_FAILED) return res;
	}
	finfo = fileinfo_find_file(fsession->directory, fsession->target_fname);
	if(!finfo) return 0;
	fsession->target_ftime = finfo->tstamp;
	return 0;
}
/* try to establish a data-connection.
 * either port or pasv mode based upon commandline preference / default-values
 * fall back if either won't work */
/* error-levels: ERR_FAILED, get_msg() */
int create_data_socket(_fsession * fsession){
  int res;
  printout(vDEBUG, "Portmode: %d\n", opt.portmode);
  if(!opt.portmode){
    res = do_passive(fsession);
    if(res == ERR_FAILED){
	  /* revert back to port mode if passive mode fails,
	   * and adjust our settings, so that we don't need
	   * to try passive again */
      res = do_port(fsession);
      if(res < 0) return res;
	  opt.portmode = 1;
    }
  }
  else{
    res = do_port(fsession);
    if(res == ERR_FAILED){ /* s.a. */
      res = do_passive(fsession);
      if(res < 0) return res;
	  opt.portmode = 0;
    }
  }

  return res;
}

/* retrieve a LIST of the current directory */
/* error-levels: ERR_FAILED, get_msg() */

/* Global (hence stable) pseudo file pointers for Wget ftp_parse_ls(). */
char *ls_list;
char *ls_next;

int get_list(_fsession * fsession) {
  int res;
  int size = 1;
  char rbuf[1024];

  /* look it up in the list we already have */
  fsession->directory = find_directory(fsession);
  if(fsession->directory != NULL) return 0;

  /* retrieve the remote system if not done yet */
  res = do_syst();
  if(res < 0) return res;
  
  res = create_data_socket(fsession);
  if(res < 0) {
    printout(vLESS, "Error: Cannot initiate data-connection (%s)\n",
		FTP_ERROR(res) || res == ERR_FAILED ? cc.r.message : "");
    return res;
  }

  res = do_list(fsession);
  if(res < 0) return res;

  /* start buffer-size. is being reallocated as soon as data arrive */
  ls_list = malloc(1);
  ls_list[0] = 0;

  /* until the socket is done, add everything to the list-buffer */
  while( (res = read_for_sure(cc.data_socket, rbuf, 1024-1)) > 0) {
    rbuf[res] = 0;
    size += res;
    ls_list = realloc(ls_list, size);
    strcat(ls_list, rbuf);
  }

  /* make sure the socket gets closed correctly. */
  /* TODO NRV need to check error-level here too? */
  if(res == ERR_TIMEOUT) {
	res = do_abor();
	if(SOCKET_ERROR(res)) return ERR_RECONNECT;
  }

  #ifdef WIN32
  shutdown(cc.data_socket, SD_BOTH);
  #endif

  if(cc.data_socket) {
      close(cc.data_socket);
      cc.data_socket = -1;
  }
  /* receive the last message about list-completion.
   * allow 1xy messages, since they might have been discarded before... */
  do
  	res = get_msg();
  while(res == ERR_POSITIVE_PRELIMARY);

  if(res == ERR_TIMEOUT) {
	/* maybe the connection hangs. try again */
  	res = do_abor();
  }
  if(FTP_ERROR(res)) {
	free(ls_list);
	printout(vLESS, "Error while listing Directory (%s)\n", cc.r.message);
	return res;
  } else if(res < 0) {
    free(ls_list);
	return res;
  }

  printout(vDEBUG, "Directory-Listing:\n%s\n-----\n", ls_list);

  /* Set up pseudo file pointer for read_whole_line(). */
  ls_next = ls_list;
  fsession->directory = ftp_parse_ls (ls_list, cc.OS);
  free(ls_list);
  /* add it to the list of known directories */
  cc.directorylist = add_directory(cc.directorylist, fsession->directory);
  return 0;
}

/* 2003-12-09 SMS.
 * Pseudo file read-line function for Wget ftp_parse_ls().
 * Argument is for compatibility only, and is ignored.
 */
char * read_whole_line( FILE *fp)
{
  static char *ls_line;
  char *nl;

  ls_line = ls_next;
  nl = strchr( ls_line, '\n');

  if (nl == NULL)
  {
    return NULL;
  }
  else
  {
    *nl = '\0';
    ls_next = nl+ 1;
    return ls_line;
  }
}

/* 2003-12-09 SMS.
 * Pseudo file one-character look-ahead function for Wget ftp_parse_ls().
 */
char nextchr( void)
{
  return *ls_next;
}


/* finally this is about actually transmitting the file.
 * putting it through the socket and giving status information to the logfile */
/* TODO NRV do_send() contains a lot of code. maybe too much? */
/* error-levels: ERR_FAILED, get_msg() */
int do_send(_fsession * fsession){

#define DBUFSIZE 1024

	int         fd;
	FILE *      pipe        = NULL;
	char        databuf[DBUFSIZE];
	int         readbytes   = 0;
	int         res         = 0;
	UINT64      transfered_size = 0;
	int         transfered_last = 0;
	int         oflags      = O_RDONLY;
	unsigned char backupbarstyle = opt.barstyle;
	
	struct wput_timer * timers[2];
	
	char * d                = NULL;
	char * p                = NULL;
	int    convertbytes     = 0;
	char   convertbuf[DBUFSIZE];
	int    crcount          = 0;
	
		
	/* handle the resume_table. urgs ugly. TODO NRV better idea?
	* TODO NRV recheck this. make it smaller, easier */
	
	if(fsession->local_fsize > fsession->target_fsize && opt.resume_table.large_small == RESUME_TABLE_UPLOAD) {
		fsession->target_fsize = 0;
		printout(vMORE, "Remote file size is smaller than local size. Restarting at 0\n");
	}
	else if(fsession->local_fsize == fsession->target_fsize && fsession->local_fname && opt.resume_table.large_large == RESUME_TABLE_UPLOAD) {
		fsession->target_fsize = 0;
		printout(vMORE, "Remote file size is equal to local size. Restarting at 0\n");
	}
	else if(fsession->local_fsize < fsession->target_fsize && opt.resume_table.small_large == RESUME_TABLE_UPLOAD) {
		fsession->target_fsize = 0;
		printout(vMORE, "Remote file size is smaller than local size. Restarting at 0.\n");
	}

	/* check timestamp if whished */
	if(opt.timestamping) {
 		res = do_mdtm(fsession);
		if(SOCKET_ERROR(res)) return res;
		/* this is for getting our local ftime in UTC+0 format as ftp-servers
		 * usually issue. add the time-deviation to permit little clock-skews */
		/* TODO USS time_deviation? + or - ? */
		fsession->local_ftime = mktime(gmtime(&fsession->local_ftime)) - opt.time_deviation;
		printout(vDEBUG, "timestamping: local: %d seconds\n"
		                "             remote: %d seconds; diff: %d\n",
			fsession->local_ftime, fsession->target_ftime,
			(int) (fsession->local_ftime - fsession->target_ftime));
		printout(vDEBUG, "timestamping: local: %s", ctime(&fsession->local_ftime));
		printout(vDEBUG, "              remote: %s", ctime(&fsession->target_ftime));
		if(fsession->target_ftime > fsession->local_ftime) {
			printout(vLESS, "-- Skipping file: %s (remote is newer)\n", fsession->local_fname);
			fsession->done = 1;
			return 0;
		}
	}

	if(fsession->binary == TYPE_UNDEFINED)
		fsession->binary = get_filemode(fsession->target_fname);
	
	res = do_type(fsession->binary);
	if(SOCKET_ERROR(res)) return res;
	if(res == ERR_FAILED)
		printout(vMORE, "Unable to set transfer mode. Assuming binary\n");
	
	
	/* don't know why */
	#if defined(O_BINARY)
	if(fsession->binary == TYPE_I) oflags = oflags | O_BINARY;
	#endif
	
	/* open the input-stream. either file or a pipe */
	if(fsession->local_fname) {
		if((fd=open(fsession->local_fname, oflags)) == -1) {
			printout(vLESS, "Error: Cannot open local source file to read\n");
			fsession->done = 1;
			return ERR_FAILED;
		}
	} else if(opt.input_pipe) {
		char * cmd = (char *) malloc(
			strlen(opt.input_pipe)
			+ strlen(fsession->user)
			+ (fsession->ip ? 15 : strlen(fsession->hostname)) + 5 /*port*/
			+ strlen(fsession->target_dname)
			+ strlen(fsession->target_fname)
			+ 18);
		sprintf(cmd, "%s ftp \"%s\" \"%s\" %d \"%s\" \"%s\"",
			opt.input_pipe, fsession->user,
			(fsession->ip ? printip( (unsigned char *) &fsession->ip) : fsession->hostname),
			fsession->port, fsession->target_dname, fsession->target_fname);
	
		pipe = popen(cmd, "r");
		free(cmd);
		if(pipe == NULL) {
			printout(vLESS, "Error opening pipe: %s\n", strerror(errno));
			fsession->done = 1;
			return ERR_FAILED;
		}
		fd = fileno(pipe);
		/* TODO USS make the progressbar show, that we don't know the filesize/eta */
		/* this is for other calculation that would fail if we set a to low value. 
		* assume that this will work for now, especially since these >2GB files don't
		* work anyway. use the old-fashioned progress-output which is at least more
		* remote-size independent than the new one. */
		fsession->local_fsize = 1024 * 1024 * 1024;
		opt.barstyle = 0;
	}
	
	res = create_data_socket(fsession);
	if(res < 0) return res;
	
	/* TODO USS make resuming work for ascii-files too */
	if(fsession->binary == TYPE_A) {
		printout(vDEBUG, "Disabling resuming due to ascii-mode transfer\n");
		fsession->target_fsize = 0;
	}
	
	while( (res = do_stor(fsession)) == ERR_RETRY) ;
	if(res == ERR_FAILED)
		fsession->done = 1;
	if(res < 0) return res;
	
	/* we now have to accept the socket (if listening) and close the listening server */
	if(opt.portmode) {
		/* TODO USS keep this ugly code away from this function */
		if(opt.proxy == PROXY_SOCKS && opt.proxy_bind) {
			char t[10];
			read_for_sure(cc.s_socket, t, 10);
			if(t[1] != 0) {
				/* this would usually be a permanent error and therefore wput
				 * might not be able to transfer any file. */
				/* TODO USS recheck error-level */
				printout(vLESS, "Error: Proxy encountered an error while accepting. Error-Code: %d\n", t[1]);
				close(fd);
				/* do not try portmode again */
				fsession->portmode = 0;
				return ERR_FAILED;
			}
			cc.data_socket = cc.s_socket;
			printout(vDEBUG,
 "Proxy received an incoming connection on %s:%d.\n",
                         printip( (unsigned char *) (t+4)), *(unsigned short int *) (t+8));
		} else {
			cc.data_socket = create_data_sockfd(cc.s_socket);
			if(cc.s_socket != -1)
				close(cc.s_socket);
		}
		cc.s_socket = -1;
	}
	
	/* initiate progress-output */
	bar_create(fsession);
	
	/* set start times */
	timers[0] = wtimer_alloc();
	timers[1] = wtimer_alloc();
	wtimer_reset(timers[0]);
	wtimer_reset(timers[1]);
	
	/* prepare resuming */
	if(fsession->target_fsize > 0) {
		/* TODO USS fseek in win (don't know about other OS) does not know 
		 * TODO USS what to do about files >4GB (__int64), so we need ideas
		 * TODO USS here ;) */
		 
		/* maybe try fseeko, fseeko64. huh? what's the standart? 
		 * or we could seek step-wise if size > 2gb */
	
		fseek(fdopen(fd,"r"),
	#ifdef WIN32
			(long) 
	#endif
			fsession->target_fsize, SEEK_SET);
		
		transfered_size = fsession->target_fsize;
	}
	
	memset(databuf, 0, DBUFSIZE);
	while( (readbytes = read(fd, (char *)databuf, DBUFSIZE)) != 0 ){    
		if( readbytes == ERR_FAILED ){
		printout(vLESS, "Error reading local file\n");
		free(timers[0]);
		free(timers[1]);
		return ERR_FAILED;
		}
	
		/* TODO NRV speed_limit should take other reference-data than the average speed... maybe average-speed during the last minute or st. similar */
		if(opt.speed_limit > 0 ) {
			double elapsed_time = wtimer_elapsed(timers[0]);
			while(elapsed_time > 0 && 1000 * WINCONV(transfered_size - fsession->target_fsize) / elapsed_time > opt.speed_limit) {
				usleep(1000 * 300); /* sleep 0.3 seconds */
				elapsed_time = wtimer_elapsed(timers[0]);
			}
		}
	
		if(fsession->binary == TYPE_A){
			d = databuf;
			p = convertbuf;
			crcount = 0;
			/* TODO NRV ascii mode means more than just CRLF (7-bit), i suppose this
			* TODO NRV is enough, but maybe someone has time to play around... */
			while( d < databuf + readbytes){
				while ((p < convertbuf + DBUFSIZE) && (d < databuf + readbytes)){
					if (*d == '\n' && *(d-1) != '\r'){
						*p = '\r'; p++;
						crcount++;
					}
					*p = *d;
					d++;
					p++;
				}
				/* send data converted so far */
				convertbytes = p - convertbuf;
				res = send(cc.data_socket, convertbuf, convertbytes, 0);
				if (res != convertbytes){
					printout(vLESS, "Error: Error encountered during uploading data\n");
					free(timers[0]);
					free(timers[1]);
					opt.transfered_bytes += transfered_size - fsession->target_fsize;
					res = do_abor();
					if(SOCKET_ERROR(res)) return ERR_RECONNECT;
					return ERR_FAILED;
				}
				/* reset convert buffer to proceed */
				p = convertbuf;
			}     
			transfered_size += crcount + readbytes;
			transfered_last += crcount + readbytes;
		}
		else {
			transfered_size += readbytes;
			transfered_last += readbytes;
			res = send(cc.data_socket, databuf, readbytes, 0);
			if(res != readbytes){
				printout(vLESS, "Error encountered during uploading data\n");
				free(timers[0]);
				free(timers[1]);
				opt.transfered_bytes += transfered_size - fsession->target_fsize;
				res = do_abor();
				if(SOCKET_ERROR(res)) return ERR_RECONNECT;
				return ERR_FAILED;
			}
		}
		if(opt.verbose >= vNORMAL) {
			bar_update(fsession, transfered_size, transfered_last, timers[1]);
			transfered_last = 0;
		}
	}
	
	if(!fsession->local_fname && opt.input_pipe)
		pclose(pipe);
	else if(fd != -1)
		close(fd);
	
	#ifdef WIN32
	shutdown(cc.data_socket,SD_BOTH);
	#endif
	if(cc.data_socket != -1) {
		closesocket(cc.data_socket);
		cc.data_socket = -1;
	}
	
	/* receive the final message. allow 1xy answers because they might have
	 * been timeouted in do_stor and it's ok if we receive them here */
	printout(vNORMAL, "\n");
	while( (res = get_msg()) == ERR_POSITIVE_PRELIMARY) ;
	
	printout(vNORMAL, "%s (%s) - `%s' [%l]\n\n",
			time_str(),
			fsession->target_fname,
			calculate_transfer_rate(
				wtimer_elapsed(timers[0]), 
				transfered_size - fsession->target_fsize, 0),
			(fsession->local_fname ? fsession->local_fsize : transfered_size));
	
	free(timers[0]);
	free(timers[1]);
	
	opt.transfered_bytes += transfered_size - fsession->target_fsize;
	opt.files_transfered++;
	
	if(transfered_size == fsession->local_fsize || fsession->binary == TYPE_A || !fsession->local_fname) fsession->done = 1;
	
	if(FTP_ERROR(res)) return ERR_FAILED;
	if(SOCKET_ERROR(res)) return res;
	
	if( fsession->local_fname &&
		(transfered_size == fsession->local_fsize || fsession->binary == TYPE_A) 
		&& opt.unlink) {
			printout(vMORE, "Removing source file `%s'\n", fsession->local_fname);
			unlink(fsession->local_fname);
	}
	
	opt.barstyle = backupbarstyle;
	
	return 0;
}
/* try building a connection in passive mode.
 * => connect to an ip/port that the server issued */
/* error-levels: ERRresp = cc.r.message;_RECONNECT, ERR_FAILED */
int do_passive(_fsession * fsession) {
	unsigned short sport = 0;
	unsigned int   sip   = 0;
	int res;
	
	printout(vMORE, "==> PASV ");
	issue_cmd("PASV", NULL);
	res = get_msg();
	if(SOCKET_ERROR(res)) return ERR_RECONNECT;
	
	if(cc.r.code != 227) {
		printout(vMORE, "failed (4).\n");
		return ERR_FAILED;
	}
	printout(vMORE, "done.\n");
	
	/* parse the line and extract ip and port from it */
	parse_passive_string(cc.r.message, &sip, &sport);
	printout(vDEBUG, "Remote server data port: %s:%d\n",
         printip( (unsigned char *) &sip), sport);
	
	if(opt.proxy == PROXY_OFF)
		cc.data_socket = create_new_reply_sockfd(sip, sport);
	else
		cc.data_socket = proxy_connect(sip, sport, NULL);
	
	if(cc.data_socket == -1) {
		printout(vMORE, "connection failed.\n");
		return ERR_FAILED;
	}
	
	return 0;
}
/* ask for directory-listing.
 * error-levels: ERR_RECONNECT, ERR_FAILED */
int do_list(_fsession * fsession) {

/* 2004-12-08 SMS.
 * ASCII mode is automatic for LIST.
 */
#if 0
	/* ascii mode is a good idea, esp for listings */
    do_type(TYPE_A);
#endif /* 0 */

    printout(vNORMAL, "==> LIST ... ");
    issue_cmd("LIST", NULL);
	if(SOCKET_ERROR(get_msg()))
		return ERR_RECONNECT;

    /* we get a 1xy (positive preliminary reply), so the
	 * data-connection is open, but server still wait for the
	 * connection to close unless it will issue the completion command */

/* 2004-12-08 SMS.
 * The reply seen here seems to be the final 2xx one, not the
 * preliminary 1xx one, so it always claimed a failure.
 */
/*
    if(cc.r.reply[0] != '1') {
*/
    if(cc.r.reply[0] != '2') {
        printout(vNORMAL, "failed (5).\n");
        return ERR_FAILED;
    }
    printout(vNORMAL, "done.\n");
    return 0;
}
/* set the transfer-mode to either ascii or binary */
/* error-levels: get_msg() */
int do_type(int type) {
	/* there are other types such as L 36 or E, but i think noone needs them */
	static char * types[] = {"A", "I"};
	int res;
	
	if(cc.current_type == type) return 0;
	
	printout(vMORE, "==> TYPE %s ... ", types[type]);
	issue_cmd("TYPE", types[type]);
	res = get_msg();
	
	if(cc.r.code == 200) {
		printout(vMORE, "done\n");
		cc.current_type = type;
		return 0;
	}
	printout(vMORE, "failed (6).\n");
	return res;
}
/* issue the STOR command which precedes the actual file-transmission
 * TODO IMP catch more errors that are not rfc-conform (such as 451 resuming not allowed. but from where to know? */
/* error-levels: ERR_FAILED (=> skip), ERR_RETRY, SOCKET_ERRORs */
int do_stor(_fsession * fsession){
  int res;
  char tmpbuf[21];
  /* try to resume if required */
  if( fsession->binary != TYPE_A &&
      fsession->target_fsize != 0 && 
      fsession->target_fsize < fsession->local_fsize &&
      opt.resume_table.large_small == RESUME_TABLE_RESUME){

      if(fsession->binary == TYPE_A)
          printout(vNORMAL, "Warning: The file is about to be resumed in ascii mode.\n"
                            "Resuming for Ascii-Mode transfers is known not to work.\n");
     printout(vMORE, "==> REST %d ... ", fsession->target_fsize);
     issue_cmd("REST", int64toa(fsession->target_fsize, tmpbuf, 10));
     res = get_msg();
	 if(SOCKET_ERROR(res))
	 	return ERR_RECONNECT;

	 if(cc.r.code != 350) {
       printout(vMORE, "failed.\nServer seems not to support resuming. Restarting at 0\n");
       fsession->target_fsize = 0;
     }
     printout(vMORE, "done.\n");
  }
  
  printout(vMORE, "==> STOR %s ", fsession->target_fname);
  issue_cmd("STOR", fsession->target_fname);
  res = get_msg();
  /* usually the ftp-server issues st. like 120 go, go, go! *
   * but this might be optional. so when getting a timeout on
   * this ,we just ignore it and try to open the data-connection
   * anyway, hoping for success */
  if(res == ERR_TIMEOUT) {
	printout(vMORE, "[not done, but should be allright]\n");
	return 0;
  } else if(SOCKET_ERROR(res))
	return res;
  else if(res == ERR_PERMANENT)
  	return ERR_FAILED;
  /* we want to distinguish the errors, because some might be
   * recoverable, others not. */
  /* rfc states: 
        125 Data connection already open; transfer starting.
        150 File status okay; about to open data connection.
        226 Closing data connection.
         => retry, but fail at most opt.retry times
        425 Can't open data connection.
         => retry, try switching port/pasv. max 3failures
        450 Requested file action not taken.
            File unavailable (e.g., file busy).
		 => sleep. retry. probably allow more failures.
        451 Requested action aborted: local error in processing.
		 => could mean resuming not allowed (these damned ftp-servers should
		    issue that message, when the REST command is issued)
        452 Requested action not taken.
            Insufficient storage space in system.
		 => fatal error? think so. skip this file
   * 1 is ok, 5 is cancel, 2 and 4 is st. like retry */
	if(cc.r.reply[0] == '1') {
		printout(vMORE, "done.\n");
		return 0;
	}
	if(cc.r.code == 451 && fsession->target_fsize > 0) {
		printout(vMORE, "failed (%s). (disabling resuming)\n", cc.r.message);
        fsession->target_fsize = 0;
		return ERR_RETRY;
	}
	if((cc.r.code == 450 || cc.r.code == 451 || cc.r.code == 226 || cc.r.code == 425) && fsession->retry != 0) {
		printout(vMORE, "failed (%s). ", cc.r.message, opt.retry_interval);
		if(cc.r.code == 425) {
			opt.portmode = ~opt.portmode;
			printout(vMORE, "\nTrying to switch PORT/PASV mode\n");
		}
		retry_wait(fsession);
		return ERR_RETRY;
	}
	printout(vMORE, "failed (%d %s). (skipping)\n", cc.r.code, cc.r.message);
	return ERR_FAILED;
}
/* listen locally (or on the proxy) and issue the PORT command */
/* error-levels: ERR_FAILED, SOCKET_ERRORs */
int do_port(_fsession * fsession){
  unsigned short int sport = 0;
  unsigned       int sip   = 0;
  int res;
  cc.s_socket = -1;
  if(opt.proxy == PROXY_SOCKS && opt.proxy_bind) {
      printout(vMORE, "Trying to listen on proxy server... ");
      cc.s_socket = proxy_listen(&sip, &sport);
      if(cc.s_socket == ERR_FAILED) {
          printout(vMORE, "failed. Falling back to listen locally\n"
                   "Warning: Unless FXP is enabled remotely, your control-connection "
                   "should be from the same IP-address, as your PORT bind-request. "
                   "So you should consider PASV-mode or reconnect without a proxy.\n"
                   );
			opt.proxy_bind = 0;
      } else
          printout(vMORE, "done.\n");
  } else if(opt.proxy == PROXY_HTTP) 
      printout(vNORMAL, "Warning: Using port-mode. Unable to use the http-proxy for this connection\n"); /* TODO IMP not only warn, but disable port-mode */

  if(cc.s_socket == -1)
    if(initialize_server_master(&cc.s_socket, &sport) < 0)
  	    return ERR_FAILED;

  printout(vMORE, "==> PORT ... ");
  issue_cmd("PORT", get_port_fmt( 
		(opt.proxy == PROXY_SOCKS && opt.proxy_bind) 
			? sip : opt.local_ip
	, sport));
  res = get_msg();
  if(SOCKET_ERROR(res)) return res;
  
  if(cc.r.code != 200) {
  	printout(vMORE, "failed (7).\n");
  	return ERR_FAILED;
  }
  printout(vMORE, "done.\n");

  return 0;
}
/* establish the connection for the control-socket */
/* error-levels: ERR_FAILED */
int do_connect(_fsession * fsession) {
	int res = 0;
	/* if we have a previous connection, close it before
	* creating a new one */
	if(cc.csock != -1) {
		printout(vMORE, "Closing connection\n");
		do_quit();
	}
	printout(vNORMAL, "Connecting to %s:%d... ",
         fsession->ip ? printip( (unsigned char *) &fsession->ip) : fsession->hostname,  fsession->port);
	if(opt.proxy != PROXY_OFF)
		cc.csock = proxy_connect(fsession->ip, fsession->port, fsession->hostname);
	else
		cc.csock = create_new_reply_sockfd(fsession->ip, fsession->port);
	
	if(cc.csock == -1) {
		printout(vNORMAL, "failed (8)!\n");
		return ERR_FAILED;
	}
	printout(vNORMAL, "connected");
	/* receive the first message of the ftp-server.
	* this should be done here, because otherwise the login-process has to do it
	* and if it fails, people think login failed */
	/* ignore 1xy message at the start-point */
	do
		res = get_msg();
	while(res == ERR_POSITIVE_PRELIMARY);
	if(SOCKET_ERROR(res))
		return ERR_FAILED;
	if(cc.r.code != 220) {
		printout(vNORMAL, "Connection failed (%s)\n", cc.r.message);
		return ERR_FAILED;
	}
	
	printout(vNORMAL, "!\n");
	
	/* We always need to log in and CWD on a new connection */
	cc.loggedin     = 0;
	cc.needcwd      = 1;
	cc.OS           = ST_UNDEFINED;
	cc.current_type = TYPE_UNDEFINED;
	
	cc.ip           = fsession->ip;
	cc.port         = fsession->port;
	cc.hostname     = fsession->hostname;
	
	if(opt.proxy != PROXY_OFF) {
		if(cc.hostname) {
			free(cc.hostname);
			cc.hostname = NULL;
		}
		if(fsession->hostname)
			cc.hostname = cpy(fsession->hostname);
	}
	
	printout(vDEBUG, "determing local ip_addr\n");
	res = get_local_ip(cc.csock, (char *) &opt.local_ip);
	/* TODO NRV is this so serious that it allows us to cancel the whole process? */
	if(res == ERR_FAILED) wpAbort("Cannot determine local IP address");
	printout(vDEBUG, "Local IP: %s\n",
         printip( (unsigned char *) &opt.local_ip));
	
	return 0;
}
/* send the data but filter the PASS-command on debug-output */
int send_msg(char * msg) {
  if(strncmp(msg, "PASS", 4) != 0)
  	printout(vDEBUG, "---->%s", msg);
  /* TODO USS do we need a send_for_sure function? */
  return send(cc.csock, msg, strlen(msg), 0);
}
/* rfc-states that a reply might look like this:
	123-First line
	Second line
	 234 A line beginning with numbers
	123 The last line
 * numbers must be padded and if other lines follow they do not need to start
 * with XXX-. the last line contains the code again and should be considered
 * relevant. the others are user-information printed on vNOMRAL (or vMORE?) */
/* read a line from socket and do some basic parsing, resulting in special
 * error-levels: ERR_RECONNECT, ERR_TIMEOUT, ERR_RETRY, ERR_SKIP, ERR_POSITIVE_PRELIMARY */
int get_msg(void) {
	char * msg = read_socket_line(cc.csock);
	static int multi_line = 0;
	if(cc.r.reply) {
		free(cc.r.reply);
		cc.r.reply = NULL;
	}
	if(!msg) {
		printout(vLESS, "Receive-Error: Connection broke down.\n");
		return ERR_RECONNECT;
	}
	if(msg == (char *) ERR_TIMEOUT)
		return ERR_TIMEOUT;
	if(strlen(msg) < 4 || !ISDIGIT(msg[0]) || !ISDIGIT(msg[1]) || !ISDIGIT(msg[2])) {
		if(multi_line) {
			printout(vMORE, "# %s\n", msg);
			return get_msg();
		}
		printout(vLESS, "Receive-Error: Invalid FTP-answer (%d bytes): %s\n", strlen(msg), msg);
		free(msg);
		printout(vLESS, "Reconnecting to be sure, nothing went wrong\n");
		return ERR_RECONNECT;
	}
	if(msg[3] == '-') {
		/* hyphened lines indicate that there is another reply line coming
		 * print it out to anyone who is interested and go on walking */
		multi_line = 1;
		printout(vMORE, "# %s\n", msg+4);
		free(msg);
		return get_msg();
	}
	multi_line = 0;
	msg[3] = 0;
	cc.r.code    = atoi(msg);
	cc.r.reply   = msg;
	cc.r.message = cc.r.reply+4;
	printout(vDEBUG, "[%d] '%s'\n", cc.r.code, cc.r.message);

	/* check errors that may occur to every process and return a specific error number */
	
	/* TODO USS shall these return an error-level or simply be discarded? 
	 * TODO USS at least FTP_ERROR(x) does not see ERR_POSITIVE_PRELIMARY as an error*/
	if(cc.r.reply[0] == '1')
		return ERR_POSITIVE_PRELIMARY;
		
	if(cc.r.reply[0] == '2' || cc.r.reply[0] == '3') 
		return 0;
		
	/* rfc says that on 4xx errors, the command can be retried as it was */
	if(cc.r.reply[0] == '4')
		return ERR_RETRY;
		
	if(cc.r.reply[0] == '5')
		return ERR_PERMANENT;

	/* when this is reached there must be something wrong */
	printout(vLESS, "Dead code reached by FTP-reply:\n%d %s\n", 
			cc.r.code, cc.r.message);
	return 0;
}
/* TODO NRV this is ugly. we cannot use a static buffer, because it cannot be
 * TODO NRV free'd in the end, but a global variable for a buffer of only one
 * TODO NRV function is also nothing to be proud of */
int issue_cmd(char * cmd, char * value) {
  int len = strlen(cmd)
    + ((value) ? strlen(value) + 1 : 0) /* value + space */ 
    + 2  /* \r\n */ 
    + 1; /* \0 */
  /* for performance-reasons we only allocate a new buffer
   * if the old one is not big enough anymore */
  if(opt.sbuflen < len) {
  	opt.sbuf = realloc(opt.sbuf, len);
  	opt.sbuflen = len; 
  }
  memset(opt.sbuf, 0, opt.sbuflen);
  
  /* i don't trust sprintf */
  strcpy(opt.sbuf, cmd);
  if(value) {
  	int pos = strlen(cmd);
  	opt.sbuf[pos] = ' ';
  	strcpy(opt.sbuf+pos+1, value);
  }
  strncpy(opt.sbuf+strlen(opt.sbuf), "\r\n\0", 3);
  
  send_msg(opt.sbuf);
  return 0;
}
