1 /*
2  * $LynxId: HTFTP.c,v 1.121 2013/05/06 00:09:50 tom Exp $
3  *
4  *			File Transfer Protocol (FTP) Client
5  *			for a WorldWideWeb browser
6  *			===================================
7  *
8  *	A cache of control connections is kept.
9  *
10  * Note: Port allocation
11  *
12  *	It is essential that the port is allocated by the system, rather
13  *	than chosen in rotation by us (POLL_PORTS), or the following
14  *	problem occurs.
15  *
16  *	It seems that an attempt by the server to connect to a port which has
17  *	been used recently by a listen on the same socket, or by another
18  *	socket this or another process causes a hangup of (almost exactly)
19  *	one minute.  Therefore, we have to use a rotating port number.
20  *	The problem remains that if the application is run twice in quick
21  *	succession, it will hang for what remains of a minute.
22  *
23  * Authors
24  *	TBL	Tim Berners-lee <timbl@info.cern.ch>
25  *	DD	Denis DeLaRoca 310 825-4580 <CSP1DWD@mvs.oac.ucla.edu>
26  *	LM	Lou Montulli <montulli@ukanaix.cc.ukans.edu>
27  *	FM	Foteos Macrides <macrides@sci.wfeb.edu>
28  * History:
29  *	 2 May 91	Written TBL, as a part of the WorldWideWeb project.
30  *	15 Jan 92	Bug fix: close() was used for NETCLOSE for control soc
31  *	10 Feb 92	Retry if cached connection times out or breaks
32  *	 8 Dec 92	Bug fix 921208 TBL after DD
33  *	17 Dec 92	Anon FTP password now just WWWuser@ suggested by DD
34  *			fails on princeton.edu!
35  *	27 Dec 93 (FM)	Fixed up so FTP now works with VMS hosts.  Path
36  *			must be Unix-style and cannot include the device
37  *			or top directory.
38  *	?? ??? ?? (LM)	Added code to prompt and send passwords for non
39  *			anonymous FTP
40  *	25 Mar 94 (LM)	Added code to recognize different ftp server types
41  *			and code to parse dates and sizes on most hosts.
42  *	27 Mar 93 (FM)	Added code for getting dates and sizes on VMS hosts.
43  *
44  * Notes:
45  *			Portions Copyright 1994 Trustees of Dartmouth College
46  *			Code for recognizing different FTP servers and
47  *			parsing "ls -l" output taken from Macintosh Fetch
48  *			program with permission from Jim Matthews,
49  *			Dartmouth Software Development Team.
50  */
51 
52 /*
53  * BUGS:	@@@	Limit connection cache size!
54  * 		Error reporting to user.
55  * 		400 & 500 errors are ack'ed by user with windows.
56  * 		Use configuration file for user names
57  *
58  *		Note for portability this version does not use select() and
59  *		so does not watch the control and data channels at the
60  *		same time.
61  */
62 
63 #include <HTUtils.h>
64 
65 #include <HTAlert.h>
66 
67 #include <HTFTP.h>		/* Implemented here */
68 #include <HTTCP.h>
69 #include <HTTP.h>
70 #include <HTFont.h>
71 
72 #define REPEAT_PORT		/* Give the port number for each file */
73 #define REPEAT_LISTEN		/* Close each listen socket and open a new one */
74 
75 /* define POLL_PORTS		 If allocation does not work, poll ourselves.*/
76 #define LISTEN_BACKLOG 2	/* Number of pending connect requests (TCP) */
77 
78 #define FIRST_TCP_PORT	1024	/* Region to try for a listening port */
79 #define LAST_TCP_PORT	5999
80 
81 #define LINE_LENGTH 256
82 
83 #include <HTParse.h>
84 #include <HTAnchor.h>
85 #include <HTFile.h>		/* For HTFileFormat() */
86 #include <HTBTree.h>
87 #include <HTChunk.h>
88 #ifndef IPPORT_FTP
89 #define IPPORT_FTP	21
90 #endif /* !IPORT_FTP */
91 
92 #include <LYUtils.h>
93 #include <LYGlobalDefs.h>
94 #include <LYStrings.h>
95 #include <LYLeaks.h>
96 
97 typedef struct _connection {
98     struct _connection *next;	/* Link on list         */
99     unsigned long addr;		/* IP address           */
100     int socket;			/* Socket number for communication */
101     BOOL binary;		/* Binary mode? */
102 } connection;
103 
104 /*		Hypertext object building machinery
105 */
106 #include <HTML.h>
107 
108 /*
109  * socklen_t is the standard, but there are many pre-standard variants.
110  * This ifdef works around a few of those cases.
111  *
112  * Information was obtained from header files on these platforms:
113  *	AIX 4.3.2, 5.1
114  *	HPUX 10.20, 11.00, 11.11
115  *	IRIX64 6.5
116  *	Tru64 4.0G, 4.0D, 5.1
117  */
118 #if defined(SYS_IRIX64)
119 	/* IRIX64 6.5 socket.h may use socklen_t if SGI_SOURCE is not defined */
120 #  if _NO_XOPEN4 && _NO_XOPEN5
121 #    define LY_SOCKLEN socklen_t
122 #  elif _ABIAPI
123 #    define LY_SOCKLEN int
124 #  elif _XOPEN5
125 #    if (_MIPS_SIM != _ABIO32)
126 #      define LY_SOCKLEN socklen_t
127 #    else
128 #      define LY_SOCKLEN int
129 #    endif
130 #  else
131 #    define LY_SOCKLEN size_t
132 #  endif
133 #elif defined(SYS_HPUX)
134 #  if defined(_XOPEN_SOURCE_EXTENDED) && defined(SO_PROTOTYPE)
135 #    define LY_SOCKLEN socklen_t
136 #  else	/* HPUX 10.20, etc. */
137 #    define LY_SOCKLEN int
138 #  endif
139 #elif defined(SYS_TRU64)
140 #  if defined(_POSIX_PII_SOCKET)
141 #    define LY_SOCKLEN socklen_t
142 #  elif defined(_XOPEN_SOURCE_EXTENDED)
143 #    define LY_SOCKLEN size_t
144 #  else
145 #    define LY_SOCKLEN int
146 #  endif
147 #else
148 #  define LY_SOCKLEN socklen_t
149 #endif
150 
151 #define PUTC(c)      (*target->isa->put_character) (target, c)
152 #define PUTS(s)      (*target->isa->put_string)    (target, s)
153 #define START(e)     (*target->isa->start_element) (target, e, 0, 0, -1, 0)
154 #define END(e)       (*target->isa->end_element)   (target, e, 0)
155 #define FREE_TARGET  (*target->isa->_free)         (target)
156 #define ABORT_TARGET (*target->isa->_free)         (target)
157 
158 #define TRACE_ENTRY(tag, entry_info) \
159     CTRACE((tfp, "HTFTP: %s filename: %s  date: %s  size: %" PRI_off_t "\n", \
160 	    tag, \
161 	    entry_info->filename, \
162 	    NonNull(entry_info->date), \
163 	    entry_info->size))
164 
165 struct _HTStructured {
166     const HTStructuredClass *isa;
167     /* ... */
168 };
169 
170 /*	Global Variables
171  *	---------------------
172  */
173 int HTfileSortMethod = FILE_BY_NAME;
174 
175 #ifndef DISABLE_FTP		/*This disables everything to end-of-file */
176 static char ThisYear[8];
177 static char LastYear[8];
178 static int TheDate;
179 static BOOLEAN HaveYears = FALSE;
180 
181 /*	Module-Wide Variables
182  *	---------------------
183  */
184 static connection *connections = NULL;	/* Linked list of connections */
185 static char response_text[LINE_LENGTH + 1];	/* Last response from ftp host */
186 static connection *control = NULL;	/* Current connection */
187 static int data_soc = -1;	/* Socket for data transfer =invalid */
188 static char *user_entered_password = NULL;
189 static char *last_username_and_host = NULL;
190 
191 /*
192  * Some ftp servers are known to have a broken implementation of RETR.  If
193  * asked to retrieve a directory, they get confused and fail subsequent
194  * commands such as CWD and LIST.
195  */
196 static int Broken_RETR = FALSE;
197 
198 /*
199  * Some ftp servers are known to have a broken implementation of EPSV.  The
200  * server will hang for a long time when we attempt to connect after issuing
201  * this command.
202  */
203 #ifdef INET6
204 static int Broken_EPSV = FALSE;
205 #endif
206 
207 typedef enum {
208     GENERIC_SERVER
209     ,MACHTEN_SERVER
210     ,UNIX_SERVER
211     ,VMS_SERVER
212     ,CMS_SERVER
213     ,DCTS_SERVER
214     ,TCPC_SERVER
215     ,PETER_LEWIS_SERVER
216     ,NCSA_SERVER
217     ,WINDOWS_NT_SERVER
218     ,WINDOWS_2K_SERVER
219     ,MS_WINDOWS_SERVER
220     ,MSDOS_SERVER
221     ,APPLESHARE_SERVER
222     ,NETPRESENZ_SERVER
223     ,DLS_SERVER
224 } eServerType;
225 
226 static eServerType server_type = GENERIC_SERVER;	/* the type of ftp host */
227 static int unsure_type = FALSE;	/* sure about the type? */
228 static BOOLEAN use_list = FALSE;	/* use the LIST command? */
229 
230 static int interrupted_in_next_data_char = FALSE;
231 
232 #ifdef POLL_PORTS
233 static PortNumber port_number = FIRST_TCP_PORT;
234 #endif /* POLL_PORTS */
235 
236 static BOOL have_socket = FALSE;	/* true if master_socket is valid */
237 static unsigned master_socket;	/* Listening socket = invalid */
238 
239 static char port_command[255];	/* Command for setting the port */
240 static fd_set open_sockets;	/* Mask of active channels */
241 static unsigned num_sockets;	/* Number of sockets to scan */
242 static PortNumber passive_port;	/* Port server specified for data */
243 
244 #define NEXT_CHAR HTGetCharacter()	/* Use function in HTFormat.c */
245 
246 #define DATA_BUFFER_SIZE 2048
247 static char data_buffer[DATA_BUFFER_SIZE];	/* Input data buffer */
248 static char *data_read_pointer;
249 static char *data_write_pointer;
250 
251 #define NEXT_DATA_CHAR next_data_char()
252 static int close_connection(connection * con);
253 
254 #ifndef HAVE_ATOLL
LYatoll(const char * value)255 off_t LYatoll(const char *value)
256 {
257     off_t result = 0;
258 
259     while (*value != '\0') {
260 	result = (result * 10) + (off_t) (*value++ - '0');
261     }
262     return result;
263 }
264 #endif
265 
266 #ifdef LY_FIND_LEAKS
267 /*
268  *  This function frees module globals. - FM
269  */
free_FTPGlobals(void)270 static void free_FTPGlobals(void)
271 {
272     FREE(user_entered_password);
273     FREE(last_username_and_host);
274     if (control) {
275 	if (control->socket != -1)
276 	    close_connection(control);
277 	FREE(control);
278     }
279 }
280 #endif /* LY_FIND_LEAKS */
281 
282 /* PUBLIC						HTVMS_name()
283  *		CONVERTS WWW name into a VMS name
284  * ON ENTRY:
285  *	nn		Node Name (optional)
286  *	fn		WWW file name
287  *
288  * ON EXIT:
289  *	returns		vms file specification
290  *
291  * Bug: Returns pointer to static -- non-reentrant
292  */
HTVMS_name(const char * nn,const char * fn)293 char *HTVMS_name(const char *nn,
294 		 const char *fn)
295 {
296     /* We try converting the filename into Files-11 syntax.  That is, we assume
297      * first that the file is, like us, on a VMS node.  We try remote (or
298      * local) DECnet access.  Files-11, VMS, VAX and DECnet are trademarks of
299      * Digital Equipment Corporation.  The node is assumed to be local if the
300      * hostname WITHOUT DOMAIN matches the local one.  @@@
301      */
302     static char *vmsname;
303     char *filename = (char *) malloc(strlen(fn) + 1);
304     char *nodename = (char *) malloc(strlen(nn) + 2 + 1);	/* Copies to hack */
305     char *second;		/* 2nd slash */
306     char *last;			/* last slash */
307 
308     const char *hostname = HTHostName();
309 
310     if (!filename || !nodename)
311 	outofmem(__FILE__, "HTVMSname");
312 
313     assert(filename != NULL);
314     assert(nodename != NULL);
315 
316     strcpy(filename, fn);
317     strcpy(nodename, "");	/* On same node?  Yes if node names match */
318     if (StrNCmp(nn, "localhost", 9)) {
319 	const char *p;
320 	const char *q;
321 
322 	for (p = hostname, q = nn;
323 	     *p && *p != '.' && *q && *q != '.'; p++, q++) {
324 	    if (TOUPPER(*p) != TOUPPER(*q)) {
325 		char *r;
326 
327 		strcpy(nodename, nn);
328 		r = strchr(nodename, '.');	/* Mismatch */
329 		if (r)
330 		    *r = '\0';	/* Chop domain */
331 		strcat(nodename, "::");		/* Try decnet anyway */
332 		break;
333 	    }
334 	}
335     }
336 
337     second = strchr(filename + 1, '/');		/* 2nd slash */
338     last = strrchr(filename, '/');	/* last slash */
339 
340     if (!second) {		/* Only one slash */
341 	HTSprintf0(&vmsname, "%s%s", nodename, filename + 1);
342     } else if (second == last) {	/* Exactly two slashes */
343 	*second = '\0';		/* Split filename from disk */
344 	HTSprintf0(&vmsname, "%s%s:%s", nodename, filename + 1, second + 1);
345 	*second = '/';		/* restore */
346     } else {			/* More than two slashes */
347 	char *p;
348 
349 	*second = '\0';		/* Split disk from directories */
350 	*last = '\0';		/* Split dir from filename */
351 	HTSprintf0(&vmsname, "%s%s:[%s]%s",
352 		   nodename, filename + 1, second + 1, last + 1);
353 	*second = *last = '/';	/* restore filename */
354 	if ((p = strchr(vmsname, '[')) != 0) {
355 	    while (*p != '\0' && *p != ']') {
356 		if (*p == '/')
357 		    *p = '.';	/* Convert dir sep.  to dots */
358 		++p;
359 	    }
360 	}
361     }
362     FREE(nodename);
363     FREE(filename);
364     return vmsname;
365 }
366 
367 /*	Procedure: Read a character from the data connection
368  *	----------------------------------------------------
369  */
next_data_char(void)370 static int next_data_char(void)
371 {
372     int status;
373 
374     if (data_read_pointer >= data_write_pointer) {
375 	status = NETREAD(data_soc, data_buffer, DATA_BUFFER_SIZE);
376 	if (status == HT_INTERRUPTED)
377 	    interrupted_in_next_data_char = 1;
378 	if (status <= 0)
379 	    return EOF;
380 	data_write_pointer = data_buffer + status;
381 	data_read_pointer = data_buffer;
382     }
383 #ifdef NOT_ASCII
384     {
385 	char c = *data_read_pointer++;
386 
387 	return FROMASCII(c);
388     }
389 #else
390     return UCH(*data_read_pointer++);
391 #endif /* NOT_ASCII */
392 }
393 
394 /*	Close an individual connection
395  *
396  */
close_connection(connection * con)397 static int close_connection(connection * con)
398 {
399     connection *scan;
400     int status;
401 
402     CTRACE((tfp, "HTFTP: Closing control socket %d\n", con->socket));
403     status = NETCLOSE(con->socket);
404     if (TRACE && status != 0) {
405 #ifdef UNIX
406 	CTRACE((tfp, "HTFTP:close_connection: %s", LYStrerror(errno)));
407 #else
408 	if (con->socket != INVSOC)
409 	    HTInetStatus("HTFTP:close_connection");
410 #endif
411     }
412     con->socket = -1;
413     if (connections == con) {
414 	connections = con->next;
415 	return status;
416     }
417     for (scan = connections; scan; scan = scan->next) {
418 	if (scan->next == con) {
419 	    scan->next = con->next;	/* Unlink */
420 	    if (control == con)
421 		control = (connection *) 0;
422 	    return status;
423 	}			/*if */
424     }				/* for */
425     return -1;			/* very strange -- was not on list. */
426 }
427 
428 static char *help_message_buffer = NULL;	/* global :( */
429 
init_help_message_cache(void)430 static void init_help_message_cache(void)
431 {
432     FREE(help_message_buffer);
433 }
434 
help_message_cache_add(char * string)435 static void help_message_cache_add(char *string)
436 {
437     if (help_message_buffer)
438 	StrAllocCat(help_message_buffer, string);
439     else
440 	StrAllocCopy(help_message_buffer, string);
441 
442     CTRACE((tfp, "Adding message to help cache: %s\n", string));
443 }
444 
help_message_cache_non_empty(void)445 static char *help_message_cache_non_empty(void)
446 {
447     return (help_message_buffer);
448 }
449 
help_message_cache_contents(void)450 static char *help_message_cache_contents(void)
451 {
452     return (help_message_buffer);
453 }
454 
455 /*	Send One Command
456  *	----------------
457  *
458  *	This function checks whether we have a control connection, and sends
459  *	one command if given.
460  *
461  * On entry,
462  *	control	points to the connection which is established.
463  *	cmd	points to a command, or is zero to just get the response.
464  *
465  *	The command should already be terminated with the CRLF pair.
466  *
467  * On exit,
468  *	returns:  1 for success,
469  *		  or negative for communication failure (in which case
470  *		  the control connection will be closed).
471  */
write_cmd(const char * cmd)472 static int write_cmd(const char *cmd)
473 {
474     int status;
475 
476     if (!control) {
477 	CTRACE((tfp, "HTFTP: No control connection set up!!\n"));
478 	return HT_NO_CONNECTION;
479     }
480 
481     if (cmd) {
482 	CTRACE((tfp, "  Tx: %s", cmd));
483 #ifdef NOT_ASCII
484 	{
485 	    char *p;
486 
487 	    for (p = cmd; *p; p++) {
488 		*p = TOASCII(*p);
489 	    }
490 	}
491 #endif /* NOT_ASCII */
492 	status = (int) NETWRITE(control->socket, cmd, (unsigned) strlen(cmd));
493 	if (status < 0) {
494 	    CTRACE((tfp,
495 		    "HTFTP: Error %d sending command: closing socket %d\n",
496 		    status, control->socket));
497 	    close_connection(control);
498 	    return status;
499 	}
500     }
501     return 1;
502 }
503 
504 /*
505  * For each string in the list, check if it is found in the response text.
506  * If so, return TRUE.
507  */
find_response(HTList * list)508 static BOOL find_response(HTList *list)
509 {
510     BOOL result = FALSE;
511     HTList *p = list;
512     char *value;
513 
514     while ((value = (char *) HTList_nextObject(p)) != NULL) {
515 	if (LYstrstr(response_text, value)) {
516 	    result = TRUE;
517 	    break;
518 	}
519     }
520     return result;
521 }
522 
523 /*	Execute Command and get Response
524  *	--------------------------------
525  *
526  *	See the state machine illustrated in RFC959, p57. This implements
527  *	one command/reply sequence.  It also interprets lines which are to
528  *	be continued, which are marked with a "-" immediately after the
529  *	status code.
530  *
531  *	Continuation then goes on until a line with a matching reply code
532  *	an a space after it.
533  *
534  * On entry,
535  *	control	points to the connection which is established.
536  *	cmd	points to a command, or is zero to just get the response.
537  *
538  *	The command must already be terminated with the CRLF pair.
539  *
540  * On exit,
541  *	returns:  The first digit of the reply type,
542  *		  or negative for communication failure.
543  */
response(const char * cmd)544 static int response(const char *cmd)
545 {
546     int result;			/* Three-digit decimal code */
547     int continuation_response = -1;
548     int status;
549 
550     if ((status = write_cmd(cmd)) < 0)
551 	return status;
552 
553     do {
554 	char *p = response_text;
555 
556 	for (;;) {
557 	    int ich = NEXT_CHAR;
558 
559 	    if (((*p++ = (char) ich) == LF)
560 		|| (p == &response_text[LINE_LENGTH])) {
561 
562 		char continuation;
563 
564 		if (interrupted_in_htgetcharacter) {
565 		    CTRACE((tfp,
566 			    "HTFTP: Interrupted in HTGetCharacter, apparently.\n"));
567 		    NETCLOSE(control->socket);
568 		    control->socket = -1;
569 		    return HT_INTERRUPTED;
570 		}
571 
572 		*p = '\0';	/* Terminate the string */
573 		CTRACE((tfp, "    Rx: %s", response_text));
574 
575 		/* Check for login or help messages */
576 		if (!StrNCmp(response_text, "230-", 4) ||
577 		    !StrNCmp(response_text, "250-", 4) ||
578 		    !StrNCmp(response_text, "220-", 4))
579 		    help_message_cache_add(response_text + 4);
580 
581 		sscanf(response_text, "%d%c", &result, &continuation);
582 		if (continuation_response == -1) {
583 		    if (continuation == '-')	/* start continuation */
584 			continuation_response = result;
585 		} else {	/* continuing */
586 		    if (continuation_response == result &&
587 			continuation == ' ')
588 			continuation_response = -1;	/* ended */
589 		}
590 		if (result == 220 && find_response(broken_ftp_retr)) {
591 		    Broken_RETR = TRUE;
592 		    CTRACE((tfp, "This server is broken (RETR)\n"));
593 		}
594 #ifdef INET6
595 		if (result == 220 && find_response(broken_ftp_epsv)) {
596 		    Broken_EPSV = TRUE;
597 		    CTRACE((tfp, "This server is broken (EPSV)\n"));
598 		}
599 #endif
600 		break;
601 	    }
602 	    /* if end of line */
603 	    if (interrupted_in_htgetcharacter) {
604 		CTRACE((tfp,
605 			"HTFTP: Interrupted in HTGetCharacter, apparently.\n"));
606 		NETCLOSE(control->socket);
607 		control->socket = -1;
608 		return HT_INTERRUPTED;
609 	    }
610 
611 	    if (ich == EOF) {
612 		CTRACE((tfp, "Error on rx: closing socket %d\n",
613 			control->socket));
614 		strcpy(response_text, "000 *** TCP read error on response\n");
615 		close_connection(control);
616 		return -1;	/* End of file on response */
617 	    }
618 	}			/* Loop over characters */
619 
620     } while (continuation_response != -1);
621 
622     if (result == 421) {
623 	CTRACE((tfp, "HTFTP: They close so we close socket %d\n",
624 		control->socket));
625 	close_connection(control);
626 	return -1;
627     }
628     if ((result == 255 && server_type == CMS_SERVER) &&
629 	(0 == strncasecomp(cmd, "CWD", 3) ||
630 	 0 == strcasecomp(cmd, "CDUP"))) {
631 	/*
632 	 * Alas, CMS returns 255 on failure to CWD to parent of root.  - PG
633 	 */
634 	result = 555;
635     }
636     return result / 100;
637 }
638 
send_cmd_1(const char * verb)639 static int send_cmd_1(const char *verb)
640 {
641     char command[80];
642 
643     sprintf(command, "%.*s%c%c", (int) sizeof(command) - 4, verb, CR, LF);
644     return response(command);
645 }
646 
send_cmd_2(const char * verb,const char * param)647 static int send_cmd_2(const char *verb, const char *param)
648 {
649     char *command = 0;
650     int status;
651 
652     HTSprintf0(&command, "%s %s%c%c", verb, param, CR, LF);
653     status = response(command);
654     FREE(command);
655 
656     return status;
657 }
658 
659 #define send_cwd(path) send_cmd_2("CWD", path)
660 
661 /*
662  * This function should try to set the macintosh server into binary mode.  Some
663  * servers need an additional letter after the MACB command.
664  */
set_mac_binary(eServerType ServerType)665 static int set_mac_binary(eServerType ServerType)
666 {
667     /* try to set mac binary mode */
668     if (ServerType == APPLESHARE_SERVER ||
669 	ServerType == NETPRESENZ_SERVER) {
670 	/*
671 	 * Presumably E means "Enable".  - KW
672 	 */
673 	return (2 == response("MACB E\r\n"));
674     } else {
675 	return (2 == response("MACB\r\n"));
676     }
677 }
678 
679 /* This function gets the current working directory to help
680  * determine what kind of host it is
681  */
682 
get_ftp_pwd(eServerType * ServerType,BOOLEAN * UseList)683 static void get_ftp_pwd(eServerType *ServerType, BOOLEAN *UseList)
684 {
685     char *cp;
686 
687     /* get the working directory (to see what it looks like) */
688     int status = response("PWD\r\n");
689 
690     if (status < 0) {
691 	return;
692     } else {
693 	cp = strchr(response_text + 5, '"');
694 	if (cp)
695 	    *cp = '\0';
696 	if (*ServerType == TCPC_SERVER) {
697 	    *ServerType = ((response_text[5] == '/') ?
698 			   NCSA_SERVER : TCPC_SERVER);
699 	    CTRACE((tfp, "HTFTP: Treating as %s server.\n",
700 		    ((*ServerType == NCSA_SERVER) ?
701 		     "NCSA" : "TCPC")));
702 	} else if (response_text[5] == '/') {
703 	    /* path names beginning with / imply Unix,
704 	     * right?
705 	     */
706 	    if (set_mac_binary(*ServerType)) {
707 		*ServerType = NCSA_SERVER;
708 		CTRACE((tfp, "HTFTP: Treating as NCSA server.\n"));
709 	    } else {
710 		*ServerType = UNIX_SERVER;
711 		*UseList = TRUE;
712 		CTRACE((tfp, "HTFTP: Treating as Unix server.\n"));
713 	    }
714 	    return;
715 	} else if (response_text[strlen(response_text) - 1] == ']') {
716 	    /* path names ending with ] imply VMS, right? */
717 	    *ServerType = VMS_SERVER;
718 	    *UseList = TRUE;
719 	    CTRACE((tfp, "HTFTP: Treating as VMS server.\n"));
720 	} else {
721 	    *ServerType = GENERIC_SERVER;
722 	    CTRACE((tfp, "HTFTP: Treating as Generic server.\n"));
723 	}
724 
725 	if ((*ServerType == NCSA_SERVER) ||
726 	    (*ServerType == TCPC_SERVER) ||
727 	    (*ServerType == PETER_LEWIS_SERVER) ||
728 	    (*ServerType == NETPRESENZ_SERVER))
729 	    set_mac_binary(*ServerType);
730     }
731 }
732 
733 /* This function turns MSDOS-like directory output off for
734  * Windows NT servers.
735  */
736 
set_unix_dirstyle(eServerType * ServerType,BOOLEAN * UseList)737 static void set_unix_dirstyle(eServerType *ServerType, BOOLEAN *UseList)
738 {
739     char *cp;
740 
741     /* This is a toggle.  It seems we have to toggle in order to see
742      * the current state (after toggling), so we may end up toggling
743      * twice.  - kw
744      */
745     int status = response("SITE DIRSTYLE\r\n");
746 
747     if (status != 2) {
748 	*ServerType = GENERIC_SERVER;
749 	CTRACE((tfp, "HTFTP: DIRSTYLE failed, treating as Generic server.\n"));
750 	return;
751     } else {
752 	*UseList = TRUE;
753 	/* Expecting one of:
754 	 * 200 MSDOS-like directory output is off
755 	 * 200 MSDOS-like directory output is on
756 	 * The following code doesn't look for the full exact string -
757 	 * who knows how the wording may change in some future version.
758 	 * If the first response isn't recognized, we toggle again
759 	 * anyway, under the assumption that it's more likely that
760 	 * the MSDOS setting was "off" originally. - kw
761 	 */
762 	cp = strstr(response_text + 4, "MSDOS");
763 	if (cp && strstr(cp, " off")) {
764 	    return;		/* already off now. */
765 	} else {
766 	    response("SITE DIRSTYLE\r\n");
767 	}
768     }
769 }
770 
771 #define CheckForInterrupt(msg) \
772 	if (status == HT_INTERRUPTED) { \
773 	    CTRACE((tfp, "HTFTP: Interrupted %s.\n", msg)); \
774 	    _HTProgress(CONNECTION_INTERRUPTED); \
775 	    NETCLOSE(control->socket); \
776 	    control->socket = -1; \
777 	    return HT_INTERRUPTED; \
778 	}
779 
780 /*	Get a valid connection to the host
781  *	----------------------------------
782  *
783  * On entry,
784  *	arg	points to the name of the host in a hypertext address
785  * On exit,
786  *	returns <0 if error
787  *		socket number if success
788  *
789  *	This routine takes care of managing timed-out connections, and
790  *	limiting the number of connections in use at any one time.
791  *
792  *	It ensures that all connections are logged in if they exist.
793  *	It ensures they have the port number transferred.
794  */
get_connection(const char * arg,HTParentAnchor * anchor)795 static int get_connection(const char *arg,
796 			  HTParentAnchor *anchor)
797 {
798     int status;
799     char *command = 0;
800     connection *con;
801     char *username = NULL;
802     char *password = NULL;
803     static BOOLEAN firstuse = TRUE;
804 
805     if (firstuse) {
806 	/*
807 	 * Set up freeing at exit.  - FM
808 	 */
809 #ifdef LY_FIND_LEAKS
810 	atexit(free_FTPGlobals);
811 #endif
812 	firstuse = FALSE;
813     }
814 
815     if (control) {
816 	/*
817 	 * Reuse this object - KW, DW & FM
818 	 */
819 	if (control->socket != -1) {
820 	    NETCLOSE(control->socket);
821 	}
822 	con = control;
823 	con->addr = 0;
824 	con->binary = FALSE;
825     } else {
826 	/*
827 	 * Allocate and init control struct.
828 	 */
829 	con = typecalloc(connection);
830 	if (con == NULL)
831 	    outofmem(__FILE__, "get_connection");
832 
833 	assert(con != NULL);
834     }
835     con->socket = -1;
836 
837     if (isEmpty(arg)) {
838 	free(con);
839 	return -1;		/* Bad if no name specified     */
840     }
841 
842     /* Get node name:
843      */
844     CTRACE((tfp, "get_connection(%s)\n", arg));
845     {
846 	char *p1 = HTParse(arg, "", PARSE_HOST);
847 	char *p2 = strrchr(p1, '@');	/* user? */
848 	char *pw = NULL;
849 
850 	if (p2 != NULL) {
851 	    username = p1;
852 	    *p2 = '\0';		/* terminate */
853 	    p1 = p2 + 1;	/* point to host */
854 	    pw = strchr(username, ':');
855 	    if (pw != NULL) {
856 		*pw++ = '\0';
857 		password = HTUnEscape(pw);
858 	    }
859 	    if (*username)
860 		HTUnEscape(username);
861 
862 	    /*
863 	     * If the password doesn't exist then we are going to have to ask
864 	     * the user for it.  The only problem is that we don't want to ask
865 	     * for it every time, so we will store away in a primitive fashion.
866 	     */
867 	    if (!password) {
868 		char *tmp = NULL;
869 
870 		HTSprintf0(&tmp, "%s@%s", username, p1);
871 		/*
872 		 * If the user@host is not equal to the last time through or
873 		 * user_entered_password has no data then we need to ask the
874 		 * user for the password.
875 		 */
876 		if (!last_username_and_host ||
877 		    strcmp(tmp, last_username_and_host) ||
878 		    !user_entered_password) {
879 
880 		    StrAllocCopy(last_username_and_host, tmp);
881 		    HTSprintf0(&tmp, gettext("Enter password for user %s@%s:"),
882 			       username, p1);
883 		    FREE(user_entered_password);
884 		    user_entered_password = HTPromptPassword(tmp);
885 
886 		}		/* else we already know the password */
887 		password = user_entered_password;
888 		FREE(tmp);
889 	    }
890 	}
891 
892 	if (!username)
893 	    FREE(p1);
894     }				/* scope of p1 */
895 
896     status = HTDoConnect(arg, "FTP", IPPORT_FTP, (int *) &con->socket);
897 
898     if (status < 0) {
899 	if (status == HT_INTERRUPTED) {
900 	    CTRACE((tfp, "HTFTP: Interrupted on connect\n"));
901 	} else {
902 	    CTRACE((tfp, "HTFTP: Unable to connect to remote host for `%s'.\n",
903 		    arg));
904 	}
905 	if (status == HT_INTERRUPTED) {
906 	    _HTProgress(CONNECTION_INTERRUPTED);
907 	    status = HT_NOT_LOADED;
908 	} else {
909 	    HTAlert(gettext("Unable to connect to FTP host."));
910 	}
911 	if (con->socket != -1) {
912 	    NETCLOSE(con->socket);
913 	}
914 
915 	FREE(username);
916 	if (control == con)
917 	    control = NULL;
918 	FREE(con);
919 	return status;		/* Bad return */
920     }
921 
922     CTRACE((tfp, "FTP connected, socket %d  control %p\n",
923 	    con->socket, (void *) con));
924     control = con;		/* Current control connection */
925 
926     /* Initialise buffering for control connection */
927     HTInitInput(control->socket);
928     init_help_message_cache();	/* Clear the login message buffer. */
929 
930     /*  Now we log in           Look up username, prompt for pw.
931      */
932     status = response((char *) 0);	/* Get greeting */
933     CheckForInterrupt("at beginning of login");
934 
935     server_type = GENERIC_SERVER;	/* reset */
936     if (status == 2) {		/* Send username */
937 	char *cp;		/* look at greeting text */
938 
939 	/* don't gettext() this -- incoming text: */
940 	if (strlen(response_text) > 4) {
941 	    if ((cp = strstr(response_text, " awaits your command")) ||
942 		(cp = strstr(response_text, " ready."))) {
943 		*cp = '\0';
944 	    }
945 	    cp = response_text + 4;
946 	    if (!strncasecomp(cp, "NetPresenz", 10))
947 		server_type = NETPRESENZ_SERVER;
948 	} else {
949 	    cp = response_text;
950 	}
951 	StrAllocCopy(anchor->server, cp);
952 
953 	status = send_cmd_2("USER", (username && *username)
954 			    ? username
955 			    : "anonymous");
956 
957 	CheckForInterrupt("while sending username");
958     }
959     if (status == 3) {		/* Send password */
960 	if (password) {
961 	    /*
962 	     * We have non-zero length password, so send it. - FM
963 	     */
964 	    HTSprintf0(&command, "PASS %s%c%c", password, CR, LF);
965 	} else {
966 	    /*
967 	     * Create and send a mail address as the password. - FM
968 	     */
969 	    const char *the_address;
970 	    char *user = NULL;
971 	    const char *host = NULL;
972 	    char *cp;
973 
974 	    the_address = anonftp_password;
975 	    if (isEmpty(the_address))
976 		the_address = personal_mail_address;
977 	    if (isEmpty(the_address))
978 		the_address = LYGetEnv("USER");
979 	    if (isEmpty(the_address))
980 		the_address = "WWWuser";
981 
982 	    StrAllocCopy(user, the_address);
983 	    if ((cp = strchr(user, '@')) != NULL) {
984 		*cp++ = '\0';
985 		if (*cp == '\0')
986 		    host = HTHostName();
987 		else
988 		    host = cp;
989 	    } else {
990 		host = HTHostName();
991 	    }
992 
993 	    /*
994 	     * If host is not fully qualified, suppress it
995 	     * as ftp.uu.net prefers a blank to a bad name
996 	     */
997 	    if (!(host) || strchr(host, '.') == NULL)
998 		host = "";
999 
1000 	    HTSprintf0(&command, "PASS %s@%s%c%c", user, host, CR, LF);
1001 	    FREE(user);
1002 	}
1003 	status = response(command);
1004 	FREE(command);
1005 	CheckForInterrupt("while sending password");
1006     }
1007     FREE(username);
1008 
1009     if (status == 3) {
1010 	status = send_cmd_1("ACCT noaccount");
1011 	CheckForInterrupt("while sending password");
1012     }
1013     if (status != 2) {
1014 	CTRACE((tfp, "HTFTP: Login fail: %s", response_text));
1015 	/* if (control->socket > 0) close_connection(control->socket); */
1016 	return -1;		/* Bad return */
1017     }
1018     CTRACE((tfp, "HTFTP: Logged in.\n"));
1019 
1020     /* Check for host type */
1021     if (server_type != NETPRESENZ_SERVER)
1022 	server_type = GENERIC_SERVER;	/* reset */
1023     use_list = FALSE;		/* reset */
1024     if (response("SYST\r\n") == 2) {
1025 	/* we got a line -- what kind of server are we talking to? */
1026 	if (StrNCmp(response_text + 4,
1027 		    "UNIX Type: L8 MAC-OS MachTen", 28) == 0) {
1028 	    server_type = MACHTEN_SERVER;
1029 	    use_list = TRUE;
1030 	    CTRACE((tfp, "HTFTP: Treating as MachTen server.\n"));
1031 
1032 	} else if (strstr(response_text + 4, "UNIX") != NULL ||
1033 		   strstr(response_text + 4, "Unix") != NULL) {
1034 	    server_type = UNIX_SERVER;
1035 	    unsure_type = FALSE;	/* to the best of out knowledge... */
1036 	    use_list = TRUE;
1037 	    CTRACE((tfp, "HTFTP: Treating as Unix server.\n"));
1038 
1039 	} else if (strstr(response_text + 4, "MSDOS") != NULL) {
1040 	    server_type = MSDOS_SERVER;
1041 	    use_list = TRUE;
1042 	    CTRACE((tfp, "HTFTP: Treating as MSDOS (Unix emulation) server.\n"));
1043 
1044 	} else if (StrNCmp(response_text + 4, "VMS", 3) == 0) {
1045 	    char *tilde = strstr(arg, "/~");
1046 
1047 	    use_list = TRUE;
1048 	    if (tilde != 0
1049 		&& tilde[2] != 0
1050 		&& strstr(response_text + 4, "MadGoat") != 0) {
1051 		server_type = UNIX_SERVER;
1052 		CTRACE((tfp, "HTFTP: Treating VMS as UNIX server.\n"));
1053 	    } else {
1054 		server_type = VMS_SERVER;
1055 		CTRACE((tfp, "HTFTP: Treating as VMS server.\n"));
1056 	    }
1057 
1058 	} else if ((StrNCmp(response_text + 4, "VM/CMS", 6) == 0) ||
1059 		   (StrNCmp(response_text + 4, "VM ", 3) == 0)) {
1060 	    server_type = CMS_SERVER;
1061 	    use_list = TRUE;
1062 	    CTRACE((tfp, "HTFTP: Treating as CMS server.\n"));
1063 
1064 	} else if (StrNCmp(response_text + 4, "DCTS", 4) == 0) {
1065 	    server_type = DCTS_SERVER;
1066 	    CTRACE((tfp, "HTFTP: Treating as DCTS server.\n"));
1067 
1068 	} else if (strstr(response_text + 4, "MAC-OS TCP/Connect II") != NULL) {
1069 	    server_type = TCPC_SERVER;
1070 	    CTRACE((tfp, "HTFTP: Looks like a TCPC server.\n"));
1071 	    get_ftp_pwd(&server_type, &use_list);
1072 	    unsure_type = TRUE;
1073 
1074 	} else if (server_type == NETPRESENZ_SERVER) {	/* already set above */
1075 	    use_list = TRUE;
1076 	    set_mac_binary(server_type);
1077 	    CTRACE((tfp, "HTFTP: Treating as NetPresenz (MACOS) server.\n"));
1078 
1079 	} else if (StrNCmp(response_text + 4, "MACOS Peter's Server", 20) == 0) {
1080 	    server_type = PETER_LEWIS_SERVER;
1081 	    use_list = TRUE;
1082 	    set_mac_binary(server_type);
1083 	    CTRACE((tfp, "HTFTP: Treating as Peter Lewis (MACOS) server.\n"));
1084 
1085 	} else if (StrNCmp(response_text + 4, "Windows_NT", 10) == 0) {
1086 	    server_type = WINDOWS_NT_SERVER;
1087 	    CTRACE((tfp, "HTFTP: Treating as Window_NT server.\n"));
1088 	    set_unix_dirstyle(&server_type, &use_list);
1089 
1090 	} else if (StrNCmp(response_text + 4, "Windows2000", 11) == 0) {
1091 	    server_type = WINDOWS_2K_SERVER;
1092 	    CTRACE((tfp, "HTFTP: Treating as Window_2K server.\n"));
1093 	    set_unix_dirstyle(&server_type, &use_list);
1094 
1095 	} else if (StrNCmp(response_text + 4, "MS Windows", 10) == 0) {
1096 	    server_type = MS_WINDOWS_SERVER;
1097 	    use_list = TRUE;
1098 	    CTRACE((tfp, "HTFTP: Treating as MS Windows server.\n"));
1099 
1100 	} else if (StrNCmp(response_text + 4,
1101 			   "MACOS AppleShare IP FTP Server", 30) == 0) {
1102 	    server_type = APPLESHARE_SERVER;
1103 	    use_list = TRUE;
1104 	    set_mac_binary(server_type);
1105 	    CTRACE((tfp, "HTFTP: Treating as AppleShare server.\n"));
1106 
1107 	} else {
1108 	    server_type = GENERIC_SERVER;
1109 	    CTRACE((tfp, "HTFTP: Ugh!  A Generic server.\n"));
1110 	    get_ftp_pwd(&server_type, &use_list);
1111 	    unsure_type = TRUE;
1112 	}
1113     } else {
1114 	/* SYST fails :(  try to get the type from the PWD command */
1115 	get_ftp_pwd(&server_type, &use_list);
1116     }
1117 
1118     return con->socket;		/* Good return */
1119 }
1120 
reset_master_socket(void)1121 static void reset_master_socket(void)
1122 {
1123     have_socket = FALSE;
1124 }
1125 
set_master_socket(int value)1126 static void set_master_socket(int value)
1127 {
1128     have_socket = (BOOLEAN) (value >= 0);
1129     if (have_socket)
1130 	master_socket = (unsigned) value;
1131 }
1132 
1133 /*	Close Master (listening) socket
1134  *	-------------------------------
1135  *
1136  *
1137  */
close_master_socket(void)1138 static int close_master_socket(void)
1139 {
1140     int status;
1141 
1142     if (have_socket)
1143 	FD_CLR(master_socket, &open_sockets);
1144 
1145     status = NETCLOSE((int) master_socket);
1146     CTRACE((tfp, "HTFTP: Closed master socket %u\n", master_socket));
1147 
1148     reset_master_socket();
1149 
1150     if (status < 0)
1151 	return HTInetStatus(gettext("close master socket"));
1152     else
1153 	return status;
1154 }
1155 
1156 /*	Open a master socket for listening on
1157  *	-------------------------------------
1158  *
1159  *	When data is transferred, we open a port, and wait for the server to
1160  *	connect with the data.
1161  *
1162  * On entry,
1163  *	have_socket	Must be false, if master_socket is not setup already
1164  *	master_socket	Must be negative if not set up already.
1165  * On exit,
1166  *	Returns		socket number if good
1167  *			less than zero if error.
1168  *	master_socket	is socket number if good, else negative.
1169  *	port_number	is valid if good.
1170  */
get_listen_socket(void)1171 static int get_listen_socket(void)
1172 {
1173 #ifdef INET6
1174     struct sockaddr_storage soc_address;	/* Binary network address */
1175     struct sockaddr_in *soc_in = (struct sockaddr_in *) &soc_address;
1176     int af;
1177     LY_SOCKLEN slen;
1178 
1179 #else
1180     struct sockaddr_in soc_address;	/* Binary network address */
1181     struct sockaddr_in *soc_in = &soc_address;
1182 #endif /* INET6 */
1183     int new_socket;		/* Will be master_socket */
1184 
1185     FD_ZERO(&open_sockets);	/* Clear our record of open sockets */
1186     num_sockets = 0;
1187 
1188 #ifndef REPEAT_LISTEN
1189     if (have_socket)
1190 	return master_socket;	/* Done already */
1191 #endif /* !REPEAT_LISTEN */
1192 
1193 #ifdef INET6
1194     /* query address family of control connection */
1195     slen = (LY_SOCKLEN) sizeof(soc_address);
1196     if (getsockname(control->socket, (struct sockaddr *) &soc_address,
1197 		    &slen) < 0) {
1198 	return HTInetStatus("getsockname failed");
1199     }
1200     af = ((struct sockaddr *) &soc_address)->sa_family;
1201     memset(&soc_address, 0, sizeof(soc_address));
1202 #endif /* INET6 */
1203 
1204 /*  Create internet socket
1205 */
1206 #ifdef INET6
1207     new_socket = socket(af, SOCK_STREAM, IPPROTO_TCP);
1208 #else
1209     new_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
1210 #endif /* INET6 */
1211 
1212     if (new_socket < 0)
1213 	return HTInetStatus(gettext("socket for master socket"));
1214 
1215     CTRACE((tfp, "HTFTP: Opened master socket number %d\n", new_socket));
1216 
1217 /*  Search for a free port.
1218 */
1219 #ifdef INET6
1220     memset(&soc_address, 0, sizeof(soc_address));
1221     ((struct sockaddr *) &soc_address)->sa_family = af;
1222     switch (af) {
1223     case AF_INET:
1224 #ifdef SIN6_LEN
1225 	((struct sockaddr *) &soc_address)->sa_len = sizeof(struct sockaddr_in);
1226 #endif /* SIN6_LEN */
1227 	break;
1228     case AF_INET6:
1229 #ifdef SIN6_LEN
1230 	((struct sockaddr *) &soc_address)->sa_len = sizeof(struct sockaddr_in6);
1231 #endif /* SIN6_LEN */
1232 	break;
1233     default:
1234 	HTInetStatus("AF");
1235     }
1236 #else
1237     soc_in->sin_family = AF_INET;	/* Family = internet, host order  */
1238     soc_in->sin_addr.s_addr = INADDR_ANY;	/* Any peer address */
1239 #endif /* INET6 */
1240 #ifdef POLL_PORTS
1241     {
1242 	PortNumber old_port_number = port_number;
1243 
1244 	for (port_number = (old_port_number + 1);; port_number++) {
1245 	    int status;
1246 
1247 	    if (port_number > LAST_TCP_PORT)
1248 		port_number = FIRST_TCP_PORT;
1249 	    if (port_number == old_port_number) {
1250 		return HTInetStatus("bind");
1251 	    }
1252 #ifdef INET6
1253 	    soc_in->sin_port = htons(port_number);
1254 #else
1255 	    soc_address.sin_port = htons(port_number);
1256 #endif /* INET6 */
1257 #ifdef SOCKS
1258 	    if (socks_flag)
1259 		if ((status = Rbind(new_socket,
1260 				    (struct sockaddr *) &soc_address,
1261 		/* Cast to generic sockaddr */
1262 				    SOCKADDR_LEN(soc_address)
1263 #ifndef SHORTENED_RBIND
1264 				    ,socks_bind_remoteAddr
1265 #endif /* !SHORTENED_RBIND */
1266 		     )) == 0) {
1267 		    break;
1268 		} else
1269 #endif /* SOCKS */
1270 		    if ((status = bind(new_socket,
1271 				       (struct sockaddr *) &soc_address,
1272 		    /* Cast to generic sockaddr */
1273 				       SOCKADDR_LEN(soc_address)
1274 			 )) == 0) {
1275 		    break;
1276 		}
1277 	    CTRACE((tfp, "TCP bind attempt to port %d yields %d, errno=%d\n",
1278 		    port_number, status, SOCKET_ERRNO));
1279 	}			/* for */
1280     }
1281 #else
1282     {
1283 	int status;
1284 	LY_SOCKLEN address_length = (LY_SOCKLEN) sizeof(soc_address);
1285 
1286 #ifdef SOCKS
1287 	if (socks_flag)
1288 	    status = Rgetsockname(control->socket,
1289 				  (struct sockaddr *) &soc_address,
1290 				  &address_length);
1291 	else
1292 #endif /* SOCKS */
1293 	    status = getsockname(control->socket,
1294 				 (struct sockaddr *) &soc_address,
1295 				 &address_length);
1296 	if (status < 0) {
1297 	    close(new_socket);
1298 	    return HTInetStatus("getsockname");
1299 	}
1300 #ifdef INET6
1301 	CTRACE((tfp, "HTFTP: This host is %s\n",
1302 		HTInetString((void *) soc_in)));
1303 
1304 	soc_in->sin_port = 0;	/* Unspecified: please allocate */
1305 #else
1306 	CTRACE((tfp, "HTFTP: This host is %s\n",
1307 		HTInetString(soc_in)));
1308 
1309 	soc_address.sin_port = 0;	/* Unspecified: please allocate */
1310 #endif /* INET6 */
1311 #ifdef SOCKS
1312 	if (socks_flag)
1313 	    status = Rbind(new_socket,
1314 			   (struct sockaddr *) &soc_address,
1315 	    /* Cast to generic sockaddr */
1316 			   sizeof(soc_address)
1317 #ifndef SHORTENED_RBIND
1318 			   ,socks_bind_remoteAddr
1319 #endif /* !SHORTENED_RBIND */
1320 		);
1321 	else
1322 #endif /* SOCKS */
1323 	    status = bind(new_socket,
1324 			  (struct sockaddr *) &soc_address,
1325 	    /* Cast to generic sockaddr */
1326 			  SOCKADDR_LEN(soc_address)
1327 		);
1328 	if (status < 0) {
1329 	    close(new_socket);
1330 	    return HTInetStatus("bind");
1331 	}
1332 
1333 	address_length = sizeof(soc_address);
1334 #ifdef SOCKS
1335 	if (socks_flag)
1336 	    status = Rgetsockname(new_socket,
1337 				  (struct sockaddr *) &soc_address,
1338 				  &address_length);
1339 	else
1340 #endif /* SOCKS */
1341 	    status = getsockname(new_socket,
1342 				 (struct sockaddr *) &soc_address,
1343 				 &address_length);
1344 	if (status < 0) {
1345 	    close(new_socket);
1346 	    return HTInetStatus("getsockname");
1347 	}
1348     }
1349 #endif /* POLL_PORTS */
1350 
1351 #ifdef INET6
1352     CTRACE((tfp, "HTFTP: bound to port %d on %s\n",
1353 	    (int) ntohs(soc_in->sin_port),
1354 	    HTInetString((void *) soc_in)));
1355 #else
1356     CTRACE((tfp, "HTFTP: bound to port %d on %s\n",
1357 	    (int) ntohs(soc_in->sin_port),
1358 	    HTInetString(soc_in)));
1359 #endif /* INET6 */
1360 
1361 #ifdef REPEAT_LISTEN
1362     if (have_socket)
1363 	(void) close_master_socket();
1364 #endif /* REPEAT_LISTEN */
1365 
1366     set_master_socket(new_socket);
1367 
1368 /*	Now we must find out who we are to tell the other guy
1369 */
1370     (void) HTHostName();	/* Make address valid - doesn't work */
1371 #ifdef INET6
1372     switch (((struct sockaddr *) &soc_address)->sa_family) {
1373     case AF_INET:
1374 #endif /* INET6 */
1375 	sprintf(port_command, "PORT %d,%d,%d,%d,%d,%d%c%c",
1376 		(int) *((unsigned char *) (&soc_in->sin_addr) + 0),
1377 		(int) *((unsigned char *) (&soc_in->sin_addr) + 1),
1378 		(int) *((unsigned char *) (&soc_in->sin_addr) + 2),
1379 		(int) *((unsigned char *) (&soc_in->sin_addr) + 3),
1380 		(int) *((unsigned char *) (&soc_in->sin_port) + 0),
1381 		(int) *((unsigned char *) (&soc_in->sin_port) + 1),
1382 		CR, LF);
1383 
1384 #ifdef INET6
1385 	break;
1386 
1387     case AF_INET6:
1388 	{
1389 	    char hostbuf[MAXHOSTNAMELEN];
1390 	    char portbuf[MAXHOSTNAMELEN];
1391 
1392 	    getnameinfo((struct sockaddr *) &soc_address,
1393 			SOCKADDR_LEN(soc_address),
1394 			hostbuf,
1395 			(socklen_t) sizeof(hostbuf),
1396 			portbuf,
1397 			(socklen_t) sizeof(portbuf),
1398 			NI_NUMERICHOST | NI_NUMERICSERV);
1399 	    sprintf(port_command, "EPRT |%d|%s|%s|%c%c", 2, hostbuf, portbuf,
1400 		    CR, LF);
1401 	    break;
1402 	}
1403     default:
1404 	sprintf(port_command, "JUNK%c%c", CR, LF);
1405 	break;
1406     }
1407 #endif /* INET6 */
1408 
1409     /*  Inform TCP that we will accept connections
1410      */
1411     {
1412 	int status;
1413 
1414 #ifdef SOCKS
1415 	if (socks_flag)
1416 	    status = Rlisten((int) master_socket, 1);
1417 	else
1418 #endif /* SOCKS */
1419 	    status = listen((int) master_socket, 1);
1420 	if (status < 0) {
1421 	    reset_master_socket();
1422 	    return HTInetStatus("listen");
1423 	}
1424     }
1425     CTRACE((tfp, "TCP: Master socket(), bind() and listen() all OK\n"));
1426     FD_SET(master_socket, &open_sockets);
1427     if ((master_socket + 1) > num_sockets)
1428 	num_sockets = master_socket + 1;
1429 
1430     return (int) master_socket;	/* Good */
1431 
1432 }				/* get_listen_socket */
1433 
1434 static const char *months[12] =
1435 {
1436     "Jan", "Feb", "Mar", "Apr", "May", "Jun",
1437     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
1438 };
1439 
1440 /*	Procedure: Set the current and last year strings and date integer
1441  *	-----------------------------------------------------------------
1442  *
1443  *	Bug:
1444  *		This code is for sorting listings by date, if that option
1445  *		is selected in Lynx, and doesn't take into account time
1446  *		zones or ensure resetting at midnight, so the sort may not
1447  *		be perfect, but the actual date isn't changed in the display,
1448  *		i.e., the date is still correct. - FM
1449  */
set_years_and_date(void)1450 static void set_years_and_date(void)
1451 {
1452     char day[8], month[8], date[12];
1453     time_t NowTime;
1454     int i;
1455     char *printable;
1456 
1457     NowTime = time(NULL);
1458     printable = ctime(&NowTime);
1459     LYStrNCpy(day, printable + 8, 2);
1460     if (day[0] == ' ') {
1461 	day[0] = '0';
1462     }
1463     LYStrNCpy(month, printable + 4, 3);
1464     for (i = 0; i < 12; i++) {
1465 	if (!strcasecomp(month, months[i])) {
1466 	    break;
1467 	}
1468     }
1469     i++;
1470     sprintf(date, "9999%02d%.2s", i, day);
1471     TheDate = atoi(date);
1472     LYStrNCpy(ThisYear, printable + 20, 4);
1473     sprintf(LastYear, "%d", (atoi(ThisYear) - 1));
1474     HaveYears = TRUE;
1475 }
1476 
1477 typedef struct _EntryInfo {
1478     char *filename;
1479     char *linkname;		/* symbolic link, if any */
1480     char *type;
1481     char *date;
1482     off_t size;
1483     BOOLEAN display;		/* show this entry? */
1484 #ifdef LONG_LIST
1485     unsigned long file_links;
1486     char *file_mode;
1487     char *file_user;
1488     char *file_group;
1489 #endif
1490 } EntryInfo;
1491 
free_entryinfo_struct_contents(EntryInfo * entry_info)1492 static void free_entryinfo_struct_contents(EntryInfo *entry_info)
1493 {
1494     if (entry_info) {
1495 	FREE(entry_info->filename);
1496 	FREE(entry_info->linkname);
1497 	FREE(entry_info->type);
1498 	FREE(entry_info->date);
1499     }
1500     /* dont free the struct */
1501 }
1502 
1503 /*
1504  * is_ls_date() --
1505  *	Return TRUE if s points to a string of the form:
1506  *		"Sep  1  1990 " or
1507  *		"Sep 11 11:59 " or
1508  *		"Dec 12 1989  " or
1509  *		"FCv 23 1990  " ...
1510  */
is_ls_date(char * s)1511 static BOOLEAN is_ls_date(char *s)
1512 {
1513     /* must start with three alpha characters */
1514     if (!isalpha(UCH(*s++)) || !isalpha(UCH(*s++)) || !isalpha(UCH(*s++)))
1515 	return FALSE;
1516 
1517     /* space or HT_NON_BREAK_SPACE */
1518     if (!(*s == ' ' || *s == HT_NON_BREAK_SPACE)) {
1519 	return FALSE;
1520     }
1521     s++;
1522 
1523     /* space or digit */
1524     if (!(*s == ' ' || isdigit(UCH(*s)))) {
1525 	return FALSE;
1526     }
1527     s++;
1528 
1529     /* digit */
1530     if (!isdigit(UCH(*s++)))
1531 	return FALSE;
1532 
1533     /* space */
1534     if (*s++ != ' ')
1535 	return FALSE;
1536 
1537     /* space or digit */
1538     if (!(*s == ' ' || isdigit(UCH(*s)))) {
1539 	return FALSE;
1540     }
1541     s++;
1542 
1543     /* digit */
1544     if (!isdigit(UCH(*s++)))
1545 	return FALSE;
1546 
1547     /* colon or digit */
1548     if (!(*s == ':' || isdigit(UCH(*s)))) {
1549 	return FALSE;
1550     }
1551     s++;
1552 
1553     /* digit */
1554     if (!isdigit(UCH(*s++)))
1555 	return FALSE;
1556 
1557     /* space or digit */
1558     if (!(*s == ' ' || isdigit(UCH(*s)))) {
1559 	return FALSE;
1560     }
1561     s++;
1562 
1563     /* space */
1564     if (*s != ' ')
1565 	return FALSE;
1566 
1567     return TRUE;
1568 }				/* is_ls_date() */
1569 
1570 /*
1571  * Extract the name, size, and date from an EPLF line.  - 08-06-96 DJB
1572  */
parse_eplf_line(char * line,EntryInfo * info)1573 static void parse_eplf_line(char *line,
1574 			    EntryInfo *info)
1575 {
1576     char *cp = line;
1577     char ct[26];
1578     off_t size;
1579     time_t secs;
1580     static time_t base;		/* time() value on this OS in 1970 */
1581     static int flagbase = 0;
1582 
1583     if (!flagbase) {
1584 	struct tm t;
1585 
1586 	t.tm_year = 70;
1587 	t.tm_mon = 0;
1588 	t.tm_mday = 0;
1589 	t.tm_hour = 0;
1590 	t.tm_min = 0;
1591 	t.tm_sec = 0;
1592 	t.tm_isdst = -1;
1593 	base = mktime(&t);	/* could return -1 */
1594 	flagbase = 1;
1595     }
1596 
1597     while (*cp) {
1598 	switch (*cp) {
1599 	case '\t':
1600 	    StrAllocCopy(info->filename, cp + 1);
1601 	    return;
1602 	case 's':
1603 	    size = 0;
1604 	    while (*(++cp) && (*cp != ','))
1605 		size = (size * 10) + (off_t) (*cp - '0');
1606 	    info->size = size;
1607 	    break;
1608 	case 'm':
1609 	    secs = 0;
1610 	    while (*(++cp) && (*cp != ','))
1611 		secs = (secs * 10) + (*cp - '0');
1612 	    secs += base;	/* assumes that time_t is #seconds */
1613 	    LYStrNCpy(ct, ctime(&secs), 24);
1614 	    StrAllocCopy(info->date, ct);
1615 	    break;
1616 	case '/':
1617 	    StrAllocCopy(info->type, ENTRY_IS_DIRECTORY);
1618 	    /* FALLTHRU */
1619 	default:
1620 	    while (*cp) {
1621 		if (*cp++ == ',')
1622 		    break;
1623 	    }
1624 	    break;
1625 	}
1626     }
1627 }				/* parse_eplf_line */
1628 
1629 /*
1630  * Extract the name, size, and date from an ls -l line.
1631  */
parse_ls_line(char * line,EntryInfo * entry)1632 static void parse_ls_line(char *line,
1633 			  EntryInfo *entry)
1634 {
1635 #ifdef LONG_LIST
1636     char *next;
1637     char *cp;
1638 #endif
1639     int i, j;
1640     off_t base = 1;
1641     off_t size_num = 0;
1642 
1643     for (i = (int) strlen(line) - 1;
1644 	 (i > 13) && (!isspace(UCH(line[i])) || !is_ls_date(&line[i - 12]));
1645 	 i--) {
1646 	;			/* null body */
1647     }
1648     line[i] = '\0';
1649     if (i > 13) {
1650 	StrAllocCopy(entry->date, &line[i - 12]);
1651 	/* replace the 4th location with nbsp if it is a space or zero */
1652 	if (entry->date[4] == ' ' || entry->date[4] == '0')
1653 	    entry->date[4] = HT_NON_BREAK_SPACE;
1654 	/* make sure year or time is flush right */
1655 	if (entry->date[11] == ' ') {
1656 	    for (j = 11; j > 6; j--) {
1657 		entry->date[j] = entry->date[j - 1];
1658 	    }
1659 	}
1660     }
1661     j = i - 14;
1662     while (isdigit(UCH(line[j]))) {
1663 	size_num += ((off_t) (line[j] - '0') * base);
1664 	base *= 10;
1665 	j--;
1666     }
1667     entry->size = size_num;
1668     StrAllocCopy(entry->filename, &line[i + 1]);
1669 
1670 #ifdef LONG_LIST
1671     line[j] = '\0';
1672 
1673     /*
1674      * Extract the file-permissions, as a string.
1675      */
1676     if ((cp = strchr(line, ' ')) != 0) {
1677 	if ((cp - line) == 10) {
1678 	    *cp = '\0';
1679 	    StrAllocCopy(entry->file_mode, line);
1680 	    *cp = ' ';
1681 	}
1682 
1683 	/*
1684 	 * Next is the link-count.
1685 	 */
1686 	next = 0;
1687 	entry->file_links = (unsigned long) strtol(cp, &next, 10);
1688 	if (next == 0 || *next != ' ') {
1689 	    entry->file_links = 0;
1690 	    next = cp;
1691 	} else {
1692 	    cp = next;
1693 	}
1694 	/*
1695 	 * Next is the user-name.
1696 	 */
1697 	while (isspace(UCH(*cp)))
1698 	    ++cp;
1699 	if ((next = strchr(cp, ' ')) != 0)
1700 	    *next = '\0';
1701 	if (*cp != '\0')
1702 	    StrAllocCopy(entry->file_user, cp);
1703 	/*
1704 	 * Next is the group-name (perhaps).
1705 	 */
1706 	if (next != NULL) {
1707 	    cp = (next + 1);
1708 	    while (isspace(UCH(*cp)))
1709 		++cp;
1710 	    if ((next = strchr(cp, ' ')) != 0)
1711 		*next = '\0';
1712 	    if (*cp != '\0')
1713 		StrAllocCopy(entry->file_group, cp);
1714 	}
1715     }
1716 #endif
1717 }
1718 
1719 /*
1720  * Extract the name and size info and whether it refers to a directory from a
1721  * LIST line in "dls" format.
1722  */
parse_dls_line(char * line,EntryInfo * entry_info,char ** pspilledname)1723 static void parse_dls_line(char *line,
1724 			   EntryInfo *entry_info,
1725 			   char **pspilledname)
1726 {
1727     short j;
1728     int base = 1;
1729     off_t size_num = 0;
1730     int len;
1731     char *cps = NULL;
1732 
1733     /* README              763  Information about this server\0
1734        bin/                  -  \0
1735        etc/                  =  \0
1736        ls-lR                 0  \0
1737        ls-lR.Z               3  \0
1738        pub/                  =  Public area\0
1739        usr/                  -  \0
1740        morgan               14  -> ../real/morgan\0
1741        TIMIT.mostlikely.Z\0
1742        79215    \0
1743      */
1744 
1745     len = (int) strlen(line);
1746     if (len == 0) {
1747 	FREE(*pspilledname);
1748 	entry_info->display = FALSE;
1749 	return;
1750     }
1751     cps = LYSkipNonBlanks(line);
1752     if (*cps == '\0') {		/* only a filename, save it and return. */
1753 	StrAllocCopy(*pspilledname, line);
1754 	entry_info->display = FALSE;
1755 	return;
1756     }
1757     if (len < 24 || line[23] != ' ' ||
1758 	(isspace(UCH(line[0])) && !*pspilledname)) {
1759 	/* this isn't the expected "dls" format! */
1760 	if (!isspace(UCH(line[0])))
1761 	    *cps = '\0';
1762 	if (*pspilledname && !*line) {
1763 	    entry_info->filename = *pspilledname;
1764 	    *pspilledname = NULL;
1765 	    if (entry_info->filename[strlen(entry_info->filename) - 1] == '/')
1766 		StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
1767 	    else
1768 		StrAllocCopy(entry_info->type, "");
1769 	} else {
1770 	    StrAllocCopy(entry_info->filename, line);
1771 	    if (cps != line && *(cps - 1) == '/')
1772 		StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
1773 	    else
1774 		StrAllocCopy(entry_info->type, "");
1775 	    FREE(*pspilledname);
1776 	}
1777 	return;
1778     }
1779 
1780     j = 22;
1781     if (line[j] == '=' || line[j] == '-') {
1782 	StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
1783     } else {
1784 	while (isdigit(UCH(line[j]))) {
1785 	    size_num += (line[j] - '0') * base;
1786 	    base *= 10;
1787 	    j--;
1788 	}
1789     }
1790     entry_info->size = size_num;
1791 
1792     cps = LYSkipBlanks(&line[23]);
1793     if (!StrNCmp(cps, "-> ", 3) && cps[3] != '\0' && cps[3] != ' ') {
1794 	StrAllocCopy(entry_info->type, ENTRY_IS_SYMBOLIC_LINK);
1795 	StrAllocCopy(entry_info->linkname, LYSkipBlanks(cps + 3));
1796 	entry_info->size = 0;	/* don't display size */
1797     }
1798 
1799     if (j > 0)
1800 	line[j] = '\0';
1801 
1802     LYTrimTrailing(line);
1803 
1804     len = (int) strlen(line);
1805     if (len == 0 && *pspilledname && **pspilledname) {
1806 	line = *pspilledname;
1807 	len = (int) strlen(*pspilledname);
1808     }
1809     if (len > 0 && line[len - 1] == '/') {
1810 	/*
1811 	 * It's a dir, remove / and mark it as such.
1812 	 */
1813 	if (len > 1)
1814 	    line[len - 1] = '\0';
1815 	if (!entry_info->type)
1816 	    StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
1817     }
1818 
1819     StrAllocCopy(entry_info->filename, line);
1820     FREE(*pspilledname);
1821 }				/* parse_dls_line() */
1822 
1823 /*
1824  * parse_vms_dir_entry()
1825  *	Format the name, date, and size from a VMS LIST line
1826  *	into the EntryInfo structure - FM
1827  */
parse_vms_dir_entry(char * line,EntryInfo * entry_info)1828 static void parse_vms_dir_entry(char *line,
1829 				EntryInfo *entry_info)
1830 {
1831     int i, j;
1832     off_t ialloc;
1833     char *cp, *cpd, *cps, date[16];
1834     const char *sp = " ";
1835 
1836     /* Get rid of blank lines, and information lines.  Valid lines have the ';'
1837      * version number token.
1838      */
1839     if (!strlen(line) || (cp = strchr(line, ';')) == NULL) {
1840 	entry_info->display = FALSE;
1841 	return;
1842     }
1843 
1844     /* Cut out file or directory name at VMS version number. */
1845     *cp++ = '\0';
1846     StrAllocCopy(entry_info->filename, line);
1847 
1848     /* Cast VMS non-README file and directory names to lowercase. */
1849     if (strstr(entry_info->filename, "READ") == NULL) {
1850 	LYLowerCase(entry_info->filename);
1851 	i = (int) strlen(entry_info->filename);
1852     } else {
1853 	i = (int) ((strstr(entry_info->filename, "READ")
1854 		    - entry_info->filename)
1855 		   + 4);
1856 	if (!StrNCmp(&entry_info->filename[i], "ME", 2)) {
1857 	    i += 2;
1858 	    while (entry_info->filename[i] && entry_info->filename[i] != '.') {
1859 		i++;
1860 	    }
1861 	} else if (!StrNCmp(&entry_info->filename[i], ".ME", 3)) {
1862 	    i = (int) strlen(entry_info->filename);
1863 	} else {
1864 	    i = 0;
1865 	}
1866 	LYLowerCase(entry_info->filename + i);
1867     }
1868 
1869     /* Uppercase terminal .zs or _zs. */
1870     if ((--i > 2) &&
1871 	entry_info->filename[i] == 'z' &&
1872 	(entry_info->filename[i - 1] == '.' ||
1873 	 entry_info->filename[i - 1] == '_'))
1874 	entry_info->filename[i] = 'Z';
1875 
1876     /* Convert any tabs in rest of line to spaces. */
1877     cps = cp - 1;
1878     while ((cps = strchr(cps + 1, '\t')) != NULL)
1879 	*cps = ' ';
1880 
1881     /* Collapse serial spaces. */
1882     i = 0;
1883     j = 1;
1884     cps = cp;
1885     while (cps[j] != '\0') {
1886 	if (cps[i] == ' ' && cps[j] == ' ')
1887 	    j++;
1888 	else
1889 	    cps[++i] = cps[j++];
1890     }
1891     cps[++i] = '\0';
1892 
1893     /* Set the years and date, if we don't have them yet. * */
1894     if (!HaveYears) {
1895 	set_years_and_date();
1896     }
1897 
1898     /* Track down the date. */
1899     if ((cpd = strchr(cp, '-')) != NULL &&
1900 	strlen(cpd) > 9 && isdigit(UCH(*(cpd - 1))) &&
1901 	isalpha(UCH(*(cpd + 1))) && *(cpd + 4) == '-') {
1902 
1903 	/* Month */
1904 	*(cpd + 2) = (char) TOLOWER(*(cpd + 2));
1905 	*(cpd + 3) = (char) TOLOWER(*(cpd + 3));
1906 	sprintf(date, "%.3s ", cpd + 1);
1907 
1908 	/* Day */
1909 	if (isdigit(UCH(*(cpd - 2))))
1910 	    sprintf(date + 4, "%.2s ", cpd - 2);
1911 	else
1912 	    sprintf(date + 4, "%c%.1s ", HT_NON_BREAK_SPACE, cpd - 1);
1913 
1914 	/* Time or Year */
1915 	if (!StrNCmp(ThisYear, cpd + 5, 4) &&
1916 	    strlen(cpd) > 15 && *(cpd + 12) == ':') {
1917 	    sprintf(date + 7, "%.5s", cpd + 10);
1918 	} else {
1919 	    sprintf(date + 7, " %.4s", cpd + 5);
1920 	}
1921 
1922 	StrAllocCopy(entry_info->date, date);
1923     }
1924 
1925     /* Track down the size */
1926     if ((cpd = strchr(cp, '/')) != NULL) {
1927 	/* Appears be in used/allocated format */
1928 	cps = cpd;
1929 	while (isdigit(UCH(*(cps - 1))))
1930 	    cps--;
1931 	if (cps < cpd)
1932 	    *cpd = '\0';
1933 	entry_info->size = LYatoll(cps);
1934 	cps = cpd + 1;
1935 	while (isdigit(UCH(*cps)))
1936 	    cps++;
1937 	*cps = '\0';
1938 	ialloc = LYatoll(cpd + 1);
1939 	/* Check if used is in blocks or bytes */
1940 	if (entry_info->size <= ialloc)
1941 	    entry_info->size *= 512;
1942 
1943     } else if (strtok(cp, sp) != NULL) {
1944 	/* We just initialized on the version number */
1945 	/* Now let's hunt for a lone, size number    */
1946 	while ((cps = strtok(NULL, sp)) != NULL) {
1947 	    cpd = cps;
1948 	    while (isdigit(UCH(*cpd)))
1949 		cpd++;
1950 	    if (*cpd == '\0') {
1951 		/* Assume it's blocks */
1952 		entry_info->size = (LYatoll(cps) * 512);
1953 		break;
1954 	    }
1955 	}
1956     }
1957 
1958     TRACE_ENTRY("VMS", entry_info);
1959     return;
1960 }				/* parse_vms_dir_entry() */
1961 
1962 /*
1963  * parse_ms_windows_dir_entry() --
1964  *	Format the name, date, and size from an MS_WINDOWS LIST line into
1965  *	the EntryInfo structure (assumes Chameleon NEWT format). - FM
1966  */
parse_ms_windows_dir_entry(char * line,EntryInfo * entry_info)1967 static void parse_ms_windows_dir_entry(char *line,
1968 				       EntryInfo *entry_info)
1969 {
1970     char *cp = line;
1971     char *cps, *cpd, date[16];
1972     char *end = line + strlen(line);
1973 
1974     /*  Get rid of blank or junk lines.  */
1975     cp = LYSkipBlanks(cp);
1976     if (!(*cp)) {
1977 	entry_info->display = FALSE;
1978 	return;
1979     }
1980 
1981     /* Cut out file or directory name. */
1982     cps = LYSkipNonBlanks(cp);
1983     *cps++ = '\0';
1984     cpd = cps;
1985     StrAllocCopy(entry_info->filename, cp);
1986 
1987     /* Track down the size */
1988     if (cps < end) {
1989 	cps = LYSkipBlanks(cps);
1990 	cpd = LYSkipNonBlanks(cps);
1991 	*cpd++ = '\0';
1992 	if (isdigit(UCH(*cps))) {
1993 	    entry_info->size = LYatoll(cps);
1994 	} else {
1995 	    StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
1996 	}
1997     } else {
1998 	StrAllocCopy(entry_info->type, "");
1999     }
2000 
2001     /* Set the years and date, if we don't have them yet. * */
2002     if (!HaveYears) {
2003 	set_years_and_date();
2004     }
2005 
2006     /* Track down the date. */
2007     if (cpd < end) {
2008 	cpd = LYSkipBlanks(cpd);
2009 	if (strlen(cpd) > 17) {
2010 	    *(cpd + 6) = '\0';	/* Month and Day */
2011 	    *(cpd + 11) = '\0';	/* Year */
2012 	    *(cpd + 17) = '\0';	/* Time */
2013 	    if (strcmp(ThisYear, cpd + 7))
2014 		/* Not this year, so show the year */
2015 		sprintf(date, "%.6s  %.4s", cpd, (cpd + 7));
2016 	    else
2017 		/* Is this year, so show the time */
2018 		sprintf(date, "%.6s %.5s", cpd, (cpd + 12));
2019 	    StrAllocCopy(entry_info->date, date);
2020 	    if (entry_info->date[4] == ' ' || entry_info->date[4] == '0') {
2021 		entry_info->date[4] = HT_NON_BREAK_SPACE;
2022 	    }
2023 	}
2024     }
2025 
2026     TRACE_ENTRY("MS Windows", entry_info);
2027     return;
2028 }				/* parse_ms_windows_dir_entry */
2029 
2030 /*
2031  * parse_windows_nt_dir_entry() --
2032  *	Format the name, date, and size from a WINDOWS_NT LIST line into
2033  *	the EntryInfo structure (assumes Chameleon NEWT format). - FM
2034  */
2035 #ifdef NOTDEFINED
parse_windows_nt_dir_entry(char * line,EntryInfo * entry_info)2036 static void parse_windows_nt_dir_entry(char *line,
2037 				       EntryInfo *entry_info)
2038 {
2039     char *cp = line;
2040     char *cps, *cpd, date[16];
2041     char *end = line + strlen(line);
2042     int i;
2043 
2044     /*  Get rid of blank or junk lines.  */
2045     cp = LYSkipBlanks(cp);
2046     if (!(*cp)) {
2047 	entry_info->display = FALSE;
2048 	return;
2049     }
2050 
2051     /* Cut out file or directory name. */
2052     cpd = cp;
2053     cps = LYSkipNonBlanks(end - 1);
2054     cp = (cps + 1);
2055     if (!strcmp(cp, ".") || !strcmp(cp, "..")) {
2056 	entry_info->display = FALSE;
2057 	return;
2058     }
2059     StrAllocCopy(entry_info->filename, cp);
2060     if (cps < cpd)
2061 	return;
2062     *cp = '\0';
2063     end = cp;
2064 
2065     /* Set the years and date, if we don't have them yet. * */
2066     if (!HaveYears) {
2067 	set_years_and_date();
2068     }
2069 
2070     /* Cut out the date. */
2071     cp = cps = cpd;
2072     cps = LYSkipNonBlanks(cps);
2073     *cps++ = '\0';
2074     if (cps > end) {
2075 	entry_info->display = FALSE;
2076 	return;
2077     }
2078     cps = LYSkipBlanks(cps);
2079     cpd = LYSkipNonBlanks(cps);
2080     *cps++ = '\0';
2081     if (cps > end || cpd == cps || strlen(cpd) < 7) {
2082 	entry_info->display = FALSE;
2083 	return;
2084     }
2085     if (strlen(cp) == 8 &&
2086 	isdigit(*cp) && isdigit(*(cp + 1)) && *(cp + 2) == '-' &&
2087 	isdigit(*(cp + 3)) && isdigit(*(cp + 4)) && *(cp + 5) == '-') {
2088 	*(cp + 2) = '\0';	/* Month */
2089 	i = atoi(cp) - 1;
2090 	*(cp + 5) = '\0';	/* Day */
2091 	sprintf(date, "%.3s %.2s", months[i], (cp + 3));
2092 	if (date[4] == '0')
2093 	    date[4] = ' ';
2094 	cp += 6;		/* Year */
2095 	if (strcmp((ThisYear + 2), cp)) {
2096 	    /* Not this year, so show the year */
2097 	    if (atoi(cp) < 70) {
2098 		sprintf(&date[6], "  20%.2s", cp);
2099 	    } else {
2100 		sprintf(&date[6], "  19%.2s", cp);
2101 	    }
2102 	} else {
2103 	    /* Is this year, so show the time */
2104 	    *(cpd + 2) = '\0';	/* Hour */
2105 	    i = atoi(cpd);
2106 	    if (*(cpd + 5) == 'P' || *(cpd + 5) == 'p')
2107 		i += 12;
2108 	    sprintf(&date[6], " %02d:%.2s", i, (cpd + 3));
2109 	}
2110 	StrAllocCopy(entry_info->date, date);
2111 	if (entry_info->date[4] == ' ' || entry_info->date[4] == '0') {
2112 	    entry_info->date[4] = HT_NON_BREAK_SPACE;
2113 	}
2114     }
2115 
2116     /* Track down the size */
2117     if (cps < end) {
2118 	cps = LYSkipBlanks(cps);
2119 	cpd = LYSkipNonBlanks(cps);
2120 	*cpd = '\0';
2121 	if (isdigit(*cps)) {
2122 	    entry_info->size = LYatoll(cps);
2123 	} else {
2124 	    StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
2125 	}
2126     } else {
2127 	StrAllocCopy(entry_info->type, "");
2128     }
2129 
2130     /* Wrap it up */
2131     CTRACE((tfp, "HTFTP: Windows NT filename: %s  date: %s  size: %d\n",
2132 	    entry_info->filename,
2133 	    NonNull(entry_info->date),
2134 	    entry_info->size));
2135     return;
2136 }				/* parse_windows_nt_dir_entry */
2137 #endif /* NOTDEFINED */
2138 
2139 /*
2140  * parse_cms_dir_entry() --
2141  *	Format the name, date, and size from a VM/CMS line into
2142  *	the EntryInfo structure. - FM
2143  */
parse_cms_dir_entry(char * line,EntryInfo * entry_info)2144 static void parse_cms_dir_entry(char *line,
2145 				EntryInfo *entry_info)
2146 {
2147     char *cp = line;
2148     char *cps, *cpd, date[16];
2149     char *end = line + strlen(line);
2150     int RecordLength = 0;
2151     int Records = 0;
2152     int i;
2153 
2154     /*  Get rid of blank or junk lines.  */
2155     cp = LYSkipBlanks(cp);
2156     if (!(*cp)) {
2157 	entry_info->display = FALSE;
2158 	return;
2159     }
2160 
2161     /* Cut out file or directory name. */
2162     cps = LYSkipNonBlanks(cp);
2163     *cps++ = '\0';
2164     StrAllocCopy(entry_info->filename, cp);
2165     if (strchr(entry_info->filename, '.') != NULL)
2166 	/* If we already have a dot, we did an NLST. */
2167 	return;
2168     cp = LYSkipBlanks(cps);
2169     if (!(*cp)) {
2170 	/* If we don't have more, we've misparsed. */
2171 	FREE(entry_info->filename);
2172 	FREE(entry_info->type);
2173 	entry_info->display = FALSE;
2174 	return;
2175     }
2176     cps = LYSkipNonBlanks(cp);
2177     *cps++ = '\0';
2178     if ((0 == strcasecomp(cp, "DIR")) && (cp - line) > 17) {
2179 	/* It's an SFS directory. */
2180 	StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
2181 	entry_info->size = 0;
2182     } else {
2183 	/* It's a file. */
2184 	cp--;
2185 	*cp = '.';
2186 	StrAllocCat(entry_info->filename, cp);
2187 
2188 	/* Track down the VM/CMS RECFM or type. */
2189 	cp = cps;
2190 	if (cp < end) {
2191 	    cp = LYSkipBlanks(cp);
2192 	    cps = LYSkipNonBlanks(cp);
2193 	    *cps++ = '\0';
2194 	    /* Check cp here, if it's relevant someday. */
2195 	}
2196     }
2197 
2198     /* Track down the record length or dash. */
2199     cp = cps;
2200     if (cp < end) {
2201 	cp = LYSkipBlanks(cp);
2202 	cps = LYSkipNonBlanks(cp);
2203 	*cps++ = '\0';
2204 	if (isdigit(UCH(*cp))) {
2205 	    RecordLength = atoi(cp);
2206 	}
2207     }
2208 
2209     /* Track down the number of records or the dash. */
2210     cp = cps;
2211     if (cps < end) {
2212 	cp = LYSkipBlanks(cp);
2213 	cps = LYSkipNonBlanks(cp);
2214 	*cps++ = '\0';
2215 	if (isdigit(UCH(*cp))) {
2216 	    Records = atoi(cp);
2217 	}
2218 	if (Records > 0 && RecordLength > 0) {
2219 	    /* Compute an approximate size. */
2220 	    entry_info->size = ((off_t) Records * (off_t) RecordLength);
2221 	}
2222     }
2223 
2224     /* Set the years and date, if we don't have them yet. */
2225     if (!HaveYears) {
2226 	set_years_and_date();
2227     }
2228 
2229     /* Track down the date. */
2230     cpd = cps;
2231     if (((cps < end) &&
2232 	 (cps = strchr(cpd, ':')) != NULL) &&
2233 	(cps < (end - 3) &&
2234 	 isdigit(UCH(*(cps + 1))) && isdigit(UCH(*(cps + 2))) && *(cps + 3) == ':')) {
2235 	cps += 3;
2236 	*cps = '\0';
2237 	if ((cps - cpd) >= 14) {
2238 	    cpd = (cps - 14);
2239 	    *(cpd + 2) = '\0';	/* Month */
2240 	    *(cpd + 5) = '\0';	/* Day */
2241 	    *(cpd + 8) = '\0';	/* Year */
2242 	    cps -= 5;		/* Time */
2243 	    if (*cpd == ' ')
2244 		*cpd = '0';
2245 	    i = atoi(cpd) - 1;
2246 	    sprintf(date, "%.3s %.2s", months[i], (cpd + 3));
2247 	    if (date[4] == '0')
2248 		date[4] = ' ';
2249 	    cpd += 6;		/* Year */
2250 	    if (strcmp((ThisYear + 2), cpd)) {
2251 		/* Not this year, so show the year. */
2252 		if (atoi(cpd) < 70) {
2253 		    sprintf(&date[6], "  20%.2s", cpd);
2254 		} else {
2255 		    sprintf(&date[6], "  19%.2s", cpd);
2256 		}
2257 	    } else {
2258 		/* Is this year, so show the time. */
2259 		*(cps + 2) = '\0';	/* Hour */
2260 		i = atoi(cps);
2261 		sprintf(&date[6], " %02d:%.2s", i, (cps + 3));
2262 	    }
2263 	    StrAllocCopy(entry_info->date, date);
2264 	    if (entry_info->date[4] == ' ' || entry_info->date[4] == '0') {
2265 		entry_info->date[4] = HT_NON_BREAK_SPACE;
2266 	    }
2267 	}
2268     }
2269 
2270     TRACE_ENTRY("VM/CMS", entry_info);
2271     return;
2272 }				/* parse_cms_dir_entry */
2273 
2274 /*
2275  * Given a line of LIST/NLST output in entry, return results and a file/dir
2276  * name in entry_info struct
2277  *
2278  * If first is true, this is the first name in a directory.
2279  */
parse_dir_entry(char * entry,BOOLEAN * first,char ** pspilledname)2280 static EntryInfo *parse_dir_entry(char *entry,
2281 				  BOOLEAN *first,
2282 				  char **pspilledname)
2283 {
2284     EntryInfo *entry_info;
2285     int i;
2286     int len;
2287     BOOLEAN remove_size = FALSE;
2288     char *cp;
2289 
2290     entry_info = typecalloc(EntryInfo);
2291 
2292     if (entry_info == NULL)
2293 	outofmem(__FILE__, "parse_dir_entry");
2294 
2295     assert(entry_info != NULL);
2296 
2297     entry_info->display = TRUE;
2298 
2299     switch (server_type) {
2300     case DLS_SERVER:
2301 
2302 	/*
2303 	 * Interpret and edit LIST output from a Unix server in "dls" format.
2304 	 * This one must have claimed to be Unix in order to get here; if the
2305 	 * first line looks fishy, we revert to Unix and hope that fits better
2306 	 * (this recovery is untested).  - kw
2307 	 */
2308 
2309 	if (*first) {
2310 	    len = (int) strlen(entry);
2311 	    if (!len || entry[0] == ' ' ||
2312 		(len >= 24 && entry[23] != ' ') ||
2313 		(len < 24 && strchr(entry, ' '))) {
2314 		server_type = UNIX_SERVER;
2315 		CTRACE((tfp,
2316 			"HTFTP: Falling back to treating as Unix server.\n"));
2317 	    } else {
2318 		*first = FALSE;
2319 	    }
2320 	}
2321 
2322 	if (server_type == DLS_SERVER) {
2323 	    /* if still unchanged... */
2324 	    parse_dls_line(entry, entry_info, pspilledname);
2325 
2326 	    if (isEmpty(entry_info->filename)) {
2327 		entry_info->display = FALSE;
2328 		return (entry_info);
2329 	    }
2330 	    if (!strcmp(entry_info->filename, "..") ||
2331 		!strcmp(entry_info->filename, "."))
2332 		entry_info->display = FALSE;
2333 	    if (entry_info->type && *entry_info->type == '\0') {
2334 		FREE(entry_info->type);
2335 		return (entry_info);
2336 	    }
2337 	    /*
2338 	     * Goto the bottom and get real type.
2339 	     */
2340 	    break;
2341 	}
2342 	/* fall through if server_type changed for *first == TRUE ! */
2343     case UNIX_SERVER:
2344     case PETER_LEWIS_SERVER:
2345     case MACHTEN_SERVER:
2346     case MSDOS_SERVER:
2347     case WINDOWS_NT_SERVER:
2348     case WINDOWS_2K_SERVER:
2349     case APPLESHARE_SERVER:
2350     case NETPRESENZ_SERVER:
2351 	/*
2352 	 * Check for EPLF output (local times).
2353 	 */
2354 	if (*entry == '+') {
2355 	    parse_eplf_line(entry, entry_info);
2356 	    break;
2357 	}
2358 
2359 	/*
2360 	 * Interpret and edit LIST output from Unix server.
2361 	 */
2362 	len = (int) strlen(entry);
2363 	if (*first) {
2364 	    /* don't gettext() this -- incoming text: */
2365 	    if (!strcmp(entry, "can not access directory .")) {
2366 		/*
2367 		 * Don't reset *first, nothing real will follow.  - KW
2368 		 */
2369 		entry_info->display = FALSE;
2370 		return (entry_info);
2371 	    }
2372 	    *first = FALSE;
2373 	    if (!StrNCmp(entry, "total ", 6) ||
2374 		strstr(entry, "not available") != NULL) {
2375 		entry_info->display = FALSE;
2376 		return (entry_info);
2377 	    } else if (unsure_type) {
2378 		/* this isn't really a unix server! */
2379 		server_type = GENERIC_SERVER;
2380 		entry_info->display = FALSE;
2381 		return (entry_info);
2382 	    }
2383 	}
2384 
2385 	/*
2386 	 * Check first character of ls -l output.
2387 	 */
2388 	if (TOUPPER(entry[0]) == 'D') {
2389 	    /*
2390 	     * It's a directory.
2391 	     */
2392 	    StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
2393 	    remove_size = TRUE;	/* size is not useful */
2394 	} else if (entry[0] == 'l') {
2395 	    /*
2396 	     * It's a symbolic link, does the user care about knowing if it is
2397 	     * symbolic?  I think so since it might be a directory.
2398 	     */
2399 	    StrAllocCopy(entry_info->type, ENTRY_IS_SYMBOLIC_LINK);
2400 	    remove_size = TRUE;	/* size is not useful */
2401 
2402 	    /*
2403 	     * Strip off " -> pathname".
2404 	     */
2405 	    for (i = len - 1; (i > 3) &&
2406 		 (!isspace(UCH(entry[i])) ||
2407 		  (entry[i - 1] != '>') ||
2408 		  (entry[i - 2] != '-') ||
2409 		  (entry[i - 3] != ' ')); i--) ;	/* null body */
2410 	    if (i > 3) {
2411 		entry[i - 3] = '\0';
2412 		StrAllocCopy(entry_info->linkname, LYSkipBlanks(entry + i));
2413 	    }
2414 	}
2415 	/* link */
2416 	parse_ls_line(entry, entry_info);
2417 
2418 	if (!strcmp(entry_info->filename, "..") ||
2419 	    !strcmp(entry_info->filename, "."))
2420 	    entry_info->display = FALSE;
2421 	/*
2422 	 * Goto the bottom and get real type.
2423 	 */
2424 	break;
2425 
2426     case VMS_SERVER:
2427 	/*
2428 	 * Interpret and edit LIST output from VMS server and convert
2429 	 * information lines to zero length.
2430 	 */
2431 	parse_vms_dir_entry(entry, entry_info);
2432 
2433 	/*
2434 	 * Get rid of any junk lines.
2435 	 */
2436 	if (!entry_info->display)
2437 	    return (entry_info);
2438 
2439 	/*
2440 	 * Trim off VMS directory extensions.
2441 	 */
2442 	len = (int) strlen(entry_info->filename);
2443 	if ((len > 4) && !strcmp(&entry_info->filename[len - 4], ".dir")) {
2444 	    entry_info->filename[len - 4] = '\0';
2445 	    StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
2446 	    remove_size = TRUE;	/* size is not useful */
2447 	}
2448 	/*
2449 	 * Goto the bottom and get real type.
2450 	 */
2451 	break;
2452 
2453     case MS_WINDOWS_SERVER:
2454 	/*
2455 	 * Interpret and edit LIST output from MS_WINDOWS server and convert
2456 	 * information lines to zero length.
2457 	 */
2458 	parse_ms_windows_dir_entry(entry, entry_info);
2459 
2460 	/*
2461 	 * Get rid of any junk lines.
2462 	 */
2463 	if (!entry_info->display)
2464 	    return (entry_info);
2465 	if (entry_info->type && *entry_info->type == '\0') {
2466 	    FREE(entry_info->type);
2467 	    return (entry_info);
2468 	}
2469 	/*
2470 	 * Goto the bottom and get real type.
2471 	 */
2472 	break;
2473 
2474 #ifdef NOTDEFINED
2475     case WINDOWS_NT_SERVER:
2476 	/*
2477 	 * Interpret and edit LIST output from MS_WINDOWS server and convert
2478 	 * information lines to zero length.
2479 	 */
2480 	parse_windows_nt_dir_entry(entry, entry_info);
2481 
2482 	/*
2483 	 * Get rid of any junk lines.
2484 	 */
2485 	if (!entry_info->display)
2486 	    return (entry_info);
2487 	if (entry_info->type && *entry_info->type == '\0') {
2488 	    FREE(entry_info->type);
2489 	    return (entry_info);
2490 	}
2491 	/*
2492 	 * Goto the bottom and get real type.
2493 	 */
2494 	break;
2495 #endif /* NOTDEFINED */
2496 
2497     case CMS_SERVER:
2498 	{
2499 	    /*
2500 	     * Interpret and edit LIST output from VM/CMS server and convert
2501 	     * any information lines to zero length.
2502 	     */
2503 	    parse_cms_dir_entry(entry, entry_info);
2504 
2505 	    /*
2506 	     * Get rid of any junk lines.
2507 	     */
2508 	    if (!entry_info->display)
2509 		return (entry_info);
2510 	    if (entry_info->type && *entry_info->type == '\0') {
2511 		FREE(entry_info->type);
2512 		return (entry_info);
2513 	    }
2514 	    /*
2515 	     * Goto the bottom and get real type.
2516 	     */
2517 	    break;
2518 	}
2519 
2520     case NCSA_SERVER:
2521     case TCPC_SERVER:
2522 	/*
2523 	 * Directories identified by trailing "/" characters.
2524 	 */
2525 	StrAllocCopy(entry_info->filename, entry);
2526 	len = (int) strlen(entry);
2527 	if (entry[len - 1] == '/') {
2528 	    /*
2529 	     * It's a dir, remove / and mark it as such.
2530 	     */
2531 	    entry[len - 1] = '\0';
2532 	    StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
2533 	    remove_size = TRUE;	/* size is not useful */
2534 	}
2535 	/*
2536 	 * Goto the bottom and get real type.
2537 	 */
2538 	break;
2539 
2540     default:
2541 	/*
2542 	 * We can't tell if it is a directory since we only did an NLST :( List
2543 	 * bad file types anyways?  NOT!
2544 	 */
2545 	StrAllocCopy(entry_info->filename, entry);
2546 	return (entry_info);	/* mostly empty info */
2547 
2548     }				/* switch (server_type) */
2549 
2550 #ifdef LONG_LIST
2551     (void) remove_size;
2552 #else
2553     if (remove_size && entry_info->size) {
2554 	entry_info->size = 0;
2555     }
2556 #endif
2557 
2558     if (isEmpty(entry_info->filename)) {
2559 	entry_info->display = FALSE;
2560 	return (entry_info);
2561     }
2562     if (strlen(entry_info->filename) > 3) {
2563 	if (((cp = strrchr(entry_info->filename, '.')) != NULL &&
2564 	     0 == strncasecomp(cp, ".me", 3)) &&
2565 	    (cp[3] == '\0' || cp[3] == ';')) {
2566 	    /*
2567 	     * Don't treat this as application/x-Troff-me if it's a Unix server
2568 	     * but has the string "read.me", or if it's not a Unix server.  -
2569 	     * FM
2570 	     */
2571 	    if ((server_type != UNIX_SERVER) ||
2572 		(cp > (entry_info->filename + 3) &&
2573 		 0 == strncasecomp((cp - 4), "read.me", 7))) {
2574 		StrAllocCopy(entry_info->type, "text/plain");
2575 	    }
2576 	}
2577     }
2578 
2579     /*
2580      * Get real types eventually.
2581      */
2582     if (!entry_info->type) {
2583 	const char *cp2;
2584 	HTFormat format;
2585 	HTAtom *encoding;	/* @@ not used at all */
2586 
2587 	format = HTFileFormat(entry_info->filename, &encoding, &cp2);
2588 
2589 	if (cp2 == NULL) {
2590 	    if (!StrNCmp(HTAtom_name(format), "application", 11)) {
2591 		cp2 = HTAtom_name(format) + 12;
2592 		if (!StrNCmp(cp2, "x-", 2))
2593 		    cp2 += 2;
2594 	    } else {
2595 		cp2 = HTAtom_name(format);
2596 	    }
2597 	}
2598 
2599 	StrAllocCopy(entry_info->type, cp2);
2600     }
2601 
2602     return (entry_info);
2603 }
2604 
formatDate(char target[16],EntryInfo * entry)2605 static void formatDate(char target[16], EntryInfo *entry)
2606 {
2607     char temp[8], month[4];
2608     int i;
2609 
2610     /*
2611      * Set up for sorting in reverse chronological order. - FM
2612      */
2613     if (entry->date[9] == ':') {
2614 	strcpy(target, "9999");
2615 	LYStrNCpy(temp, &entry->date[7], 5);
2616 	if (temp[0] == ' ') {
2617 	    temp[0] = '0';
2618 	}
2619     } else {
2620 	LYStrNCpy(target, &entry->date[8], 4);
2621 	strcpy(temp, "00:00");
2622     }
2623     LYStrNCpy(month, entry->date, 3);
2624     for (i = 0; i < 12; i++) {
2625 	if (!strcasecomp(month, months[i])) {
2626 	    break;
2627 	}
2628     }
2629     i++;
2630     sprintf(month, "%02d", i);
2631     strcat(target, month);
2632     StrNCat(target, &entry->date[4], 2);
2633     if (target[6] == ' ' || target[6] == HT_NON_BREAK_SPACE) {
2634 	target[6] = '0';
2635     }
2636 
2637     /* If no year given, assume last year if it would otherwise be in the
2638      * future by more than one day.  The one day tolerance is to account for a
2639      * possible timezone difference. - kw
2640      */
2641     if (target[0] == '9' && atoi(target) > TheDate + 1) {
2642 	for (i = 0; i < 4; i++) {
2643 	    target[i] = LastYear[i];
2644 	}
2645     }
2646     strcat(target, temp);
2647 }
2648 
compare_EntryInfo_structs(EntryInfo * entry1,EntryInfo * entry2)2649 static int compare_EntryInfo_structs(EntryInfo *entry1, EntryInfo *entry2)
2650 {
2651     int status;
2652     char date1[16], date2[16];
2653     int result = strcmp(entry1->filename, entry2->filename);
2654 
2655     switch (HTfileSortMethod) {
2656     case FILE_BY_SIZE:
2657 	/* both equal or both 0 */
2658 	if (entry1->size > entry2->size)
2659 	    result = 1;
2660 	else if (entry1->size < entry2->size)
2661 	    result = -1;
2662 	break;
2663 
2664     case FILE_BY_TYPE:
2665 	if (entry1->type && entry2->type) {
2666 	    status = strcasecomp(entry1->type, entry2->type);
2667 	    if (status)
2668 		result = status;
2669 	}
2670 	break;
2671 
2672     case FILE_BY_DATE:
2673 	if (entry1->date && entry2->date &&
2674 	    strlen(entry1->date) == 12 &&
2675 	    strlen(entry2->date) == 12) {
2676 	    /*
2677 	     * Set the years and date, if we don't have them yet.
2678 	     */
2679 	    if (!HaveYears) {
2680 		set_years_and_date();
2681 	    }
2682 	    formatDate(date1, entry1);
2683 	    formatDate(date2, entry2);
2684 	    /*
2685 	     * Do the comparison. - FM
2686 	     */
2687 	    status = strcasecomp(date2, date1);
2688 	    if (status)
2689 		result = status;
2690 	}
2691 	break;
2692 
2693     case FILE_BY_NAME:
2694     default:
2695 	break;
2696     }
2697     return result;
2698 }
2699 
2700 #ifdef LONG_LIST
FormatStr(char ** bufp,char * start,const char * value)2701 static char *FormatStr(char **bufp,
2702 		       char *start,
2703 		       const char *value)
2704 {
2705     char fmt[512];
2706 
2707     if (*start) {
2708 	sprintf(fmt, "%%%.*ss", (int) sizeof(fmt) - 3, start);
2709 	HTSprintf(bufp, fmt, value);
2710     } else if (*bufp && !(value && *value)) {
2711 	;
2712     } else if (value) {
2713 	StrAllocCat(*bufp, value);
2714     }
2715     return *bufp;
2716 }
2717 
FormatSize(char ** bufp,char * start,off_t value)2718 static char *FormatSize(char **bufp,
2719 			char *start,
2720 			off_t value)
2721 {
2722     char fmt[512];
2723 
2724     if (*start) {
2725 	sprintf(fmt, "%%%.*s" PRI_off_t, (int) sizeof(fmt) - 3, start);
2726 
2727 	HTSprintf(bufp, fmt, value);
2728     } else {
2729 	sprintf(fmt, "%" PRI_off_t, value);
2730 
2731 	StrAllocCat(*bufp, fmt);
2732     }
2733     return *bufp;
2734 }
2735 
FormatNum(char ** bufp,char * start,unsigned long value)2736 static char *FormatNum(char **bufp,
2737 		       char *start,
2738 		       unsigned long value)
2739 {
2740     char fmt[512];
2741 
2742     if (*start) {
2743 	sprintf(fmt, "%%%.*sld", (int) sizeof(fmt) - 3, start);
2744 	HTSprintf(bufp, fmt, value);
2745     } else {
2746 	sprintf(fmt, "%lu", value);
2747 	StrAllocCat(*bufp, fmt);
2748     }
2749     return *bufp;
2750 }
2751 
FlushParse(HTStructured * target,char ** buf)2752 static void FlushParse(HTStructured * target, char **buf)
2753 {
2754     if (*buf && **buf) {
2755 	PUTS(*buf);
2756 	**buf = '\0';
2757     }
2758 }
2759 
LYListFmtParse(const char * fmtstr,EntryInfo * data,HTStructured * target,char * tail)2760 static void LYListFmtParse(const char *fmtstr,
2761 			   EntryInfo *data,
2762 			   HTStructured * target,
2763 			   char *tail)
2764 {
2765     char c;
2766     char *s;
2767     char *end;
2768     char *start;
2769     char *str = NULL;
2770     char *buf = NULL;
2771     BOOL is_directory = (BOOL) (data->file_mode != 0 &&
2772 				(TOUPPER(data->file_mode[0]) == 'D'));
2773     BOOL is_symlinked = (BOOL) (data->file_mode != 0 &&
2774 				(TOUPPER(data->file_mode[0]) == 'L'));
2775     BOOL remove_size = (BOOL) (is_directory || is_symlinked);
2776 
2777     StrAllocCopy(str, fmtstr);
2778     s = str;
2779     end = str + strlen(str);
2780     while (*s) {
2781 	start = s;
2782 	while (*s) {
2783 	    if (*s == '%') {
2784 		if (*(s + 1) == '%')	/* literal % */
2785 		    s++;
2786 		else
2787 		    break;
2788 	    }
2789 	    s++;
2790 	}
2791 	/* s is positioned either at a % or at \0 */
2792 	*s = '\0';
2793 	if (s > start) {	/* some literal chars. */
2794 	    StrAllocCat(buf, start);
2795 	}
2796 	if (s == end)
2797 	    break;
2798 	start = ++s;
2799 	while (isdigit(UCH(*s)) || *s == '.' || *s == '-' || *s == ' ' ||
2800 	       *s == '#' || *s == '+' || *s == '\'')
2801 	    s++;
2802 	c = *s;			/* the format char. or \0 */
2803 	*s = '\0';
2804 
2805 	switch (c) {
2806 	case '\0':
2807 	    StrAllocCat(buf, start);
2808 	    continue;
2809 
2810 	case 'A':
2811 	case 'a':		/* anchor */
2812 	    FlushParse(target, &buf);
2813 	    HTDirEntry(target, tail, data->filename);
2814 	    FormatStr(&buf, start, data->filename);
2815 	    PUTS(buf);
2816 	    END(HTML_A);
2817 	    if (buf != 0)
2818 		*buf = '\0';
2819 	    if (c != 'A' && data->linkname != 0) {
2820 		PUTS(" -> ");
2821 		PUTS(data->linkname);
2822 	    }
2823 	    break;
2824 
2825 	case 'T':		/* MIME type */
2826 	case 't':		/* MIME type description */
2827 	    if (is_directory) {
2828 		if (c != 'T') {
2829 		    FormatStr(&buf, start, ENTRY_IS_DIRECTORY);
2830 		} else {
2831 		    FormatStr(&buf, start, "");
2832 		}
2833 	    } else if (is_symlinked) {
2834 		if (c != 'T') {
2835 		    FormatStr(&buf, start, ENTRY_IS_SYMBOLIC_LINK);
2836 		} else {
2837 		    FormatStr(&buf, start, "");
2838 		}
2839 	    } else {
2840 		const char *cp2;
2841 		HTFormat format;
2842 
2843 		format = HTFileFormat(data->filename, NULL, &cp2);
2844 
2845 		if (c != 'T') {
2846 		    if (cp2 == NULL) {
2847 			if (!StrNCmp(HTAtom_name(format),
2848 				     "application", 11)) {
2849 			    cp2 = HTAtom_name(format) + 12;
2850 			    if (!StrNCmp(cp2, "x-", 2))
2851 				cp2 += 2;
2852 			} else {
2853 			    cp2 = HTAtom_name(format);
2854 			}
2855 		    }
2856 		    FormatStr(&buf, start, cp2);
2857 		} else {
2858 		    FormatStr(&buf, start, HTAtom_name(format));
2859 		}
2860 	    }
2861 	    break;
2862 
2863 	case 'd':		/* date */
2864 	    if (data->date) {
2865 		FormatStr(&buf, start, data->date);
2866 	    } else {
2867 		FormatStr(&buf, start, " * ");
2868 	    }
2869 	    break;
2870 
2871 	case 's':		/* size in bytes */
2872 	    FormatSize(&buf, start, data->size);
2873 	    break;
2874 
2875 	case 'K':		/* size in Kilobytes but not for directories */
2876 	    if (remove_size) {
2877 		FormatStr(&buf, start, "");
2878 		StrAllocCat(buf, " ");
2879 		break;
2880 	    }
2881 	    /* FALL THROUGH */
2882 	case 'k':		/* size in Kilobytes */
2883 	    /* FIXME - this is inconsistent with HTFile.c, but historical */
2884 	    if (data->size < 1024) {
2885 		FormatSize(&buf, start, data->size);
2886 		StrAllocCat(buf, " bytes");
2887 	    } else {
2888 		FormatSize(&buf, start, data->size / 1024);
2889 		StrAllocCat(buf, "Kb");
2890 	    }
2891 	    break;
2892 
2893 #ifdef LONG_LIST
2894 	case 'p':		/* unix-style permission bits */
2895 	    FormatStr(&buf, start, NonNull(data->file_mode));
2896 	    break;
2897 
2898 	case 'o':		/* owner */
2899 	    FormatStr(&buf, start, NonNull(data->file_user));
2900 	    break;
2901 
2902 	case 'g':		/* group */
2903 	    FormatStr(&buf, start, NonNull(data->file_group));
2904 	    break;
2905 
2906 	case 'l':		/* link count */
2907 	    FormatNum(&buf, start, data->file_links);
2908 	    break;
2909 #endif
2910 
2911 	case '%':		/* literal % with flags/width */
2912 	    FormatStr(&buf, start, "%");
2913 	    break;
2914 
2915 	default:
2916 	    fprintf(stderr,
2917 		    "Unknown format character `%c' in list format\n", c);
2918 	    break;
2919 	}
2920 
2921 	s++;
2922     }
2923     if (buf) {
2924 	LYTrimTrailing(buf);
2925 	FlushParse(target, &buf);
2926 	FREE(buf);
2927     }
2928     PUTC('\n');
2929     FREE(str);
2930 }
2931 #endif /* LONG_LIST */
2932 
2933 /*	Read a directory into an hypertext object from the data socket
2934  *	--------------------------------------------------------------
2935  *
2936  * On entry,
2937  *	anchor		Parent anchor to link the this node to
2938  *	address		Address of the directory
2939  * On exit,
2940  *	returns		HT_LOADED if OK
2941  *			<0 if error.
2942  */
read_directory(HTParentAnchor * parent,const char * address,HTFormat format_out,HTStream * sink)2943 static int read_directory(HTParentAnchor *parent,
2944 			  const char *address,
2945 			  HTFormat format_out,
2946 			  HTStream *sink)
2947 {
2948     int status;
2949     BOOLEAN WasInterrupted = FALSE;
2950     HTStructured *target = HTML_new(parent, format_out, sink);
2951     char *filename = HTParse(address, "", PARSE_PATH + PARSE_PUNCTUATION);
2952     EntryInfo *entry_info;
2953     BOOLEAN first = TRUE;
2954     char *lastpath = NULL;	/* prefix for link, either "" (for root) or xxx  */
2955     BOOL tildeIsTop = FALSE;
2956 
2957 #ifndef LONG_LIST
2958     char string_buffer[64];
2959 #endif
2960 
2961     _HTProgress(gettext("Receiving FTP directory."));
2962 
2963     /*
2964      * Force the current Date and Year (TheDate, ThisYear, and LastYear) to be
2965      * recalculated for each directory request.  Otherwise we have a problem
2966      * with long-running sessions assuming the wrong date for today.  - kw
2967      */
2968     HaveYears = FALSE;
2969     /*
2970      * Check whether we always want the home directory treated as Welcome.  -
2971      * FM
2972      */
2973     if (server_type == VMS_SERVER)
2974 	tildeIsTop = TRUE;
2975 
2976     /*
2977      * This should always come back FALSE, since the flag is set only for local
2978      * directory listings if LONG_LIST was defined on compilation, but we could
2979      * someday set up an equivalent listing for Unix ftp servers.  - FM
2980      */
2981     (void) HTDirTitles(target, parent, format_out, tildeIsTop);
2982 
2983     data_read_pointer = data_write_pointer = data_buffer;
2984 
2985     if (*filename == '\0') {	/* Empty filename: use root. */
2986 	StrAllocCopy(lastpath, "/");
2987     } else if (!strcmp(filename, "/")) {	/* Root path. */
2988 	StrAllocCopy(lastpath, "/foo/..");
2989     } else {
2990 	char *p = strrchr(filename, '/');	/* Find the lastslash. */
2991 	char *cp;
2992 
2993 	if (server_type == CMS_SERVER) {
2994 	    StrAllocCopy(lastpath, filename);	/* Use absolute path for CMS. */
2995 	} else {
2996 	    StrAllocCopy(lastpath, p + 1);	/* Take slash off the beginning. */
2997 	}
2998 	if ((cp = strrchr(lastpath, ';')) != NULL) {	/* Trim type= param. */
2999 	    if (!strncasecomp((cp + 1), "type=", 5)) {
3000 		if (TOUPPER(*(cp + 6)) == 'D' ||
3001 		    TOUPPER(*(cp + 6)) == 'A' ||
3002 		    TOUPPER(*(cp + 6)) == 'I')
3003 		    *cp = '\0';
3004 	    }
3005 	}
3006     }
3007     FREE(filename);
3008 
3009     {
3010 	HTBTree *bt = HTBTree_new((HTComparer) compare_EntryInfo_structs);
3011 	int ic;
3012 	HTChunk *chunk = HTChunkCreate(128);
3013 	int BytesReceived = 0;
3014 	int BytesReported = 0;
3015 	char NumBytes[64];
3016 	char *spilledname = NULL;
3017 
3018 	PUTC('\n');		/* prettier LJM */
3019 	for (ic = 0; ic != EOF;) {	/* For each entry in the directory */
3020 	    HTChunkClear(chunk);
3021 
3022 	    if (HTCheckForInterrupt()) {
3023 		CTRACE((tfp,
3024 			"read_directory: interrupted after %d bytes\n",
3025 			BytesReceived));
3026 		WasInterrupted = TRUE;
3027 		if (BytesReceived) {
3028 		    goto unload_btree;	/* unload btree */
3029 		} else {
3030 		    ABORT_TARGET;
3031 		    HTBTreeAndObject_free(bt);
3032 		    FREE(spilledname);
3033 		    HTChunkFree(chunk);
3034 		    return HT_INTERRUPTED;
3035 		}
3036 	    }
3037 
3038 	    /*   read directory entry
3039 	     */
3040 	    interrupted_in_next_data_char = FALSE;
3041 	    for (;;) {		/* Read in one line as filename */
3042 		ic = NEXT_DATA_CHAR;
3043 	      AgainForMultiNet:
3044 		if (interrupted_in_next_data_char) {
3045 		    CTRACE((tfp,
3046 			    "read_directory: interrupted_in_next_data_char after %d bytes\n",
3047 			    BytesReceived));
3048 		    WasInterrupted = TRUE;
3049 		    if (BytesReceived) {
3050 			goto unload_btree;	/* unload btree */
3051 		    } else {
3052 			ABORT_TARGET;
3053 			HTBTreeAndObject_free(bt);
3054 			FREE(spilledname);
3055 			HTChunkFree(chunk);
3056 			return HT_INTERRUPTED;
3057 		    }
3058 		} else if ((char) ic == CR || (char) ic == LF) {	/* Terminator? */
3059 		    if (chunk->size != 0) {	/* got some text */
3060 			/* Deal with MultiNet's wrapping of long lines */
3061 			if (server_type == VMS_SERVER) {
3062 			    /* Deal with MultiNet's wrapping of long lines - F.M. */
3063 			    if (data_read_pointer < data_write_pointer &&
3064 				*(data_read_pointer + 1) == ' ')
3065 				data_read_pointer++;
3066 			    else if (data_read_pointer >= data_write_pointer) {
3067 				status = NETREAD(data_soc, data_buffer,
3068 						 DATA_BUFFER_SIZE);
3069 				if (status == HT_INTERRUPTED) {
3070 				    interrupted_in_next_data_char = 1;
3071 				    goto AgainForMultiNet;
3072 				}
3073 				if (status <= 0) {
3074 				    ic = EOF;
3075 				    break;
3076 				}
3077 				data_write_pointer = data_buffer + status;
3078 				data_read_pointer = data_buffer;
3079 				if (*data_read_pointer == ' ')
3080 				    data_read_pointer++;
3081 				else
3082 				    break;
3083 			    } else
3084 				break;
3085 			} else
3086 			    break;	/* finish getting one entry */
3087 		    }
3088 		} else if (ic == EOF) {
3089 		    break;	/* End of file */
3090 		} else {
3091 		    HTChunkPutc(chunk, UCH(ic));
3092 		}
3093 	    }
3094 	    HTChunkTerminate(chunk);
3095 
3096 	    BytesReceived += chunk->size;
3097 	    if (BytesReceived > BytesReported + 1024) {
3098 #ifdef _WINDOWS
3099 		sprintf(NumBytes, gettext("Transferred %d bytes (%5d)"),
3100 			BytesReceived, ws_read_per_sec);
3101 #else
3102 		sprintf(NumBytes, TRANSFERRED_X_BYTES, BytesReceived);
3103 #endif
3104 		HTProgress(NumBytes);
3105 		BytesReported = BytesReceived;
3106 	    }
3107 
3108 	    if (ic == EOF && chunk->size == 1)
3109 		/* 1 means empty: includes terminating 0 */
3110 		break;
3111 	    CTRACE((tfp, "HTFTP: Line in %s is %s\n",
3112 		    lastpath, chunk->data));
3113 
3114 	    entry_info = parse_dir_entry(chunk->data, &first, &spilledname);
3115 	    if (entry_info->display) {
3116 		FREE(spilledname);
3117 		CTRACE((tfp, "Adding file to BTree: %s\n",
3118 			entry_info->filename));
3119 		HTBTree_add(bt, entry_info);
3120 	    } else {
3121 		free_entryinfo_struct_contents(entry_info);
3122 		FREE(entry_info);
3123 	    }
3124 
3125 	}			/* next entry */
3126 
3127       unload_btree:
3128 
3129 	HTChunkFree(chunk);
3130 	FREE(spilledname);
3131 
3132 	/* print out the handy help message if it exists :) */
3133 	if (help_message_cache_non_empty()) {
3134 	    START(HTML_PRE);
3135 	    START(HTML_HR);
3136 	    PUTC('\n');
3137 	    PUTS(help_message_cache_contents());
3138 	    init_help_message_cache();	/* to free memory */
3139 	    START(HTML_HR);
3140 	    PUTC('\n');
3141 	} else {
3142 	    START(HTML_PRE);
3143 	    PUTC('\n');
3144 	}
3145 
3146 	/* Run through tree printing out in order
3147 	 */
3148 	{
3149 #ifndef LONG_LIST
3150 #ifdef SH_EX			/* 1997/10/18 (Sat) 14:14:28 */
3151 	    char *p, name_buff[256];
3152 	    int name_len, dot_len;
3153 
3154 #define	FNAME_WIDTH	30
3155 #define	FILE_GAP	1
3156 
3157 #endif
3158 	    int i;
3159 #endif
3160 	    HTBTElement *ele;
3161 
3162 	    for (ele = HTBTree_next(bt, NULL);
3163 		 ele != NULL;
3164 		 ele = HTBTree_next(bt, ele)) {
3165 		entry_info = (EntryInfo *) HTBTree_object(ele);
3166 
3167 #ifdef LONG_LIST
3168 		LYListFmtParse(ftp_format,
3169 			       entry_info,
3170 			       target,
3171 			       lastpath);
3172 #else
3173 		if (entry_info->date) {
3174 		    PUTS(entry_info->date);
3175 		    PUTS("  ");
3176 		} else {
3177 		    PUTS("     * ");
3178 		}
3179 
3180 		if (entry_info->type) {
3181 		    for (i = 0; entry_info->type[i] != '\0' && i < 16; i++)
3182 			PUTC(entry_info->type[i]);
3183 		    for (; i < 17; i++)
3184 			PUTC(' ');
3185 		}
3186 		/* start the anchor */
3187 		HTDirEntry(target, lastpath, entry_info->filename);
3188 #ifdef SH_EX			/* 1997/10/18 (Sat) 16:00 */
3189 		name_len = strlen(entry_info->filename);
3190 
3191 		sprintf(name_buff, "%-*s", FNAME_WIDTH, entry_info->filename);
3192 
3193 		if (name_len < FNAME_WIDTH) {
3194 		    dot_len = FNAME_WIDTH - FILE_GAP - name_len;
3195 		    if (dot_len > 0) {
3196 			p = name_buff + name_len + 1;
3197 			while (dot_len-- > 0)
3198 			    *p++ = '.';
3199 		    }
3200 		} else {
3201 		    name_buff[FNAME_WIDTH] = '\0';
3202 		}
3203 
3204 		PUTS(name_buff);
3205 #else
3206 		PUTS(entry_info->filename);
3207 #endif
3208 		END(HTML_A);
3209 
3210 		if (entry_info->size) {
3211 #ifdef SH_EX			/* 1998/02/02 (Mon) 16:34:52 */
3212 		    if (entry_info->size < 1024)
3213 			sprintf(string_buffer, "%6ld bytes",
3214 				entry_info->size);
3215 		    else
3216 			sprintf(string_buffer, "%6ld Kb",
3217 				entry_info->size / 1024);
3218 #else
3219 		    if (entry_info->size < 1024)
3220 			sprintf(string_buffer, "  %lu bytes",
3221 				entry_info->size);
3222 		    else
3223 			sprintf(string_buffer, "  %luKb",
3224 				entry_info->size / 1024);
3225 #endif
3226 		    PUTS(string_buffer);
3227 		} else if (entry_info->linkname != 0) {
3228 		    PUTS(" -> ");
3229 		    PUTS(entry_info->linkname);
3230 		}
3231 
3232 		PUTC('\n');	/* end of this entry */
3233 #endif
3234 
3235 		free_entryinfo_struct_contents(entry_info);
3236 	    }
3237 	}
3238 	END(HTML_PRE);
3239 	END(HTML_BODY);
3240 	FREE_TARGET;
3241 	HTBTreeAndObject_free(bt);
3242     }
3243 
3244     FREE(lastpath);
3245 
3246     if (WasInterrupted || data_soc != -1) {	/* should always be true */
3247 	/*
3248 	 * Without closing the data socket first, the response(0) later may
3249 	 * hang.  Some servers expect the client to fin/ack the close of the
3250 	 * data connection before proceeding with the conversation on the
3251 	 * control connection.  - kw
3252 	 */
3253 	CTRACE((tfp, "HTFTP: Closing data socket %d\n", data_soc));
3254 	status = NETCLOSE(data_soc);
3255 	if (status == -1)
3256 	    HTInetStatus("close");	/* Comment only */
3257 	data_soc = -1;
3258     }
3259 
3260     if (WasInterrupted || HTCheckForInterrupt()) {
3261 	_HTProgress(TRANSFER_INTERRUPTED);
3262     }
3263     return HT_LOADED;
3264 }
3265 
3266 /*
3267  * Setup an FTP connection.
3268  */
setup_connection(const char * name,HTParentAnchor * anchor)3269 static int setup_connection(const char *name,
3270 			    HTParentAnchor *anchor)
3271 {
3272     int retry;			/* How many times tried? */
3273     int status = HT_NO_CONNECTION;
3274 
3275     CTRACE((tfp, "setup_connection(%s)\n", name));
3276 
3277     /* set use_list to NOT since we don't know what kind of server
3278      * this is yet.  And set the type to GENERIC
3279      */
3280     use_list = FALSE;
3281     server_type = GENERIC_SERVER;
3282     Broken_RETR = FALSE;
3283 
3284 #ifdef INET6
3285     Broken_EPSV = FALSE;
3286 #endif
3287 
3288     for (retry = 0; retry < 2; retry++) {	/* For timed out/broken connections */
3289 	status = get_connection(name, anchor);
3290 	if (status < 0) {
3291 	    break;
3292 	}
3293 
3294 	if (!ftp_local_passive) {
3295 	    status = get_listen_socket();
3296 	    if (status < 0) {
3297 		NETCLOSE(control->socket);
3298 		control->socket = -1;
3299 #ifdef INET6
3300 		if (have_socket)
3301 		    (void) close_master_socket();
3302 #else
3303 		close_master_socket();
3304 #endif /* INET6 */
3305 		/* HT_INTERRUPTED would fall through, if we could interrupt
3306 		   somehow in the middle of it, which we currently can't. */
3307 		break;
3308 	    }
3309 #ifdef REPEAT_PORT
3310 	    /*  Inform the server of the port number we will listen on
3311 	     */
3312 	    status = response(port_command);
3313 	    if (status == HT_INTERRUPTED) {
3314 		CTRACE((tfp, "HTFTP: Interrupted in response (port_command)\n"));
3315 		_HTProgress(CONNECTION_INTERRUPTED);
3316 		NETCLOSE(control->socket);
3317 		control->socket = -1;
3318 		close_master_socket();
3319 		status = HT_INTERRUPTED;
3320 		break;
3321 	    }
3322 	    if (status != 2) {	/* Could have timed out */
3323 		if (status < 0)
3324 		    continue;	/* try again - net error */
3325 		status = -status;	/* bad reply */
3326 		break;
3327 	    }
3328 	    CTRACE((tfp, "HTFTP: Port defined.\n"));
3329 #endif /* REPEAT_PORT */
3330 	} else {		/* Tell the server to be passive */
3331 	    char *command = NULL;
3332 	    const char *p = "?";
3333 	    int h0, h1, h2, h3, p0, p1;		/* Parts of reply */
3334 
3335 #ifdef INET6
3336 	    char dst[LINE_LENGTH + 1];
3337 #endif
3338 
3339 	    data_soc = status;
3340 
3341 #ifdef INET6
3342 	    /* see RFC 2428 */
3343 	    if (Broken_EPSV)
3344 		status = 1;
3345 	    else
3346 		status = send_cmd_1(p = "EPSV");
3347 	    if (status < 0)	/* retry or Bad return */
3348 		continue;
3349 	    else if (status != 2) {
3350 		status = send_cmd_1(p = "PASV");
3351 		if (status < 0) {	/* retry or Bad return */
3352 		    continue;
3353 		} else if (status != 2) {
3354 		    status = -status;	/* bad reply */
3355 		    break;
3356 		}
3357 	    }
3358 
3359 	    if (strcmp(p, "PASV") == 0) {
3360 		for (p = response_text; *p && *p != ','; p++) {
3361 		    ;		/* null body */
3362 		}
3363 
3364 		while (--p > response_text && '0' <= *p && *p <= '9') {
3365 		    ;		/* null body */
3366 		}
3367 		status = sscanf(p + 1, "%d,%d,%d,%d,%d,%d",
3368 				&h0, &h1, &h2, &h3, &p0, &p1);
3369 		if (status < 4) {
3370 		    fprintf(tfp, "HTFTP: PASV reply has no inet address!\n");
3371 		    status = HT_NO_CONNECTION;
3372 		    break;
3373 		}
3374 		passive_port = (PortNumber) ((p0 << 8) + p1);
3375 		sprintf(dst, "%d.%d.%d.%d", h0, h1, h2, h3);
3376 	    } else if (strcmp(p, "EPSV") == 0) {
3377 		char c0, c1, c2, c3;
3378 		struct sockaddr_storage ss;
3379 		LY_SOCKLEN sslen;
3380 
3381 		/*
3382 		 * EPSV bla (|||port|)
3383 		 */
3384 		for (p = response_text; *p && !isspace(UCH(*p)); p++) {
3385 		    ;		/* null body */
3386 		}
3387 		for ( /*nothing */ ;
3388 		     *p && *p != '(';
3389 		     p++) {	/*) */
3390 		    ;		/* null body */
3391 		}
3392 		status = sscanf(p, "(%c%c%c%d%c)", &c0, &c1, &c2, &p0, &c3);
3393 		if (status != 5) {
3394 		    fprintf(tfp, "HTFTP: EPSV reply has invalid format!\n");
3395 		    status = HT_NO_CONNECTION;
3396 		    break;
3397 		}
3398 		passive_port = (PortNumber) p0;
3399 
3400 		sslen = (LY_SOCKLEN) sizeof(ss);
3401 		if (getpeername(control->socket, (struct sockaddr *) &ss,
3402 				&sslen) < 0) {
3403 		    fprintf(tfp, "HTFTP: getpeername(control) failed\n");
3404 		    status = HT_NO_CONNECTION;
3405 		    break;
3406 		}
3407 		if (getnameinfo((struct sockaddr *) &ss,
3408 				sslen,
3409 				dst,
3410 				(socklen_t) sizeof(dst),
3411 				NULL, 0, NI_NUMERICHOST)) {
3412 		    fprintf(tfp, "HTFTP: getnameinfo failed\n");
3413 		    status = HT_NO_CONNECTION;
3414 		    break;
3415 		}
3416 	    }
3417 #else
3418 	    status = send_cmd_1("PASV");
3419 	    if (status != 2) {
3420 		if (status < 0)
3421 		    continue;	/* retry or Bad return */
3422 		status = -status;	/* bad reply */
3423 		break;
3424 	    }
3425 	    for (p = response_text; *p && *p != ','; p++) {
3426 		;		/* null body */
3427 	    }
3428 
3429 	    while (--p > response_text && '0' <= *p && *p <= '9') {
3430 		;		/* null body */
3431 	    }
3432 
3433 	    status = sscanf(p + 1, "%d,%d,%d,%d,%d,%d",
3434 			    &h0, &h1, &h2, &h3, &p0, &p1);
3435 	    if (status < 4) {
3436 		fprintf(tfp, "HTFTP: PASV reply has no inet address!\n");
3437 		status = HT_NO_CONNECTION;
3438 		break;
3439 	    }
3440 	    passive_port = (PortNumber) ((p0 << 8) + p1);
3441 #endif /* INET6 */
3442 	    CTRACE((tfp, "HTFTP: Server is listening on port %d\n",
3443 		    passive_port));
3444 
3445 	    /* Open connection for data:  */
3446 
3447 #ifdef INET6
3448 	    HTSprintf0(&command, "%s//%s:%d/", STR_FTP_URL, dst, passive_port);
3449 #else
3450 	    HTSprintf0(&command, "%s//%d.%d.%d.%d:%d/",
3451 		       STR_FTP_URL, h0, h1, h2, h3, passive_port);
3452 #endif
3453 	    status = HTDoConnect(command, "FTP data", passive_port, &data_soc);
3454 	    FREE(command);
3455 
3456 	    if (status < 0) {
3457 		(void) HTInetStatus(gettext("connect for data"));
3458 		NETCLOSE(data_soc);
3459 		break;
3460 	    }
3461 
3462 	    CTRACE((tfp, "FTP data connected, socket %d\n", data_soc));
3463 	}
3464 	status = 0;
3465 	break;			/* No more retries */
3466 
3467     }				/* for retries */
3468     CTRACE((tfp, "setup_connection returns %d\n", status));
3469     return status;
3470 }
3471 
3472 /*	Retrieve File from Server
3473  *	-------------------------
3474  *
3475  * On entry,
3476  *	name		WWW address of a file: document, including hostname
3477  * On exit,
3478  *	returns		Socket number for file if good.
3479  *			<0 if bad.
3480  */
HTFTPLoad(const char * name,HTParentAnchor * anchor,HTFormat format_out,HTStream * sink)3481 int HTFTPLoad(const char *name,
3482 	      HTParentAnchor *anchor,
3483 	      HTFormat format_out,
3484 	      HTStream *sink)
3485 {
3486     BOOL isDirectory = NO;
3487     HTAtom *encoding = NULL;
3488     int status, final_status;
3489     int outstanding = 1;	/* outstanding control connection responses
3490 
3491 				   that we are willing to wait for, if we
3492 				   get to the point of reading data - kw */
3493     HTFormat format;
3494 
3495     CTRACE((tfp, "HTFTPLoad(%s) %s connection\n",
3496 	    name,
3497 	    (ftp_local_passive
3498 	     ? "passive"
3499 	     : "normal")));
3500 
3501     HTReadProgress((off_t) 0, (off_t) 0);
3502 
3503     status = setup_connection(name, anchor);
3504     if (status < 0)
3505 	return status;		/* Failed with this code */
3506 
3507     /*  Ask for the file:
3508      */
3509     {
3510 	char *filename = HTParse(name, "", PARSE_PATH + PARSE_PUNCTUATION);
3511 	char *fname = filename;	/* Save for subsequent free() */
3512 	char *vmsname = NULL;
3513 	BOOL binary;
3514 	const char *type = NULL;
3515 	char *types = NULL;
3516 	char *cp;
3517 
3518 	if (server_type == CMS_SERVER) {
3519 	    /* If the unescaped path has a %2f, reject it as illegal. - FM */
3520 	    if (((cp = strstr(filename, "%2")) != NULL) &&
3521 		TOUPPER(cp[2]) == 'F') {
3522 		FREE(fname);
3523 		init_help_message_cache();	/* to free memory */
3524 		NETCLOSE(control->socket);
3525 		control->socket = -1;
3526 		CTRACE((tfp,
3527 			"HTFTP: Rejecting path due to illegal escaped slash.\n"));
3528 		return -1;
3529 	    }
3530 	}
3531 
3532 	if (!*filename) {
3533 	    StrAllocCopy(filename, "/");
3534 	    type = "D";
3535 	} else if ((type = types = strrchr(filename, ';')) != NULL) {
3536 	    /*
3537 	     * Check and trim the type= parameter.  - FM
3538 	     */
3539 	    if (!strncasecomp((type + 1), "type=", 5)) {
3540 		switch (TOUPPER(*(type + 6))) {
3541 		case 'D':
3542 		    *types = '\0';
3543 		    type = "D";
3544 		    break;
3545 		case 'A':
3546 		    *types = '\0';
3547 		    type = "A";
3548 		    break;
3549 		case 'I':
3550 		    *types = '\0';
3551 		    type = "I";
3552 		    break;
3553 		default:
3554 		    type = "";
3555 		    break;
3556 		}
3557 		if (!*filename) {
3558 		    *filename = '/';
3559 		    *(filename + 1) = '\0';
3560 		}
3561 	    }
3562 	    if (*type != '\0') {
3563 		CTRACE((tfp, "HTFTP: type=%s\n", type));
3564 	    }
3565 	}
3566 	HTUnEscape(filename);
3567 	CTRACE((tfp, "HTFTP: UnEscaped %s\n", filename));
3568 	if (filename[1] == '~') {
3569 	    /*
3570 	     * Check if translation of HOME as tilde is supported,
3571 	     * and adjust filename if so. - FM
3572 	     */
3573 	    char *cp2 = NULL;
3574 	    char *fn = NULL;
3575 
3576 	    if ((cp2 = strchr((filename + 1), '/')) != NULL) {
3577 		*cp2 = '\0';
3578 	    }
3579 	    status = send_cmd_1("PWD");
3580 	    if (status == 2 && response_text[5] == '/') {
3581 		status = send_cwd(filename + 1);
3582 		if (status == 2) {
3583 		    StrAllocCopy(fn, (filename + 1));
3584 		    if (cp2) {
3585 			*cp2 = '/';
3586 			if (fn[strlen(fn) - 1] != '/') {
3587 			    StrAllocCat(fn, cp2);
3588 			} else {
3589 			    StrAllocCat(fn, (cp2 + 1));
3590 			}
3591 			cp2 = NULL;
3592 		    }
3593 		    FREE(fname);
3594 		    fname = filename = fn;
3595 		}
3596 	    }
3597 	    if (cp2) {
3598 		*cp2 = '/';
3599 	    }
3600 	}
3601 	if (strlen(filename) > 3) {
3602 	    char *cp2;
3603 
3604 	    if (((cp2 = strrchr(filename, '.')) != NULL &&
3605 		 0 == strncasecomp(cp2, ".me", 3)) &&
3606 		(cp2[3] == '\0' || cp2[3] == ';')) {
3607 		/*
3608 		 * Don't treat this as application/x-Troff-me if it's a Unix
3609 		 * server but has the string "read.me", or if it's not a Unix
3610 		 * server.  - FM
3611 		 */
3612 		if ((server_type != UNIX_SERVER) ||
3613 		    (cp2 > (filename + 3) &&
3614 		     0 == strncasecomp((cp2 - 4), "read.me", 7))) {
3615 		    *cp2 = '\0';
3616 		    format = HTFileFormat(filename, &encoding, NULL);
3617 		    *cp2 = '.';
3618 		} else {
3619 		    format = HTFileFormat(filename, &encoding, NULL);
3620 		}
3621 	    } else {
3622 		format = HTFileFormat(filename, &encoding, NULL);
3623 	    }
3624 	} else {
3625 	    format = HTFileFormat(filename, &encoding, NULL);
3626 	}
3627 	format = HTCharsetFormat(format, anchor, -1);
3628 	binary = (BOOL) (encoding != HTAtom_for("8bit") &&
3629 			 encoding != HTAtom_for("7bit"));
3630 	if (!binary &&
3631 	/*
3632 	 * Force binary if we're in source, download or dump mode and this is
3633 	 * not a VM/CMS server, so we don't get CRLF instead of LF (or CR) for
3634 	 * newlines in text files.  Can't do this for VM/CMS or we'll get raw
3635 	 * EBCDIC.  - FM
3636 	 */
3637 	    (format_out == WWW_SOURCE ||
3638 	     format_out == HTAtom_for("www/download") ||
3639 	     format_out == HTAtom_for("www/dump")) &&
3640 	    (server_type != CMS_SERVER))
3641 	    binary = TRUE;
3642 	if (!binary && type && *type == 'I') {
3643 	    /*
3644 	     * Force binary if we had ;type=I - FM
3645 	     */
3646 	    binary = TRUE;
3647 	} else if (binary && type && *type == 'A') {
3648 	    /*
3649 	     * Force ASCII if we had ;type=A - FM
3650 	     */
3651 	    binary = FALSE;
3652 	}
3653 	if (binary != control->binary) {
3654 	    /*
3655 	     * Act on our setting if not already set.  - FM
3656 	     */
3657 	    const char *mode = binary ? "I" : "A";
3658 
3659 	    status = send_cmd_2("TYPE", mode);
3660 	    if (status != 2) {
3661 		init_help_message_cache();	/* to free memory */
3662 		return ((status < 0) ? status : -status);
3663 	    }
3664 	    control->binary = binary;
3665 	}
3666 	switch (server_type) {
3667 	    /*
3668 	     * Handle what for Lynx are special case servers, e.g., for which
3669 	     * we respect RFC 1738, or which have known conflicts in suffix
3670 	     * mappings.  - FM
3671 	     */
3672 	case VMS_SERVER:
3673 	    {
3674 		char *cp1, *cp2;
3675 		BOOL included_device = FALSE;
3676 		BOOL found_tilde = FALSE;
3677 
3678 		/* Accept only Unix-style filename */
3679 		if (strchr(filename, ':') != NULL ||
3680 		    strchr(filename, '[') != NULL) {
3681 		    FREE(fname);
3682 		    init_help_message_cache();	/* to free memory */
3683 		    NETCLOSE(control->socket);
3684 		    control->socket = -1;
3685 		    CTRACE((tfp,
3686 			    "HTFTP: Rejecting path due to non-Unix-style syntax.\n"));
3687 		    return -1;
3688 		}
3689 		/* Handle any unescaped "/%2F" path */
3690 		if (!StrNCmp(filename, "//", 2)) {
3691 		    int i;
3692 
3693 		    included_device = TRUE;
3694 		    for (i = 0; filename[(i + 1)]; i++)
3695 			filename[i] = filename[(i + 1)];
3696 		    filename[i] = '\0';
3697 		    CTRACE((tfp, "HTFTP: Trimmed '%s'\n", filename));
3698 		    cp = HTVMS_name("", filename);
3699 		    CTRACE((tfp, "HTFTP: VMSized '%s'\n", cp));
3700 		    if ((cp1 = strrchr(cp, ']')) != NULL) {
3701 			strcpy(filename, ++cp1);
3702 			CTRACE((tfp, "HTFTP: Filename '%s'\n", filename));
3703 			*cp1 = '\0';
3704 			status = send_cwd(cp);
3705 			if (status != 2) {
3706 			    char *dotslash = 0;
3707 
3708 			    if ((cp1 = strchr(cp, '[')) != NULL) {
3709 				*cp1++ = '\0';
3710 				status = send_cwd(cp);
3711 				if (status != 2) {
3712 				    FREE(fname);
3713 				    init_help_message_cache();	/* to free memory */
3714 				    NETCLOSE(control->socket);
3715 				    control->socket = -1;
3716 				    return ((status < 0) ? status : -status);
3717 				}
3718 				HTSprintf0(&dotslash, "[.%s", cp1);
3719 				status = send_cwd(dotslash);
3720 				FREE(dotslash);
3721 				if (status != 2) {
3722 				    FREE(fname);
3723 				    init_help_message_cache();	/* to free memory */
3724 				    NETCLOSE(control->socket);
3725 				    control->socket = -1;
3726 				    return ((status < 0) ? status : -status);
3727 				}
3728 			    } else {
3729 				FREE(fname);
3730 				init_help_message_cache();	/* to free memory */
3731 				NETCLOSE(control->socket);
3732 				control->socket = -1;
3733 				return ((status < 0) ? status : -status);
3734 			    }
3735 			}
3736 		    } else if ((cp1 = strchr(cp, ':')) != NULL &&
3737 			       strchr(cp, '[') == NULL &&
3738 			       strchr(cp, ']') == NULL) {
3739 			cp1++;
3740 			if (*cp1 != '\0') {
3741 			    int cplen = (int) (cp1 - cp);
3742 
3743 			    strcpy(filename, cp1);
3744 			    CTRACE((tfp, "HTFTP: Filename '%s'\n", filename));
3745 			    HTSprintf0(&vmsname, "%.*s[%s]", cplen, cp, filename);
3746 			    status = send_cwd(vmsname);
3747 			    if (status != 2) {
3748 				HTSprintf(&vmsname, "%.*s[000000]", cplen, cp);
3749 				status = send_cwd(vmsname);
3750 				if (status != 2) {
3751 				    HTSprintf(&vmsname, "%.*s", cplen, cp);
3752 				    status = send_cwd(vmsname);
3753 				    if (status != 2) {
3754 					FREE(fname);
3755 					init_help_message_cache();
3756 					NETCLOSE(control->socket);
3757 					control->socket = -1;
3758 					return ((status < 0) ? status : -status);
3759 				    }
3760 				}
3761 			    } else {
3762 				HTSprintf0(&vmsname, "000000");
3763 				filename = vmsname;
3764 			    }
3765 			}
3766 		    } else if (0 == strcmp(cp, (filename + 1))) {
3767 			status = send_cwd(cp);
3768 			if (status != 2) {
3769 			    HTSprintf0(&vmsname, "%s:", cp);
3770 			    status = send_cwd(vmsname);
3771 			    if (status != 2) {
3772 				FREE(fname);
3773 				init_help_message_cache();	/* to free memory */
3774 				NETCLOSE(control->socket);
3775 				control->socket = -1;
3776 				return ((status < 0) ? status : -status);
3777 			    }
3778 			}
3779 			HTSprintf0(&vmsname, "000000");
3780 			filename = vmsname;
3781 		    }
3782 		}
3783 		/* Trim trailing slash if filename is not the top directory */
3784 		if (strlen(filename) > 1 && filename[strlen(filename) - 1] == '/')
3785 		    filename[strlen(filename) - 1] = '\0';
3786 
3787 #ifdef MAINTAIN_CONNECTION	/* Don't need this if always new connection - F.M. */
3788 		if (!included_device) {
3789 		    /* Get the current default VMS device:[directory] */
3790 		    status = send_cmd_1("PWD");
3791 		    if (status != 2) {
3792 			FREE(fname);
3793 			init_help_message_cache();	/* to free memory */
3794 			NETCLOSE(control->socket);
3795 			control->socket = -1;
3796 			return ((status < 0) ? status : -status);
3797 		    }
3798 		    /* Go to the VMS account's top directory */
3799 		    if ((cp = strchr(response_text, '[')) != NULL &&
3800 			(cp1 = strrchr(response_text, ']')) != NULL) {
3801 			char *tmp = 0;
3802 			unsigned len = 4;
3803 
3804 			StrAllocCopy(tmp, cp);
3805 			if ((cp2 = strchr(cp, '.')) != NULL && cp2 < cp1) {
3806 			    len += (cp2 - cp);
3807 			} else {
3808 			    len += (cp1 - cp);
3809 			}
3810 			tmp[len] = 0;
3811 			StrAllocCat(tmp, "]");
3812 
3813 			status = send_cwd(tmp);
3814 			FREE(tmp);
3815 
3816 			if (status != 2) {
3817 			    FREE(fname);
3818 			    init_help_message_cache();	/* to free memory */
3819 			    NETCLOSE(control->socket);
3820 			    control->socket = -1;
3821 			    return ((status < 0) ? status : -status);
3822 			}
3823 		    }
3824 		}
3825 #endif /* MAINTAIN_CONNECTION */
3826 
3827 		/* If we want the VMS account's top directory, list it now */
3828 		if (!(strcmp(filename, "/~")) ||
3829 		    (included_device && 0 == strcmp(filename, "000000")) ||
3830 		    (strlen(filename) == 1 && *filename == '/')) {
3831 		    isDirectory = YES;
3832 		    status = send_cmd_1("LIST");
3833 		    FREE(fname);
3834 		    if (status != 1) {
3835 			/* Action not started */
3836 			init_help_message_cache();	/* to free memory */
3837 			NETCLOSE(control->socket);
3838 			control->socket = -1;
3839 			return ((status < 0) ? status : -status);
3840 		    }
3841 		    /* Big goto! */
3842 		    goto listen;
3843 		}
3844 		/* Otherwise, go to appropriate directory and doctor filename */
3845 		if (!StrNCmp(filename, "/~", 2)) {
3846 		    filename += 2;
3847 		    found_tilde = TRUE;
3848 		}
3849 		CTRACE((tfp, "check '%s' to translate x/y/ to [.x.y]\n", filename));
3850 		if (!included_device &&
3851 		    (cp = strchr(filename, '/')) != NULL &&
3852 		    (cp1 = strrchr(cp, '/')) != NULL &&
3853 		    (cp1 - cp) > 1) {
3854 		    char *tmp = 0;
3855 
3856 		    HTSprintf0(&tmp, "[.%.*s]", (int) (cp1 - cp - 1), cp + 1);
3857 
3858 		    CTRACE((tfp, "change path '%s'\n", tmp));
3859 		    while ((cp2 = strrchr(tmp, '/')) != NULL)
3860 			*cp2 = '.';
3861 		    CTRACE((tfp, "...to  path '%s'\n", tmp));
3862 
3863 		    status = send_cwd(tmp);
3864 		    FREE(tmp);
3865 
3866 		    if (status != 2) {
3867 			FREE(fname);
3868 			init_help_message_cache();	/* to free memory */
3869 			NETCLOSE(control->socket);
3870 			control->socket = -1;
3871 			return ((status < 0) ? status : -status);
3872 		    }
3873 		    filename = cp1 + 1;
3874 		} else {
3875 		    if (!included_device && !found_tilde) {
3876 			filename += 1;
3877 		    }
3878 		}
3879 		break;
3880 	    }
3881 	case CMS_SERVER:
3882 	    {
3883 		/*
3884 		 * If we want the CMS account's top directory, or a base SFS or
3885 		 * anonymous directory path (i.e., without a slash), list it
3886 		 * now.  FM
3887 		 */
3888 		if ((strlen(filename) == 1 && *filename == '/') ||
3889 		    ((0 == strncasecomp((filename + 1), "vmsysu:", 7)) &&
3890 		     (cp = strchr((filename + 1), '.')) != NULL &&
3891 		     strchr(cp, '/') == NULL) ||
3892 		    (0 == strncasecomp(filename + 1, "anonymou.", 9) &&
3893 		     strchr(filename + 1, '/') == NULL)) {
3894 		    if (filename[1] != '\0') {
3895 			status = send_cwd(filename + 1);
3896 			if (status != 2) {
3897 			    /* Action not started */
3898 			    init_help_message_cache();	/* to free memory */
3899 			    NETCLOSE(control->socket);
3900 			    control->socket = -1;
3901 			    return ((status < 0) ? status : -status);
3902 			}
3903 		    }
3904 		    isDirectory = YES;
3905 		    if (use_list)
3906 			status = send_cmd_1("LIST");
3907 		    else
3908 			status = send_cmd_1("NLST");
3909 		    FREE(fname);
3910 		    if (status != 1) {
3911 			/* Action not started */
3912 			init_help_message_cache();	/* to free memory */
3913 			NETCLOSE(control->socket);
3914 			control->socket = -1;
3915 			return ((status < 0) ? status : -status);
3916 		    }
3917 		    /* Big goto! */
3918 		    goto listen;
3919 		}
3920 		filename++;
3921 
3922 		/* Otherwise, go to appropriate directory and adjust filename */
3923 		while ((cp = strchr(filename, '/')) != NULL) {
3924 		    *cp++ = '\0';
3925 		    status = send_cwd(filename);
3926 		    if (status == 2) {
3927 			if (*cp == '\0') {
3928 			    isDirectory = YES;
3929 			    if (use_list)
3930 				status = send_cmd_1("LIST");
3931 			    else
3932 				status = send_cmd_1("NLST");
3933 			    FREE(fname);
3934 			    if (status != 1) {
3935 				/* Action not started */
3936 				init_help_message_cache();	/* to free memory */
3937 				NETCLOSE(control->socket);
3938 				control->socket = -1;
3939 				return ((status < 0) ? status : -status);
3940 			    }
3941 			    /* Clear any messages from the login directory */
3942 			    init_help_message_cache();
3943 			    /* Big goto! */
3944 			    goto listen;
3945 			}
3946 			filename = cp;
3947 		    }
3948 		}
3949 		break;
3950 	    }
3951 	default:
3952 	    /* Shift for any unescaped "/%2F" path */
3953 	    if (!StrNCmp(filename, "//", 2))
3954 		filename++;
3955 	    break;
3956 	}
3957 	/*
3958 	 * Act on a file or listing request, or try to figure out which we're
3959 	 * dealing with if we don't know yet.  - FM
3960 	 */
3961 	if (!(type) || (type && *type != 'D')) {
3962 	    /*
3963 	     * If we are retrieving a file we will (except for CMS) use
3964 	     * binary mode, which lets us use the size command supported by
3965 	     * ftp servers which implement RFC 3659.  Knowing the size lets
3966 	     * us in turn display ETA in the progress message -TD
3967 	     */
3968 	    if (control->binary) {
3969 		int code;
3970 		off_t size;
3971 
3972 		status = send_cmd_2("SIZE", filename);
3973 		if (status == 2 &&
3974 		    sscanf(response_text, "%d %" PRI_off_t, &code, &size) == 2) {
3975 		    anchor->content_length = size;
3976 		}
3977 	    }
3978 	    status = send_cmd_2("RETR", filename);
3979 	    if (status >= 5) {
3980 		int check;
3981 
3982 		if (Broken_RETR) {
3983 		    CTRACE((tfp, "{{reconnecting...\n"));
3984 		    close_connection(control);
3985 		    check = setup_connection(name, anchor);
3986 		    CTRACE((tfp, "...done }}reconnecting\n"));
3987 		    if (check < 0)
3988 			return check;
3989 		}
3990 	    }
3991 	} else {
3992 	    status = 5;		/* Failed status set as flag. - FM */
3993 	}
3994 	if (status != 1) {	/* Failed : try to CWD to it */
3995 	    /* Clear any login messages if this isn't the login directory */
3996 	    if (strcmp(filename, "/"))
3997 		init_help_message_cache();
3998 
3999 	    status = send_cwd(filename);
4000 	    if (status == 2) {	/* Succeeded : let's NAME LIST it */
4001 		isDirectory = YES;
4002 		if (use_list)
4003 		    status = send_cmd_1("LIST");
4004 		else
4005 		    status = send_cmd_1("NLST");
4006 	    }
4007 	}
4008 	FREE(fname);
4009 	FREE(vmsname);
4010 	if (status != 1) {
4011 	    init_help_message_cache();	/* to free memory */
4012 	    NETCLOSE(control->socket);
4013 	    control->socket = -1;
4014 	    if (status < 0)
4015 		return status;
4016 	    else
4017 		return -status;
4018 	}
4019     }
4020 
4021   listen:
4022     if (!ftp_local_passive) {
4023 	/* Wait for the connection */
4024 #ifdef INET6
4025 	struct sockaddr_storage soc_address;
4026 
4027 #else
4028 	struct sockaddr_in soc_address;
4029 #endif /* INET6 */
4030 	LY_SOCKLEN soc_addrlen = (LY_SOCKLEN) sizeof(soc_address);
4031 
4032 #ifdef SOCKS
4033 	if (socks_flag)
4034 	    status = Raccept((int) master_socket,
4035 			     (struct sockaddr *) &soc_address,
4036 			     &soc_addrlen);
4037 	else
4038 #endif /* SOCKS */
4039 	    status = accept((int) master_socket,
4040 			    (struct sockaddr *) &soc_address,
4041 			    &soc_addrlen);
4042 	if (status < 0) {
4043 	    init_help_message_cache();	/* to free memory */
4044 	    return HTInetStatus("accept");
4045 	}
4046 	CTRACE((tfp, "TCP: Accepted new socket %d\n", status));
4047 	data_soc = status;
4048     }
4049 
4050     if (isDirectory) {
4051 	if (server_type == UNIX_SERVER && !unsure_type &&
4052 	    !strcmp(response_text,
4053 		    "150 Opening ASCII mode data connection for /bin/dl.\n")) {
4054 	    CTRACE((tfp, "HTFTP: Treating as \"dls\" server.\n"));
4055 	    server_type = DLS_SERVER;
4056 	}
4057 	final_status = read_directory(anchor, name, format_out, sink);
4058 	if (final_status > 0) {
4059 	    if (server_type != CMS_SERVER)
4060 		if (outstanding-- > 0) {
4061 		    status = response(0);
4062 		    if (status < 0 ||
4063 			(status == 2 && !StrNCmp(response_text, "221", 3)))
4064 			outstanding = 0;
4065 		}
4066 	} else {		/* HT_INTERRUPTED */
4067 	    /* User may have pressed 'z' to give up because no
4068 	       packets got through, so let's not make them wait
4069 	       any longer - kw */
4070 	    outstanding = 0;
4071 	}
4072 
4073 	if (data_soc != -1) {	/* normally done in read_directory */
4074 	    CTRACE((tfp, "HTFTP: Closing data socket %d\n", data_soc));
4075 	    status = NETCLOSE(data_soc);
4076 	    if (status == -1)
4077 		HTInetStatus("close");	/* Comment only */
4078 	}
4079 	status = final_status;
4080     } else {
4081 	int rv;
4082 	char *FileName = HTParse(name, "", PARSE_PATH + PARSE_PUNCTUATION);
4083 
4084 	/* Clear any login messages */
4085 	init_help_message_cache();
4086 
4087 	/* Fake a Content-Encoding for compressed files. - FM */
4088 	HTUnEscape(FileName);
4089 	if (!IsUnityEnc(encoding)) {
4090 	    /*
4091 	     * We already know from the call to HTFileFormat above that this is
4092 	     * a compressed file, no need to look at the filename again.  - kw
4093 	     */
4094 	    StrAllocCopy(anchor->content_type, format->name);
4095 	    StrAllocCopy(anchor->content_encoding, HTAtom_name(encoding));
4096 	    format = HTAtom_for("www/compressed");
4097 
4098 	} else {
4099 	    int rootlen;
4100 	    CompressFileType cft = HTCompressFileType(FileName, "._-", &rootlen);
4101 
4102 	    if (cft != cftNone) {
4103 		FileName[rootlen] = '\0';
4104 		format = HTFileFormat(FileName, &encoding, NULL);
4105 		format = HTCharsetFormat(format, anchor, -1);
4106 		StrAllocCopy(anchor->content_type, format->name);
4107 		format = HTAtom_for("www/compressed");
4108 	    }
4109 
4110 	    switch (cft) {
4111 	    case cftCompress:
4112 		StrAllocCopy(anchor->content_encoding, "x-compress");
4113 		break;
4114 	    case cftGzip:
4115 		StrAllocCopy(anchor->content_encoding, "x-gzip");
4116 		break;
4117 	    case cftDeflate:
4118 		StrAllocCopy(anchor->content_encoding, "x-deflate");
4119 		break;
4120 	    case cftBzip2:
4121 		StrAllocCopy(anchor->content_encoding, "x-bzip2");
4122 		break;
4123 	    case cftNone:
4124 		break;
4125 	    }
4126 	}
4127 	FREE(FileName);
4128 
4129 	_HTProgress(gettext("Receiving FTP file."));
4130 	rv = HTParseSocket(format, format_out, anchor, data_soc, sink);
4131 
4132 	HTInitInput(control->socket);
4133 	/* Reset buffering to control connection DD 921208 */
4134 
4135 	if (rv < 0) {
4136 	    if (rv == -2)	/* weird error, don't expect much response */
4137 		outstanding--;
4138 	    else if (rv == HT_INTERRUPTED || rv == -1)
4139 		/* User may have pressed 'z' to give up because no
4140 		   packets got through, so let's not make them wait
4141 		   longer - kw */
4142 		outstanding = 0;
4143 	    CTRACE((tfp, "HTFTP: Closing data socket %d\n", data_soc));
4144 	    status = NETCLOSE(data_soc);
4145 	} else {
4146 	    status = 2;		/* data_soc already closed in HTCopy - kw */
4147 	}
4148 
4149 	if (status < 0 && rv != HT_INTERRUPTED && rv != -1) {
4150 	    (void) HTInetStatus("close");	/* Comment only */
4151 	} else {
4152 	    if (rv != HT_LOADED && outstanding--) {
4153 		status = response(0);	/* Pick up final reply */
4154 		if (status != 2 && rv != HT_INTERRUPTED && rv != -1) {
4155 		    data_soc = -1;	/* invalidate it */
4156 		    init_help_message_cache();	/* to free memory */
4157 		    return HTLoadError(sink, 500, response_text);
4158 		} else if (status == 2 && !StrNCmp(response_text, "221", 3)) {
4159 		    outstanding = 0;
4160 		}
4161 	    }
4162 	}
4163 	final_status = HT_LOADED;
4164     }
4165     while (outstanding-- > 0 &&
4166 	   (status > 0)) {
4167 	status = response(0);
4168 	if (status == 2 && !StrNCmp(response_text, "221", 3))
4169 	    break;
4170     }
4171     data_soc = -1;		/* invalidate it */
4172     CTRACE((tfp, "HTFTPLoad: normal end; "));
4173     if (control->socket < 0) {
4174 	CTRACE((tfp, "control socket is %d\n", control->socket));
4175     } else {
4176 	CTRACE((tfp, "closing control socket %d\n", control->socket));
4177 	status = NETCLOSE(control->socket);
4178 	if (status == -1)
4179 	    HTInetStatus("control connection close");	/* Comment only */
4180     }
4181     control->socket = -1;
4182     init_help_message_cache();	/* to free memory */
4183     /* returns HT_LOADED (always for file if we get here) or error */
4184     return final_status;
4185 }				/* open_file_read */
4186 
4187 /*
4188  *  This function frees any user entered password, so that
4189  *  it must be entered again for a future request. - FM
4190  */
HTClearFTPPassword(void)4191 void HTClearFTPPassword(void)
4192 {
4193     /*
4194      * Need code to check cached documents from non-anonymous ftp accounts and
4195      * do something to ensure that they no longer can be accessed without a new
4196      * retrieval.  - FM
4197      */
4198 
4199     /*
4200      * Now free the current user entered password, if any.  - FM
4201      */
4202     FREE(user_entered_password);
4203 }
4204 
4205 #endif /* ifndef DISABLE_FTP */
4206