about summary refs log blame commit diff stats
path: root/WWW/Library/Implementation/HTFTP.c
blob: 49af1828dbba2e303adfb364de01eaae1c3eda1d (plain) (tree)
1
2
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
3205
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236
3237
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
3265
3266
3267
3268
3269
3270
3271
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282
3283
3284
3285
3286
3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
3297
3298
3299
3300
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
3312
3313
3314
3315
3316
3317
3318
3319
3320
3321
3322
3323
3324
3325
3326
3327
3328
3329
3330
3331
3332
3333
3334
3335
3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
3348
3349
3350
3351
3352
3353
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
  
                                                         































































































                                                                                 
                                                                     
                                                  























































                                                                                




                                                                              
                                          
 
























































































                                                                                  

                                









                                                          





























































                                                                                    
                                                              







                                                                       
                                                               















                                                                            
                                              





                                                               











































































































































































































































































































































                                                                                    
                                            












































































                                                                               








                                                             


































                                                                      


                                         


                                      

                                               
            







                                                 

                       
                                                                  
     
 

                     









                                                   
                                       










































































                                                                               

                                                                
                                                          
                                               
 





















                                                                       
                                                    

                                                   
                                  


                                                                  
                                                       














                                                    
                                                   












                                                          
                                                     






                                                                       
                                                    




                                              
                                                    









































































































                                                                                 

















































































































































































                                                                                 

                              
                                               
         



























                                                                          

                              
                                        
         











                                                                   

                              
                                               
         










































































































                                                                             
                    

                         

                                     


                        
                                       







                                             
                                           








                                                           
               






























































































                                                                          
               

























                                                                     
                                                         






                                                                     
                                            

























                                                         

                       




















                                                                            
                                                     











                                                 
                                        





















                                                                  
                                          









                                               
                                              

















                                                                             
                       








































                                                                              
                                                 

















                                                                   
                                








































                                                                     
                 





                                                                               
                                                            





































                                                                               
                                                 



















                                                              
                                          

























                                                                      
                                          





                                                 
                                        



                                  
                                  












                                                       
                                                        




                      
                                   

































                                                                         
                                            































                                                                           
                                          






























































































                                                                        
                                            









































                                                                        
                                                  





















































                                                            
                                                                        










                                                            
                                             




































                                                                                      
                                      









































                                                                              
                                                   











                                                                              
                                                






































































































































































































































                                                                               




                                           









































                                                                               











































                                                                              

                                                                          


                                                            



                                  




                                             




                                                             
                                
         
              

                      


                                           
              
                                                                 



                                     

                                      




                                               
                                
         
              


                      
              
     
                  



















                                                              










                                                                       
                                                        





                                
















































































                                                                         

                            




















































                                                                   
                                                











                                                                               
                                                    

                                           
                                                           










































                                                                         
 



































































































                                                                                   
                                       




















                                                                                             
                                           











































































































































































































































































































































                                                                                         
                                     


























































































































































































                                                                                  
                                                              











































































                                                                              
                                           









                                                                    
                                        













                                                                             

                                                    


























                                                                                      
                                                                  


























                                                                                    


                                                                 



























































                                                                                     
                                                                    




                                                                      
                                                                           












































                                                                                    
                                                           





































                                                                               

                                                                  
                                                                       
                                                          




























                                                                                
                                                              





































                                                                                    





                                                                           
                                     








                                                                                 






































































                                                                            


























































                                                                                 
             
 














                                                                     










                                                                       








                                                                             
                
                                                                            
         









                                                                      
                                                                              
                                    
                 












































                                                                               
/*
 * $LynxId: HTFTP.c,v 1.126 2014/07/24 22:08:24 tom Exp $
 *
 *			File Transfer Protocol (FTP) Client
 *			for a WorldWideWeb browser
 *			===================================
 *
 *	A cache of control connections is kept.
 *
 * Note: Port allocation
 *
 *	It is essential that the port is allocated by the system, rather
 *	than chosen in rotation by us (POLL_PORTS), or the following
 *	problem occurs.
 *
 *	It seems that an attempt by the server to connect to a port which has
 *	been used recently by a listen on the same socket, or by another
 *	socket this or another process causes a hangup of (almost exactly)
 *	one minute.  Therefore, we have to use a rotating port number.
 *	The problem remains that if the application is run twice in quick
 *	succession, it will hang for what remains of a minute.
 *
 * Authors
 *	TBL	Tim Berners-lee <timbl@info.cern.ch>
 *	DD	Denis DeLaRoca 310 825-4580 <CSP1DWD@mvs.oac.ucla.edu>
 *	LM	Lou Montulli <montulli@ukanaix.cc.ukans.edu>
 *	FM	Foteos Macrides <macrides@sci.wfeb.edu>
 * History:
 *	 2 May 91	Written TBL, as a part of the WorldWideWeb project.
 *	15 Jan 92	Bug fix: close() was used for NETCLOSE for control soc
 *	10 Feb 92	Retry if cached connection times out or breaks
 *	 8 Dec 92	Bug fix 921208 TBL after DD
 *	17 Dec 92	Anon FTP password now just WWWuser@ suggested by DD
 *			fails on princeton.edu!
 *	27 Dec 93 (FM)	Fixed up so FTP now works with VMS hosts.  Path
 *			must be Unix-style and cannot include the device
 *			or top directory.
 *	?? ??? ?? (LM)	Added code to prompt and send passwords for non
 *			anonymous FTP
 *	25 Mar 94 (LM)	Added code to recognize different ftp server types
 *			and code to parse dates and sizes on most hosts.
 *	27 Mar 93 (FM)	Added code for getting dates and sizes on VMS hosts.
 *
 * Notes:
 *			Portions Copyright 1994 Trustees of Dartmouth College
 *			Code for recognizing different FTP servers and
 *			parsing "ls -l" output taken from Macintosh Fetch
 *			program with permission from Jim Matthews,
 *			Dartmouth Software Development Team.
 */

/*
 * BUGS:	@@@	Limit connection cache size!
 * 		Error reporting to user.
 * 		400 & 500 errors are ack'ed by user with windows.
 * 		Use configuration file for user names
 * 
 *		Note for portability this version does not use select() and
 *		so does not watch the control and data channels at the
 *		same time.
 */

#include <HTUtils.h>

#include <HTAlert.h>

#include <HTFTP.h>		/* Implemented here */
#include <HTTCP.h>
#include <HTTP.h>
#include <HTFont.h>

#define REPEAT_PORT		/* Give the port number for each file */
#define REPEAT_LISTEN		/* Close each listen socket and open a new one */

/* define POLL_PORTS		 If allocation does not work, poll ourselves.*/
#define LISTEN_BACKLOG 2	/* Number of pending connect requests (TCP) */

#define FIRST_TCP_PORT	1024	/* Region to try for a listening port */
#define LAST_TCP_PORT	5999

#define LINE_LENGTH 256

#include <HTParse.h>
#include <HTAnchor.h>
#include <HTFile.h>		/* For HTFileFormat() */
#include <HTBTree.h>
#include <HTChunk.h>
#ifndef IPPORT_FTP
#define IPPORT_FTP	21
#endif /* !IPORT_FTP */

#include <LYUtils.h>
#include <LYGlobalDefs.h>
#include <LYStrings.h>
#include <LYLeaks.h>

typedef struct _connection {
    struct _connection *next;	/* Link on list         */
    int socket;			/* Socket number for communication */
    BOOL is_binary;		/* Binary mode? */
} connection;

/*		Hypertext object building machinery
*/
#include <HTML.h>

/*
 * socklen_t is the standard, but there are many pre-standard variants.
 * This ifdef works around a few of those cases.
 *
 * Information was obtained from header files on these platforms:
 *	AIX 4.3.2, 5.1
 *	HPUX 10.20, 11.00, 11.11
 *	IRIX64 6.5
 *	Tru64 4.0G, 4.0D, 5.1
 */
#if defined(SYS_IRIX64)
	/* IRIX64 6.5 socket.h may use socklen_t if SGI_SOURCE is not defined */
#  if _NO_XOPEN4 && _NO_XOPEN5
#    define LY_SOCKLEN socklen_t
#  elif _ABIAPI
#    define LY_SOCKLEN int
#  elif _XOPEN5
#    if (_MIPS_SIM != _ABIO32)
#      define LY_SOCKLEN socklen_t
#    else
#      define LY_SOCKLEN int
#    endif
#  else
#    define LY_SOCKLEN size_t
#  endif
#elif defined(SYS_HPUX)
#  if defined(_XOPEN_SOURCE_EXTENDED) && defined(SO_PROTOTYPE)
#    define LY_SOCKLEN socklen_t
#  else	/* HPUX 10.20, etc. */
#    define LY_SOCKLEN int
#  endif
#elif defined(SYS_TRU64)
#  if defined(_POSIX_PII_SOCKET)
#    define LY_SOCKLEN socklen_t
#  elif defined(_XOPEN_SOURCE_EXTENDED)
#    define LY_SOCKLEN size_t
#  else
#    define LY_SOCKLEN int
#  endif
#else
#  define LY_SOCKLEN socklen_t
#endif

#define PUTC(c)      (*target->isa->put_character) (target, c)
#define PUTS(s)      (*target->isa->put_string)    (target, s)
#define START(e)     (*target->isa->start_element) (target, e, 0, 0, -1, 0)
#define END(e)       (*target->isa->end_element)   (target, e, 0)
#define FREE_TARGET  (*target->isa->_free)         (target)
#define ABORT_TARGET (*target->isa->_free)         (target)

#define TRACE_ENTRY(tag, entry_info) \
    CTRACE((tfp, "HTFTP: %s filename: %s  date: %s  size: %" PRI_off_t "\n", \
	    tag, \
	    entry_info->filename, \
	    NonNull(entry_info->date), \
	    CAST_off_t(entry_info->size)))

struct _HTStructured {
    const HTStructuredClass *isa;
    /* ... */
};

/*	Global Variables
 *	---------------------
 */
int HTfileSortMethod = FILE_BY_NAME;

#ifndef DISABLE_FTP		/*This disables everything to end-of-file */
static char ThisYear[8];
static char LastYear[8];
static int TheDate;
static BOOLEAN HaveYears = FALSE;

/*	Module-Wide Variables
 *	---------------------
 */
static connection *connections = NULL;	/* Linked list of connections */
static char response_text[LINE_LENGTH + 1];	/* Last response from ftp host */
static connection *control = NULL;	/* Current connection */
static int data_soc = -1;	/* Socket for data transfer =invalid */
static char *user_entered_password = NULL;
static char *last_username_and_host = NULL;

/*
 * Some ftp servers are known to have a broken implementation of RETR.  If
 * asked to retrieve a directory, they get confused and fail subsequent
 * commands such as CWD and LIST.
 */
static int Broken_RETR = FALSE;

/*
 * Some ftp servers are known to have a broken implementation of EPSV.  The
 * server will hang for a long time when we attempt to connect after issuing
 * this command.
 */
#ifdef INET6
static int Broken_EPSV = FALSE;
#endif

typedef enum {
    GENERIC_SERVER
    ,MACHTEN_SERVER
    ,UNIX_SERVER
    ,VMS_SERVER
    ,CMS_SERVER
    ,DCTS_SERVER
    ,TCPC_SERVER
    ,PETER_LEWIS_SERVER
    ,NCSA_SERVER
    ,WINDOWS_NT_SERVER
    ,WINDOWS_2K_SERVER
    ,MS_WINDOWS_SERVER
    ,MSDOS_SERVER
    ,APPLESHARE_SERVER
    ,NETPRESENZ_SERVER
    ,DLS_SERVER
} eServerType;

static eServerType server_type = GENERIC_SERVER;	/* the type of ftp host */
static int unsure_type = FALSE;	/* sure about the type? */
static BOOLEAN use_list = FALSE;	/* use the LIST command? */

static int interrupted_in_next_data_char = FALSE;

#ifdef POLL_PORTS
static PortNumber port_number = FIRST_TCP_PORT;
#endif /* POLL_PORTS */

static BOOL have_socket = FALSE;	/* true if master_socket is valid */
static unsigned master_socket;	/* Listening socket = invalid */

static char port_command[255];	/* Command for setting the port */
static fd_set open_sockets;	/* Mask of active channels */
static unsigned num_sockets;	/* Number of sockets to scan */
static PortNumber passive_port;	/* Port server specified for data */

#define NEXT_CHAR HTGetCharacter()	/* Use function in HTFormat.c */

#define DATA_BUFFER_SIZE 2048
static char data_buffer[DATA_BUFFER_SIZE];	/* Input data buffer */
static char *data_read_pointer;
static char *data_write_pointer;

#define NEXT_DATA_CHAR next_data_char()
static int close_connection(connection * con);

#ifndef HAVE_ATOLL
off_t LYatoll(const char *value)
{
    off_t result = 0;

    while (*value != '\0') {
	result = (result * 10) + (off_t) (*value++ - '0');
    }
    return result;
}
#endif

#ifdef LY_FIND_LEAKS
/*
 *  This function frees module globals. - FM
 */
static void free_FTPGlobals(void)
{
    FREE(user_entered_password);
    FREE(last_username_and_host);
    if (control) {
	if (control->socket != -1)
	    close_connection(control);
	FREE(control);
    }
}
#endif /* LY_FIND_LEAKS */

/* PUBLIC						HTVMS_name()
 *		CONVERTS WWW name into a VMS name
 * ON ENTRY:
 *	nn		Node Name (optional)
 *	fn		WWW file name
 *
 * ON EXIT:
 *	returns		vms file specification
 *
 * Bug: Returns pointer to static -- non-reentrant
 */
char *HTVMS_name(const char *nn,
		 const char *fn)
{
    /* We try converting the filename into Files-11 syntax.  That is, we assume
     * first that the file is, like us, on a VMS node.  We try remote (or
     * local) DECnet access.  Files-11, VMS, VAX and DECnet are trademarks of
     * Digital Equipment Corporation.  The node is assumed to be local if the
     * hostname WITHOUT DOMAIN matches the local one.  @@@
     */
    static char *vmsname;
    char *filename = (char *) malloc(strlen(fn) + 1);
    char *nodename = (char *) malloc(strlen(nn) + 2 + 1);	/* Copies to hack */
    char *second;		/* 2nd slash */
    char *last;			/* last slash */

    const char *hostname = HTHostName();

    if (!filename || !nodename)
	outofmem(__FILE__, "HTVMSname");

    assert(filename != NULL);
    assert(nodename != NULL);

    strcpy(filename, fn);
    strcpy(nodename, "");	/* On same node?  Yes if node names match */
    if (StrNCmp(nn, "localhost", 9)) {
	const char *p;
	const char *q;

	for (p = hostname, q = nn;
	     *p && *p != '.' && *q && *q != '.'; p++, q++) {
	    if (TOUPPER(*p) != TOUPPER(*q)) {
		char *r;

		strcpy(nodename, nn);
		r = StrChr(nodename, '.');	/* Mismatch */
		if (r)
		    *r = '\0';	/* Chop domain */
		strcat(nodename, "::");		/* Try decnet anyway */
		break;
	    }
	}
    }

    second = StrChr(filename + 1, '/');		/* 2nd slash */
    last = strrchr(filename, '/');	/* last slash */

    if (!second) {		/* Only one slash */
	HTSprintf0(&vmsname, "%s%s", nodename, filename + 1);
    } else if (second == last) {	/* Exactly two slashes */
	*second = '\0';		/* Split filename from disk */
	HTSprintf0(&vmsname, "%s%s:%s", nodename, filename + 1, second + 1);
	*second = '/';		/* restore */
    } else {			/* More than two slashes */
	char *p;

	*second = '\0';		/* Split disk from directories */
	*last = '\0';		/* Split dir from filename */
	HTSprintf0(&vmsname, "%s%s:[%s]%s",
		   nodename, filename + 1, second + 1, last + 1);
	*second = *last = '/';	/* restore filename */
	if ((p = StrChr(vmsname, '[')) != 0) {
	    while (*p != '\0' && *p != ']') {
		if (*p == '/')
		    *p = '.';	/* Convert dir sep.  to dots */
		++p;
	    }
	}
    }
    FREE(nodename);
    FREE(filename);
    return vmsname;
}

/*	Procedure: Read a character from the data connection
 *	----------------------------------------------------
 */
static int next_data_char(void)
{
    int status;

    if (data_read_pointer >= data_write_pointer) {
	status = NETREAD(data_soc, data_buffer, DATA_BUFFER_SIZE);
	if (status == HT_INTERRUPTED)
	    interrupted_in_next_data_char = 1;
	if (status <= 0)
	    return EOF;
	data_write_pointer = data_buffer + status;
	data_read_pointer = data_buffer;
    }
#ifdef NOT_ASCII
    {
	char c = *data_read_pointer++;

	return FROMASCII(c);
    }
#else
    return UCH(*data_read_pointer++);
#endif /* NOT_ASCII */
}

/*	Close an individual connection
 *
 */
static int close_connection(connection * con)
{
    connection *scan;
    int status;

    CTRACE((tfp, "HTFTP: Closing control socket %d\n", con->socket));
    status = NETCLOSE(con->socket);
    if (TRACE && status != 0) {
#ifdef UNIX
	CTRACE((tfp, "HTFTP:close_connection: %s", LYStrerror(errno)));
#else
	if (con->socket != INVSOC)
	    HTInetStatus("HTFTP:close_connection");
#endif
    }
    con->socket = -1;
    if (connections == con) {
	connections = con->next;
	return status;
    }
    for (scan = connections; scan; scan = scan->next) {
	if (scan->next == con) {
	    scan->next = con->next;	/* Unlink */
	    if (control == con)
		control = (connection *) 0;
	    return status;
	}			/*if */
    }				/* for */
    return -1;			/* very strange -- was not on list. */
}

static char *help_message_buffer = NULL;	/* global :( */

static void init_help_message_cache(void)
{
    FREE(help_message_buffer);
}

static void help_message_cache_add(char *string)
{
    if (help_message_buffer)
	StrAllocCat(help_message_buffer, string);
    else
	StrAllocCopy(help_message_buffer, string);

    CTRACE((tfp, "Adding message to help cache: %s\n", string));
}

static char *help_message_cache_non_empty(void)
{
    return (help_message_buffer);
}

static char *help_message_cache_contents(void)
{
    return (help_message_buffer);
}

/*	Send One Command
 *	----------------
 *
 *	This function checks whether we have a control connection, and sends
 *	one command if given.
 *
 * On entry,
 *	control	points to the connection which is established.
 *	cmd	points to a command, or is zero to just get the response.
 *
 *	The command should already be terminated with the CRLF pair.
 *
 * On exit,
 *	returns:  1 for success,
 *		  or negative for communication failure (in which case
 *		  the control connection will be closed).
 */
static int write_cmd(const char *cmd)
{
    int status;

    if (!control) {
	CTRACE((tfp, "HTFTP: No control connection set up!!\n"));
	return HT_NO_CONNECTION;
    }

    if (cmd) {
	CTRACE((tfp, "  Tx: %s", cmd));
#ifdef NOT_ASCII
	{
	    char *p;

	    for (p = cmd; *p; p++) {
		*p = TOASCII(*p);
	    }
	}
#endif /* NOT_ASCII */
	status = (int) NETWRITE(control->socket, cmd, (unsigned) strlen(cmd));
	if (status < 0) {
	    CTRACE((tfp,
		    "HTFTP: Error %d sending command: closing socket %d\n",
		    status, control->socket));
	    close_connection(control);
	    return status;
	}
    }
    return 1;
}

/*
 * For each string in the list, check if it is found in the response text.
 * If so, return TRUE.
 */
static BOOL find_response(HTList *list)
{
    BOOL result = FALSE;
    HTList *p = list;
    char *value;

    while ((value = (char *) HTList_nextObject(p)) != NULL) {
	if (LYstrstr(response_text, value)) {
	    result = TRUE;
	    break;
	}
    }
    return result;
}

/*	Execute Command and get Response
 *	--------------------------------
 *
 *	See the state machine illustrated in RFC959, p57. This implements
 *	one command/reply sequence.  It also interprets lines which are to
 *	be continued, which are marked with a "-" immediately after the
 *	status code.
 *
 *	Continuation then goes on until a line with a matching reply code
 *	an a space after it.
 *
 * On entry,
 *	control	points to the connection which is established.
 *	cmd	points to a command, or is zero to just get the response.
 *
 *	The command must already be terminated with the CRLF pair.
 *
 * On exit,
 *	returns:  The first digit of the reply type,
 *		  or negative for communication failure.
 */
static int response(const char *cmd)
{
    int result;			/* Three-digit decimal code */
    int continuation_response = -1;
    int status;

    if ((status = write_cmd(cmd)) < 0)
	return status;

    do {
	char *p = response_text;

	for (;;) {
	    int ich = NEXT_CHAR;

	    if (((*p++ = (char) ich) == LF)
		|| (p == &response_text[LINE_LENGTH])) {

		char continuation;

		if (interrupted_in_htgetcharacter) {
		    CTRACE((tfp,
			    "HTFTP: Interrupted in HTGetCharacter, apparently.\n"));
		    NETCLOSE(control->socket);
		    control->socket = -1;
		    return HT_INTERRUPTED;
		}

		*p = '\0';	/* Terminate the string */
		CTRACE((tfp, "    Rx: %s", response_text));

		/* Check for login or help messages */
		if (!StrNCmp(response_text, "230-", 4) ||
		    !StrNCmp(response_text, "250-", 4) ||
		    !StrNCmp(response_text, "220-", 4))
		    help_message_cache_add(response_text + 4);

		sscanf(response_text, "%d%c", &result, &continuation);
		if (continuation_response == -1) {
		    if (continuation == '-')	/* start continuation */
			continuation_response = result;
		} else {	/* continuing */
		    if (continuation_response == result &&
			continuation == ' ')
			continuation_response = -1;	/* ended */
		}
		if (result == 220 && find_response(broken_ftp_retr)) {
		    Broken_RETR = TRUE;
		    CTRACE((tfp, "This server is broken (RETR)\n"));
		}
#ifdef INET6
		if (result == 220 && find_response(broken_ftp_epsv)) {
		    Broken_EPSV = TRUE;
		    CTRACE((tfp, "This server is broken (EPSV)\n"));
		}
#endif
		break;
	    }
	    /* if end of line */
	    if (interrupted_in_htgetcharacter) {
		CTRACE((tfp,
			"HTFTP: Interrupted in HTGetCharacter, apparently.\n"));
		NETCLOSE(control->socket);
		control->socket = -1;
		return HT_INTERRUPTED;
	    }

	    if (ich == EOF) {
		CTRACE((tfp, "Error on rx: closing socket %d\n",
			control->socket));
		strcpy(response_text, "000 *** TCP read error on response\n");
		close_connection(control);
		return -1;	/* End of file on response */
	    }
	}			/* Loop over characters */

    } while (continuation_response != -1);

    if (result == 421) {
	CTRACE((tfp, "HTFTP: They close so we close socket %d\n",
		control->socket));
	close_connection(control);
	return -1;
    }
    if ((result == 255 && server_type == CMS_SERVER) &&
	(0 == strncasecomp(cmd, "CWD", 3) ||
	 0 == strcasecomp(cmd, "CDUP"))) {
	/*
	 * Alas, CMS returns 255 on failure to CWD to parent of root.  - PG
	 */
	result = 555;
    }
    return result / 100;
}

static int send_cmd_1(const char *verb)
{
    char command[80];

    sprintf(command, "%.*s%c%c", (int) sizeof(command) - 4, verb, CR, LF);
    return response(command);
}

static int send_cmd_2(const char *verb, const char *param)
{
    char *command = 0;
    int status;

    HTSprintf0(&command, "%s %s%c%c", verb, param, CR, LF);
    status = response(command);
    FREE(command);

    return status;
}

#define send_cwd(path) send_cmd_2("CWD", path)

/*
 * This function should try to set the macintosh server into binary mode.  Some
 * servers need an additional letter after the MACB command.
 */
static int set_mac_binary(eServerType ServerType)
{
    /* try to set mac binary mode */
    if (ServerType == APPLESHARE_SERVER ||
	ServerType == NETPRESENZ_SERVER) {
	/*
	 * Presumably E means "Enable".  - KW
	 */
	return (2 == response("MACB E\r\n"));
    } else {
	return (2 == response("MACB\r\n"));
    }
}

/* This function gets the current working directory to help
 * determine what kind of host it is
 */

static void get_ftp_pwd(eServerType *ServerType, BOOLEAN *UseList)
{
    char *cp;

    /* get the working directory (to see what it looks like) */
    int status = response("PWD\r\n");

    if (status < 0) {
	return;
    } else {
	cp = StrChr(response_text + 5, '"');
	if (cp)
	    *cp = '\0';
	if (*ServerType == TCPC_SERVER) {
	    *ServerType = ((response_text[5] == '/') ?
			   NCSA_SERVER : TCPC_SERVER);
	    CTRACE((tfp, "HTFTP: Treating as %s server.\n",
		    ((*ServerType == NCSA_SERVER) ?
		     "NCSA" : "TCPC")));
	} else if (response_text[5] == '/') {
	    /* path names beginning with / imply Unix,
	     * right?
	     */
	    if (set_mac_binary(*ServerType)) {
		*ServerType = NCSA_SERVER;
		CTRACE((tfp, "HTFTP: Treating as NCSA server.\n"));
	    } else {
		*ServerType = UNIX_SERVER;
		*UseList = TRUE;
		CTRACE((tfp, "HTFTP: Treating as Unix server.\n"));
	    }
	    return;
	} else if (response_text[strlen(response_text) - 1] == ']') {
	    /* path names ending with ] imply VMS, right? */
	    *ServerType = VMS_SERVER;
	    *UseList = TRUE;
	    CTRACE((tfp, "HTFTP: Treating as VMS server.\n"));
	} else {
	    *ServerType = GENERIC_SERVER;
	    CTRACE((tfp, "HTFTP: Treating as Generic server.\n"));
	}

	if ((*ServerType == NCSA_SERVER) ||
	    (*ServerType == TCPC_SERVER) ||
	    (*ServerType == PETER_LEWIS_SERVER) ||
	    (*ServerType == NETPRESENZ_SERVER))
	    set_mac_binary(*ServerType);
    }
}

/* This function turns MSDOS-like directory output off for
 * Windows NT servers.
 */

static void set_unix_dirstyle(eServerType *ServerType, BOOLEAN *UseList)
{
    char *cp;

    /* This is a toggle.  It seems we have to toggle in order to see
     * the current state (after toggling), so we may end up toggling
     * twice.  - kw
     */
    int status = response("SITE DIRSTYLE\r\n");

    if (status != 2) {
	*ServerType = GENERIC_SERVER;
	CTRACE((tfp, "HTFTP: DIRSTYLE failed, treating as Generic server.\n"));
	return;
    } else {
	*UseList = TRUE;
	/* Expecting one of:
	 * 200 MSDOS-like directory output is off
	 * 200 MSDOS-like directory output is on
	 * The following code doesn't look for the full exact string -
	 * who knows how the wording may change in some future version.
	 * If the first response isn't recognized, we toggle again
	 * anyway, under the assumption that it's more likely that
	 * the MSDOS setting was "off" originally. - kw
	 */
	cp = strstr(response_text + 4, "MSDOS");
	if (cp && strstr(cp, " off")) {
	    return;		/* already off now. */
	} else {
	    response("SITE DIRSTYLE\r\n");
	}
    }
}

#define CheckForInterrupt(msg) \
	if (status == HT_INTERRUPTED) { \
	    CTRACE((tfp, "HTFTP: Interrupted %s.\n", msg)); \
	    _HTProgress(CONNECTION_INTERRUPTED); \
	    NETCLOSE(control->socket); \
	    control->socket = -1; \
	    return HT_INTERRUPTED; \
	}

/*	Get a valid connection to the host
 *	----------------------------------
 *
 * On entry,
 *	arg	points to the name of the host in a hypertext address
 * On exit,
 *	returns <0 if error
 *		socket number if success
 *
 *	This routine takes care of managing timed-out connections, and
 *	limiting the number of connections in use at any one time.
 *
 *	It ensures that all connections are logged in if they exist.
 *	It ensures they have the port number transferred.
 */
static int get_connection(const char *arg,
			  HTParentAnchor *anchor)
{
    int status;
    char *command = 0;
    connection *con;
    char *username = NULL;
    char *password = NULL;
    static BOOLEAN firstuse = TRUE;

    if (firstuse) {
	/*
	 * Set up freeing at exit.  - FM
	 */
#ifdef LY_FIND_LEAKS
	atexit(free_FTPGlobals);
#endif
	firstuse = FALSE;
    }

    if (control != 0) {
	connection *next = control->next;

	if (control->socket != -1) {
	    NETCLOSE(control->socket);
	}
	memset(con = control, 0, sizeof(*con));
	con->next = next;
    } else {
	con = typecalloc(connection);
	if (con == NULL)
	    outofmem(__FILE__, "get_connection");

	assert(con != NULL);
    }
    con->socket = -1;

    if (isEmpty(arg)) {
	free(con);
	return -1;		/* Bad if no name specified     */
    }

    /* Get node name:
     */
    CTRACE((tfp, "get_connection(%s)\n", arg));
    {
	char *p1 = HTParse(arg, "", PARSE_HOST);
	char *p2 = strrchr(p1, '@');	/* user? */
	char *pw = NULL;

	if (p2 != NULL) {
	    username = p1;
	    *p2 = '\0';		/* terminate */
	    p1 = p2 + 1;	/* point to host */
	    pw = StrChr(username, ':');
	    if (pw != NULL) {
		*pw++ = '\0';
		password = HTUnEscape(pw);
	    }
	    if (*username)
		HTUnEscape(username);

	    /*
	     * If the password doesn't exist then we are going to have to ask
	     * the user for it.  The only problem is that we don't want to ask
	     * for it every time, so we will store away in a primitive fashion.
	     */
	    if (!password) {
		char *tmp = NULL;

		HTSprintf0(&tmp, "%s@%s", username, p1);
		/*
		 * If the user@host is not equal to the last time through or
		 * user_entered_password has no data then we need to ask the
		 * user for the password.
		 */
		if (!last_username_and_host ||
		    strcmp(tmp, last_username_and_host) ||
		    !user_entered_password) {

		    StrAllocCopy(last_username_and_host, tmp);
		    HTSprintf0(&tmp, gettext("Enter password for user %s@%s:"),
			       username, p1);
		    FREE(user_entered_password);
		    user_entered_password = HTPromptPassword(tmp);

		}		/* else we already know the password */
		password = user_entered_password;
		FREE(tmp);
	    }
	}

	if (!username)
	    FREE(p1);
    }				/* scope of p1 */

    status = HTDoConnect(arg, "FTP", IPPORT_FTP, (int *) &con->socket);

    if (status < 0) {
	if (status == HT_INTERRUPTED) {
	    CTRACE((tfp, "HTFTP: Interrupted on connect\n"));
	} else {
	    CTRACE((tfp, "HTFTP: Unable to connect to remote host for `%s'.\n",
		    arg));
	}
	if (status == HT_INTERRUPTED) {
	    _HTProgress(CONNECTION_INTERRUPTED);
	    status = HT_NOT_LOADED;
	} else {
	    HTAlert(gettext("Unable to connect to FTP host."));
	}
	if (con->socket != -1) {
	    NETCLOSE(con->socket);
	}

	FREE(username);
	if (control == con)
	    control = NULL;
	FREE(con);
	return status;		/* Bad return */
    }

    CTRACE((tfp, "FTP connected, socket %d  control %p\n",
	    con->socket, (void *) con));
    control = con;		/* Current control connection */

    /* Initialise buffering for control connection */
    HTInitInput(control->socket);
    init_help_message_cache();	/* Clear the login message buffer. */

    /*  Now we log in           Look up username, prompt for pw.
     */
    status = response((char *) 0);	/* Get greeting */
    CheckForInterrupt("at beginning of login");

    server_type = GENERIC_SERVER;	/* reset */
    if (status == 2) {		/* Send username */
	char *cp;		/* look at greeting text */

	/* don't gettext() this -- incoming text: */
	if (strlen(response_text) > 4) {
	    if ((cp = strstr(response_text, " awaits your command")) ||
		(cp = strstr(response_text, " ready."))) {
		*cp = '\0';
	    }
	    cp = response_text + 4;
	    if (!strncasecomp(cp, "NetPresenz", 10))
		server_type = NETPRESENZ_SERVER;
	} else {
	    cp = response_text;
	}
	StrAllocCopy(anchor->server, cp);

	status = send_cmd_2("USER", (username && *username)
			    ? username
			    : "anonymous");

	CheckForInterrupt("while sending username");
    }
    if (status == 3) {		/* Send password */
	if (non_empty(password)) {
	    HTSprintf0(&command, "PASS %s%c%c", password, CR, LF);
	} else {
	    /*
	     * No password was given; use mail-address.
	     */
	    const char *the_address;
	    char *user = NULL;
	    const char *host = NULL;
	    char *cp;

	    the_address = anonftp_password;
	    if (isEmpty(the_address))
		the_address = personal_mail_address;
	    if (isEmpty(the_address))
		the_address = LYGetEnv("USER");
	    if (isEmpty(the_address))
		the_address = "WWWuser";

	    StrAllocCopy(user, the_address);
	    if ((cp = StrChr(user, '@')) != NULL) {
		*cp++ = '\0';
		if (*cp == '\0')
		    host = HTHostName();
		else
		    host = cp;
	    } else {
		host = HTHostName();
	    }

	    /*
	     * If host is not fully qualified, suppress it
	     * as ftp.uu.net prefers a blank to a bad name
	     */
	    if (!(host) || StrChr(host, '.') == NULL)
		host = "";

	    HTSprintf0(&command, "PASS %s@%s%c%c", user, host, CR, LF);
	    FREE(user);
	}
	status = response(command);
	FREE(command);
	CheckForInterrupt("while sending password");
    }
    FREE(username);

    if (status == 3) {
	status = send_cmd_1("ACCT noaccount");
	CheckForInterrupt("while sending password");
    }
    if (status != 2) {
	CTRACE((tfp, "HTFTP: Login fail: %s", response_text));
	/* if (control->socket > 0) close_connection(control->socket); */
	return -1;		/* Bad return */
    }
    CTRACE((tfp, "HTFTP: Logged in.\n"));

    /* Check for host type */
    if (server_type != NETPRESENZ_SERVER)
	server_type = GENERIC_SERVER;	/* reset */
    use_list = FALSE;		/* reset */
    if (response("SYST\r\n") == 2) {
	/* we got a line -- what kind of server are we talking to? */
	if (StrNCmp(response_text + 4,
		    "UNIX Type: L8 MAC-OS MachTen", 28) == 0) {
	    server_type = MACHTEN_SERVER;
	    use_list = TRUE;
	    CTRACE((tfp, "HTFTP: Treating as MachTen server.\n"));

	} else if (strstr(response_text + 4, "UNIX") != NULL ||
		   strstr(response_text + 4, "Unix") != NULL) {
	    server_type = UNIX_SERVER;
	    unsure_type = FALSE;	/* to the best of out knowledge... */
	    use_list = TRUE;
	    CTRACE((tfp, "HTFTP: Treating as Unix server.\n"));

	} else if (strstr(response_text + 4, "MSDOS") != NULL) {
	    server_type = MSDOS_SERVER;
	    use_list = TRUE;
	    CTRACE((tfp, "HTFTP: Treating as MSDOS (Unix emulation) server.\n"));

	} else if (StrNCmp(response_text + 4, "VMS", 3) == 0) {
	    char *tilde = strstr(arg, "/~");

	    use_list = TRUE;
	    if (tilde != 0
		&& tilde[2] != 0
		&& strstr(response_text + 4, "MadGoat") != 0) {
		server_type = UNIX_SERVER;
		CTRACE((tfp, "HTFTP: Treating VMS as UNIX server.\n"));
	    } else {
		server_type = VMS_SERVER;
		CTRACE((tfp, "HTFTP: Treating as VMS server.\n"));
	    }

	} else if ((StrNCmp(response_text + 4, "VM/CMS", 6) == 0) ||
		   (StrNCmp(response_text + 4, "VM ", 3) == 0)) {
	    server_type = CMS_SERVER;
	    use_list = TRUE;
	    CTRACE((tfp, "HTFTP: Treating as CMS server.\n"));

	} else if (StrNCmp(response_text + 4, "DCTS", 4) == 0) {
	    server_type = DCTS_SERVER;
	    CTRACE((tfp, "HTFTP: Treating as DCTS server.\n"));

	} else if (strstr(response_text + 4, "MAC-OS TCP/Connect II") != NULL) {
	    server_type = TCPC_SERVER;
	    CTRACE((tfp, "HTFTP: Looks like a TCPC server.\n"));
	    get_ftp_pwd(&server_type, &use_list);
	    unsure_type = TRUE;

	} else if (server_type == NETPRESENZ_SERVER) {	/* already set above */
	    use_list = TRUE;
	    set_mac_binary(server_type);
	    CTRACE((tfp, "HTFTP: Treating as NetPresenz (MACOS) server.\n"));

	} else if (StrNCmp(response_text + 4, "MACOS Peter's Server", 20) == 0) {
	    server_type = PETER_LEWIS_SERVER;
	    use_list = TRUE;
	    set_mac_binary(server_type);
	    CTRACE((tfp, "HTFTP: Treating as Peter Lewis (MACOS) server.\n"));

	} else if (StrNCmp(response_text + 4, "Windows_NT", 10) == 0) {
	    server_type = WINDOWS_NT_SERVER;
	    CTRACE((tfp, "HTFTP: Treating as Window_NT server.\n"));
	    set_unix_dirstyle(&server_type, &use_list);

	} else if (StrNCmp(response_text + 4, "Windows2000", 11) == 0) {
	    server_type = WINDOWS_2K_SERVER;
	    CTRACE((tfp, "HTFTP: Treating as Window_2K server.\n"));
	    set_unix_dirstyle(&server_type, &use_list);

	} else if (StrNCmp(response_text + 4, "MS Windows", 10) == 0) {
	    server_type = MS_WINDOWS_SERVER;
	    use_list = TRUE;
	    CTRACE((tfp, "HTFTP: Treating as MS Windows server.\n"));

	} else if (StrNCmp(response_text + 4,
			   "MACOS AppleShare IP FTP Server", 30) == 0) {
	    server_type = APPLESHARE_SERVER;
	    use_list = TRUE;
	    set_mac_binary(server_type);
	    CTRACE((tfp, "HTFTP: Treating as AppleShare server.\n"));

	} else {
	    server_type = GENERIC_SERVER;
	    CTRACE((tfp, "HTFTP: Ugh!  A Generic server.\n"));
	    get_ftp_pwd(&server_type, &use_list);
	    unsure_type = TRUE;
	}
    } else {
	/* SYST fails :(  try to get the type from the PWD command */
	get_ftp_pwd(&server_type, &use_list);
    }

    return con->socket;		/* Good return */
}

static void reset_master_socket(void)
{
    have_socket = FALSE;
}

static void set_master_socket(int value)
{
    have_socket = (BOOLEAN) (value >= 0);
    if (have_socket)
	master_socket = (unsigned) value;
}

/*	Close Master (listening) socket
 *	-------------------------------
 *
 *
 */
static int close_master_socket(void)
{
    int status;

    if (have_socket)
	FD_CLR(master_socket, &open_sockets);

    status = NETCLOSE((int) master_socket);
    CTRACE((tfp, "HTFTP: Closed master socket %u\n", master_socket));

    reset_master_socket();

    if (status < 0)
	return HTInetStatus(gettext("close master socket"));
    else
	return status;
}

/*	Open a master socket for listening on
 *	-------------------------------------
 *
 *	When data is transferred, we open a port, and wait for the server to
 *	connect with the data.
 *
 * On entry,
 *	have_socket	Must be false, if master_socket is not setup already
 *	master_socket	Must be negative if not set up already.
 * On exit,
 *	Returns		socket number if good
 *			less than zero if error.
 *	master_socket	is socket number if good, else negative.
 *	port_number	is valid if good.
 */
static int get_listen_socket(void)
{
#ifdef INET6
    struct sockaddr_storage soc_address;	/* Binary network address */
    struct sockaddr_in *soc_in = (struct sockaddr_in *) &soc_address;
    int af;
    LY_SOCKLEN slen;

#else
    struct sockaddr_in soc_address;	/* Binary network address */
    struct sockaddr_in *soc_in = &soc_address;
#endif /* INET6 */
    int new_socket;		/* Will be master_socket */

    FD_ZERO(&open_sockets);	/* Clear our record of open sockets */
    num_sockets = 0;

#ifndef REPEAT_LISTEN
    if (have_socket)
	return master_socket;	/* Done already */
#endif /* !REPEAT_LISTEN */

#ifdef INET6
    /* query address family of control connection */
    slen = (LY_SOCKLEN) sizeof(soc_address);
    if (getsockname(control->socket, (struct sockaddr *) &soc_address,
		    &slen) < 0) {
	return HTInetStatus("getsockname failed");
    }
    af = ((struct sockaddr *) &soc_address)->sa_family;
    memset(&soc_address, 0, sizeof(soc_address));
#endif /* INET6 */

/*  Create internet socket
*/
#ifdef INET6
    new_socket = socket(af, SOCK_STREAM, IPPROTO_TCP);
#else
    new_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
#endif /* INET6 */

    if (new_socket < 0)
	return HTInetStatus(gettext("socket for master socket"));

    CTRACE((tfp, "HTFTP: Opened master socket number %d\n", new_socket));

/*  Search for a free port.
*/
#ifdef INET6
    memset(&soc_address, 0, sizeof(soc_address));
    ((struct sockaddr *) &soc_address)->sa_family = af;
    switch (af) {
    case AF_INET:
#ifdef SIN6_LEN
	((struct sockaddr *) &soc_address)->sa_len = sizeof(struct sockaddr_in);
#endif /* SIN6_LEN */
	break;
    case AF_INET6:
#ifdef SIN6_LEN
	((struct sockaddr *) &soc_address)->sa_len = sizeof(struct sockaddr_in6);
#endif /* SIN6_LEN */
	break;
    default:
	HTInetStatus("AF");
    }
#else
    soc_in->sin_family = AF_INET;	/* Family = internet, host order  */
    soc_in->sin_addr.s_addr = INADDR_ANY;	/* Any peer address */
#endif /* INET6 */
#ifdef POLL_PORTS
    {
	PortNumber old_port_number = port_number;

	for (port_number = (old_port_number + 1);; port_number++) {
	    int status;

	    if (port_number > LAST_TCP_PORT)
		port_number = FIRST_TCP_PORT;
	    if (port_number == old_port_number) {
		return HTInetStatus("bind");
	    }
#ifdef INET6
	    soc_in->sin_port = htons(port_number);
#else
	    soc_address.sin_port = htons(port_number);
#endif /* INET6 */
#ifdef SOCKS
	    if (socks_flag)
		if ((status = Rbind(new_socket,
				    (struct sockaddr *) &soc_address,
		/* Cast to generic sockaddr */
				    SOCKADDR_LEN(soc_address)
#ifndef SHORTENED_RBIND
				    ,socks_bind_remoteAddr
#endif /* !SHORTENED_RBIND */
		     )) == 0) {
		    break;
		} else
#endif /* SOCKS */
		    if ((status = bind(new_socket,
				       (struct sockaddr *) &soc_address,
		    /* Cast to generic sockaddr */
				       SOCKADDR_LEN(soc_address)
			 )) == 0) {
		    break;
		}
	    CTRACE((tfp, "TCP bind attempt to port %d yields %d, errno=%d\n",
		    port_number, status, SOCKET_ERRNO));
	}			/* for */
    }
#else
    {
	int status;
	LY_SOCKLEN address_length = (LY_SOCKLEN) sizeof(soc_address);

#ifdef SOCKS
	if (socks_flag)
	    status = Rgetsockname(control->socket,
				  (struct sockaddr *) &soc_address,
				  &address_length);
	else
#endif /* SOCKS */
	    status = getsockname(control->socket,
				 (struct sockaddr *) &soc_address,
				 &address_length);
	if (status < 0) {
	    close(new_socket);
	    return HTInetStatus("getsockname");
	}
#ifdef INET6
	CTRACE((tfp, "HTFTP: This host is %s\n",
		HTInetString((void *) soc_in)));

	soc_in->sin_port = 0;	/* Unspecified: please allocate */
#else
	CTRACE((tfp, "HTFTP: This host is %s\n",
		HTInetString(soc_in)));

	soc_address.sin_port = 0;	/* Unspecified: please allocate */
#endif /* INET6 */
#ifdef SOCKS
	if (socks_flag)
	    status = Rbind(new_socket,
			   (struct sockaddr *) &soc_address,
	    /* Cast to generic sockaddr */
			   sizeof(soc_address)
#ifndef SHORTENED_RBIND
			   ,socks_bind_remoteAddr
#endif /* !SHORTENED_RBIND */
		);
	else
#endif /* SOCKS */
	    status = bind(new_socket,
			  (struct sockaddr *) &soc_address,
	    /* Cast to generic sockaddr */
			  SOCKADDR_LEN(soc_address)
		);
	if (status < 0) {
	    close(new_socket);
	    return HTInetStatus("bind");
	}

	address_length = sizeof(soc_address);
#ifdef SOCKS
	if (socks_flag)
	    status = Rgetsockname(new_socket,
				  (struct sockaddr *) &soc_address,
				  &address_length);
	else
#endif /* SOCKS */
	    status = getsockname(new_socket,
				 (struct sockaddr *) &soc_address,
				 &address_length);
	if (status < 0) {
	    close(new_socket);
	    return HTInetStatus("getsockname");
	}
    }
#endif /* POLL_PORTS */

#ifdef INET6
    CTRACE((tfp, "HTFTP: bound to port %d on %s\n",
	    (int) ntohs(soc_in->sin_port),
	    HTInetString((void *) soc_in)));
#else
    CTRACE((tfp, "HTFTP: bound to port %d on %s\n",
	    (int) ntohs(soc_in->sin_port),
	    HTInetString(soc_in)));
#endif /* INET6 */

#ifdef REPEAT_LISTEN
    if (have_socket)
	(void) close_master_socket();
#endif /* REPEAT_LISTEN */

    set_master_socket(new_socket);

/*	Now we must find out who we are to tell the other guy
*/
    (void) HTHostName();	/* Make address valid - doesn't work */
#ifdef INET6
    switch (((struct sockaddr *) &soc_address)->sa_family) {
    case AF_INET:
#endif /* INET6 */
	sprintf(port_command, "PORT %d,%d,%d,%d,%d,%d%c%c",
		(int) *((unsigned char *) (&soc_in->sin_addr) + 0),
		(int) *((unsigned char *) (&soc_in->sin_addr) + 1),
		(int) *((unsigned char *) (&soc_in->sin_addr) + 2),
		(int) *((unsigned char *) (&soc_in->sin_addr) + 3),
		(int) *((unsigned char *) (&soc_in->sin_port) + 0),
		(int) *((unsigned char *) (&soc_in->sin_port) + 1),
		CR, LF);

#ifdef INET6
	break;

    case AF_INET6:
	{
	    char hostbuf[MAXHOSTNAMELEN];
	    char portbuf[MAXHOSTNAMELEN];

	    getnameinfo((struct sockaddr *) &soc_address,
			SOCKADDR_LEN(soc_address),
			hostbuf,
			(socklen_t) sizeof(hostbuf),
			portbuf,
			(socklen_t) sizeof(portbuf),
			NI_NUMERICHOST | NI_NUMERICSERV);
	    sprintf(port_command, "EPRT |%d|%s|%s|%c%c", 2, hostbuf, portbuf,
		    CR, LF);
	    break;
	}
    default:
	sprintf(port_command, "JUNK%c%c", CR, LF);
	break;
    }
#endif /* INET6 */

    /*  Inform TCP that we will accept connections
     */
    {
	int status;

#ifdef SOCKS
	if (socks_flag)
	    status = Rlisten((int) master_socket, 1);
	else
#endif /* SOCKS */
	    status = listen((int) master_socket, 1);
	if (status < 0) {
	    reset_master_socket();
	    return HTInetStatus("listen");
	}
    }
    CTRACE((tfp, "TCP: Master socket(), bind() and listen() all OK\n"));
    FD_SET(master_socket, &open_sockets);
    if ((master_socket + 1) > num_sockets)
	num_sockets = master_socket + 1;

    return (int) master_socket;	/* Good */

}				/* get_listen_socket */

static const char *months[12] =
{
    "Jan", "Feb", "Mar", "Apr", "May", "Jun",
    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};

/*	Procedure: Set the current and last year strings and date integer
 *	-----------------------------------------------------------------
 *
 *	Bug:
 *		This code is for sorting listings by date, if that option
 *		is selected in Lynx, and doesn't take into account time
 *		zones or ensure resetting at midnight, so the sort may not
 *		be perfect, but the actual date isn't changed in the display,
 *		i.e., the date is still correct. - FM
 */
static void set_years_and_date(void)
{
    char day[8], month[8], date[12];
    time_t NowTime;
    int i;
    char *printable;

    NowTime = time(NULL);
    printable = ctime(&NowTime);
    LYStrNCpy(day, printable + 8, 2);
    if (day[0] == ' ') {
	day[0] = '0';
    }
    LYStrNCpy(month, printable + 4, 3);
    for (i = 0; i < 12; i++) {
	if (!strcasecomp(month, months[i])) {
	    break;
	}
    }
    i++;
    sprintf(date, "9999%02d%.2s", i, day);
    TheDate = atoi(date);
    LYStrNCpy(ThisYear, printable + 20, 4);
    sprintf(LastYear, "%d", (atoi(ThisYear) - 1));
    HaveYears = TRUE;
}

typedef struct _EntryInfo {
    char *filename;
    char *linkname;		/* symbolic link, if any */
    char *type;
    char *date;
    off_t size;
    BOOLEAN display;		/* show this entry? */
#ifdef LONG_LIST
    unsigned long file_links;
    char *file_mode;
    char *file_user;
    char *file_group;
#endif
} EntryInfo;

static void free_entryinfo_struct_contents(EntryInfo *entry_info)
{
    if (entry_info) {
	FREE(entry_info->filename);
	FREE(entry_info->linkname);
	FREE(entry_info->type);
	FREE(entry_info->date);
    }
    /* dont free the struct */
}

/*
 * is_ls_date() --
 *	Return TRUE if s points to a string of the form:
 *		"Sep  1  1990 " or
 *		"Sep 11 11:59 " or
 *		"Dec 12 1989  " or
 *		"FCv 23 1990  " ...
 */
static BOOLEAN is_ls_date(char *s)
{
    /* must start with three alpha characters */
    if (!isalpha(UCH(*s++)) || !isalpha(UCH(*s++)) || !isalpha(UCH(*s++)))
	return FALSE;

    /* space or HT_NON_BREAK_SPACE */
    if (!(*s == ' ' || *s == HT_NON_BREAK_SPACE)) {
	return FALSE;
    }
    s++;

    /* space or digit */
    if (!(*s == ' ' || isdigit(UCH(*s)))) {
	return FALSE;
    }
    s++;

    /* digit */
    if (!isdigit(UCH(*s++)))
	return FALSE;

    /* space */
    if (*s++ != ' ')
	return FALSE;

    /* space or digit */
    if (!(*s == ' ' || isdigit(UCH(*s)))) {
	return FALSE;
    }
    s++;

    /* digit */
    if (!isdigit(UCH(*s++)))
	return FALSE;

    /* colon or digit */
    if (!(*s == ':' || isdigit(UCH(*s)))) {
	return FALSE;
    }
    s++;

    /* digit */
    if (!isdigit(UCH(*s++)))
	return FALSE;

    /* space or digit */
    if (!(*s == ' ' || isdigit(UCH(*s)))) {
	return FALSE;
    }
    s++;

    /* space */
    if (*s != ' ')
	return FALSE;

    return TRUE;
}				/* is_ls_date() */

/*
 * Extract the name, size, and date from an EPLF line.  - 08-06-96 DJB
 */
static void parse_eplf_line(char *line,
			    EntryInfo *info)
{
    char *cp = line;
    char ct[26];
    off_t size;
    time_t secs;
    static time_t base;		/* time() value on this OS in 1970 */
    static int flagbase = 0;

    if (!flagbase) {
	struct tm t;

	t.tm_year = 70;
	t.tm_mon = 0;
	t.tm_mday = 0;
	t.tm_hour = 0;
	t.tm_min = 0;
	t.tm_sec = 0;
	t.tm_isdst = -1;
	base = mktime(&t);	/* could return -1 */
	flagbase = 1;
    }

    while (*cp) {
	switch (*cp) {
	case '\t':
	    StrAllocCopy(info->filename, cp + 1);
	    return;
	case 's':
	    size = 0;
	    while (*(++cp) && (*cp != ','))
		size = (size * 10) + (off_t) (*cp - '0');
	    info->size = size;
	    break;
	case 'm':
	    secs = 0;
	    while (*(++cp) && (*cp != ','))
		secs = (secs * 10) + (*cp - '0');
	    secs += base;	/* assumes that time_t is #seconds */
	    LYStrNCpy(ct, ctime(&secs), 24);
	    StrAllocCopy(info->date, ct);
	    break;
	case '/':
	    StrAllocCopy(info->type, ENTRY_IS_DIRECTORY);
	    /* FALLTHRU */
	default:
	    while (*cp) {
		if (*cp++ == ',')
		    break;
	    }
	    break;
	}
    }
}				/* parse_eplf_line */

/*
 * Extract the name, size, and date from an ls -l line.
 */
static void parse_ls_line(char *line,
			  EntryInfo *entry)
{
#ifdef LONG_LIST
    char *next;
    char *cp;
#endif
    int i, j;
    off_t base = 1;
    off_t size_num = 0;

    for (i = (int) strlen(line) - 1;
	 (i > 13) && (!isspace(UCH(line[i])) || !is_ls_date(&line[i - 12]));
	 i--) {
	;			/* null body */
    }
    line[i] = '\0';
    if (i > 13) {
	StrAllocCopy(entry->date, &line[i - 12]);
	/* replace the 4th location with nbsp if it is a space or zero */
	if (entry->date[4] == ' ' || entry->date[4] == '0')
	    entry->date[4] = HT_NON_BREAK_SPACE;
	/* make sure year or time is flush right */
	if (entry->date[11] == ' ') {
	    for (j = 11; j > 6; j--) {
		entry->date[j] = entry->date[j - 1];
	    }
	}
    }
    j = i - 14;
    while (isdigit(UCH(line[j]))) {
	size_num += ((off_t) (line[j] - '0') * base);
	base *= 10;
	j--;
    }
    entry->size = size_num;
    StrAllocCopy(entry->filename, &line[i + 1]);

#ifdef LONG_LIST
    line[j] = '\0';

    /*
     * Extract the file-permissions, as a string.
     */
    if ((cp = StrChr(line, ' ')) != 0) {
	if ((cp - line) == 10) {
	    *cp = '\0';
	    StrAllocCopy(entry->file_mode, line);
	    *cp = ' ';
	}

	/*
	 * Next is the link-count.
	 */
	next = 0;
	entry->file_links = (unsigned long) strtol(cp, &next, 10);
	if (next == 0 || *next != ' ') {
	    entry->file_links = 0;
	    next = cp;
	} else {
	    cp = next;
	}
	/*
	 * Next is the user-name.
	 */
	while (isspace(UCH(*cp)))
	    ++cp;
	if ((next = StrChr(cp, ' ')) != 0)
	    *next = '\0';
	if (*cp != '\0')
	    StrAllocCopy(entry->file_user, cp);
	/*
	 * Next is the group-name (perhaps).
	 */
	if (next != NULL) {
	    cp = (next + 1);
	    while (isspace(UCH(*cp)))
		++cp;
	    if ((next = StrChr(cp, ' ')) != 0)
		*next = '\0';
	    if (*cp != '\0')
		StrAllocCopy(entry->file_group, cp);
	}
    }
#endif
}

/*
 * Extract the name and size info and whether it refers to a directory from a
 * LIST line in "dls" format.
 */
static void parse_dls_line(char *line,
			   EntryInfo *entry_info,
			   char **pspilledname)
{
    short j;
    int base = 1;
    off_t size_num = 0;
    int len;
    char *cps = NULL;

    /* README              763  Information about this server\0
       bin/                  -  \0
       etc/                  =  \0
       ls-lR                 0  \0
       ls-lR.Z               3  \0
       pub/                  =  Public area\0
       usr/                  -  \0
       morgan               14  -> ../real/morgan\0
       TIMIT.mostlikely.Z\0
       79215    \0
     */

    len = (int) strlen(line);
    if (len == 0) {
	FREE(*pspilledname);
	entry_info->display = FALSE;
	return;
    }
    cps = LYSkipNonBlanks(line);
    if (*cps == '\0') {		/* only a filename, save it and return. */
	StrAllocCopy(*pspilledname, line);
	entry_info->display = FALSE;
	return;
    }
    if (len < 24 || line[23] != ' ' ||
	(isspace(UCH(line[0])) && !*pspilledname)) {
	/* this isn't the expected "dls" format! */
	if (!isspace(UCH(line[0])))
	    *cps = '\0';
	if (*pspilledname && !*line) {
	    entry_info->filename = *pspilledname;
	    *pspilledname = NULL;
	    if (entry_info->filename[strlen(entry_info->filename) - 1] == '/')
		StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
	    else
		StrAllocCopy(entry_info->type, "");
	} else {
	    StrAllocCopy(entry_info->filename, line);
	    if (cps != line && *(cps - 1) == '/')
		StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
	    else
		StrAllocCopy(entry_info->type, "");
	    FREE(*pspilledname);
	}
	return;
    }

    j = 22;
    if (line[j] == '=' || line[j] == '-') {
	StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
    } else {
	while (isdigit(UCH(line[j]))) {
	    size_num += (line[j] - '0') * base;
	    base *= 10;
	    j--;
	}
    }
    entry_info->size = size_num;

    cps = LYSkipBlanks(&line[23]);
    if (!StrNCmp(cps, "-> ", 3) && cps[3] != '\0' && cps[3] != ' ') {
	StrAllocCopy(entry_info->type, ENTRY_IS_SYMBOLIC_LINK);
	StrAllocCopy(entry_info->linkname, LYSkipBlanks(cps + 3));
	entry_info->size = 0;	/* don't display size */
    }

    if (j > 0)
	line[j] = '\0';

    LYTrimTrailing(line);

    len = (int) strlen(line);
    if (len == 0 && *pspilledname && **pspilledname) {
	line = *pspilledname;
	len = (int) strlen(*pspilledname);
    }
    if (len > 0 && line[len - 1] == '/') {
	/*
	 * It's a dir, remove / and mark it as such.
	 */
	if (len > 1)
	    line[len - 1] = '\0';
	if (!entry_info->type)
	    StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
    }

    StrAllocCopy(entry_info->filename, line);
    FREE(*pspilledname);
}				/* parse_dls_line() */

/*
 * parse_vms_dir_entry()
 *	Format the name, date, and size from a VMS LIST line
 *	into the EntryInfo structure - FM
 */
static void parse_vms_dir_entry(char *line,
				EntryInfo *entry_info)
{
    int i, j;
    off_t ialloc;
    char *cp, *cpd, *cps, date[16];
    const char *sp = " ";

    /* Get rid of blank lines, and information lines.  Valid lines have the ';'
     * version number token.
     */
    if (!strlen(line) || (cp = StrChr(line, ';')) == NULL) {
	entry_info->display = FALSE;
	return;
    }

    /* Cut out file or directory name at VMS version number. */
    *cp++ = '\0';
    StrAllocCopy(entry_info->filename, line);

    /* Cast VMS non-README file and directory names to lowercase. */
    if (strstr(entry_info->filename, "READ") == NULL) {
	LYLowerCase(entry_info->filename);
	i = (int) strlen(entry_info->filename);
    } else {
	i = (int) ((strstr(entry_info->filename, "READ")
		    - entry_info->filename)
		   + 4);
	if (!StrNCmp(&entry_info->filename[i], "ME", 2)) {
	    i += 2;
	    while (entry_info->filename[i] && entry_info->filename[i] != '.') {
		i++;
	    }
	} else if (!StrNCmp(&entry_info->filename[i], ".ME", 3)) {
	    i = (int) strlen(entry_info->filename);
	} else {
	    i = 0;
	}
	LYLowerCase(entry_info->filename + i);
    }

    /* Uppercase terminal .z's or _z's. */
    if ((--i > 2) &&
	entry_info->filename[i] == 'z' &&
	(entry_info->filename[i - 1] == '.' ||
	 entry_info->filename[i - 1] == '_'))
	entry_info->filename[i] = 'Z';

    /* Convert any tabs in rest of line to spaces. */
    cps = cp - 1;
    while ((cps = StrChr(cps + 1, '\t')) != NULL)
	*cps = ' ';

    /* Collapse serial spaces. */
    i = 0;
    j = 1;
    cps = cp;
    while (cps[j] != '\0') {
	if (cps[i] == ' ' && cps[j] == ' ')
	    j++;
	else
	    cps[++i] = cps[j++];
    }
    cps[++i] = '\0';

    /* Set the years and date, if we don't have them yet. * */
    if (!HaveYears) {
	set_years_and_date();
    }

    /* Track down the date. */
    if ((cpd = StrChr(cp, '-')) != NULL &&
	strlen(cpd) > 9 && isdigit(UCH(*(cpd - 1))) &&
	isalpha(UCH(*(cpd + 1))) && *(cpd + 4) == '-') {

	/* Month */
	*(cpd + 2) = (char) TOLOWER(*(cpd + 2));
	*(cpd + 3) = (char) TOLOWER(*(cpd + 3));
	sprintf(date, "%.3s ", cpd + 1);

	/* Day */
	if (isdigit(UCH(*(cpd - 2))))
	    sprintf(date + 4, "%.2s ", cpd - 2);
	else
	    sprintf(date + 4, "%c%.1s ", HT_NON_BREAK_SPACE, cpd - 1);

	/* Time or Year */
	if (!StrNCmp(ThisYear, cpd + 5, 4) &&
	    strlen(cpd) > 15 && *(cpd + 12) == ':') {
	    sprintf(date + 7, "%.5s", cpd + 10);
	} else {
	    sprintf(date + 7, " %.4s", cpd + 5);
	}

	StrAllocCopy(entry_info->date, date);
    }

    /* Track down the size */
    if ((cpd = StrChr(cp, '/')) != NULL) {
	/* Appears be in used/allocated format */
	cps = cpd;
	while (isdigit(UCH(*(cps - 1))))
	    cps--;
	if (cps < cpd)
	    *cpd = '\0';
	entry_info->size = LYatoll(cps);
	cps = cpd + 1;
	while (isdigit(UCH(*cps)))
	    cps++;
	*cps = '\0';
	ialloc = LYatoll(cpd + 1);
	/* Check if used is in blocks or bytes */
	if (entry_info->size <= ialloc)
	    entry_info->size *= 512;

    } else if (strtok(cp, sp) != NULL) {
	/* We just initialized on the version number */
	/* Now let's hunt for a lone, size number    */
	while ((cps = strtok(NULL, sp)) != NULL) {
	    cpd = cps;
	    while (isdigit(UCH(*cpd)))
		cpd++;
	    if (*cpd == '\0') {
		/* Assume it's blocks */
		entry_info->size = (LYatoll(cps) * 512);
		break;
	    }
	}
    }

    TRACE_ENTRY("VMS", entry_info);
    return;
}				/* parse_vms_dir_entry() */

/*
 * parse_ms_windows_dir_entry() --
 *	Format the name, date, and size from an MS_WINDOWS LIST line into
 *	the EntryInfo structure (assumes Chameleon NEWT format). - FM
 */
static void parse_ms_windows_dir_entry(char *line,
				       EntryInfo *entry_info)
{
    char *cp = line;
    char *cps, *cpd, date[16];
    char *end = line + strlen(line);

    /*  Get rid of blank or junk lines.  */
    cp = LYSkipBlanks(cp);
    if (!(*cp)) {
	entry_info->display = FALSE;
	return;
    }

    /* Cut out file or directory name. */
    cps = LYSkipNonBlanks(cp);
    *cps++ = '\0';
    cpd = cps;
    StrAllocCopy(entry_info->filename, cp);

    /* Track down the size */
    if (cps < end) {
	cps = LYSkipBlanks(cps);
	cpd = LYSkipNonBlanks(cps);
	*cpd++ = '\0';
	if (isdigit(UCH(*cps))) {
	    entry_info->size = LYatoll(cps);
	} else {
	    StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
	}
    } else {
	StrAllocCopy(entry_info->type, "");
    }

    /* Set the years and date, if we don't have them yet. * */
    if (!HaveYears) {
	set_years_and_date();
    }

    /* Track down the date. */
    if (cpd < end) {
	cpd = LYSkipBlanks(cpd);
	if (strlen(cpd) > 17) {
	    *(cpd + 6) = '\0';	/* Month and Day */
	    *(cpd + 11) = '\0';	/* Year */
	    *(cpd + 17) = '\0';	/* Time */
	    if (strcmp(ThisYear, cpd + 7))
		/* Not this year, so show the year */
		sprintf(date, "%.6s  %.4s", cpd, (cpd + 7));
	    else
		/* Is this year, so show the time */
		sprintf(date, "%.6s %.5s", cpd, (cpd + 12));
	    StrAllocCopy(entry_info->date, date);
	    if (entry_info->date[4] == ' ' || entry_info->date[4] == '0') {
		entry_info->date[4] = HT_NON_BREAK_SPACE;
	    }
	}
    }

    TRACE_ENTRY("MS Windows", entry_info);
    return;
}				/* parse_ms_windows_dir_entry */

/*
 * parse_windows_nt_dir_entry() --
 *	Format the name, date, and size from a WINDOWS_NT LIST line into
 *	the EntryInfo structure (assumes Chameleon NEWT format). - FM
 */
#ifdef NOTDEFINED
static void parse_windows_nt_dir_entry(char *line,
				       EntryInfo *entry_info)
{
    char *cp = line;
    char *cps, *cpd, date[16];
    char *end = line + strlen(line);
    int i;

    /*  Get rid of blank or junk lines.  */
    cp = LYSkipBlanks(cp);
    if (!(*cp)) {
	entry_info->display = FALSE;
	return;
    }

    /* Cut out file or directory name. */
    cpd = cp;
    cps = LYSkipNonBlanks(end - 1);
    cp = (cps + 1);
    if (!strcmp(cp, ".") || !strcmp(cp, "..")) {
	entry_info->display = FALSE;
	return;
    }
    StrAllocCopy(entry_info->filename, cp);
    if (cps < cpd)
	return;
    *cp = '\0';
    end = cp;

    /* Set the years and date, if we don't have them yet. * */
    if (!HaveYears) {
	set_years_and_date();
    }

    /* Cut out the date. */
    cp = cps = cpd;
    cps = LYSkipNonBlanks(cps);
    *cps++ = '\0';
    if (cps > end) {
	entry_info->display = FALSE;
	return;
    }
    cps = LYSkipBlanks(cps);
    cpd = LYSkipNonBlanks(cps);
    *cps++ = '\0';
    if (cps > end || cpd == cps || strlen(cpd) < 7) {
	entry_info->display = FALSE;
	return;
    }
    if (strlen(cp) == 8 &&
	isdigit(*cp) && isdigit(*(cp + 1)) && *(cp + 2) == '-' &&
	isdigit(*(cp + 3)) && isdigit(*(cp + 4)) && *(cp + 5) == '-') {
	*(cp + 2) = '\0';	/* Month */
	i = atoi(cp) - 1;
	*(cp + 5) = '\0';	/* Day */
	sprintf(date, "%.3s %.2s", months[i], (cp + 3));
	if (date[4] == '0')
	    date[4] = ' ';
	cp += 6;		/* Year */
	if (strcmp((ThisYear + 2), cp)) {
	    /* Not this year, so show the year */
	    if (atoi(cp) < 70) {
		sprintf(&date[6], "  20%.2s", cp);
	    } else {
		sprintf(&date[6], "  19%.2s", cp);
	    }
	} else {
	    /* Is this year, so show the time */
	    *(cpd + 2) = '\0';	/* Hour */
	    i = atoi(cpd);
	    if (*(cpd + 5) == 'P' || *(cpd + 5) == 'p')
		i += 12;
	    sprintf(&date[6], " %02d:%.2s", i, (cpd + 3));
	}
	StrAllocCopy(entry_info->date, date);
	if (entry_info->date[4] == ' ' || entry_info->date[4] == '0') {
	    entry_info->date[4] = HT_NON_BREAK_SPACE;
	}
    }

    /* Track down the size */
    if (cps < end) {
	cps = LYSkipBlanks(cps);
	cpd = LYSkipNonBlanks(cps);
	*cpd = '\0';
	if (isdigit(*cps)) {
	    entry_info->size = LYatoll(cps);
	} else {
	    StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
	}
    } else {
	StrAllocCopy(entry_info->type, "");
    }

    /* Wrap it up */
    CTRACE((tfp, "HTFTP: Windows NT filename: %s  date: %s  size: %d\n",
	    entry_info->filename,
	    NonNull(entry_info->date),
	    entry_info->size));
    return;
}				/* parse_windows_nt_dir_entry */
#endif /* NOTDEFINED */

/*
 * parse_cms_dir_entry() --
 *	Format the name, date, and size from a VM/CMS line into
 *	the EntryInfo structure. - FM
 */
static void parse_cms_dir_entry(char *line,
				EntryInfo *entry_info)
{
    char *cp = line;
    char *cps, *cpd, date[16];
    char *end = line + strlen(line);
    int RecordLength = 0;
    int Records = 0;
    int i;

    /*  Get rid of blank or junk lines.  */
    cp = LYSkipBlanks(cp);
    if (!(*cp)) {
	entry_info->display = FALSE;
	return;
    }

    /* Cut out file or directory name. */
    cps = LYSkipNonBlanks(cp);
    *cps++ = '\0';
    StrAllocCopy(entry_info->filename, cp);
    if (StrChr(entry_info->filename, '.') != NULL)
	/* If we already have a dot, we did an NLST. */
	return;
    cp = LYSkipBlanks(cps);
    if (!(*cp)) {
	/* If we don't have more, we've misparsed. */
	FREE(entry_info->filename);
	FREE(entry_info->type);
	entry_info->display = FALSE;
	return;
    }
    cps = LYSkipNonBlanks(cp);
    *cps++ = '\0';
    if ((0 == strcasecomp(cp, "DIR")) && (cp - line) > 17) {
	/* It's an SFS directory. */
	StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
	entry_info->size = 0;
    } else {
	/* It's a file. */
	cp--;
	*cp = '.';
	StrAllocCat(entry_info->filename, cp);

	/* Track down the VM/CMS RECFM or type. */
	cp = cps;
	if (cp < end) {
	    cp = LYSkipBlanks(cp);
	    cps = LYSkipNonBlanks(cp);
	    *cps++ = '\0';
	    /* Check cp here, if it's relevant someday. */
	}
    }

    /* Track down the record length or dash. */
    cp = cps;
    if (cp < end) {
	cp = LYSkipBlanks(cp);
	cps = LYSkipNonBlanks(cp);
	*cps++ = '\0';
	if (isdigit(UCH(*cp))) {
	    RecordLength = atoi(cp);
	}
    }

    /* Track down the number of records or the dash. */
    cp = cps;
    if (cps < end) {
	cp = LYSkipBlanks(cp);
	cps = LYSkipNonBlanks(cp);
	*cps++ = '\0';
	if (isdigit(UCH(*cp))) {
	    Records = atoi(cp);
	}
	if (Records > 0 && RecordLength > 0) {
	    /* Compute an approximate size. */
	    entry_info->size = ((off_t) Records * (off_t) RecordLength);
	}
    }

    /* Set the years and date, if we don't have them yet. */
    if (!HaveYears) {
	set_years_and_date();
    }

    /* Track down the date. */
    cpd = cps;
    if (((cps < end) &&
	 (cps = StrChr(cpd, ':')) != NULL) &&
	(cps < (end - 3) &&
	 isdigit(UCH(*(cps + 1))) && isdigit(UCH(*(cps + 2))) && *(cps + 3) == ':')) {
	cps += 3;
	*cps = '\0';
	if ((cps - cpd) >= 14) {
	    cpd = (cps - 14);
	    *(cpd + 2) = '\0';	/* Month */
	    *(cpd + 5) = '\0';	/* Day */
	    *(cpd + 8) = '\0';	/* Year */
	    cps -= 5;		/* Time */
	    if (*cpd == ' ')
		*cpd = '0';
	    i = atoi(cpd) - 1;
	    sprintf(date, "%.3s %.2s", months[i], (cpd + 3));
	    if (date[4] == '0')
		date[4] = ' ';
	    cpd += 6;		/* Year */
	    if (strcmp((ThisYear + 2), cpd)) {
		/* Not this year, so show the year. */
		if (atoi(cpd) < 70) {
		    sprintf(&date[6], "  20%.2s", cpd);
		} else {
		    sprintf(&date[6], "  19%.2s", cpd);
		}
	    } else {
		/* Is this year, so show the time. */
		*(cps + 2) = '\0';	/* Hour */
		i = atoi(cps);
		sprintf(&date[6], " %02d:%.2s", i, (cps + 3));
	    }
	    StrAllocCopy(entry_info->date, date);
	    if (entry_info->date[4] == ' ' || entry_info->date[4] == '0') {
		entry_info->date[4] = HT_NON_BREAK_SPACE;
	    }
	}
    }

    TRACE_ENTRY("VM/CMS", entry_info);
    return;
}				/* parse_cms_dir_entry */

/*
 * Given a line of LIST/NLST output in entry, return results and a file/dir
 * name in entry_info struct
 *
 * If first is true, this is the first name in a directory.
 */
static EntryInfo *parse_dir_entry(char *entry,
				  BOOLEAN *first,
				  char **pspilledname)
{
    EntryInfo *entry_info;
    int i;
    int len;
    BOOLEAN remove_size = FALSE;
    char *cp;

    entry_info = typecalloc(EntryInfo);

    if (entry_info == NULL)
	outofmem(__FILE__, "parse_dir_entry");

    assert(entry_info != NULL);

    entry_info->display = TRUE;

    switch (server_type) {
    case DLS_SERVER:

	/*
	 * Interpret and edit LIST output from a Unix server in "dls" format. 
	 * This one must have claimed to be Unix in order to get here; if the
	 * first line looks fishy, we revert to Unix and hope that fits better
	 * (this recovery is untested).  - kw
	 */

	if (*first) {
	    len = (int) strlen(entry);
	    if (!len || entry[0] == ' ' ||
		(len >= 24 && entry[23] != ' ') ||
		(len < 24 && StrChr(entry, ' '))) {
		server_type = UNIX_SERVER;
		CTRACE((tfp,
			"HTFTP: Falling back to treating as Unix server.\n"));
	    } else {
		*first = FALSE;
	    }
	}

	if (server_type == DLS_SERVER) {
	    /* if still unchanged... */
	    parse_dls_line(entry, entry_info, pspilledname);

	    if (isEmpty(entry_info->filename)) {
		entry_info->display = FALSE;
		return (entry_info);
	    }
	    if (!strcmp(entry_info->filename, "..") ||
		!strcmp(entry_info->filename, "."))
		entry_info->display = FALSE;
	    if (entry_info->type && *entry_info->type == '\0') {
		FREE(entry_info->type);
		return (entry_info);
	    }
	    /*
	     * Goto the bottom and get real type.
	     */
	    break;
	}
	/* fall through if server_type changed for *first == TRUE ! */
    case UNIX_SERVER:
    case PETER_LEWIS_SERVER:
    case MACHTEN_SERVER:
    case MSDOS_SERVER:
    case WINDOWS_NT_SERVER:
    case WINDOWS_2K_SERVER:
    case APPLESHARE_SERVER:
    case NETPRESENZ_SERVER:
	/*
	 * Check for EPLF output (local times).
	 */
	if (*entry == '+') {
	    parse_eplf_line(entry, entry_info);
	    break;
	}

	/*
	 * Interpret and edit LIST output from Unix server.
	 */
	len = (int) strlen(entry);
	if (*first) {
	    /* don't gettext() this -- incoming text: */
	    if (!strcmp(entry, "can not access directory .")) {
		/*
		 * Don't reset *first, nothing real will follow.  - KW
		 */
		entry_info->display = FALSE;
		return (entry_info);
	    }
	    *first = FALSE;
	    if (!StrNCmp(entry, "total ", 6) ||
		strstr(entry, "not available") != NULL) {
		entry_info->display = FALSE;
		return (entry_info);
	    } else if (unsure_type) {
		/* this isn't really a unix server! */
		server_type = GENERIC_SERVER;
		entry_info->display = FALSE;
		return (entry_info);
	    }
	}

	/*
	 * Check first character of ls -l output.
	 */
	if (TOUPPER(entry[0]) == 'D') {
	    /*
	     * It's a directory.
	     */
	    StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
	    remove_size = TRUE;	/* size is not useful */
	} else if (entry[0] == 'l') {
	    /*
	     * It's a symbolic link, does the user care about knowing if it is
	     * symbolic?  I think so since it might be a directory.
	     */
	    StrAllocCopy(entry_info->type, ENTRY_IS_SYMBOLIC_LINK);
	    remove_size = TRUE;	/* size is not useful */

	    /*
	     * Strip off " -> pathname".
	     */
	    for (i = len - 1; (i > 3) &&
		 (!isspace(UCH(entry[i])) ||
		  (entry[i - 1] != '>') ||
		  (entry[i - 2] != '-') ||
		  (entry[i - 3] != ' ')); i--) ;	/* null body */
	    if (i > 3) {
		entry[i - 3] = '\0';
		StrAllocCopy(entry_info->linkname, LYSkipBlanks(entry + i));
	    }
	}
	/* link */
	parse_ls_line(entry, entry_info);

	if (!strcmp(entry_info->filename, "..") ||
	    !strcmp(entry_info->filename, "."))
	    entry_info->display = FALSE;
	/*
	 * Goto the bottom and get real type.
	 */
	break;

    case VMS_SERVER:
	/*
	 * Interpret and edit LIST output from VMS server and convert
	 * information lines to zero length.
	 */
	parse_vms_dir_entry(entry, entry_info);

	/*
	 * Get rid of any junk lines.
	 */
	if (!entry_info->display)
	    return (entry_info);

	/*
	 * Trim off VMS directory extensions.
	 */
	len = (int) strlen(entry_info->filename);
	if ((len > 4) && !strcmp(&entry_info->filename[len - 4], ".dir")) {
	    entry_info->filename[len - 4] = '\0';
	    StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
	    remove_size = TRUE;	/* size is not useful */
	}
	/*
	 * Goto the bottom and get real type.
	 */
	break;

    case MS_WINDOWS_SERVER:
	/*
	 * Interpret and edit LIST output from MS_WINDOWS server and convert
	 * information lines to zero length.
	 */
	parse_ms_windows_dir_entry(entry, entry_info);

	/*
	 * Get rid of any junk lines.
	 */
	if (!entry_info->display)
	    return (entry_info);
	if (entry_info->type && *entry_info->type == '\0') {
	    FREE(entry_info->type);
	    return (entry_info);
	}
	/*
	 * Goto the bottom and get real type.
	 */
	break;

#ifdef NOTDEFINED
    case WINDOWS_NT_SERVER:
	/*
	 * Interpret and edit LIST output from MS_WINDOWS server and convert
	 * information lines to zero length.
	 */
	parse_windows_nt_dir_entry(entry, entry_info);

	/*
	 * Get rid of any junk lines.
	 */
	if (!entry_info->display)
	    return (entry_info);
	if (entry_info->type && *entry_info->type == '\0') {
	    FREE(entry_info->type);
	    return (entry_info);
	}
	/*
	 * Goto the bottom and get real type.
	 */
	break;
#endif /* NOTDEFINED */

    case CMS_SERVER:
	{
	    /*
	     * Interpret and edit LIST output from VM/CMS server and convert
	     * any information lines to zero length.
	     */
	    parse_cms_dir_entry(entry, entry_info);

	    /*
	     * Get rid of any junk lines.
	     */
	    if (!entry_info->display)
		return (entry_info);
	    if (entry_info->type && *entry_info->type == '\0') {
		FREE(entry_info->type);
		return (entry_info);
	    }
	    /*
	     * Goto the bottom and get real type.
	     */
	    break;
	}

    case NCSA_SERVER:
    case TCPC_SERVER:
	/*
	 * Directories identified by trailing "/" characters.
	 */
	StrAllocCopy(entry_info->filename, entry);
	len = (int) strlen(entry);
	if (entry[len - 1] == '/') {
	    /*
	     * It's a dir, remove / and mark it as such.
	     */
	    entry[len - 1] = '\0';
	    StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
	    remove_size = TRUE;	/* size is not useful */
	}
	/*
	 * Goto the bottom and get real type.
	 */
	break;

    default:
	/*
	 * We can't tell if it is a directory since we only did an NLST :( List
	 * bad file types anyways?  NOT!
	 */
	StrAllocCopy(entry_info->filename, entry);
	return (entry_info);	/* mostly empty info */

    }				/* switch (server_type) */

#ifdef LONG_LIST
    (void) remove_size;
#else
    if (remove_size && entry_info->size) {
	entry_info->size = 0;
    }
#endif

    if (isEmpty(entry_info->filename)) {
	entry_info->display = FALSE;
	return (entry_info);
    }
    if (strlen(entry_info->filename) > 3) {
	if (((cp = strrchr(entry_info->filename, '.')) != NULL &&
	     0 == strncasecomp(cp, ".me", 3)) &&
	    (cp[3] == '\0' || cp[3] == ';')) {
	    /*
	     * Don't treat this as application/x-Troff-me if it's a Unix server
	     * but has the string "read.me", or if it's not a Unix server.  -
	     * FM
	     */
	    if ((server_type != UNIX_SERVER) ||
		(cp > (entry_info->filename + 3) &&
		 0 == strncasecomp((cp - 4), "read.me", 7))) {
		StrAllocCopy(entry_info->type, "text/plain");
	    }
	}
    }

    /*
     * Get real types eventually.
     */
    if (!entry_info->type) {
	const char *cp2;
	HTFormat format;
	HTAtom *encoding;	/* @@ not used at all */

	format = HTFileFormat(entry_info->filename, &encoding, &cp2);

	if (cp2 == NULL) {
	    if (!StrNCmp(HTAtom_name(format), "application", 11)) {
		cp2 = HTAtom_name(format) + 12;
		if (!StrNCmp(cp2, "x-", 2))
		    cp2 += 2;
	    } else {
		cp2 = HTAtom_name(format);
	    }
	}

	StrAllocCopy(entry_info->type, cp2);
    }

    return (entry_info);
}

static void formatDate(char target[16], EntryInfo *entry)
{
    char temp[8], month[4];
    int i;

    /*
     * Set up for sorting in reverse chronological order. - FM
     */
    if (entry->date[9] == ':') {
	strcpy(target, "9999");
	LYStrNCpy(temp, &entry->date[7], 5);
	if (temp[0] == ' ') {
	    temp[0] = '0';
	}
    } else {
	LYStrNCpy(target, &entry->date[8], 4);
	strcpy(temp, "00:00");
    }
    LYStrNCpy(month, entry->date, 3);
    for (i = 0; i < 12; i++) {
	if (!strcasecomp(month, months[i])) {
	    break;
	}
    }
    i++;
    sprintf(month, "%02d", i);
    strcat(target, month);
    StrNCat(target, &entry->date[4], 2);
    if (target[6] == ' ' || target[6] == HT_NON_BREAK_SPACE) {
	target[6] = '0';
    }

    /* If no year given, assume last year if it would otherwise be in the
     * future by more than one day.  The one day tolerance is to account for a
     * possible timezone difference. - kw
     */
    if (target[0] == '9' && atoi(target) > TheDate + 1) {
	for (i = 0; i < 4; i++) {
	    target[i] = LastYear[i];
	}
    }
    strcat(target, temp);
}

static int compare_EntryInfo_structs(EntryInfo *entry1, EntryInfo *entry2)
{
    int status;
    char date1[16], date2[16];
    int result = strcmp(entry1->filename, entry2->filename);

    switch (HTfileSortMethod) {
    case FILE_BY_SIZE:
	/* both equal or both 0 */
	if (entry1->size > entry2->size)
	    result = 1;
	else if (entry1->size < entry2->size)
	    result = -1;
	break;

    case FILE_BY_TYPE:
	if (entry1->type && entry2->type) {
	    status = strcasecomp(entry1->type, entry2->type);
	    if (status)
		result = status;
	}
	break;

    case FILE_BY_DATE:
	if (entry1->date && entry2->date &&
	    strlen(entry1->date) == 12 &&
	    strlen(entry2->date) == 12) {
	    /*
	     * Set the years and date, if we don't have them yet.
	     */
	    if (!HaveYears) {
		set_years_and_date();
	    }
	    formatDate(date1, entry1);
	    formatDate(date2, entry2);
	    /*
	     * Do the comparison. - FM
	     */
	    status = strcasecomp(date2, date1);
	    if (status)
		result = status;
	}
	break;

    case FILE_BY_NAME:
    default:
	break;
    }
    return result;
}

#ifdef LONG_LIST
static char *FormatStr(char **bufp,
		       char *start,
		       const char *value)
{
    char fmt[512];

    if (*start) {
	sprintf(fmt, "%%%.*ss", (int) sizeof(fmt) - 3, start);
	HTSprintf(bufp, fmt, value);
    } else if (*bufp && !(value && *value)) {
	;
    } else if (value) {
	StrAllocCat(*bufp, value);
    }
    return *bufp;
}

static char *FormatSize(char **bufp,
			char *start,
			off_t value)
{
    char fmt[512];

    if (*start) {
	sprintf(fmt, "%%%.*s" PRI_off_t, (int) sizeof(fmt) - 3, start);

	HTSprintf(bufp, fmt, value);
    } else {
	sprintf(fmt, "%" PRI_off_t, CAST_off_t (value));

	StrAllocCat(*bufp, fmt);
    }
    return *bufp;
}

static char *FormatNum(char **bufp,
		       char *start,
		       unsigned long value)
{
    char fmt[512];

    if (*start) {
	sprintf(fmt, "%%%.*sld", (int) sizeof(fmt) - 3, start);
	HTSprintf(bufp, fmt, value);
    } else {
	sprintf(fmt, "%lu", value);
	StrAllocCat(*bufp, fmt);
    }
    return *bufp;
}

static void FlushParse(HTStructured * target, char **buf)
{
    if (*buf && **buf) {
	PUTS(*buf);
	**buf = '\0';
    }
}

static void LYListFmtParse(const char *fmtstr,
			   EntryInfo *data,
			   HTStructured * target,
			   char *tail)
{
    char c;
    char *s;
    char *end;
    char *start;
    char *str = NULL;
    char *buf = NULL;
    BOOL is_directory = (BOOL) (data->file_mode != 0 &&
				(TOUPPER(data->file_mode[0]) == 'D'));
    BOOL is_symlinked = (BOOL) (data->file_mode != 0 &&
				(TOUPPER(data->file_mode[0]) == 'L'));
    BOOL remove_size = (BOOL) (is_directory || is_symlinked);

    StrAllocCopy(str, fmtstr);
    s = str;
    end = str + strlen(str);
    while (*s) {
	start = s;
	while (*s) {
	    if (*s == '%') {
		if (*(s + 1) == '%')	/* literal % */
		    s++;
		else
		    break;
	    }
	    s++;
	}
	/* s is positioned either at a % or at \0 */
	*s = '\0';
	if (s > start) {	/* some literal chars. */
	    StrAllocCat(buf, start);
	}
	if (s == end)
	    break;
	start = ++s;
	while (isdigit(UCH(*s)) || *s == '.' || *s == '-' || *s == ' ' ||
	       *s == '#' || *s == '+' || *s == '\'')
	    s++;
	c = *s;			/* the format char. or \0 */
	*s = '\0';

	switch (c) {
	case '\0':
	    StrAllocCat(buf, start);
	    continue;

	case 'A':
	case 'a':		/* anchor */
	    FlushParse(target, &buf);
	    HTDirEntry(target, tail, data->filename);
	    FormatStr(&buf, start, data->filename);
	    PUTS(buf);
	    END(HTML_A);
	    if (buf != 0)
		*buf = '\0';
	    if (c != 'A' && data->linkname != 0) {
		PUTS(" -> ");
		PUTS(data->linkname);
	    }
	    break;

	case 'T':		/* MIME type */
	case 't':		/* MIME type description */
	    if (is_directory) {
		if (c != 'T') {
		    FormatStr(&buf, start, ENTRY_IS_DIRECTORY);
		} else {
		    FormatStr(&buf, start, "");
		}
	    } else if (is_symlinked) {
		if (c != 'T') {
		    FormatStr(&buf, start, ENTRY_IS_SYMBOLIC_LINK);
		} else {
		    FormatStr(&buf, start, "");
		}
	    } else {
		const char *cp2;
		HTFormat format;

		format = HTFileFormat(data->filename, NULL, &cp2);

		if (c != 'T') {
		    if (cp2 == NULL) {
			if (!StrNCmp(HTAtom_name(format),
				     "application", 11)) {
			    cp2 = HTAtom_name(format) + 12;
			    if (!StrNCmp(cp2, "x-", 2))
				cp2 += 2;
			} else {
			    cp2 = HTAtom_name(format);
			}
		    }
		    FormatStr(&buf, start, cp2);
		} else {
		    FormatStr(&buf, start, HTAtom_name(format));
		}
	    }
	    break;

	case 'd':		/* date */
	    if (data->date) {
		FormatStr(&buf, start, data->date);
	    } else {
		FormatStr(&buf, start, " * ");
	    }
	    break;

	case 's':		/* size in bytes */
	    FormatSize(&buf, start, data->size);
	    break;

	case 'K':		/* size in Kilobytes but not for directories */
	    if (remove_size) {
		FormatStr(&buf, start, "");
		StrAllocCat(buf, " ");
		break;
	    }
	    /* FALL THROUGH */
	case 'k':		/* size in Kilobytes */
	    /* FIXME - this is inconsistent with HTFile.c, but historical */
	    if (data->size < 1024) {
		FormatSize(&buf, start, data->size);
		StrAllocCat(buf, " bytes");
	    } else {
		FormatSize(&buf, start, data->size / 1024);
		StrAllocCat(buf, "Kb");
	    }
	    break;

#ifdef LONG_LIST
	case 'p':		/* unix-style permission bits */
	    FormatStr(&buf, start, NonNull(data->file_mode));
	    break;

	case 'o':		/* owner */
	    FormatStr(&buf, start, NonNull(data->file_user));
	    break;

	case 'g':		/* group */
	    FormatStr(&buf, start, NonNull(data->file_group));
	    break;

	case 'l':		/* link count */
	    FormatNum(&buf, start, data->file_links);
	    break;
#endif

	case '%':		/* literal % with flags/width */
	    FormatStr(&buf, start, "%");
	    break;

	default:
	    fprintf(stderr,
		    "Unknown format character `%c' in list format\n", c);
	    break;
	}

	s++;
    }
    if (buf) {
	LYTrimTrailing(buf);
	FlushParse(target, &buf);
	FREE(buf);
    }
    PUTC('\n');
    FREE(str);
}
#endif /* LONG_LIST */

/*	Read a directory into an hypertext object from the data socket
 *	--------------------------------------------------------------
 *
 * On entry,
 *	anchor		Parent anchor to link the this node to
 *	address		Address of the directory
 * On exit,
 *	returns		HT_LOADED if OK
 *			<0 if error.
 */
static int read_directory(HTParentAnchor *parent,
			  const char *address,
			  HTFormat format_out,
			  HTStream *sink)
{
    int status;
    BOOLEAN WasInterrupted = FALSE;
    HTStructured *target = HTML_new(parent, format_out, sink);
    char *filename = HTParse(address, "", PARSE_PATH + PARSE_PUNCTUATION);
    EntryInfo *entry_info;
    BOOLEAN first = TRUE;
    char *lastpath = NULL;	/* prefix for link, either "" (for root) or xxx  */
    BOOL tildeIsTop = FALSE;

#ifndef LONG_LIST
    char string_buffer[64];
#endif

    _HTProgress(gettext("Receiving FTP directory."));

    /*
     * Force the current Date and Year (TheDate, ThisYear, and LastYear) to be
     * recalculated for each directory request.  Otherwise we have a problem
     * with long-running sessions assuming the wrong date for today.  - kw
     */
    HaveYears = FALSE;
    /*
     * Check whether we always want the home directory treated as Welcome.  -
     * FM
     */
    if (server_type == VMS_SERVER)
	tildeIsTop = TRUE;

    /*
     * This should always come back FALSE, since the flag is set only for local
     * directory listings if LONG_LIST was defined on compilation, but we could
     * someday set up an equivalent listing for Unix ftp servers.  - FM
     */
    (void) HTDirTitles(target, parent, format_out, tildeIsTop);

    data_read_pointer = data_write_pointer = data_buffer;

    if (*filename == '\0') {	/* Empty filename: use root. */
	StrAllocCopy(lastpath, "/");
    } else if (!strcmp(filename, "/")) {	/* Root path. */
	StrAllocCopy(lastpath, "/foo/..");
    } else {
	char *p = strrchr(filename, '/');	/* Find the lastslash. */
	char *cp;

	if (server_type == CMS_SERVER) {
	    StrAllocCopy(lastpath, filename);	/* Use absolute path for CMS. */
	} else {
	    StrAllocCopy(lastpath, p + 1);	/* Take slash off the beginning. */
	}
	if ((cp = strrchr(lastpath, ';')) != NULL) {	/* Trim type= param. */
	    if (!strncasecomp((cp + 1), "type=", 5)) {
		if (TOUPPER(*(cp + 6)) == 'D' ||
		    TOUPPER(*(cp + 6)) == 'A' ||
		    TOUPPER(*(cp + 6)) == 'I')
		    *cp = '\0';
	    }
	}
    }
    FREE(filename);

    {
	HTBTree *bt = HTBTree_new((HTComparer) compare_EntryInfo_structs);
	int ic;
	HTChunk *chunk = HTChunkCreate(128);
	int BytesReceived = 0;
	int BytesReported = 0;
	char NumBytes[64];
	char *spilledname = NULL;

	PUTC('\n');		/* prettier LJM */
	for (ic = 0; ic != EOF;) {	/* For each entry in the directory */
	    HTChunkClear(chunk);

	    if (HTCheckForInterrupt()) {
		CTRACE((tfp,
			"read_directory: interrupted after %d bytes\n",
			BytesReceived));
		WasInterrupted = TRUE;
		if (BytesReceived) {
		    goto unload_btree;	/* unload btree */
		} else {
		    ABORT_TARGET;
		    HTBTreeAndObject_free(bt);
		    FREE(spilledname);
		    HTChunkFree(chunk);
		    return HT_INTERRUPTED;
		}
	    }

	    /*   read directory entry
	     */
	    interrupted_in_next_data_char = FALSE;
	    for (;;) {		/* Read in one line as filename */
		ic = NEXT_DATA_CHAR;
	      AgainForMultiNet:
		if (interrupted_in_next_data_char) {
		    CTRACE((tfp,
			    "read_directory: interrupted_in_next_data_char after %d bytes\n",
			    BytesReceived));
		    WasInterrupted = TRUE;
		    if (BytesReceived) {
			goto unload_btree;	/* unload btree */
		    } else {
			ABORT_TARGET;
			HTBTreeAndObject_free(bt);
			FREE(spilledname);
			HTChunkFree(chunk);
			return HT_INTERRUPTED;
		    }
		} else if ((char) ic == CR || (char) ic == LF) {	/* Terminator? */
		    if (chunk->size != 0) {	/* got some text */
			/* Deal with MultiNet's wrapping of long lines */
			if (server_type == VMS_SERVER) {
			    /* Deal with MultiNet's wrapping of long lines - F.M. */
			    if (data_read_pointer < data_write_pointer &&
				*(data_read_pointer + 1) == ' ')
				data_read_pointer++;
			    else if (data_read_pointer >= data_write_pointer) {
				status = NETREAD(data_soc, data_buffer,
						 DATA_BUFFER_SIZE);
				if (status == HT_INTERRUPTED) {
				    interrupted_in_next_data_char = 1;
				    goto AgainForMultiNet;
				}
				if (status <= 0) {
				    ic = EOF;
				    break;
				}
				data_write_pointer = data_buffer + status;
				data_read_pointer = data_buffer;
				if (*data_read_pointer == ' ')
				    data_read_pointer++;
				else
				    break;
			    } else
				break;
			} else
			    break;	/* finish getting one entry */
		    }
		} else if (ic == EOF) {
		    break;	/* End of file */
		} else {
		    HTChunkPutc(chunk, UCH(ic));
		}
	    }
	    HTChunkTerminate(chunk);

	    BytesReceived += chunk->size;
	    if (BytesReceived > BytesReported + 1024) {
#ifdef _WINDOWS
		sprintf(NumBytes, gettext("Transferred %d bytes (%5d)"),
			BytesReceived, ws_read_per_sec);
#else
		sprintf(NumBytes, TRANSFERRED_X_BYTES, BytesReceived);
#endif
		HTProgress(NumBytes);
		BytesReported = BytesReceived;
	    }

	    if (ic == EOF && chunk->size == 1)
		/* 1 means empty: includes terminating 0 */
		break;
	    CTRACE((tfp, "HTFTP: Line in %s is %s\n",
		    lastpath, chunk->data));

	    entry_info = parse_dir_entry(chunk->data, &first, &spilledname);
	    if (entry_info->display) {
		FREE(spilledname);
		CTRACE((tfp, "Adding file to BTree: %s\n",
			entry_info->filename));
		HTBTree_add(bt, entry_info);
	    } else {
		free_entryinfo_struct_contents(entry_info);
		FREE(entry_info);
	    }

	}			/* next entry */

      unload_btree:

	HTChunkFree(chunk);
	FREE(spilledname);

	/* print out the handy help message if it exists :) */
	if (help_message_cache_non_empty()) {
	    START(HTML_PRE);
	    START(HTML_HR);
	    PUTC('\n');
	    PUTS(help_message_cache_contents());
	    init_help_message_cache();	/* to free memory */
	    START(HTML_HR);
	    PUTC('\n');
	} else {
	    START(HTML_PRE);
	    PUTC('\n');
	}

	/* Run through tree printing out in order
	 */
	{
#ifndef LONG_LIST
#ifdef SH_EX			/* 1997/10/18 (Sat) 14:14:28 */
	    char *p, name_buff[256];
	    int name_len, dot_len;

#define	FNAME_WIDTH	30
#define	FILE_GAP	1

#endif
	    int i;
#endif
	    HTBTElement *ele;

	    for (ele = HTBTree_next(bt, NULL);
		 ele != NULL;
		 ele = HTBTree_next(bt, ele)) {
		entry_info = (EntryInfo *) HTBTree_object(ele);

#ifdef LONG_LIST
		LYListFmtParse(ftp_format,
			       entry_info,
			       target,
			       lastpath);
#else
		if (entry_info->date) {
		    PUTS(entry_info->date);
		    PUTS("  ");
		} else {
		    PUTS("     * ");
		}

		if (entry_info->type) {
		    for (i = 0; entry_info->type[i] != '\0' && i < 16; i++)
			PUTC(entry_info->type[i]);
		    for (; i < 17; i++)
			PUTC(' ');
		}
		/* start the anchor */
		HTDirEntry(target, lastpath, entry_info->filename);
#ifdef SH_EX			/* 1997/10/18 (Sat) 16:00 */
		name_len = strlen(entry_info->filename);

		sprintf(name_buff, "%-*s", FNAME_WIDTH, entry_info->filename);

		if (name_len < FNAME_WIDTH) {
		    dot_len = FNAME_WIDTH - FILE_GAP - name_len;
		    if (dot_len > 0) {
			p = name_buff + name_len + 1;
			while (dot_len-- > 0)
			    *p++ = '.';
		    }
		} else {
		    name_buff[FNAME_WIDTH] = '\0';
		}

		PUTS(name_buff);
#else
		PUTS(entry_info->filename);
#endif
		END(HTML_A);

		if (entry_info->size) {
#ifdef SH_EX			/* 1998/02/02 (Mon) 16:34:52 */
		    if (entry_info->size < 1024)
			sprintf(string_buffer, "%6ld bytes",
				entry_info->size);
		    else
			sprintf(string_buffer, "%6ld Kb",
				entry_info->size / 1024);
#else
		    if (entry_info->size < 1024)
			sprintf(string_buffer, "  %lu bytes",
				entry_info->size);
		    else
			sprintf(string_buffer, "  %luKb",
				entry_info->size / 1024);
#endif
		    PUTS(string_buffer);
		} else if (entry_info->linkname != 0) {
		    PUTS(" -> ");
		    PUTS(entry_info->linkname);
		}

		PUTC('\n');	/* end of this entry */
#endif

		free_entryinfo_struct_contents(entry_info);
	    }
	}
	END(HTML_PRE);
	END(HTML_BODY);
	FREE_TARGET;
	HTBTreeAndObject_free(bt);
    }

    FREE(lastpath);

    if (WasInterrupted || data_soc != -1) {	/* should always be true */
	/*
	 * Without closing the data socket first, the response(0) later may
	 * hang.  Some servers expect the client to fin/ack the close of the
	 * data connection before proceeding with the conversation on the
	 * control connection.  - kw
	 */
	CTRACE((tfp, "HTFTP: Closing data socket %d\n", data_soc));
	status = NETCLOSE(data_soc);
	if (status == -1)
	    HTInetStatus("close");	/* Comment only */
	data_soc = -1;
    }

    if (WasInterrupted || HTCheckForInterrupt()) {
	_HTProgress(TRANSFER_INTERRUPTED);
    }
    return HT_LOADED;
}

/*
 * Setup an FTP connection.
 */
static int setup_connection(const char *name,
			    HTParentAnchor *anchor)
{
    int retry;			/* How many times tried? */
    int status = HT_NO_CONNECTION;

    CTRACE((tfp, "setup_connection(%s)\n", name));

    /* set use_list to NOT since we don't know what kind of server
     * this is yet.  And set the type to GENERIC
     */
    use_list = FALSE;
    server_type = GENERIC_SERVER;
    Broken_RETR = FALSE;

#ifdef INET6
    Broken_EPSV = FALSE;
#endif

    for (retry = 0; retry < 2; retry++) {	/* For timed out/broken connections */
	status = get_connection(name, anchor);
	if (status < 0) {
	    break;
	}

	if (!ftp_local_passive) {
	    status = get_listen_socket();
	    if (status < 0) {
		NETCLOSE(control->socket);
		control->socket = -1;
#ifdef INET6
		if (have_socket)
		    (void) close_master_socket();
#else
		close_master_socket();
#endif /* INET6 */
		/* HT_INTERRUPTED would fall through, if we could interrupt
		   somehow in the middle of it, which we currently can't. */
		break;
	    }
#ifdef REPEAT_PORT
	    /*  Inform the server of the port number we will listen on
	     */
	    status = response(port_command);
	    if (status == HT_INTERRUPTED) {
		CTRACE((tfp, "HTFTP: Interrupted in response (port_command)\n"));
		_HTProgress(CONNECTION_INTERRUPTED);
		NETCLOSE(control->socket);
		control->socket = -1;
		close_master_socket();
		status = HT_INTERRUPTED;
		break;
	    }
	    if (status != 2) {	/* Could have timed out */
		if (status < 0)
		    continue;	/* try again - net error */
		status = -status;	/* bad reply */
		break;
	    }
	    CTRACE((tfp, "HTFTP: Port defined.\n"));
#endif /* REPEAT_PORT */
	} else {		/* Tell the server to be passive */
	    char *command = NULL;
	    const char *p = "?";
	    int h0, h1, h2, h3, p0, p1;		/* Parts of reply */

#ifdef INET6
	    char dst[LINE_LENGTH + 1];
#endif

	    data_soc = status;

#ifdef INET6
	    /* see RFC 2428 */
	    if (Broken_EPSV)
		status = 1;
	    else
		status = send_cmd_1(p = "EPSV");
	    if (status < 0)	/* retry or Bad return */
		continue;
	    else if (status != 2) {
		status = send_cmd_1(p = "PASV");
		if (status < 0) {	/* retry or Bad return */
		    continue;
		} else if (status != 2) {
		    status = -status;	/* bad reply */
		    break;
		}
	    }

	    if (strcmp(p, "PASV") == 0) {
		for (p = response_text; *p && *p != ','; p++) {
		    ;		/* null body */
		}

		while (--p > response_text && '0' <= *p && *p <= '9') {
		    ;		/* null body */
		}
		status = sscanf(p + 1, "%d,%d,%d,%d,%d,%d",
				&h0, &h1, &h2, &h3, &p0, &p1);
		if (status < 4) {
		    fprintf(tfp, "HTFTP: PASV reply has no inet address!\n");
		    status = HT_NO_CONNECTION;
		    break;
		}
		passive_port = (PortNumber) ((p0 << 8) + p1);
		sprintf(dst, "%d.%d.%d.%d", h0, h1, h2, h3);
	    } else if (strcmp(p, "EPSV") == 0) {
		char c0, c1, c2, c3;
		struct sockaddr_storage ss;
		LY_SOCKLEN sslen;

		/*
		 * EPSV bla (|||port|)
		 */
		for (p = response_text; *p && !isspace(UCH(*p)); p++) {
		    ;		/* null body */
		}
		for ( /*nothing */ ;
		     *p && *p != '(';
		     p++) {	/*) */
		    ;		/* null body */
		}
		status = sscanf(p, "(%c%c%c%d%c)", &c0, &c1, &c2, &p0, &c3);
		if (status != 5) {
		    fprintf(tfp, "HTFTP: EPSV reply has invalid format!\n");
		    status = HT_NO_CONNECTION;
		    break;
		}
		passive_port = (PortNumber) p0;

		sslen = (LY_SOCKLEN) sizeof(ss);
		if (getpeername(control->socket, (struct sockaddr *) &ss,
				&sslen) < 0) {
		    fprintf(tfp, "HTFTP: getpeername(control) failed\n");
		    status = HT_NO_CONNECTION;
		    break;
		}
		if (getnameinfo((struct sockaddr *) &ss,
				sslen,
				dst,
				(socklen_t) sizeof(dst),
				NULL, 0, NI_NUMERICHOST)) {
		    fprintf(tfp, "HTFTP: getnameinfo failed\n");
		    status = HT_NO_CONNECTION;
		    break;
		}
	    }
#else
	    status = send_cmd_1("PASV");
	    if (status != 2) {
		if (status < 0)
		    continue;	/* retry or Bad return */
		status = -status;	/* bad reply */
		break;
	    }
	    for (p = response_text; *p && *p != ','; p++) {
		;		/* null body */
	    }

	    while (--p > response_text && '0' <= *p && *p <= '9') {
		;		/* null body */
	    }

	    status = sscanf(p + 1, "%d,%d,%d,%d,%d,%d",
			    &h0, &h1, &h2, &h3, &p0, &p1);
	    if (status < 4) {
		fprintf(tfp, "HTFTP: PASV reply has no inet address!\n");
		status = HT_NO_CONNECTION;
		break;
	    }
	    passive_port = (PortNumber) ((p0 << 8) + p1);
#endif /* INET6 */
	    CTRACE((tfp, "HTFTP: Server is listening on port %d\n",
		    passive_port));

	    /* Open connection for data:  */

#ifdef INET6
	    HTSprintf0(&command, "%s//%s:%d/", STR_FTP_URL, dst, passive_port);
#else
	    HTSprintf0(&command, "%s//%d.%d.%d.%d:%d/",
		       STR_FTP_URL, h0, h1, h2, h3, passive_port);
#endif
	    status = HTDoConnect(command, "FTP data", passive_port, &data_soc);
	    FREE(command);

	    if (status < 0) {
		(void) HTInetStatus(gettext("connect for data"));
		NETCLOSE(data_soc);
		break;
	    }

	    CTRACE((tfp, "FTP data connected, socket %d\n", data_soc));
	}
	status = 0;
	break;			/* No more retries */

    }				/* for retries */
    CTRACE((tfp, "setup_connection returns %d\n", status));
    return status;
}

/*	Retrieve File from Server
 *	-------------------------
 *
 * On entry,
 *	name		WWW address of a file: document, including hostname
 * On exit,
 *	returns		Socket number for file if good.
 *			<0 if bad.
 */
int HTFTPLoad(const char *name,
	      HTParentAnchor *anchor,
	      HTFormat format_out,
	      HTStream *sink)
{
    BOOL isDirectory = NO;
    HTAtom *encoding = NULL;
    int status, final_status;
    int outstanding = 1;	/* outstanding control connection responses

				   that we are willing to wait for, if we
				   get to the point of reading data - kw */
    HTFormat format;

    CTRACE((tfp, "HTFTPLoad(%s) %s connection\n",
	    name,
	    (ftp_local_passive
	     ? "passive"
	     : "normal")));

    HTReadProgress((off_t) 0, (off_t) 0);

    status = setup_connection(name, anchor);
    if (status < 0)
	return status;		/* Failed with this code */

    /*  Ask for the file:
     */
    {
	char *filename = HTParse(name, "", PARSE_PATH + PARSE_PUNCTUATION);
	char *fname = filename;	/* Save for subsequent free() */
	char *vmsname = NULL;
	BOOL binary;
	const char *type = NULL;
	char *types = NULL;
	char *cp;

	if (server_type == CMS_SERVER) {
	    /* If the unescaped path has a %2f, reject it as illegal. - FM */
	    if (((cp = strstr(filename, "%2")) != NULL) &&
		TOUPPER(cp[2]) == 'F') {
		FREE(fname);
		init_help_message_cache();	/* to free memory */
		NETCLOSE(control->socket);
		control->socket = -1;
		CTRACE((tfp,
			"HTFTP: Rejecting path due to illegal escaped slash.\n"));
		return -1;
	    }
	}

	if (!*filename) {
	    StrAllocCopy(filename, "/");
	    type = "D";
	} else if ((type = types = strrchr(filename, ';')) != NULL) {
	    /*
	     * Check and trim the type= parameter.  - FM
	     */
	    if (!strncasecomp((type + 1), "type=", 5)) {
		switch (TOUPPER(*(type + 6))) {
		case 'D':
		    *types = '\0';
		    type = "D";
		    break;
		case 'A':
		    *types = '\0';
		    type = "A";
		    break;
		case 'I':
		    *types = '\0';
		    type = "I";
		    break;
		default:
		    type = "";
		    break;
		}
		if (!*filename) {
		    *filename = '/';
		    *(filename + 1) = '\0';
		}
	    }
	    if (*type != '\0') {
		CTRACE((tfp, "HTFTP: type=%s\n", type));
	    }
	}
	HTUnEscape(filename);
	CTRACE((tfp, "HTFTP: UnEscaped %s\n", filename));
	if (filename[1] == '~') {
	    /*
	     * Check if translation of HOME as tilde is supported,
	     * and adjust filename if so. - FM
	     */
	    char *cp2 = NULL;
	    char *fn = NULL;

	    if ((cp2 = StrChr((filename + 1), '/')) != NULL) {
		*cp2 = '\0';
	    }
	    status = send_cmd_1("PWD");
	    if (status == 2 && response_text[5] == '/') {
		status = send_cwd(filename + 1);
		if (status == 2) {
		    StrAllocCopy(fn, (filename + 1));
		    if (cp2) {
			*cp2 = '/';
			if (fn[strlen(fn) - 1] != '/') {
			    StrAllocCat(fn, cp2);
			} else {
			    StrAllocCat(fn, (cp2 + 1));
			}
			cp2 = NULL;
		    }
		    FREE(fname);
		    fname = filename = fn;
		}
	    }
	    if (cp2) {
		*cp2 = '/';
	    }
	}
	if (strlen(filename) > 3) {
	    char *cp2;

	    if (((cp2 = strrchr(filename, '.')) != NULL &&
		 0 == strncasecomp(cp2, ".me", 3)) &&
		(cp2[3] == '\0' || cp2[3] == ';')) {
		/*
		 * Don't treat this as application/x-Troff-me if it's a Unix
		 * server but has the string "read.me", or if it's not a Unix
		 * server.  - FM
		 */
		if ((server_type != UNIX_SERVER) ||
		    (cp2 > (filename + 3) &&
		     0 == strncasecomp((cp2 - 4), "read.me", 7))) {
		    *cp2 = '\0';
		    format = HTFileFormat(filename, &encoding, NULL);
		    *cp2 = '.';
		} else {
		    format = HTFileFormat(filename, &encoding, NULL);
		}
	    } else {
		format = HTFileFormat(filename, &encoding, NULL);
	    }
	} else {
	    format = HTFileFormat(filename, &encoding, NULL);
	}
	format = HTCharsetFormat(format, anchor, -1);
	binary = (BOOL) (encoding != HTAtom_for("8bit") &&
			 encoding != HTAtom_for("7bit"));
	if (!binary &&
	/*
	 * Force binary if we're in source, download or dump mode and this is
	 * not a VM/CMS server, so we don't get CRLF instead of LF (or CR) for
	 * newlines in text files.  Can't do this for VM/CMS or we'll get raw
	 * EBCDIC.  - FM
	 */
	    (format_out == WWW_SOURCE ||
	     format_out == HTAtom_for("www/download") ||
	     format_out == HTAtom_for("www/dump")) &&
	    (server_type != CMS_SERVER))
	    binary = TRUE;
	if (!binary && type && *type == 'I') {
	    /*
	     * Force binary if we had ;type=I - FM
	     */
	    binary = TRUE;
	} else if (binary && type && *type == 'A') {
	    /*
	     * Force ASCII if we had ;type=A - FM
	     */
	    binary = FALSE;
	}
	if (binary != control->is_binary) {
	    /*
	     * Act on our setting if not already set.  - FM
	     */
	    const char *mode = binary ? "I" : "A";

	    status = send_cmd_2("TYPE", mode);
	    if (status != 2) {
		init_help_message_cache();	/* to free memory */
		return ((status < 0) ? status : -status);
	    }
	    control->is_binary = binary;
	}
	switch (server_type) {
	    /*
	     * Handle what for Lynx are special case servers, e.g., for which
	     * we respect RFC 1738, or which have known conflicts in suffix
	     * mappings.  - FM
	     */
	case VMS_SERVER:
	    {
		char *cp1, *cp2;
		BOOL included_device = FALSE;
		BOOL found_tilde = FALSE;

		/* Accept only Unix-style filename */
		if (StrChr(filename, ':') != NULL ||
		    StrChr(filename, '[') != NULL) {
		    FREE(fname);
		    init_help_message_cache();	/* to free memory */
		    NETCLOSE(control->socket);
		    control->socket = -1;
		    CTRACE((tfp,
			    "HTFTP: Rejecting path due to non-Unix-style syntax.\n"));
		    return -1;
		}
		/* Handle any unescaped "/%2F" path */
		if (!StrNCmp(filename, "//", 2)) {
		    int i;

		    included_device = TRUE;
		    for (i = 0; filename[(i + 1)]; i++)
			filename[i] = filename[(i + 1)];
		    filename[i] = '\0';
		    CTRACE((tfp, "HTFTP: Trimmed '%s'\n", filename));
		    cp = HTVMS_name("", filename);
		    CTRACE((tfp, "HTFTP: VMSized '%s'\n", cp));
		    if ((cp1 = strrchr(cp, ']')) != NULL) {
			strcpy(filename, ++cp1);
			CTRACE((tfp, "HTFTP: Filename '%s'\n", filename));
			*cp1 = '\0';
			status = send_cwd(cp);
			if (status != 2) {
			    char *dotslash = 0;

			    if ((cp1 = StrChr(cp, '[')) != NULL) {
				*cp1++ = '\0';
				status = send_cwd(cp);
				if (status != 2) {
				    FREE(fname);
				    init_help_message_cache();	/* to free memory */
				    NETCLOSE(control->socket);
				    control->socket = -1;
				    return ((status < 0) ? status : -status);
				}
				HTSprintf0(&dotslash, "[.%s", cp1);
				status = send_cwd(dotslash);
				FREE(dotslash);
				if (status != 2) {
				    FREE(fname);
				    init_help_message_cache();	/* to free memory */
				    NETCLOSE(control->socket);
				    control->socket = -1;
				    return ((status < 0) ? status : -status);
				}
			    } else {
				FREE(fname);
				init_help_message_cache();	/* to free memory */
				NETCLOSE(control->socket);
				control->socket = -1;
				return ((status < 0) ? status : -status);
			    }
			}
		    } else if ((cp1 = StrChr(cp, ':')) != NULL &&
			       StrChr(cp, '[') == NULL &&
			       StrChr(cp, ']') == NULL) {
			cp1++;
			if (*cp1 != '\0') {
			    int cplen = (int) (cp1 - cp);

			    strcpy(filename, cp1);
			    CTRACE((tfp, "HTFTP: Filename '%s'\n", filename));
			    HTSprintf0(&vmsname, "%.*s[%s]", cplen, cp, filename);
			    status = send_cwd(vmsname);
			    if (status != 2) {
				HTSprintf(&vmsname, "%.*s[000000]", cplen, cp);
				status = send_cwd(vmsname);
				if (status != 2) {
				    HTSprintf(&vmsname, "%.*s", cplen, cp);
				    status = send_cwd(vmsname);
				    if (status != 2) {
					FREE(fname);
					init_help_message_cache();
					NETCLOSE(control->socket);
					control->socket = -1;
					return ((status < 0) ? status : -status);
				    }
				}
			    } else {
				HTSprintf0(&vmsname, "000000");
				filename = vmsname;
			    }
			}
		    } else if (0 == strcmp(cp, (filename + 1))) {
			status = send_cwd(cp);
			if (status != 2) {
			    HTSprintf0(&vmsname, "%s:", cp);
			    status = send_cwd(vmsname);
			    if (status != 2) {
				FREE(fname);
				init_help_message_cache();	/* to free memory */
				NETCLOSE(control->socket);
				control->socket = -1;
				return ((status < 0) ? status : -status);
			    }
			}
			HTSprintf0(&vmsname, "000000");
			filename = vmsname;
		    }
		}
		/* Trim trailing slash if filename is not the top directory */
		if (strlen(filename) > 1 && filename[strlen(filename) - 1] == '/')
		    filename[strlen(filename) - 1] = '\0';

#ifdef MAINTAIN_CONNECTION	/* Don't need this if always new connection - F.M. */
		if (!included_device) {
		    /* Get the current default VMS device:[directory] */
		    status = send_cmd_1("PWD");
		    if (status != 2) {
			FREE(fname);
			init_help_message_cache();	/* to free memory */
			NETCLOSE(control->socket);
			control->socket = -1;
			return ((status < 0) ? status : -status);
		    }
		    /* Go to the VMS account's top directory */
		    if ((cp = StrChr(response_text, '[')) != NULL &&
			(cp1 = strrchr(response_text, ']')) != NULL) {
			char *tmp = 0;
			unsigned len = 4;

			StrAllocCopy(tmp, cp);
			if ((cp2 = StrChr(cp, '.')) != NULL && cp2 < cp1) {
			    len += (cp2 - cp);
			} else {
			    len += (cp1 - cp);
			}
			tmp[len] = 0;
			StrAllocCat(tmp, "]");

			status = send_cwd(tmp);
			FREE(tmp);

			if (status != 2) {
			    FREE(fname);
			    init_help_message_cache();	/* to free memory */
			    NETCLOSE(control->socket);
			    control->socket = -1;
			    return ((status < 0) ? status : -status);
			}
		    }
		}
#endif /* MAINTAIN_CONNECTION */

		/* If we want the VMS account's top directory, list it now */
		if (!(strcmp(filename, "/~")) ||
		    (included_device && 0 == strcmp(filename, "000000")) ||
		    (strlen(filename) == 1 && *filename == '/')) {
		    isDirectory = YES;
		    status = send_cmd_1("LIST");
		    FREE(fname);
		    if (status != 1) {
			/* Action not started */
			init_help_message_cache();	/* to free memory */
			NETCLOSE(control->socket);
			control->socket = -1;
			return ((status < 0) ? status : -status);
		    }
		    /* Big goto! */
		    goto listen;
		}
		/* Otherwise, go to appropriate directory and doctor filename */
		if (!StrNCmp(filename, "/~", 2)) {
		    filename += 2;
		    found_tilde = TRUE;
		}
		CTRACE((tfp, "check '%s' to translate x/y/ to [.x.y]\n", filename));
		if (!included_device &&
		    (cp = StrChr(filename, '/')) != NULL &&
		    (cp1 = strrchr(cp, '/')) != NULL &&
		    (cp1 - cp) > 1) {
		    char *tmp = 0;

		    HTSprintf0(&tmp, "[.%.*s]", (int) (cp1 - cp - 1), cp + 1);

		    CTRACE((tfp, "change path '%s'\n", tmp));
		    while ((cp2 = strrchr(tmp, '/')) != NULL)
			*cp2 = '.';
		    CTRACE((tfp, "...to  path '%s'\n", tmp));

		    status = send_cwd(tmp);
		    FREE(tmp);

		    if (status != 2) {
			FREE(fname);
			init_help_message_cache();	/* to free memory */
			NETCLOSE(control->socket);
			control->socket = -1;
			return ((status < 0) ? status : -status);
		    }
		    filename = cp1 + 1;
		} else {
		    if (!included_device && !found_tilde) {
			filename += 1;
		    }
		}
		break;
	    }
	case CMS_SERVER:
	    {
		/*
		 * If we want the CMS account's top directory, or a base SFS or
		 * anonymous directory path (i.e., without a slash), list it
		 * now.  FM
		 */
		if ((strlen(filename) == 1 && *filename == '/') ||
		    ((0 == strncasecomp((filename + 1), "vmsysu:", 7)) &&
		     (cp = StrChr((filename + 1), '.')) != NULL &&
		     StrChr(cp, '/') == NULL) ||
		    (0 == strncasecomp(filename + 1, "anonymou.", 9) &&
		     StrChr(filename + 1, '/') == NULL)) {
		    if (filename[1] != '\0') {
			status = send_cwd(filename + 1);
			if (status != 2) {
			    /* Action not started */
			    init_help_message_cache();	/* to free memory */
			    NETCLOSE(control->socket);
			    control->socket = -1;
			    return ((status < 0) ? status : -status);
			}
		    }
		    isDirectory = YES;
		    if (use_list)
			status = send_cmd_1("LIST");
		    else
			status = send_cmd_1("NLST");
		    FREE(fname);
		    if (status != 1) {
			/* Action not started */
			init_help_message_cache();	/* to free memory */
			NETCLOSE(control->socket);
			control->socket = -1;
			return ((status < 0) ? status : -status);
		    }
		    /* Big goto! */
		    goto listen;
		}
		filename++;

		/* Otherwise, go to appropriate directory and adjust filename */
		while ((cp = StrChr(filename, '/')) != NULL) {
		    *cp++ = '\0';
		    status = send_cwd(filename);
		    if (status == 2) {
			if (*cp == '\0') {
			    isDirectory = YES;
			    if (use_list)
				status = send_cmd_1("LIST");
			    else
				status = send_cmd_1("NLST");
			    FREE(fname);
			    if (status != 1) {
				/* Action not started */
				init_help_message_cache();	/* to free memory */
				NETCLOSE(control->socket);
				control->socket = -1;
				return ((status < 0) ? status : -status);
			    }
			    /* Clear any messages from the login directory */
			    init_help_message_cache();
			    /* Big goto! */
			    goto listen;
			}
			filename = cp;
		    }
		}
		break;
	    }
	default:
	    /* Shift for any unescaped "/%2F" path */
	    if (!StrNCmp(filename, "//", 2))
		filename++;
	    break;
	}
	/*
	 * Act on a file or listing request, or try to figure out which we're
	 * dealing with if we don't know yet.  - FM
	 */
	if (!(type) || (type && *type != 'D')) {
	    /*
	     * If we are retrieving a file we will (except for CMS) use
	     * binary mode, which lets us use the size command supported by
	     * ftp servers which implement RFC 3659.  Knowing the size lets
	     * us in turn display ETA in the progress message -TD
	     */
	    if (control->is_binary) {
		int code;
		off_t size;

		status = send_cmd_2("SIZE", filename);
		if (status == 2 &&
		    sscanf(response_text, "%d %" PRI_off_t, &code, &size) == 2) {
		    anchor->content_length = size;
		}
	    }
	    status = send_cmd_2("RETR", filename);
	    if (status >= 5) {
		int check;

		if (Broken_RETR) {
		    CTRACE((tfp, "{{reconnecting...\n"));
		    close_connection(control);
		    check = setup_connection(name, anchor);
		    CTRACE((tfp, "...done }}reconnecting\n"));
		    if (check < 0)
			return check;
		}
	    }
	} else {
	    status = 5;		/* Failed status set as flag. - FM */
	}
	if (status != 1) {	/* Failed : try to CWD to it */
	    /* Clear any login messages if this isn't the login directory */
	    if (strcmp(filename, "/"))
		init_help_message_cache();

	    status = send_cwd(filename);
	    if (status == 2) {	/* Succeeded : let's NAME LIST it */
		isDirectory = YES;
		if (use_list)
		    status = send_cmd_1("LIST");
		else
		    status = send_cmd_1("NLST");
	    }
	}
	FREE(fname);
	FREE(vmsname);
	if (status != 1) {
	    init_help_message_cache();	/* to free memory */
	    NETCLOSE(control->socket);
	    control->socket = -1;
	    if (status < 0)
		return status;
	    else
		return -status;
	}
    }

  listen:
    if (!ftp_local_passive) {
	/* Wait for the connection */
#ifdef INET6
	struct sockaddr_storage soc_address;

#else
	struct sockaddr_in soc_address;
#endif /* INET6 */
	LY_SOCKLEN soc_addrlen = (LY_SOCKLEN) sizeof(soc_address);

#ifdef SOCKS
	if (socks_flag)
	    status = Raccept((int) master_socket,
			     (struct sockaddr *) &soc_address,
			     &soc_addrlen);
	else
#endif /* SOCKS */
	    status = accept((int) master_socket,
			    (struct sockaddr *) &soc_address,
			    &soc_addrlen);
	if (status < 0) {
	    init_help_message_cache();	/* to free memory */
	    return HTInetStatus("accept");
	}
	CTRACE((tfp, "TCP: Accepted new socket %d\n", status));
	data_soc = status;
    }

    if (isDirectory) {
	if (server_type == UNIX_SERVER && !unsure_type &&
	    !strcmp(response_text,
		    "150 Opening ASCII mode data connection for /bin/dl.\n")) {
	    CTRACE((tfp, "HTFTP: Treating as \"dls\" server.\n"));
	    server_type = DLS_SERVER;
	}
	final_status = read_directory(anchor, name, format_out, sink);
	if (final_status > 0) {
	    if (server_type != CMS_SERVER)
		if (outstanding-- > 0) {
		    status = response(0);
		    if (status < 0 ||
			(status == 2 && !StrNCmp(response_text, "221", 3)))
			outstanding = 0;
		}
	} else {		/* HT_INTERRUPTED */
	    /* User may have pressed 'z' to give up because no
	       packets got through, so let's not make them wait
	       any longer - kw */
	    outstanding = 0;
	}

	if (data_soc != -1) {	/* normally done in read_directory */
	    CTRACE((tfp, "HTFTP: Closing data socket %d\n", data_soc));
	    status = NETCLOSE(data_soc);
	    if (status == -1)
		HTInetStatus("close");	/* Comment only */
	}
	status = final_status;
    } else {
	int rv;
	char *FileName = HTParse(name, "", PARSE_PATH + PARSE_PUNCTUATION);

	/* Clear any login messages */
	init_help_message_cache();

	/* Fake a Content-Encoding for compressed files. - FM */
	HTUnEscape(FileName);
	if (!IsUnityEnc(encoding)) {
	    /*
	     * We already know from the call to HTFileFormat above that this is
	     * a compressed file, no need to look at the filename again.  - kw
	     */
	    StrAllocCopy(anchor->content_type, format->name);
	    StrAllocCopy(anchor->content_encoding, HTAtom_name(encoding));
	    format = HTAtom_for("www/compressed");

	} else {
	    int rootlen;
	    CompressFileType cft = HTCompressFileType(FileName, "._-", &rootlen);

	    if (cft != cftNone) {
		FileName[rootlen] = '\0';
		format = HTFileFormat(FileName, &encoding, NULL);
		format = HTCharsetFormat(format, anchor, -1);
		StrAllocCopy(anchor->content_type, format->name);
		format = HTAtom_for("www/compressed");
	    }

	    switch (cft) {
	    case cftCompress:
		StrAllocCopy(anchor->content_encoding, "x-compress");
		break;
	    case cftGzip:
		StrAllocCopy(anchor->content_encoding, "x-gzip");
		break;
	    case cftDeflate:
		StrAllocCopy(anchor->content_encoding, "x-deflate");
		break;
	    case cftBzip2:
		StrAllocCopy(anchor->content_encoding, "x-bzip2");
		break;
	    case cftNone:
		break;
	    }
	}
	FREE(FileName);

	_HTProgress(gettext("Receiving FTP file."));
	rv = HTParseSocket(format, format_out, anchor, data_soc, sink);

	HTInitInput(control->socket);
	/* Reset buffering to control connection DD 921208 */

	if (rv < 0) {
	    if (rv == -2)	/* weird error, don't expect much response */
		outstanding--;
	    else if (rv == HT_INTERRUPTED || rv == -1)
		/* User may have pressed 'z' to give up because no
		   packets got through, so let's not make them wait
		   longer - kw */
		outstanding = 0;
	    CTRACE((tfp, "HTFTP: Closing data socket %d\n", data_soc));
	    status = NETCLOSE(data_soc);
	} else {
	    status = 2;		/* data_soc already closed in HTCopy - kw */
	}

	if (status < 0 && rv != HT_INTERRUPTED && rv != -1) {
	    (void) HTInetStatus("close");	/* Comment only */
	} else {
	    if (rv != HT_LOADED && outstanding--) {
		status = response(0);	/* Pick up final reply */
		if (status != 2 && rv != HT_INTERRUPTED && rv != -1) {
		    data_soc = -1;	/* invalidate it */
		    init_help_message_cache();	/* to free memory */
		    return HTLoadError(sink, 500, response_text);
		} else if (status == 2 && !StrNCmp(response_text, "221", 3)) {
		    outstanding = 0;
		}
	    }
	}
	final_status = HT_LOADED;
    }
    while (outstanding-- > 0 &&
	   (status > 0)) {
	status = response(0);
	if (status == 2 && !StrNCmp(response_text, "221", 3))
	    break;
    }
    data_soc = -1;		/* invalidate it */
    CTRACE((tfp, "HTFTPLoad: normal end; "));
    if (control->socket < 0) {
	CTRACE((tfp, "control socket is %d\n", control->socket));
    } else {
	CTRACE((tfp, "closing control socket %d\n", control->socket));
	status = NETCLOSE(control->socket);
	if (status == -1)
	    HTInetStatus("control connection close");	/* Comment only */
    }
    control->socket = -1;
    init_help_message_cache();	/* to free memory */
    /* returns HT_LOADED (always for file if we get here) or error */
    return final_status;
}				/* open_file_read */

/*
 *  This function frees any user entered password, so that
 *  it must be entered again for a future request. - FM
 */
void HTClearFTPPassword(void)
{
    /*
     * Need code to check cached documents from non-anonymous ftp accounts and
     * do something to ensure that they no longer can be accessed without a new
     * retrieval.  - FM
     */

    /*
     * Now free the current user entered password, if any.  - FM
     */
    FREE(user_entered_password);
}

#endif /* ifndef DISABLE_FTP */