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