1
/* File Transfer Protocol (FTP) Client
2
** for a WorldWideWeb browser
3
** ===================================
5
** A cache of control connections is kept.
7
** Note: Port allocation
9
** It is essential that the port is allocated by the system, rather
10
** than chosen in rotation by us (POLL_PORTS), or the following
13
** It seems that an attempt by the server to connect to a port which has
14
** been used recently by a listen on the same socket, or by another
15
** socket this or another process causes a hangup of (almost exactly)
16
** one minute. Therefore, we have to use a rotating port number.
17
** The problem remains that if the application is run twice in quick
18
** succession, it will hang for what remains of a minute.
21
** TBL Tim Berners-lee <timbl@info.cern.ch>
22
** DD Denis DeLaRoca 310 825-4580 <CSP1DWD@mvs.oac.ucla.edu>
23
** LM Lou Montulli <montulli@ukanaix.cc.ukans.edu>
24
** FM Foteos Macrides <macrides@sci.wfeb.edu>
26
** 2 May 91 Written TBL, as a part of the WorldWideWeb project.
27
** 15 Jan 92 Bug fix: close() was used for NETCLOSE for control soc
28
** 10 Feb 92 Retry if cached connection times out or breaks
29
** 8 Dec 92 Bug fix 921208 TBL after DD
30
** 17 Dec 92 Anon FTP password now just WWWuser@ suggested by DD
31
** fails on princeton.edu!
32
** 27 Dec 93 (FM) Fixed up so FTP now works with VMS hosts. Path
33
** must be Unix-style and cannot include the device
35
** ?? ??? ?? (LM) Added code to prompt and send passwords for non
37
** 25 Mar 94 (LM) Added code to recognize different ftp server types
38
** and code to parse dates and sizes on most hosts.
39
** 27 Mar 93 (FM) Added code for getting dates and sizes on VMS hosts.
42
** Portions Copyright 1994 Trustees of Dartmouth College
43
** Code for recognizing different FTP servers and
44
** parsing "ls -l" output taken from Macintosh Fetch
45
** program with permission from Jim Matthews,
46
** Dartmouth Software Development Team.
50
BUGS: @@@ Limit connection cache size!
51
Error reporting to user.
52
400 & 500 errors are ack'ed by user with windows.
53
Use configuration file for user names
55
** Note for portability this version does not use select() and
56
** so does not watch the control and data channels at the
64
#include <HTFTP.h> /* Implemented here */
69
#define REPEAT_PORT /* Give the port number for each file */
70
#define REPEAT_LISTEN /* Close each listen socket and open a new one */
72
/* define POLL_PORTS If allocation does not work, poll ourselves.*/
73
#define LISTEN_BACKLOG 2 /* Number of pending connect requests (TCP)*/
75
#define FIRST_TCP_PORT 1024 /* Region to try for a listening port */
76
#define LAST_TCP_PORT 5999
78
#define LINE_LENGTH 256
82
#include <HTFile.h> /* For HTFileFormat() */
87
#endif /* !IPORT_FTP */
90
#include <LYGlobalDefs.h>
91
#include <LYStrings.h>
94
typedef struct _connection {
95
struct _connection * next; /* Link on list */
96
unsigned long addr; /* IP address */
97
int socket; /* Socket number for communication */
98
BOOL binary; /* Binary mode? */
101
/* Hypertext object building machinery
105
#define PUTC(c) (*targetClass.put_character) (target, c)
106
#define PUTS(s) (*targetClass.put_string) (target, s)
107
#define START(e) (*targetClass.start_element) (target, e, 0, 0, -1, 0)
108
#define END(e) (*targetClass.end_element) (target, e, 0)
109
#define FREE_TARGET (*targetClass._free) (target)
110
#define ABORT_TARGET (*targetClass._free) (target)
112
struct _HTStructured {
113
CONST HTStructuredClass * isa;
118
** ---------------------
120
PUBLIC int HTfileSortMethod = FILE_BY_NAME;
122
#ifndef DISABLE_FTP /*This disables everything to end-of-file */
123
PRIVATE char ThisYear[8];
124
PRIVATE char LastYear[8];
126
PRIVATE BOOLEAN HaveYears = FALSE;
128
/* Module-Wide Variables
129
** ---------------------
131
PRIVATE connection * connections = NULL;/* Linked list of connections */
132
PRIVATE char response_text[LINE_LENGTH+1];/* Last response from ftp host */
133
PRIVATE connection * control = NULL; /* Current connection */
134
PRIVATE int data_soc = -1; /* Socket for data transfer =invalid */
135
PRIVATE char *user_entered_password = NULL;
136
PRIVATE char *last_username_and_host = NULL;
139
* ProFTPD 1.2.5rc1 is known to have a broken implementation of RETR. If asked
140
* to retrieve a directory, it gets confused and fails subsequent commands such
141
* as CWD and LIST. Since this is an unusual bug, we should remove this ifdef
142
* at some point - TD 2004/1/1.
144
#define BROKEN_PROFTPD 1
145
PRIVATE int ProFTPD_bugs = FALSE;
166
PRIVATE eServerType server_type = GENERIC_SERVER; /* the type of ftp host */
167
PRIVATE int unsure_type = FALSE; /* sure about the type? */
168
PRIVATE BOOLEAN use_list = FALSE; /* use the LIST command? */
170
PRIVATE int interrupted_in_next_data_char = FALSE;
173
PRIVATE PortNumber port_number = FIRST_TCP_PORT;
174
#endif /* POLL_PORTS */
176
PRIVATE int master_socket = -1; /* Listening socket = invalid */
177
PRIVATE char port_command[255]; /* Command for setting the port */
178
PRIVATE fd_set open_sockets; /* Mask of active channels */
179
PRIVATE int num_sockets; /* Number of sockets to scan */
180
PRIVATE PortNumber passive_port; /* Port server specified for data */
183
#define NEXT_CHAR HTGetCharacter() /* Use function in HTFormat.c */
185
#define DATA_BUFFER_SIZE 2048
186
PRIVATE char data_buffer[DATA_BUFFER_SIZE]; /* Input data buffer */
187
PRIVATE char * data_read_pointer;
188
PRIVATE char * data_write_pointer;
189
#define NEXT_DATA_CHAR next_data_char()
190
PRIVATE int close_connection PARAMS((
196
** This function frees module globals. - FM
198
PRIVATE void free_FTPGlobals NOARGS
200
FREE(user_entered_password);
201
FREE(last_username_and_host);
203
if (control->socket != -1)
204
close_connection(control);
208
#endif /* LY_FIND_LEAKS */
210
/* PUBLIC HTVMS_name()
211
** CONVERTS WWW name into a VMS name
213
** nn Node Name (optional)
217
** returns vms file specification
219
** Bug: Returns pointer to static -- non-reentrant
221
PUBLIC char * HTVMS_name ARGS2(
226
/* We try converting the filename into Files-11 syntax. That is, we assume
227
** first that the file is, like us, on a VMS node. We try remote
228
** (or local) DECnet access. Files-11, VMS, VAX and DECnet
229
** are trademarks of Digital Equipment Corporation.
230
** The node is assumed to be local if the hostname WITHOUT DOMAIN
231
** matches the local one. @@@
233
static char *vmsname;
234
char * filename = (char*)malloc(strlen(fn)+1);
235
char * nodename = (char*)malloc(strlen(nn)+2+1); /* Copies to hack */
236
char *second; /* 2nd slash */
237
char *last; /* last slash */
239
CONST char * hostname = HTHostName();
241
if (!filename || !nodename)
242
outofmem(__FILE__, "HTVMSname");
243
strcpy(filename, fn);
244
strcpy(nodename, ""); /* On same node? Yes if node names match */
245
if (strncmp(nn, "localhost", 9)) {
248
for (p = hostname, q = nn;
249
*p && *p != '.' && *q && *q != '.'; p++, q++){
250
if (TOUPPER(*p) != TOUPPER(*q)) {
252
strcpy(nodename, nn);
253
r = strchr(nodename, '.'); /* Mismatch */
255
*r = '\0'; /* Chop domain */
256
strcat(nodename, "::"); /* Try decnet anyway */
262
second = strchr(filename+1, '/'); /* 2nd slash */
263
last = strrchr(filename, '/'); /* last slash */
265
if (!second) { /* Only one slash */
266
HTSprintf0(&vmsname, "%s%s", nodename, filename + 1);
267
} else if (second == last) { /* Exactly two slashes */
268
*second = '\0'; /* Split filename from disk */
269
HTSprintf0(&vmsname, "%s%s:%s", nodename, filename+1, second+1);
270
*second = '/'; /* restore */
271
} else { /* More than two slashes */
273
*second = '\0'; /* Split disk from directories */
274
*last = '\0'; /* Split dir from filename */
275
HTSprintf0(&vmsname, "%s%s:[%s]%s",
276
nodename, filename+1, second+1, last+1);
277
*second = *last = '/'; /* restore filename */
278
for (p = strchr(vmsname, '['); *p!=']'; p++)
280
*p = '.'; /* Convert dir sep. to dots */
287
/* Procedure: Read a character from the data connection
288
** ----------------------------------------------------
290
PRIVATE int next_data_char NOARGS
293
if (data_read_pointer >= data_write_pointer) {
294
status = NETREAD(data_soc, data_buffer, DATA_BUFFER_SIZE);
295
if (status == HT_INTERRUPTED)
296
interrupted_in_next_data_char = 1;
299
data_write_pointer = data_buffer + status;
300
data_read_pointer = data_buffer;
304
char c = *data_read_pointer++;
308
return UCH(*data_read_pointer++);
309
#endif /* NOT_ASCII */
313
/* Close an individual connection
316
PRIVATE int close_connection ARGS1(
321
CTRACE((tfp, "HTFTP: Closing control socket %d\n", con->socket));
322
status = NETCLOSE(con->socket);
323
if (TRACE && status != 0) {
325
CTRACE((tfp, "HTFTP:close_connection: %s", LYStrerror(errno)));
327
if (con->socket != INVSOC)
328
HTInetStatus("HTFTP:close_connection");
332
if (connections == con) {
333
connections = con->next;
336
for (scan = connections; scan; scan = scan->next) {
337
if (scan->next == con) {
338
scan->next = con->next; /* Unlink */
340
control = (connection*)0;
344
return -1; /* very strange -- was not on list. */
347
PRIVATE char *help_message_buffer = NULL; /* global :( */
349
PRIVATE void init_help_message_cache NOARGS
351
FREE(help_message_buffer);
354
PRIVATE void help_message_cache_add ARGS1(
357
if (help_message_buffer)
358
StrAllocCat(help_message_buffer, string);
360
StrAllocCopy(help_message_buffer, string);
362
CTRACE((tfp,"Adding message to help cache: %s\n",string));
365
PRIVATE char *help_message_cache_non_empty NOARGS
367
return(help_message_buffer);
370
PRIVATE char *help_message_cache_contents NOARGS
372
return(help_message_buffer);
378
** This function checks whether we have a control connection, and sends
379
** one command if given.
382
** control points to the connection which is established.
383
** cmd points to a command, or is zero to just get the response.
385
** The command should already be terminated with the CRLF pair.
388
** returns: 1 for success,
389
** or negative for communication failure (in which case
390
** the control connection will be closed).
392
PRIVATE int write_cmd ARGS1(
398
CTRACE((tfp, "HTFTP: No control connection set up!!\n"));
403
CTRACE((tfp, " Tx: %s", cmd));
407
for (p = cmd; *p; p++) {
411
#endif /* NOT_ASCII */
412
status = NETWRITE(control->socket, cmd, (int)strlen(cmd));
414
CTRACE((tfp, "HTFTP: Error %d sending command: closing socket %d\n",
415
status, control->socket));
416
close_connection(control);
423
/* Execute Command and get Response
424
** --------------------------------
426
** See the state machine illustrated in RFC959, p57. This implements
427
** one command/reply sequence. It also interprets lines which are to
428
** be continued, which are marked with a "-" immediately after the
431
** Continuation then goes on until a line with a matching reply code
432
** an a space after it.
435
** control points to the connection which is established.
436
** cmd points to a command, or is zero to just get the response.
438
** The command must already be terminated with the CRLF pair.
441
** returns: The first digit of the reply type,
442
** or negative for communication failure.
444
PRIVATE int response ARGS1(
447
int result; /* Three-digit decimal code */
448
int continuation_response = -1;
451
if ((status = write_cmd(cmd)) < 0)
455
char *p = response_text;
458
if (((*p++ = (char) ich) == LF)
459
|| (p == &response_text[LINE_LENGTH])) {
463
if (interrupted_in_htgetcharacter) {
464
CTRACE((tfp, "HTFTP: Interrupted in HTGetCharacter, apparently.\n"));
465
NETCLOSE (control->socket);
466
control->socket = -1;
467
return HT_INTERRUPTED;
470
*p = '\0'; /* Terminate the string */
471
CTRACE((tfp, " Rx: %s", response_text));
473
/* Check for login or help messages */
474
if (!strncmp(response_text,"230-",4) ||
475
!strncmp(response_text,"250-",4) ||
476
!strncmp(response_text,"220-",4))
477
help_message_cache_add(response_text+4);
479
sscanf(response_text, "%d%c", &result, &continuation);
480
if (continuation_response == -1) {
481
if (continuation == '-') /* start continuation */
482
continuation_response = result;
483
} else { /* continuing */
484
if (continuation_response == result &&
486
continuation_response = -1; /* ended */
488
#ifdef BROKEN_PROFTPD
489
if (result == 220 && LYstrstr(response_text, "ProFTPD 1.2.5")) {
491
CTRACE((tfp, "This server is broken (RETR)\n"));
495
} /* if end of line */
497
if (interrupted_in_htgetcharacter) {
498
CTRACE((tfp, "HTFTP: Interrupted in HTGetCharacter, apparently.\n"));
499
NETCLOSE (control->socket);
500
control->socket = -1;
501
return HT_INTERRUPTED;
505
CTRACE((tfp, "Error on rx: closing socket %d\n",
507
strcpy(response_text, "000 *** TCP read error on response\n");
508
close_connection(control);
509
return -1; /* End of file on response */
511
} /* Loop over characters */
513
} while (continuation_response != -1);
516
CTRACE((tfp, "HTFTP: They close so we close socket %d\n",
518
close_connection(control);
521
if ((result == 255 && server_type == CMS_SERVER) &&
522
(0 == strncasecomp(cmd, "CWD", 3) ||
523
0 == strcasecomp(cmd, "CDUP"))) {
525
** Alas, CMS returns 255 on failure to CWD to parent of root. - PG
532
PRIVATE int send_cmd_1 ARGS1(char *, verb)
536
sprintf(command, "%.*s%c%c", (int) sizeof(command)-4, verb, CR, LF);
537
return response (command);
540
PRIVATE int send_cmd_2 ARGS2(char *, verb, char *, param)
545
HTSprintf0(&command, "%s %s%c%c", verb, param, CR, LF);
546
status = response (command);
552
#define send_cwd(path) send_cmd_2("CWD", path)
555
* This function should try to set the macintosh server into binary mode.
556
* Some servers need an additional letter after the MACB command.
558
PRIVATE int set_mac_binary ARGS1(
559
eServerType, ServerType)
561
/* try to set mac binary mode */
562
if (ServerType == APPLESHARE_SERVER ||
563
ServerType == NETPRESENZ_SERVER) {
565
* Presumably E means "Enable". - KW
567
return(2 == response("MACB E\r\n"));
569
return(2 == response("MACB\r\n"));
573
/* This function gets the current working directory to help
574
* determine what kind of host it is
577
PRIVATE void get_ftp_pwd ARGS2(
578
eServerType *, ServerType,
583
/* get the working directory (to see what it looks like) */
584
int status = response("PWD\r\n");
588
cp = strchr(response_text+5,'"');
591
if (*ServerType == TCPC_SERVER) {
592
*ServerType = ((response_text[5] == '/') ?
593
NCSA_SERVER : TCPC_SERVER);
594
CTRACE((tfp, "HTFTP: Treating as %s server.\n",
595
((*ServerType == NCSA_SERVER) ?
597
} else if (response_text[5] == '/') {
598
/* path names beginning with / imply Unix,
601
if (set_mac_binary(*ServerType)) {
602
*ServerType = NCSA_SERVER;
603
CTRACE((tfp, "HTFTP: Treating as NCSA server.\n"));
605
*ServerType = UNIX_SERVER;
607
CTRACE((tfp, "HTFTP: Treating as Unix server.\n"));
610
} else if (response_text[strlen(response_text)-1] == ']') {
611
/* path names ending with ] imply VMS, right? */
612
*ServerType = VMS_SERVER;
614
CTRACE((tfp, "HTFTP: Treating as VMS server.\n"));
616
*ServerType = GENERIC_SERVER;
617
CTRACE((tfp, "HTFTP: Treating as Generic server.\n"));
620
if ((*ServerType == NCSA_SERVER) ||
621
(*ServerType == TCPC_SERVER) ||
622
(*ServerType == PETER_LEWIS_SERVER) ||
623
(*ServerType == NETPRESENZ_SERVER))
624
set_mac_binary(*ServerType);
628
/* This function turns MSDOS-like directory output off for
629
* Windows NT servers.
632
PRIVATE void set_unix_dirstyle ARGS2(
633
eServerType *, ServerType,
638
/* This is a toggle. It seems we have to toggle in order to see
639
* the current state (after toggling), so we may end up toggling
642
int status = response("SITE DIRSTYLE\r\n");
644
*ServerType = GENERIC_SERVER;
645
CTRACE((tfp, "HTFTP: DIRSTYLE failed, treating as Generic server.\n"));
650
* 200 MSDOS-like directory output is off
651
* 200 MSDOS-like directory output is on
652
* The following code doesn't look for the full exact string -
653
* who knows how the wording may change in some future version.
654
* If the first response isn't recognized, we toggle again
655
* anyway, under the assumption that it's more likely that
656
* the MSDOS setting was "off" originally. - kw
658
cp = strstr(response_text+4, "MSDOS");
659
if (cp && strstr(cp, " off")) {
660
return; /* already off now. */
662
response("SITE DIRSTYLE\r\n");
667
/* Get a valid connection to the host
668
** ----------------------------------
671
** arg points to the name of the host in a hypertext address
673
** returns <0 if error
674
** socket number if success
676
** This routine takes care of managing timed-out connections, and
677
** limiting the number of connections in use at any one time.
679
** It ensures that all connections are logged in if they exist.
680
** It ensures they have the port number transferred.
682
PRIVATE int get_connection ARGS2(
684
HTParentAnchor *, anchor)
689
char * username = NULL;
690
char * password = NULL;
691
static BOOLEAN firstuse = TRUE;
695
** Set up freeing at exit. - FM
698
atexit(free_FTPGlobals);
705
** Reuse this object - KW, DW & FM
707
if (control->socket != -1) {
708
NETCLOSE(control->socket);
715
** Allocate and init control struct.
717
con = typecalloc(connection);
719
outofmem(__FILE__, "get_connection");
723
if (!arg) return -1; /* Bad if no name specified */
724
if (!*arg) return -1; /* Bad if name had zero length */
728
CTRACE((tfp, "get_connection(%s)\n", arg));
730
char *p1 = HTParse(arg, "", PARSE_HOST);
731
char *p2 = strrchr(p1, '@'); /* user? */
736
*p2 = '\0'; /* terminate */
737
p1 = p2+1; /* point to host */
738
pw = strchr(username, ':');
741
password = HTUnEscape(pw);
744
HTUnEscape(username);
747
* If the password doesn't exist then we are going to have
748
* to ask the user for it. The only problem is that we
749
* don't want to ask for it every time, so we will store
750
* away in a primitive fashion.
755
HTSprintf0(&tmp, "%s@%s", username, p1);
757
* If the user@host is not equal to the last time through
758
* or user_entered_password has no data then we need
759
* to ask the user for the password.
761
if (!last_username_and_host ||
762
strcmp(tmp, last_username_and_host) ||
763
!user_entered_password) {
765
StrAllocCopy(last_username_and_host, tmp);
766
HTSprintf0(&tmp, gettext("Enter password for user %s@%s:"),
768
FREE(user_entered_password);
769
user_entered_password = HTPromptPassword(tmp);
771
} /* else we already know the password */
772
password = user_entered_password;
781
status = HTDoConnect (arg, "FTP", IPPORT_FTP, (int *)&con->socket);
784
if (status == HT_INTERRUPTED) {
785
CTRACE((tfp, "HTFTP: Interrupted on connect\n"));
787
CTRACE((tfp, "HTFTP: Unable to connect to remote host for `%s'.\n",
790
if (status == HT_INTERRUPTED) {
791
_HTProgress (CONNECTION_INTERRUPTED);
792
status = HT_NOT_LOADED;
794
HTAlert(gettext("Unable to connect to FTP host."));
796
if (con->socket != -1)
798
NETCLOSE(con->socket);
805
return status; /* Bad return */
808
CTRACE((tfp, "FTP connected, socket %d control %p\n",
810
control = con; /* Current control connection */
812
/* Initialise buffering for control connection */
813
HTInitInput(control->socket);
814
init_help_message_cache(); /* Clear the login message buffer. */
817
/* Now we log in Look up username, prompt for pw.
819
status = response((char *)0); /* Get greeting */
821
if (status == HT_INTERRUPTED) {
822
CTRACE((tfp, "HTFTP: Interrupted at beginning of login.\n"));
823
_HTProgress (CONNECTION_INTERRUPTED);
824
NETCLOSE(control->socket);
825
control->socket = -1;
826
return HT_INTERRUPTED;
828
server_type = GENERIC_SERVER; /* reset */
829
if (status == 2) { /* Send username */
830
char *cp; /* look at greeting text */
832
/* don't gettext() this -- incoming text: */
833
if (strlen(response_text) > 4) {
834
if ((cp = strstr(response_text, " awaits your command")) ||
835
(cp = strstr(response_text, " ready."))) {
838
cp = response_text + 4;
839
if (!strncasecomp(cp, "NetPresenz", 10))
840
server_type = NETPRESENZ_SERVER;
844
StrAllocCopy(anchor->server, cp);
846
status = send_cmd_2("USER", (username && *username)
850
if (status == HT_INTERRUPTED) {
851
CTRACE((tfp, "HTFTP: Interrupted while sending username.\n"));
852
_HTProgress (CONNECTION_INTERRUPTED);
853
NETCLOSE(control->socket);
854
control->socket = -1;
855
return HT_INTERRUPTED;
858
if (status == 3) { /* Send password */
861
* We have non-zero length password, so send it. - FM
863
HTSprintf0(&command, "PASS %s%c%c", password, CR, LF);
866
* Create and send a mail address as the password. - FM
869
CONST char *host = NULL;
872
if (personal_mail_address && *personal_mail_address) {
874
* We have a non-zero length personal
875
* mail address, so use that. - FM
877
StrAllocCopy(user, personal_mail_address);
878
if ((cp=strchr(user, '@')) != NULL) {
886
* Use an environment variable and the host global. - FM
888
if ((cp=LYGetEnv("USER")) != NULL)
889
StrAllocCopy(user, cp);
891
StrAllocCopy(user, "WWWuser");
896
* If host is not fully qualified, suppress it
897
* as ftp.uu.net prefers a blank to a bad name
899
if (!(host) || strchr(host, '.') == NULL)
902
HTSprintf0(&command, "PASS %s@%s%c%c", user, host, CR, LF);
905
status = response(command);
907
if (status == HT_INTERRUPTED) {
908
CTRACE((tfp, "HTFTP: Interrupted while sending password.\n"));
909
_HTProgress (CONNECTION_INTERRUPTED);
910
NETCLOSE(control->socket);
911
control->socket = -1;
912
return HT_INTERRUPTED;
918
status = send_cmd_1("ACCT noaccount");
919
if (status == HT_INTERRUPTED) {
920
CTRACE((tfp, "HTFTP: Interrupted while sending password.\n"));
921
_HTProgress (CONNECTION_INTERRUPTED);
922
NETCLOSE(control->socket);
923
control->socket = -1;
924
return HT_INTERRUPTED;
929
CTRACE((tfp, "HTFTP: Login fail: %s", response_text));
930
/* if (control->socket > 0) close_connection(control->socket); */
931
return -1; /* Bad return */
933
CTRACE((tfp, "HTFTP: Logged in.\n"));
935
/** Check for host type **/
936
if (server_type != NETPRESENZ_SERVER)
937
server_type = GENERIC_SERVER; /* reset */
938
use_list = FALSE; /* reset */
939
if ((status=response("SYST\r\n")) == 2) {
940
/* we got a line -- what kind of server are we talking to? */
941
if (strncmp(response_text+4,
942
"UNIX Type: L8 MAC-OS MachTen", 28) == 0) {
943
server_type = MACHTEN_SERVER;
945
CTRACE((tfp, "HTFTP: Treating as MachTen server.\n"));
947
} else if (strstr(response_text+4, "UNIX") != NULL ||
948
strstr(response_text+4, "Unix") != NULL) {
949
server_type = UNIX_SERVER;
950
unsure_type = FALSE; /* to the best of out knowledge... */
952
CTRACE((tfp, "HTFTP: Treating as Unix server.\n"));
954
} else if (strstr(response_text+4, "MSDOS") != NULL) {
955
server_type = MSDOS_SERVER;
957
CTRACE((tfp, "HTFTP: Treating as MSDOS (Unix emulation) server.\n"));
959
} else if (strncmp(response_text+4, "VMS", 3) == 0) {
960
char *tilde = strstr(arg, "/~");
964
&& strstr(response_text+4, "MadGoat") != 0) {
965
server_type = UNIX_SERVER;
966
CTRACE((tfp, "HTFTP: Treating VMS as UNIX server.\n"));
968
server_type = VMS_SERVER;
969
CTRACE((tfp, "HTFTP: Treating as VMS server.\n"));
972
} else if ((strncmp(response_text+4, "VM/CMS", 6) == 0) ||
973
(strncmp(response_text+4, "VM ", 3) == 0)) {
974
server_type = CMS_SERVER;
976
CTRACE((tfp, "HTFTP: Treating as CMS server.\n"));
978
} else if (strncmp(response_text+4, "DCTS", 4) == 0) {
979
server_type = DCTS_SERVER;
980
CTRACE((tfp, "HTFTP: Treating as DCTS server.\n"));
982
} else if (strstr(response_text+4, "MAC-OS TCP/Connect II") != NULL) {
983
server_type = TCPC_SERVER;
984
CTRACE((tfp, "HTFTP: Looks like a TCPC server.\n"));
985
get_ftp_pwd(&server_type, &use_list);
988
} else if (server_type == NETPRESENZ_SERVER) { /* already set above */
990
set_mac_binary(server_type);
991
CTRACE((tfp, "HTFTP: Treating as NetPresenz (MACOS) server.\n"));
993
} else if (strncmp(response_text+4, "MACOS Peter's Server", 20) == 0) {
994
server_type = PETER_LEWIS_SERVER;
996
set_mac_binary(server_type);
997
CTRACE((tfp, "HTFTP: Treating as Peter Lewis (MACOS) server.\n"));
999
} else if (strncmp(response_text+4, "Windows_NT", 10) == 0) {
1000
server_type = WINDOWS_NT_SERVER;
1001
CTRACE((tfp, "HTFTP: Treating as Window_NT server.\n"));
1002
set_unix_dirstyle(&server_type, &use_list);
1004
} else if (strncmp(response_text+4, "Windows2000", 11) == 0) {
1005
server_type = WINDOWS_2K_SERVER;
1006
CTRACE((tfp, "HTFTP: Treating as Window_2K server.\n"));
1007
set_unix_dirstyle(&server_type, &use_list);
1009
} else if (strncmp(response_text+4, "MS Windows", 10) == 0) {
1010
server_type = MS_WINDOWS_SERVER;
1012
CTRACE((tfp, "HTFTP: Treating as MS Windows server.\n"));
1014
} else if (strncmp(response_text+4,
1015
"MACOS AppleShare IP FTP Server", 30) == 0) {
1016
server_type = APPLESHARE_SERVER;
1018
set_mac_binary(server_type);
1019
CTRACE((tfp, "HTFTP: Treating as AppleShare server.\n"));
1022
server_type = GENERIC_SERVER;
1023
CTRACE((tfp, "HTFTP: Ugh! A Generic server.\n"));
1024
get_ftp_pwd(&server_type, &use_list);
1028
/* SYST fails :( try to get the type from the PWD command */
1029
get_ftp_pwd(&server_type, &use_list);
1032
/* Now we inform the server of the port number we will listen on
1034
#ifdef NOTREPEAT_PORT
1036
int status = response(port_command);
1038
if (control->socket)
1039
close_connection(control->socket);
1040
return -status; /* Bad return */
1042
CTRACE((tfp, "HTFTP: Port defined.\n"));
1044
#endif /* NOTREPEAT_PORT */
1045
return con->socket; /* Good return */
1049
/* Close Master (listening) socket
1050
** -------------------------------
1054
PRIVATE int close_master_socket NOARGS
1058
if (master_socket != -1)
1059
FD_CLR(master_socket, &open_sockets);
1060
status = NETCLOSE(master_socket);
1061
CTRACE((tfp, "HTFTP: Closed master socket %d\n", master_socket));
1064
return HTInetStatus(gettext("close master socket"));
1070
/* Open a master socket for listening on
1071
** -------------------------------------
1073
** When data is transferred, we open a port, and wait for the server to
1074
** connect with the data.
1077
** master_socket Must be negative if not set up already.
1079
** Returns socket number if good
1080
** less than zero if error.
1081
** master_socket is socket number if good, else negative.
1082
** port_number is valid if good.
1084
PRIVATE int get_listen_socket NOARGS
1087
struct sockaddr_storage soc_address; /* Binary network address */
1088
struct sockaddr_in* soc_in = (struct sockaddr_in *)&soc_address;
1092
struct sockaddr_in soc_address; /* Binary network address */
1093
struct sockaddr_in* soc_in = &soc_address;
1095
int new_socket; /* Will be master_socket */
1098
FD_ZERO(&open_sockets); /* Clear our record of open sockets */
1101
#ifndef REPEAT_LISTEN
1102
if (master_socket >= 0)
1103
return master_socket; /* Done already */
1104
#endif /* !REPEAT_LISTEN */
1107
/* query address family of control connection */
1108
slen = sizeof(soc_address);
1109
if (getsockname(control->socket, (struct sockaddr *)&soc_address,
1111
return HTInetStatus("getsockname failed");
1113
af = ((struct sockaddr *)&soc_address)->sa_family;
1114
memset(&soc_address, 0, sizeof(soc_address));
1117
/* Create internet socket
1120
new_socket = socket(af, SOCK_STREAM, IPPROTO_TCP);
1122
new_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
1126
return HTInetStatus(gettext("socket for master socket"));
1128
CTRACE((tfp, "HTFTP: Opened master socket number %d\n", new_socket));
1130
/* Search for a free port.
1133
memset(&soc_address, 0, sizeof(soc_address));
1134
((struct sockaddr *)&soc_address)->sa_family = af;
1138
((struct sockaddr *)&soc_address)->sa_len = sizeof(struct sockaddr_in);
1139
#endif /* SIN6_LEN */
1143
((struct sockaddr *)&soc_address)->sa_len = sizeof(struct sockaddr_in6);
1144
#endif /* SIN6_LEN */
1150
soc_in->sin_family = AF_INET; /* Family = internet, host order */
1151
soc_in->sin_addr.s_addr = INADDR_ANY; /* Any peer address */
1155
PortNumber old_port_number = port_number;
1156
for (port_number = (old_port_number+1); ; port_number++) {
1158
if (port_number > LAST_TCP_PORT)
1159
port_number = FIRST_TCP_PORT;
1160
if (port_number == old_port_number) {
1161
return HTInetStatus("bind");
1164
soc_in->sin_port = htons(port_number);
1166
soc_address.sin_port = htons(port_number);
1170
if ((status=Rbind(new_socket,
1171
(struct sockaddr*)&soc_address,
1172
/* Cast to generic sockaddr */
1173
SOCKADDR_LEN(soc_address)
1174
#ifndef SHORTENED_RBIND
1175
,socks_bind_remoteAddr
1176
#endif /* !SHORTENED_RBIND */
1181
if ((status=bind(new_socket,
1182
(struct sockaddr*)&soc_address,
1183
/* Cast to generic sockaddr */
1184
SOCKADDR_LEN(soc_address)
1188
CTRACE((tfp, "TCP bind attempt to port %d yields %d, errno=%d\n",
1189
port_number, status, SOCKET_ERRNO));
1195
int address_length = sizeof(soc_address);
1198
status = Rgetsockname(control->socket,
1199
(struct sockaddr *)&soc_address,
1200
(void *)&address_length);
1203
status = getsockname(control->socket,
1204
(struct sockaddr *)&soc_address,
1205
(void *)&address_length);
1206
if (status<0) return HTInetStatus("getsockname");
1208
CTRACE((tfp, "HTFTP: This host is %s\n",
1209
HTInetString((SockA *)soc_in)));
1211
soc_in->sin_port = 0; /* Unspecified: please allocate */
1213
CTRACE((tfp, "HTFTP: This host is %s\n",
1214
HTInetString(soc_in)));
1216
soc_address.sin_port = 0; /* Unspecified: please allocate */
1220
status=Rbind(new_socket,
1221
(struct sockaddr*)&soc_address,
1222
/* Cast to generic sockaddr */
1224
#ifndef SHORTENED_RBIND
1226
socks_bind_remoteAddr
1228
,socks_bind_remoteAddr
1230
#endif /* !SHORTENED_RBIND */
1234
status=bind(new_socket,
1235
(struct sockaddr*)&soc_address,
1236
/* Cast to generic sockaddr */
1237
SOCKADDR_LEN(soc_address)
1239
if (status<0) return HTInetStatus("bind");
1241
address_length = sizeof(soc_address);
1244
status = Rgetsockname(new_socket,
1245
(struct sockaddr*)&soc_address,
1246
(void *)&address_length);
1249
status = getsockname(new_socket,
1250
(struct sockaddr*)&soc_address,
1251
(void *)&address_length);
1252
if (status<0) return HTInetStatus("getsockname");
1254
#endif /* POLL_PORTS */
1257
CTRACE((tfp, "HTFTP: bound to port %d on %s\n",
1258
(int)ntohs(soc_in->sin_port),
1259
HTInetString((SockA *)soc_in)));
1261
CTRACE((tfp, "HTFTP: bound to port %d on %s\n",
1262
(int)ntohs(soc_in->sin_port),
1263
HTInetString(soc_in)));
1266
#ifdef REPEAT_LISTEN
1267
if (master_socket >= 0)
1268
(void) close_master_socket();
1269
#endif /* REPEAT_LISTEN */
1271
master_socket = new_socket;
1273
/* Now we must find out who we are to tell the other guy
1275
(void)HTHostName(); /* Make address valid - doesn't work*/
1277
switch (((struct sockaddr *)&soc_address)->sa_family) {
1280
sprintf(port_command, "PORT %d,%d,%d,%d,%d,%d%c%c",
1281
(int)*((unsigned char *)(&soc_in->sin_addr)+0),
1282
(int)*((unsigned char *)(&soc_in->sin_addr)+1),
1283
(int)*((unsigned char *)(&soc_in->sin_addr)+2),
1284
(int)*((unsigned char *)(&soc_in->sin_addr)+3),
1285
(int)*((unsigned char *)(&soc_in->sin_port)+0),
1286
(int)*((unsigned char *)(&soc_in->sin_port)+1),
1294
char hostbuf[MAXHOSTNAMELEN];
1295
char portbuf[MAXHOSTNAMELEN];
1296
getnameinfo((struct sockaddr *)&soc_address,
1297
SOCKADDR_LEN(soc_address),
1298
hostbuf, sizeof(hostbuf), portbuf, sizeof(portbuf),
1299
NI_NUMERICHOST | NI_NUMERICSERV);
1300
sprintf(port_command, "EPRT |%d|%s|%s|%c%c", 2, hostbuf, portbuf,
1305
sprintf(port_command, "JUNK%c%c", CR, LF);
1310
/* Inform TCP that we will accept connections
1316
status = Rlisten(master_socket, 1);
1319
status = listen(master_socket, 1);
1322
return HTInetStatus("listen");
1325
CTRACE((tfp, "TCP: Master socket(), bind() and listen() all OK\n"));
1326
FD_SET(master_socket, &open_sockets);
1327
if ((master_socket+1) > num_sockets)
1328
num_sockets = master_socket+1;
1330
return master_socket; /* Good */
1332
} /* get_listen_socket */
1334
PRIVATE char * months[12] = {
1335
"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"
1338
/* Procedure: Set the current and last year strings and date integer
1339
** -----------------------------------------------------------------
1342
** This code is for sorting listings by date, if that option
1343
** is selected in Lynx, and doesn't take into account time
1344
** zones or ensure resetting at midnight, so the sort may not
1345
** be perfect, but the actual date isn't changed in the display,
1346
** i.e., the date is still correct. - FM
1348
PRIVATE void set_years_and_date NOARGS
1350
char day[8], month[8], date[12];
1354
NowTime = time(NULL);
1355
strncpy(day, (char *)ctime(&NowTime)+8, 2);
1357
if (day[0] == ' ') {
1360
strncpy(month, (char *)ctime(&NowTime)+4, 3);
1362
for (i = 0; i < 12; i++) {
1363
if (!strcasecomp(month, months[i])) {
1368
sprintf(date, "9999%02d%.2s", i, day);
1369
TheDate = atoi(date);
1370
strcpy(ThisYear, (char *)ctime(&NowTime)+20);
1372
sprintf(LastYear, "%d", (atoi(ThisYear) - 1));
1376
typedef struct _EntryInfo {
1381
BOOLEAN display; /* show this entry? */
1384
PRIVATE void free_entryinfo_struct_contents ARGS1(
1385
EntryInfo *, entry_info)
1388
FREE(entry_info->filename);
1389
FREE(entry_info->type);
1390
FREE(entry_info->date);
1392
/* dont free the struct */
1397
* Return TRUE if s points to a string of the form:
1399
* "Sep 11 11:59 " or
1401
* "FCv 23 1990 " ...
1403
PRIVATE BOOLEAN is_ls_date ARGS1(
1406
/* must start with three alpha characters */
1407
if (!isalpha(UCH(*s++)) || !isalpha(UCH(*s++)) || !isalpha(UCH(*s++)))
1410
/* space or HT_NON_BREAK_SPACE */
1411
if (!(*s == ' ' || *s == HT_NON_BREAK_SPACE)) {
1417
/* space or digit */
1418
if (!(*s == ' ' || isdigit(UCH(*s)))) {
1425
if (!isdigit(UCH(*s++)))
1432
/* space or digit */
1433
if (!(*s == ' ' || isdigit(UCH(*s)))) {
1440
if (!isdigit(UCH(*s++)))
1443
/* colon or digit */
1444
if (!(*s == ':' || isdigit(UCH(*s)))) {
1451
if (!isdigit(UCH(*s++)))
1454
/* space or digit */
1455
if (!(*s == ' ' || isdigit(UCH(*s)))) {
1466
} /* is_ls_date() */
1469
* parse_eplf_line() --
1470
* Extract the name, size, and date from an EPLF line. - 08-06-96 DJB
1472
PRIVATE void parse_eplf_line ARGS2(
1480
static time_t base; /* time() value on this OS in 1970 */
1481
static int flagbase = 0;
1485
t.tm_year = 70; t.tm_mon = 0; t.tm_mday = 0;
1486
t.tm_hour = 0; t.tm_min = 0; t.tm_sec = 0;
1488
base = mktime(&t); /* could return -1 */
1495
StrAllocCopy(info->filename, cp + 1);
1499
while (*(++cp) && (*cp != ','))
1500
size = (size * 10) + (*cp - '0');
1505
while (*(++cp) && (*cp != ','))
1506
secs = (secs * 10) + (*cp - '0');
1507
secs += base; /* assumes that time_t is #seconds */
1508
strcpy(ct, ctime(&secs));
1510
StrAllocCopy(info->date, ct);
1513
StrAllocCopy(info->type, ENTRY_IS_DIRECTORY);
1523
} /* parse_eplf_line */
1526
* parse_ls_line() --
1527
* Extract the name, size, and date from an ls -l line.
1529
PRIVATE void parse_ls_line ARGS2(
1531
EntryInfo *, entry_info)
1537
for (i = strlen(line) - 1;
1538
(i > 13) && (!isspace(UCH(line[i])) || !is_ls_date(&line[i-12])); i--)
1542
StrAllocCopy(entry_info->date, &line[i-12]);
1543
/* replace the 4th location with nbsp if it is a space or zero */
1544
if (entry_info->date[4] == ' ' || entry_info->date[4] == '0')
1545
entry_info->date[4] = HT_NON_BREAK_SPACE;
1546
/* make sure year or time is flush right */
1547
if (entry_info->date[11] == ' ') {
1548
for (j = 11; j > 6; j--) {
1549
entry_info->date[j] = entry_info->date[j-1];
1554
while (isdigit(UCH(line[j]))) {
1555
size_num += (line[j] - '0') * base;
1559
entry_info->size = size_num;
1560
StrAllocCopy(entry_info->filename, &line[i + 1]);
1561
} /* parse_ls_line() */
1564
* parse_dls_line() --
1565
* Extract the name and size info and whether it refers to a
1566
* directory from a LIST line in "dls" format.
1568
PRIVATE void parse_dls_line ARGS3(
1570
EntryInfo *, entry_info,
1571
char **, pspilledname)
1579
/* README 763 Information about this server\0
1584
pub/ = Public area\0
1586
morgan 14 -> ../real/morgan\0
1587
TIMIT.mostlikely.Z\0
1593
FREE(*pspilledname);
1594
entry_info->display = FALSE;
1597
cps = LYSkipNonBlanks(line);
1598
if (*cps == '\0') { /* only a filename, save it and return. */
1599
StrAllocCopy(*pspilledname, line);
1600
entry_info->display = FALSE;
1603
if (len < 24 || line[23] != ' ' ||
1604
(isspace(UCH(line[0])) && !*pspilledname)) {
1605
/* this isn't the expected "dls" format! */
1606
if (!isspace(UCH(line[0])))
1608
if (*pspilledname && !*line) {
1609
entry_info->filename = *pspilledname;
1610
*pspilledname = NULL;
1611
if (entry_info->filename[strlen(entry_info->filename)-1] == '/')
1612
StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
1614
StrAllocCopy(entry_info->type, "");
1616
StrAllocCopy(entry_info->filename, line);
1617
if (cps && cps != line && *(cps-1) == '/')
1618
StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
1620
StrAllocCopy(entry_info->type, "");
1621
FREE(*pspilledname);
1627
if (line[j] == '=' || line[j] == '-') {
1628
StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
1630
while (isdigit(UCH(line[j]))) {
1631
size_num += (line[j] - '0') * base;
1636
entry_info->size = size_num;
1638
cps = LYSkipBlanks(&line[23]);
1639
if (!strncmp(cps, "-> ", 3) && cps[3] != '\0' && cps[3] != ' ') {
1640
StrAllocCopy(entry_info->type, gettext("Symbolic Link"));
1641
entry_info->size = 0; /* don't display size */
1647
LYTrimTrailing(line);
1650
if (len == 0 && *pspilledname && **pspilledname) {
1651
line = *pspilledname;
1652
len = strlen(*pspilledname);
1654
if (len > 0 && line[len-1] == '/') {
1656
** It's a dir, remove / and mark it as such.
1660
if (!entry_info->type)
1661
StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
1664
StrAllocCopy(entry_info->filename, line);
1665
FREE(*pspilledname);
1666
} /* parse_dls_line() */
1669
* parse_vms_dir_entry()
1670
* Format the name, date, and size from a VMS LIST line
1671
* into the EntryInfo structure - FM
1673
PRIVATE void parse_vms_dir_entry ARGS2(
1675
EntryInfo *, entry_info)
1678
unsigned int ialloc;
1679
char *cp, *cpd, *cps, date[16], *sp = " ";
1681
/** Get rid of blank lines, and information lines. **/
1682
/** Valid lines have the ';' version number token. **/
1683
if (!strlen(line) || (cp = strchr(line, ';')) == NULL) {
1684
entry_info->display = FALSE;
1688
/** Cut out file or directory name at VMS version number. **/
1690
StrAllocCopy(entry_info->filename,line);
1692
/** Cast VMS non-README file and directory names to lowercase. **/
1693
if (strstr(entry_info->filename, "READ") == NULL) {
1694
LYLowerCase(entry_info->filename);
1695
i = strlen(entry_info->filename);
1697
i = ((strstr(entry_info->filename, "READ") - entry_info->filename) + 4);
1698
if (!strncmp(&entry_info->filename[i], "ME", 2)) {
1700
while (entry_info->filename[i] && entry_info->filename[i] != '.') {
1703
} else if (!strncmp(&entry_info->filename[i], ".ME", 3)) {
1704
i = strlen(entry_info->filename);
1708
LYLowerCase(entry_info->filename + i);
1711
/** Uppercase terminal .z's or _z's. **/
1713
entry_info->filename[i] == 'z' &&
1714
(entry_info->filename[i-1] == '.' ||
1715
entry_info->filename[i-1] == '_'))
1716
entry_info->filename[i] = 'Z';
1718
/** Convert any tabs in rest of line to spaces. **/
1720
while ((cps=strchr(cps+1, '\t')) != NULL)
1723
/** Collapse serial spaces. **/
1726
while (cps[j] != '\0') {
1727
if (cps[i] == ' ' && cps[j] == ' ')
1730
cps[++i] = cps[j++];
1734
/* Set the years and date, if we don't have them yet. **/
1736
set_years_and_date();
1739
/** Track down the date. **/
1740
if ((cpd=strchr(cp, '-')) != NULL &&
1741
strlen(cpd) > 9 && isdigit(UCH(*(cpd-1))) &&
1742
isalpha(UCH(*(cpd+1))) && *(cpd+4) == '-') {
1745
*(cpd+2) = (char) TOLOWER(*(cpd+2));
1746
*(cpd+3) = (char) TOLOWER(*(cpd+3));
1747
sprintf(date, "%.3s ", cpd+1);
1750
if (isdigit(UCH(*(cpd-2))))
1751
sprintf(date+4, "%.2s ", cpd-2);
1753
sprintf(date+4, "%c%.1s ", HT_NON_BREAK_SPACE, cpd-1);
1755
/** Time or Year **/
1756
if (!strncmp(ThisYear, cpd+5, 4) &&
1757
strlen(cpd) > 15 && *(cpd+12) == ':') {
1758
sprintf(date+7, "%.5s", cpd+10);
1760
sprintf(date+7, " %.4s", cpd+5);
1763
StrAllocCopy(entry_info->date, date);
1766
/** Track down the size **/
1767
if ((cpd=strchr(cp, '/')) != NULL) {
1768
/* Appears be in used/allocated format */
1770
while (isdigit(UCH(*(cps-1))))
1774
entry_info->size = atoi(cps);
1776
while (isdigit(UCH(*cps)))
1779
ialloc = atoi(cpd+1);
1780
/* Check if used is in blocks or bytes */
1781
if (entry_info->size <= ialloc)
1782
entry_info->size *= 512;
1784
} else if ((cps=strtok(cp, sp)) != NULL) {
1785
/* We just initialized on the version number */
1786
/* Now let's hunt for a lone, size number */
1787
while ((cps=strtok(NULL, sp)) != NULL) {
1789
while (isdigit(UCH(*cpd)))
1792
/* Assume it's blocks */
1793
entry_info->size = atoi(cps) * 512;
1800
CTRACE((tfp, "HTFTP: VMS filename: %s date: %s size: %d\n",
1801
entry_info->filename,
1802
NonNull(entry_info->date),
1805
} /* parse_vms_dir_entry() */
1808
* parse_ms_windows_dir_entry() --
1809
* Format the name, date, and size from an MS_WINDOWS LIST line into
1810
* the EntryInfo structure (assumes Chameleon NEWT format). - FM
1812
PRIVATE void parse_ms_windows_dir_entry ARGS2(
1814
EntryInfo *, entry_info)
1817
char *cps, *cpd, date[16];
1818
char *end = line + strlen(line);
1820
/** Get rid of blank or junk lines. **/
1821
cp = LYSkipBlanks(cp);
1823
entry_info->display = FALSE;
1827
/** Cut out file or directory name. **/
1828
cps = LYSkipNonBlanks(cp);
1831
StrAllocCopy(entry_info->filename, cp);
1833
/** Track down the size **/
1835
cps = LYSkipBlanks(cps);
1836
cpd = LYSkipNonBlanks(cps);
1838
if (isdigit(UCH(*cps))) {
1839
entry_info->size = atoi(cps);
1841
StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
1844
StrAllocCopy(entry_info->type, "");
1847
/* Set the years and date, if we don't have them yet. **/
1849
set_years_and_date();
1852
/** Track down the date. **/
1854
cpd = LYSkipBlanks(cpd);
1855
if (strlen(cpd) > 17) {
1856
*(cpd+6) = '\0'; /* Month and Day */
1857
*(cpd+11) = '\0'; /* Year */
1858
*(cpd+17) = '\0'; /* Time */
1859
if (strcmp(ThisYear, cpd+7))
1860
/* Not this year, so show the year */
1861
sprintf(date, "%.6s %.4s", cpd, (cpd+7));
1863
/* Is this year, so show the time */
1864
sprintf(date, "%.6s %.5s", cpd, (cpd+12));
1865
StrAllocCopy(entry_info->date, date);
1866
if (entry_info->date[4] == ' '|| entry_info->date[4] == '0') {
1867
entry_info->date[4] = HT_NON_BREAK_SPACE;
1873
CTRACE((tfp, "HTFTP: MS Windows filename: %s date: %s size: %d\n",
1874
entry_info->filename,
1875
NonNull(entry_info->date),
1878
} /* parse_ms_windows_dir_entry */
1881
* parse_windows_nt_dir_entry() --
1882
* Format the name, date, and size from a WINDOWS_NT LIST line into
1883
* the EntryInfo structure (assumes Chameleon NEWT format). - FM
1886
PRIVATE void parse_windows_nt_dir_entry ARGS2(
1888
EntryInfo *, entry_info)
1891
char *cps, *cpd, date[16];
1892
char *end = line + strlen(line);
1895
/** Get rid of blank or junk lines. **/
1896
cp = LYSkipBlanks(cp);
1898
entry_info->display = FALSE;
1902
/** Cut out file or directory name. **/
1904
cps = LYSkipNonBlanks(end-1);
1906
if (!strcmp(cp, ".") || !strcmp(cp, "..")) {
1907
entry_info->display = FALSE;
1910
StrAllocCopy(entry_info->filename, cp);
1916
/* Set the years and date, if we don't have them yet. **/
1918
set_years_and_date();
1921
/** Cut out the date. **/
1923
cps = LYSkipNonBlanks(cps);
1926
entry_info->display = FALSE;
1929
cps = LYSkipBlanks(cps);
1930
cpd = LYSkipNonBlanks(cps);
1932
if (cps > end || cpd == cps || strlen(cpd) < 7) {
1933
entry_info->display = FALSE;
1936
if (strlen(cp) == 8 &&
1937
isdigit(*cp) && isdigit(*(cp+1)) && *(cp+2) == '-' &&
1938
isdigit(*(cp+3)) && isdigit(*(cp+4)) && *(cp+5) == '-') {
1939
*(cp+2) = '\0'; /* Month */
1941
*(cp+5) = '\0'; /* Day */
1942
sprintf(date, "%.3s %.2s", months[i], (cp+3));
1946
if (strcmp((ThisYear+2), cp)) {
1947
/* Not this year, so show the year */
1948
if (atoi(cp) < 70) {
1949
sprintf(&date[6], " 20%.2s", cp);
1951
sprintf(&date[6], " 19%.2s", cp);
1954
/* Is this year, so show the time */
1955
*(cpd+2) = '\0'; /* Hour */
1957
if (*(cpd+5) == 'P' || *(cpd+5) == 'p')
1959
sprintf(&date[6], " %02d:%.2s", i, (cpd+3));
1961
StrAllocCopy(entry_info->date, date);
1962
if (entry_info->date[4] == ' '|| entry_info->date[4] == '0') {
1963
entry_info->date[4] = HT_NON_BREAK_SPACE;
1967
/** Track down the size **/
1969
cps = LYSkipBlanks(cps);
1970
cpd = LYSkipNonBlanks(cps);
1972
if (isdigit(*cps)) {
1973
entry_info->size = atoi(cps);
1975
StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
1978
StrAllocCopy(entry_info->type, "");
1982
CTRACE((tfp, "HTFTP: Windows NT filename: %s date: %s size: %d\n",
1983
entry_info->filename,
1984
NonNull(entry_info->date),
1987
} /* parse_windows_nt_dir_entry */
1988
#endif /* NOTDEFINED */
1991
* parse_cms_dir_entry() --
1992
* Format the name, date, and size from a VM/CMS line into
1993
* the EntryInfo structure. - FM
1995
PRIVATE void parse_cms_dir_entry ARGS2(
1997
EntryInfo *, entry_info)
2000
char *cps, *cpd, date[16];
2001
char *end = line + strlen(line);
2002
int RecordLength = 0;
2006
/** Get rid of blank or junk lines. **/
2007
cp = LYSkipBlanks(cp);
2009
entry_info->display = FALSE;
2013
/** Cut out file or directory name. **/
2014
cps = LYSkipNonBlanks(cp);
2016
StrAllocCopy(entry_info->filename, cp);
2017
if (strchr(entry_info->filename, '.') != NULL)
2018
/** If we already have a dot, we did an NLST. **/
2020
cp = LYSkipBlanks(cps);
2022
/** If we don't have more, we've misparsed. **/
2023
FREE(entry_info->filename);
2024
FREE(entry_info->type);
2025
entry_info->display = FALSE;
2028
cps = LYSkipNonBlanks(cp);
2030
if ((0 == strcasecomp(cp, "DIR")) && (cp - line) > 17) {
2031
/** It's an SFS directory. **/
2032
StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
2033
entry_info->size = 0;
2035
/** It's a file. **/
2038
StrAllocCat(entry_info->filename, cp);
2040
/** Track down the VM/CMS RECFM or type. **/
2043
cp = LYSkipBlanks(cp);
2044
cps = LYSkipNonBlanks(cp);
2046
/** Check cp here, if it's relevant someday. **/
2050
/** Track down the record length or dash. **/
2053
cp = LYSkipBlanks(cp);
2054
cps = LYSkipNonBlanks(cp);
2056
if (isdigit(UCH(*cp))) {
2057
RecordLength = atoi(cp);
2061
/** Track down the number of records or the dash. **/
2064
cp = LYSkipBlanks(cp);
2065
cps = LYSkipNonBlanks(cp);
2067
if (isdigit(UCH(*cp))) {
2070
if (Records > 0 && RecordLength > 0) {
2071
/** Compute an approximate size. **/
2072
entry_info->size = (Records * RecordLength);
2076
/** Set the years and date, if we don't have them yet. **/
2078
set_years_and_date();
2081
/** Track down the date. **/
2084
(cps = strchr(cpd, ':')) != NULL) &&
2086
isdigit(UCH(*(cps+1))) && isdigit(UCH(*(cps+2))) && *(cps+3) == ':')) {
2089
if ((cps - cpd) >= 14) {
2091
*(cpd+2) = '\0'; /* Month */
2092
*(cpd+5) = '\0'; /* Day */
2093
*(cpd+8) = '\0'; /* Year */
2094
cps -= 5; /* Time */
2098
sprintf(date, "%.3s %.2s", months[i], (cpd+3));
2101
cpd += 6; /* Year */
2102
if (strcmp((ThisYear+2), cpd)) {
2103
/* Not this year, so show the year. */
2104
if (atoi(cpd) < 70) {
2105
sprintf(&date[6], " 20%.2s", cpd);
2107
sprintf(&date[6], " 19%.2s", cpd);
2110
/* Is this year, so show the time. */
2111
*(cps+2) = '\0'; /* Hour */
2113
sprintf(&date[6], " %02d:%.2s", i, (cps+3));
2115
StrAllocCopy(entry_info->date, date);
2116
if (entry_info->date[4] == ' '|| entry_info->date[4] == '0') {
2117
entry_info->date[4] = HT_NON_BREAK_SPACE;
2123
CTRACE((tfp, "HTFTP: VM/CMS filename: %s date: %s size: %d\n",
2124
entry_info->filename,
2125
NonNull(entry_info->date),
2128
} /* parse_cms_dir_entry */
2132
* Given a line of LIST/NLST output in entry, return results
2133
* and a file/dir name in entry_info struct
2135
* If first is true, this is the first name in a directory.
2138
PRIVATE EntryInfo * parse_dir_entry ARGS3(
2141
char **, pspilledname)
2143
EntryInfo *entry_info;
2146
BOOLEAN remove_size=FALSE;
2149
entry_info = (EntryInfo *)malloc(sizeof(EntryInfo));
2150
if (entry_info == NULL)
2151
outofmem(__FILE__, "parse_dir_entry");
2152
entry_info->filename = NULL;
2153
entry_info->type = NULL;
2154
entry_info->date = NULL;
2155
entry_info->size = 0;
2156
entry_info->display = TRUE;
2158
switch (server_type) {
2162
** Interpret and edit LIST output from a Unix server
2164
** This one must have claimed to be Unix in order to
2165
** get here; if the first line looks fishy, we revert
2166
** to Unix and hope that fits better (this recovery is
2171
len = strlen(entry);
2172
if (!len || entry[0] == ' ' ||
2173
(len >= 24 && entry[23] != ' ') ||
2174
(len < 24 && strchr(entry, ' '))) {
2175
server_type = UNIX_SERVER;
2177
"HTFTP: Falling back to treating as Unix server.\n"));
2183
if (server_type == DLS_SERVER) {
2184
/* if still unchanged... */
2185
parse_dls_line(entry, entry_info, pspilledname);
2187
if (!entry_info->filename || *entry_info->filename == '\0') {
2188
entry_info->display = FALSE;
2191
if (!strcmp(entry_info->filename,"..") ||
2192
!strcmp(entry_info->filename,"."))
2193
entry_info->display = FALSE;
2194
if (entry_info->type && *entry_info->type == '\0') {
2195
FREE(entry_info->type);
2199
** Goto the bottom and get real type.
2202
} /* fall through if server_type changed for *first == TRUE ! */
2205
case PETER_LEWIS_SERVER:
2206
case MACHTEN_SERVER:
2208
case WINDOWS_NT_SERVER:
2209
case WINDOWS_2K_SERVER:
2210
case APPLESHARE_SERVER:
2211
case NETPRESENZ_SERVER:
2213
** Check for EPLF output (local times).
2215
if (*entry == '+') {
2216
parse_eplf_line(entry, entry_info);
2221
** Interpret and edit LIST output from Unix server.
2223
len = strlen(entry);
2225
/* don't gettext() this -- incoming text: */
2226
if (!strcmp(entry, "can not access directory .")) {
2228
* Don't reset *first, nothing real will follow. - KW
2230
entry_info->display = FALSE;
2234
if (!strncmp(entry, "total ", 6) ||
2235
strstr(entry, "not available") != NULL) {
2236
entry_info->display = FALSE;
2238
} else if (unsure_type) {
2239
/* this isn't really a unix server! */
2240
server_type = GENERIC_SERVER;
2241
entry_info->display = FALSE;
2247
** Check first character of ls -l output.
2249
if (TOUPPER(entry[0]) == 'D') {
2251
** It's a directory.
2253
StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
2254
remove_size=TRUE; /* size is not useful */
2255
} else if (entry[0] == 'l') {
2257
** It's a symbolic link, does the user care about
2258
** knowing if it is symbolic? I think so since
2259
** it might be a directory.
2261
StrAllocCopy(entry_info->type, gettext("Symbolic Link"));
2262
remove_size=TRUE; /* size is not useful */
2265
** Strip off " -> pathname".
2267
for (i = len - 1; (i > 3) &&
2268
(!isspace(UCH(entry[i])) ||
2269
(entry[i-1] != '>') ||
2270
(entry[i-2] != '-') ||
2271
(entry[i-3] != ' ')); i--)
2279
parse_ls_line(entry, entry_info);
2281
if (!strcmp(entry_info->filename,"..") ||
2282
!strcmp(entry_info->filename,"."))
2283
entry_info->display = FALSE;
2285
** Goto the bottom and get real type.
2291
** Interpret and edit LIST output from VMS server
2292
** and convert information lines to zero length.
2294
parse_vms_dir_entry(entry, entry_info);
2297
** Get rid of any junk lines.
2299
if (!entry_info->display)
2303
** Trim off VMS directory extensions.
2305
len = strlen(entry_info->filename);
2306
if ((len > 4) && !strcmp(&entry_info->filename[len-4], ".dir")) {
2307
entry_info->filename[len-4] = '\0';
2308
StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
2309
remove_size=TRUE; /* size is not useful */
2312
** Goto the bottom and get real type.
2316
case MS_WINDOWS_SERVER:
2318
** Interpret and edit LIST output from MS_WINDOWS server
2319
** and convert information lines to zero length.
2321
parse_ms_windows_dir_entry(entry, entry_info);
2324
** Get rid of any junk lines.
2326
if (!entry_info->display)
2328
if (entry_info->type && *entry_info->type == '\0') {
2329
FREE(entry_info->type);
2333
** Goto the bottom and get real type.
2338
case WINDOWS_NT_SERVER:
2340
** Interpret and edit LIST output from MS_WINDOWS server
2341
** and convert information lines to zero length.
2343
parse_windows_nt_dir_entry(entry, entry_info);
2346
** Get rid of any junk lines.
2348
if (!entry_info->display)
2350
if (entry_info->type && *entry_info->type == '\0') {
2351
FREE(entry_info->type);
2355
** Goto the bottom and get real type.
2358
#endif /* NOTDEFINED */
2363
** Interpret and edit LIST output from VM/CMS server
2364
** and convert any information lines to zero length.
2366
parse_cms_dir_entry(entry, entry_info);
2369
** Get rid of any junk lines.
2371
if (!entry_info->display)
2373
if (entry_info->type && *entry_info->type == '\0') {
2374
FREE(entry_info->type);
2378
** Goto the bottom and get real type.
2386
** Directories identified by trailing "/" characters.
2388
StrAllocCopy(entry_info->filename, entry);
2389
len = strlen(entry);
2390
if (entry[len-1] == '/') {
2392
** It's a dir, remove / and mark it as such.
2394
entry[len-1] = '\0';
2395
StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
2396
remove_size=TRUE; /* size is not useful */
2399
** Goto the bottom and get real type.
2405
** We can't tell if it is a directory since we only
2406
** did an NLST :( List bad file types anyways? NOT!
2408
StrAllocCopy(entry_info->filename, entry);
2409
return(entry_info); /* mostly empty info */
2411
} /* switch (server_type) */
2413
if (remove_size && entry_info->size) {
2414
entry_info->size = 0;
2417
if (entry_info->filename && strlen(entry_info->filename) > 3) {
2418
if (((cp=strrchr(entry_info->filename, '.')) != NULL &&
2419
0 == strncasecomp(cp, ".me", 3)) &&
2420
(cp[3] == '\0' || cp[3] == ';')) {
2422
** Don't treat this as application/x-Troff-me
2423
** if it's a Unix server but has the string
2424
** "read.me", or if it's not a Unix server. - FM
2426
if ((server_type != UNIX_SERVER) ||
2427
(cp > (entry_info->filename + 3) &&
2428
0 == strncasecomp((cp - 4), "read.me", 7))) {
2429
StrAllocCopy(entry_info->type, "text/plain");
2435
** Get real types eventually.
2437
if (!entry_info->type) {
2440
HTAtom * encoding; /* @@ not used at all */
2441
format = HTFileFormat(entry_info->filename, &encoding, &cp2);
2444
if (!strncmp(HTAtom_name(format), "application",11)) {
2445
cp2 = HTAtom_name(format) + 12;
2446
if (!strncmp(cp2,"x-",2))
2449
cp2 = HTAtom_name(format);
2453
StrAllocCopy(entry_info->type, cp2);
2457
} /* parse_dir_entry */
2459
PRIVATE int compare_EntryInfo_structs ARGS2(
2460
EntryInfo *, entry1,
2461
EntryInfo *, entry2)
2464
char date1[16], date2[16], time1[8], time2[8], month[4];
2466
switch(HTfileSortMethod) {
2468
/* both equal or both 0 */
2469
if (entry1->size == entry2->size)
2470
return(strcmp(entry1->filename, entry2->filename));
2472
if (entry1->size > entry2->size)
2478
if (entry1->type && entry2->type) {
2479
status = strcasecomp(entry1->type, entry2->type);
2482
/* else fall to filename comparison */
2484
return (strcmp(entry1->filename, entry2->filename));
2487
if (entry1->date && entry2->date) {
2489
** Make sure we have the correct length. - FM
2491
if (strlen(entry1->date) != 12 || strlen(entry2->date) != 12) {
2492
return(strcmp(entry1->filename, entry2->filename));
2495
** Set the years and date,
2496
** if we don't have them yet.
2499
set_years_and_date();
2502
** Set up for sorting in reverse
2503
** chronological order. - FM
2505
if (entry1->date[9] == ':') {
2506
strcpy(date1, "9999");
2507
strcpy(time1, &entry1->date[7]);
2508
if (time1[0] == ' ') {
2512
strcpy(date1, &entry1->date[8]);
2513
strcpy(time1, "00:00");
2515
strncpy(month, entry1->date, 3);
2517
for (i = 0; i < 12; i++) {
2518
if (!strcasecomp(month, months[i])) {
2523
sprintf(month, "%02d", i);
2524
strcat(date1, month);
2525
strncat(date1, &entry1->date[4], 2);
2527
if (date1[6] == ' ' || date1[6] == HT_NON_BREAK_SPACE) {
2530
/* If no year given, assume last year if it would otherwise
2531
* be in the future by more than one day. The one day
2532
* tolerance is to account for a possible timezone
2533
* difference. - kw */
2534
if (date1[0] == '9' && atoi(date1) > TheDate + 1) {
2535
for (i = 0; i < 4; i++) {
2536
date1[i] = LastYear[i];
2539
strcat(date1, time1);
2540
if (entry2->date[9] == ':') {
2541
strcpy(date2, "9999");
2542
strcpy(time2, &entry2->date[7]);
2543
if (time2[0] == ' ') {
2547
strcpy(date2, &entry2->date[8]);
2548
strcpy(time2, "00:00");
2550
strncpy(month, entry2->date, 3);
2552
for (i = 0; i < 12; i++) {
2553
if (!strcasecomp(month, months[i])) {
2558
sprintf(month, "%02d", i);
2559
strcat(date2, month);
2560
strncat(date2, &entry2->date[4], 2);
2562
if (date2[6] == ' ' || date2[6] == HT_NON_BREAK_SPACE) {
2565
/* If no year given, assume last year if it would otherwise
2566
* be in the future by more than one day. The one day
2567
* tolerance is to account for a possible timezone
2568
* difference. - kw */
2569
if (date2[0] == '9' && atoi(date2) > TheDate + 1) {
2570
for (i = 0; i < 4; i++) {
2571
date2[i] = LastYear[i];
2574
strcat(date2, time2);
2576
** Do the comparison. - FM
2578
status = strcasecomp(date2, date1);
2581
/* else fall to filename comparison */
2583
return (strcmp(entry1->filename, entry2->filename));
2587
return (strcmp(entry1->filename, entry2->filename));
2592
/* Read a directory into an hypertext object from the data socket
2593
** --------------------------------------------------------------
2596
** anchor Parent anchor to link the this node to
2597
** address Address of the directory
2599
** returns HT_LOADED if OK
2602
PRIVATE int read_directory ARGS4(
2603
HTParentAnchor *, parent,
2604
CONST char *, address,
2605
HTFormat, format_out,
2609
BOOLEAN WasInterrupted = FALSE;
2610
HTStructured* target = HTML_new(parent, format_out, sink);
2611
HTStructuredClass targetClass;
2612
char *filename = HTParse(address, "", PARSE_PATH + PARSE_PUNCTUATION);
2613
EntryInfo *entry_info;
2614
BOOLEAN first = TRUE;
2615
char string_buffer[64];
2616
char *lastpath = NULL;/* prefix for link, either "" (for root) or xxx */
2617
BOOL need_parent_link = FALSE;
2618
BOOL tildeIsTop = FALSE;
2620
targetClass = *(target->isa);
2622
_HTProgress (gettext("Receiving FTP directory."));
2625
* Force the current Date and Year (TheDate, ThisYear, and LastYear)
2626
* to be recalculated for each directory request. Otherwise we have
2627
* a problem with long-running sessions assuming the wrong date for
2632
** Check whether we always want the home
2633
** directory treated as Welcome. - FM
2635
if (server_type == VMS_SERVER)
2639
** This should always come back FALSE, since the
2640
** flag is set only for local directory listings
2641
** if LONG_LIST was defined on compilation, but
2642
** we could someday set up an equivalent listing
2643
** for Unix ftp servers. - FM
2645
need_parent_link = HTDirTitles(target, parent, tildeIsTop);
2647
data_read_pointer = data_write_pointer = data_buffer;
2649
if (*filename == '\0') { /* Empty filename: use root. */
2650
StrAllocCopy (lastpath, "/");
2651
} else if (!strcmp(filename,"/")) { /* Root path. */
2652
StrAllocCopy (lastpath, "/foo/..");
2654
char * p = strrchr(filename, '/'); /* Find the lastslash. */
2657
if (server_type == CMS_SERVER) {
2658
StrAllocCopy(lastpath, filename); /* Use absolute path for CMS. */
2660
StrAllocCopy(lastpath, p+1); /* Take slash off the beginning. */
2662
if ((cp = strrchr(lastpath, ';')) != NULL) { /* Trim type= param. */
2663
if (!strncasecomp((cp+1), "type=", 5)) {
2664
if (TOUPPER(*(cp+6)) == 'D' ||
2665
TOUPPER(*(cp+6)) == 'A' ||
2666
TOUPPER(*(cp+6)) == 'I')
2674
HTBTree * bt = HTBTree_new((HTComparer)compare_EntryInfo_structs);
2676
HTChunk * chunk = HTChunkCreate(128);
2677
int BytesReceived = 0;
2678
int BytesReported = 0;
2680
char *spilledname = NULL;
2681
PUTC('\n'); /* prettier LJM */
2682
for (ic = 0; ic != EOF;) { /* For each entry in the directory */
2683
HTChunkClear(chunk);
2685
if (HTCheckForInterrupt()) {
2687
"read_directory: interrupted after %d bytes\n",
2689
WasInterrupted = TRUE;
2690
if (BytesReceived) {
2691
goto unload_btree; /* unload btree */
2694
HTBTreeAndObject_free(bt);
2696
return HT_INTERRUPTED;
2700
/* read directory entry
2702
interrupted_in_next_data_char = FALSE;
2703
for (;;) { /* Read in one line as filename */
2704
ic = NEXT_DATA_CHAR;
2706
if (interrupted_in_next_data_char) {
2708
"read_directory: interrupted_in_next_data_char after %d bytes\n",
2710
WasInterrupted = TRUE;
2711
if (BytesReceived) {
2712
goto unload_btree; /* unload btree */
2715
HTBTreeAndObject_free(bt);
2717
return HT_INTERRUPTED;
2719
} else if ((char)ic == CR || (char)ic == LF) { /* Terminator? */
2720
if (chunk->size != 0) { /* got some text */
2721
/* Deal with MultiNet's wrapping of long lines */
2722
if (server_type == VMS_SERVER) {
2723
/* Deal with MultiNet's wrapping of long lines - F.M. */
2724
if (data_read_pointer < data_write_pointer &&
2725
*(data_read_pointer+1) == ' ')
2726
data_read_pointer++;
2727
else if (data_read_pointer >= data_write_pointer) {
2728
status = NETREAD(data_soc, data_buffer,
2730
if (status == HT_INTERRUPTED) {
2731
interrupted_in_next_data_char = 1;
2732
goto AgainForMultiNet;
2738
data_write_pointer = data_buffer + status;
2739
data_read_pointer = data_buffer;
2740
if (*data_read_pointer == ' ')
2741
data_read_pointer++;
2749
break; /* finish getting one entry */
2751
} else if (ic == EOF) {
2752
break; /* End of file */
2754
HTChunkPutc(chunk, (char)ic);
2757
HTChunkTerminate(chunk);
2759
BytesReceived += chunk->size;
2760
if (BytesReceived > BytesReported + 1024) {
2762
sprintf(NumBytes,gettext("Transferred %d bytes (%5d)"),
2763
BytesReceived, ws_read_per_sec);
2765
sprintf(NumBytes, TRANSFERRED_X_BYTES, BytesReceived);
2767
HTProgress(NumBytes);
2768
BytesReported = BytesReceived;
2771
if (ic == EOF && chunk->size == 1)
2772
/* 1 means empty: includes terminating 0 */
2774
CTRACE((tfp, "HTFTP: Line in %s is %s\n",
2775
lastpath, chunk->data));
2777
entry_info = parse_dir_entry(chunk->data, &first, &spilledname);
2778
if (entry_info->display) {
2780
CTRACE((tfp, "Adding file to BTree: %s\n",
2781
entry_info->filename));
2782
HTBTree_add(bt, entry_info);
2784
free_entryinfo_struct_contents(entry_info);
2795
/* print out the handy help message if it exits :) */
2796
if (help_message_cache_non_empty()) {
2800
PUTS(help_message_cache_contents());
2801
init_help_message_cache(); /* to free memory */
2811
/* PUTS(" Date Type Size Filename\n");
2814
/* Run through tree printing out in order
2817
#ifdef SH_EX /* 1997/10/18 (Sat) 14:14:28 */
2818
char *p, name_buff[256];
2819
int name_len, dot_len;
2821
#define FNAME_WIDTH 30
2827
for (ele = HTBTree_next(bt, NULL);
2829
ele = HTBTree_next(bt, ele)) {
2830
entry_info = (EntryInfo *)HTBTree_object(ele);
2832
if (entry_info->date) {
2833
PUTS(entry_info->date);
2839
if (entry_info->type) {
2840
for (i = 0; entry_info->type[i] != '\0' && i < 16; i++)
2841
PUTC(entry_info->type[i]);
2846
/* start the anchor */
2847
HTDirEntry(target, lastpath, entry_info->filename);
2848
#ifdef SH_EX /* 1997/10/18 (Sat) 16:00 */
2849
name_len = strlen(entry_info->filename);
2851
sprintf(name_buff, "%-30s", entry_info->filename);
2853
if (name_len < FNAME_WIDTH) {
2854
dot_len = FNAME_WIDTH - FILE_GAP - name_len;
2856
p = name_buff + name_len + 1;
2861
name_buff[FNAME_WIDTH] = '\0';
2866
PUTS(entry_info->filename);
2870
if (entry_info->size) {
2871
#ifdef SH_EX /* 1998/02/02 (Mon) 16:34:52 */
2872
if (entry_info->size < 1024)
2873
sprintf(string_buffer, "%6d bytes",
2876
sprintf(string_buffer, "%6d Kb",
2877
entry_info->size/1024);
2879
if (entry_info->size < 1024)
2880
sprintf(string_buffer, " %d bytes",
2883
sprintf(string_buffer, " %dKb",
2884
entry_info->size/1024);
2886
PUTS(string_buffer);
2889
PUTC('\n'); /* end of this entry */
2891
free_entryinfo_struct_contents(entry_info);
2896
HTBTreeAndObject_free(bt);
2901
if (WasInterrupted || data_soc != -1) { /* should always be true */
2903
* Without closing the data socket first,
2904
* the response(0) later may hang.
2905
* Some servers expect the client to fin/ack the close
2906
* of the data connection before proceeding with the
2907
* conversation on the control connection. - kw
2909
CTRACE((tfp, "HTFTP: Closing data socket %d\n", data_soc));
2910
status = NETCLOSE(data_soc);
2912
HTInetStatus("close"); /* Comment only */
2916
if (WasInterrupted || HTCheckForInterrupt()) {
2917
_HTProgress(TRANSFER_INTERRUPTED);
2923
* Setup an FTP connection.
2925
PRIVATE int setup_connection ARGS2(
2927
HTParentAnchor *, anchor)
2929
int retry; /* How many times tried? */
2932
/* set use_list to NOT since we don't know what kind of server
2933
* this is yet. And set the type to GENERIC
2936
server_type = GENERIC_SERVER;
2937
ProFTPD_bugs = FALSE;
2939
for (retry = 0; retry < 2; retry++) { /* For timed out/broken connections */
2940
status = get_connection(name, anchor);
2944
if (!ftp_local_passive) {
2945
status = get_listen_socket();
2947
NETCLOSE (control->socket);
2948
control->socket = -1;
2950
if (master_socket >= 0)
2951
(void)close_master_socket ();
2953
close_master_socket ();
2955
/* HT_INTERRUPTED would fall through, if we could interrupt
2956
somehow in the middle of it, which we currently can't. */
2961
/* Inform the server of the port number we will listen on
2963
status = response(port_command);
2964
if (status == HT_INTERRUPTED) {
2965
CTRACE((tfp, "HTFTP: Interrupted in response (port_command)\n"));
2966
_HTProgress (CONNECTION_INTERRUPTED);
2967
NETCLOSE (control->socket);
2968
control->socket = -1;
2969
close_master_socket ();
2970
return HT_INTERRUPTED;
2972
if (status != 2) { /* Could have timed out */
2974
continue; /* try again - net error*/
2975
return -status; /* bad reply */
2977
CTRACE((tfp, "HTFTP: Port defined.\n"));
2978
#endif /* REPEAT_PORT */
2979
} else { /* Tell the server to be passive */
2980
char *command = NULL;
2982
int h0, h1, h2, h3, p0, p1; /* Parts of reply */
2984
char dst[LINE_LENGTH+1];
2990
status = send_cmd_1(p = "EPSV");
2991
if (status < 0) /* retry or Bad return */
2993
else if (status != 2) {
2994
status = send_cmd_1(p = "PASV");
2995
if (status < 0) /* retry or Bad return */
2997
else if (status != 2) {
2998
return -status; /* bad reply */
3002
if (strcmp(p, "PASV") == 0) {
3003
for (p = response_text; *p && *p != ','; p++)
3006
while (--p > response_text && '0' <= *p && *p <= '9')
3008
status = sscanf(p+1, "%d,%d,%d,%d,%d,%d",
3009
&h0, &h1, &h2, &h3, &p0, &p1);
3011
fprintf(tfp, "HTFTP: PASV reply has no inet address!\n");
3014
passive_port = (p0<<8) + p1;
3015
sprintf(dst, "%d.%d.%d.%d", h0, h1, h2, h3);
3016
} else if (strcmp(p, "EPSV") == 0) {
3017
unsigned char c0, c1, c2, c3;
3018
struct sockaddr_storage ss;
3022
* EPSV bla (|||port|)
3024
for (p = response_text; *p && !isspace(*p); p++)
3026
for (/*nothing*/; *p && *p && *p != '('; p++) /*)*/
3028
status = sscanf(p, "(%c%c%c%d%c)", &c0, &c1, &c2, &p0, &c3);
3030
fprintf(tfp, "HTFTP: EPSV reply has invalid format!\n");
3036
if (getpeername(control->socket, (struct sockaddr *)&ss,
3038
fprintf(tfp, "HTFTP: getpeername(control) failed\n");
3041
if (getnameinfo((struct sockaddr *)&ss, sslen, dst,
3042
sizeof(dst), NULL, 0, NI_NUMERICHOST)) {
3043
fprintf(tfp, "HTFTP: getnameinfo failed\n");
3048
status = send_cmd_1("PASV");
3051
continue; /* retry or Bad return */
3052
return -status; /* bad reply */
3054
for (p = response_text; *p && *p != ','; p++)
3057
while (--p > response_text && '0' <= *p && *p <= '9')
3060
status = sscanf(p+1, "%d,%d,%d,%d,%d,%d",
3061
&h0, &h1, &h2, &h3, &p0, &p1);
3063
fprintf(tfp, "HTFTP: PASV reply has no inet address!\n");
3066
passive_port = (PortNumber)((p0<<8) + p1);
3068
CTRACE((tfp, "HTFTP: Server is listening on port %d\n",
3071
/* Open connection for data: */
3074
HTSprintf0(&command, "%s//%s:%d/", STR_FTP_URL, dst, passive_port);
3076
HTSprintf0(&command, "%s//%d.%d.%d.%d:%d/",
3077
STR_FTP_URL, h0, h1, h2, h3, passive_port);
3079
status = HTDoConnect(command, "FTP data", passive_port, &data_soc);
3083
(void) HTInetStatus(gettext("connect for data"));
3085
return status; /* Bad return */
3088
CTRACE((tfp, "FTP data connected, socket %d\n", data_soc));
3091
break; /* No more retries */
3097
/* Retrieve File from Server
3098
** -------------------------
3101
** name WWW address of a file: document, including hostname
3103
** returns Socket number for file if good.
3106
PUBLIC int HTFTPLoad ARGS4(
3108
HTParentAnchor *, anchor,
3109
HTFormat, format_out,
3112
BOOL isDirectory = NO;
3113
HTAtom * encoding = NULL;
3114
int status, final_status;
3115
int outstanding = 1; /* outstanding control connection responses
3116
that we are willing to wait for, if we
3117
get to the point of reading data - kw */
3120
CTRACE((tfp, "HTFTPLoad(%s) %s connection\n", name, ftp_local_passive ? "passive" : "normal"));
3122
HTReadProgress(0,0);
3124
status = setup_connection(name, anchor);
3126
return status; /* Failed with this code */
3128
/* Ask for the file:
3131
char *filename = HTParse(name, "", PARSE_PATH + PARSE_PUNCTUATION);
3132
char *fname = filename; /** Save for subsequent free() **/
3133
char *vmsname = NULL;
3138
if (server_type == CMS_SERVER) {
3139
/** If the unescaped path has a %2f, reject it as illegal. - FM **/
3140
if (((cp = strstr(filename, "%2")) != NULL) &&
3141
TOUPPER(cp[2]) == 'F') {
3143
init_help_message_cache(); /* to free memory */
3144
NETCLOSE(control->socket);
3145
control->socket = -1;
3146
CTRACE((tfp, "HTFTP: Rejecting path due to illegal escaped slash.\n"));
3152
StrAllocCopy(filename, "/");
3154
} else if ((type = strrchr(filename, ';')) != NULL) {
3156
** Check and trim the type= parameter. - FM
3158
if (!strncasecomp((type+1), "type=", 5)) {
3159
switch(TOUPPER(*(type+6))) {
3178
*(filename+1) = '\0';
3181
if (*type != '\0') {
3182
CTRACE((tfp, "HTFTP: type=%s\n", type));
3185
HTUnEscape(filename);
3186
CTRACE((tfp, "HTFTP: UnEscaped %s\n", filename));
3187
if (filename[1] == '~') {
3189
** Check if translation of HOME as tilde is supported,
3190
** and adjust filename if so. - FM
3195
if ((cp2 = strchr((filename+1), '/')) != NULL) {
3198
status = send_cmd_1("PWD");
3199
if (status == 2 && response_text[5] == '/') {
3200
status = send_cwd(filename+1);
3202
StrAllocCopy(fn, (filename+1));
3205
if (fn[strlen(fn)-1] != '/') {
3206
StrAllocCat(fn, cp2);
3208
StrAllocCat(fn, (cp2+1));
3213
fname = filename = fn;
3220
if (strlen(filename) > 3) {
3222
if (((cp2=strrchr(filename, '.')) != NULL &&
3223
0 == strncasecomp(cp2, ".me", 3)) &&
3224
(cp2[3] == '\0' || cp2[3] == ';')) {
3226
** Don't treat this as application/x-Troff-me
3227
** if it's a Unix server but has the string
3228
** "read.me", or if it's not a Unix server. - FM
3230
if ((server_type != UNIX_SERVER) ||
3231
(cp2 > (filename + 3) &&
3232
0 == strncasecomp((cp2 - 4), "read.me", 7))) {
3234
format = HTFileFormat(filename, &encoding, NULL);
3237
format = HTFileFormat(filename, &encoding, NULL);
3240
format = HTFileFormat(filename, &encoding, NULL);
3243
format = HTFileFormat(filename, &encoding, NULL);
3245
format = HTCharsetFormat(format, anchor, -1);
3246
binary = (BOOL) (encoding != HTAtom_for("8bit") &&
3247
encoding != HTAtom_for("7bit"));
3250
** Force binary if we're in source, download or dump
3251
** mode and this is not a VM/CMS server, so we don't
3252
** get CRLF instead of LF (or CR) for newlines in text
3253
** files. Can't do this for VM/CMS or we'll get
3256
(format_out == WWW_SOURCE ||
3257
format_out == HTAtom_for("www/download") ||
3258
format_out == HTAtom_for("www/dump")) &&
3259
(server_type != CMS_SERVER))
3261
if (!binary && type && *type == 'I') {
3263
** Force binary if we had ;type=I - FM
3266
} else if (binary && type && *type == 'A') {
3268
** Force ASCII if we had ;type=A - FM
3272
if (binary != control->binary) {
3274
** Act on our setting if not already set. - FM
3276
char * mode = binary ? "I" : "A";
3277
status = send_cmd_2("TYPE", mode);
3279
init_help_message_cache(); /* to free memory */
3280
return ((status < 0) ? status : -status);
3282
control->binary = binary;
3284
switch (server_type) {
3286
** Handle what for Lynx are special case servers, e.g.,
3287
** for which we respect RFC 1738, or which have known
3288
** conflicts in suffix mappings. - FM
3293
BOOL included_device = FALSE;
3294
BOOL found_tilde = FALSE;
3295
/** Accept only Unix-style filename **/
3296
if (strchr(filename, ':') != NULL ||
3297
strchr(filename, '[') != NULL) {
3299
init_help_message_cache(); /* to free memory */
3300
NETCLOSE(control->socket);
3301
control->socket = -1;
3302
CTRACE((tfp, "HTFTP: Rejecting path due to non-Unix-style syntax.\n"));
3305
/** Handle any unescaped "/%2F" path **/
3306
if (!strncmp(filename, "//", 2)) {
3308
included_device = TRUE;
3309
for (i = 0; filename[(i+1)]; i++)
3310
filename[i] = filename[(i+1)];
3312
CTRACE((tfp, "HTFTP: Trimmed '%s'\n", filename));
3313
cp = HTVMS_name("", filename);
3314
CTRACE((tfp, "HTFTP: VMSized '%s'\n", cp));
3315
if ((cp1=strrchr(cp, ']')) != NULL) {
3316
strcpy(filename, ++cp1);
3317
CTRACE((tfp, "HTFTP: Filename '%s'\n", filename));
3319
status = send_cwd(cp);
3322
if ((cp1=strchr(cp, '[')) != NULL) {
3324
status = send_cwd(cp);
3327
init_help_message_cache(); /* to free memory */
3328
NETCLOSE(control->socket);
3329
control->socket = -1;
3330
return ((status < 0) ? status : -status);
3332
HTSprintf0(&dotslash, "[.%s", cp1);
3333
status = send_cwd(dotslash);
3337
init_help_message_cache(); /* to free memory */
3338
NETCLOSE(control->socket);
3339
control->socket = -1;
3340
return ((status < 0) ? status : -status);
3344
init_help_message_cache(); /* to free memory */
3345
NETCLOSE(control->socket);
3346
control->socket = -1;
3347
return ((status < 0) ? status : -status);
3350
} else if ((cp1=strchr(cp, ':')) != NULL &&
3351
strchr(cp, '[') == NULL &&
3352
strchr(cp, ']') == NULL) {
3355
strcpy(filename, cp1);
3356
CTRACE((tfp, "HTFTP: Filename '%s'\n", filename));
3357
HTSprintf0(&vmsname, "%.*s[%s]", cp1-cp, cp, filename);
3358
status = send_cwd(vmsname);
3360
HTSprintf(&vmsname, "%.*s[000000]", cp1-cp, cp);
3361
status = send_cwd(vmsname);
3363
HTSprintf(&vmsname, "%.*s", cp1-cp, cp);
3364
status = send_cwd(vmsname);
3367
init_help_message_cache();
3368
NETCLOSE(control->socket);
3369
control->socket = -1;
3370
return ((status < 0) ? status : -status);
3374
HTSprintf0(&vmsname, "000000");
3378
} else if (0==strcmp(cp, (filename+1))) {
3379
status = send_cwd(cp);
3381
HTSprintf0(&vmsname, "%s:", cp);
3382
status = send_cwd(vmsname);
3385
init_help_message_cache(); /* to free memory */
3386
NETCLOSE(control->socket);
3387
control->socket = -1;
3388
return ((status < 0) ? status : -status);
3391
HTSprintf0(&vmsname, "000000");
3395
/** Trim trailing slash if filename is not the top directory **/
3396
if (strlen(filename) > 1 && filename[strlen(filename)-1] == '/')
3397
filename[strlen(filename)-1] = '\0';
3399
#ifdef MAINTAIN_CONNECTION /* Don't need this if always new connection - F.M. */
3400
if (!included_device) {
3401
/** Get the current default VMS device:[directory] **/
3402
status = send_cmd_1("PWD");
3405
init_help_message_cache(); /* to free memory */
3406
NETCLOSE(control->socket);
3407
control->socket = -1;
3408
return ((status < 0) ? status : -status);
3410
/** Go to the VMS account's top directory **/
3411
if ((cp=strchr(response_text, '[')) != NULL &&
3412
(cp1=strrchr(response_text, ']')) != NULL) {
3416
StrAllocCopy(tmp, cp);
3417
if ((cp2=strchr(cp, '.')) != NULL && cp2 < cp1) {
3423
StrAllocCat(tmp, "]");
3425
status = send_cwd(tmp);
3430
init_help_message_cache(); /* to free memory */
3431
NETCLOSE(control->socket);
3432
control->socket = -1;
3433
return ((status < 0) ? status : -status);
3437
#endif /* MAINTAIN_CONNECTION */
3439
/** If we want the VMS account's top directory, list it now **/
3440
if (!(strcmp(filename, "/~")) ||
3441
(included_device && 0==strcmp(filename, "000000")) ||
3442
(strlen(filename) == 1 && *filename == '/')) {
3444
status = send_cmd_1("LIST");
3447
/* Action not started */
3448
init_help_message_cache(); /* to free memory */
3449
NETCLOSE(control->socket);
3450
control->socket = -1;
3451
return ((status < 0) ? status : -status);
3456
/** Otherwise, go to appropriate directory and doctor filename **/
3457
if (!strncmp(filename, "/~", 2)) {
3461
CTRACE((tfp, "check '%s' to translate x/y/ to [.x.y]\n", filename));
3462
if (!included_device &&
3463
(cp = strchr(filename, '/')) != NULL &&
3464
(cp1 = strrchr(cp, '/')) != NULL &&
3468
HTSprintf0(&tmp, "[.%.*s]", cp1-cp-1, cp+1);
3470
CTRACE((tfp, "change path '%s'\n", tmp));
3471
while ((cp2 = strrchr(tmp, '/')) != NULL)
3473
CTRACE((tfp, "...to path '%s'\n", tmp));
3475
status = send_cwd(tmp);
3480
init_help_message_cache(); /* to free memory */
3481
NETCLOSE(control->socket);
3482
control->socket = -1;
3483
return ((status < 0) ? status : -status);
3487
if (!included_device && !found_tilde) {
3496
** If we want the CMS account's top directory, or a base
3497
** SFS or anonymous directory path (i.e., without a slash),
3500
if ((strlen(filename) == 1 && *filename == '/') ||
3501
((0 == strncasecomp((filename+1), "vmsysu:", 7)) &&
3502
(cp = strchr((filename+1), '.')) != NULL &&
3503
strchr(cp, '/') == NULL) ||
3504
(0 == strncasecomp(filename+1, "anonymou.", 9) &&
3505
strchr(filename+1, '/') == NULL)) {
3506
if (filename[1] != '\0') {
3507
status = send_cwd(filename+1);
3509
/* Action not started */
3510
init_help_message_cache(); /* to free memory */
3511
NETCLOSE(control->socket);
3512
control->socket = -1;
3513
return ((status < 0) ? status : -status);
3518
status = send_cmd_1("LIST");
3520
status = send_cmd_1("NLST");
3523
/* Action not started */
3524
init_help_message_cache(); /* to free memory */
3525
NETCLOSE(control->socket);
3526
control->socket = -1;
3527
return ((status < 0) ? status : -status);
3534
/** Otherwise, go to appropriate directory and adjust filename **/
3535
while ((cp = strchr(filename, '/')) != NULL) {
3537
status = send_cwd(filename);
3542
status = send_cmd_1("LIST");
3544
status = send_cmd_1("NLST");
3547
/** Action not started **/
3548
init_help_message_cache(); /* to free memory */
3549
NETCLOSE(control->socket);
3550
control->socket = -1;
3551
return ((status < 0) ? status : -status);
3553
/** Clear any messages from the login directory **/
3554
init_help_message_cache();
3564
/** Shift for any unescaped "/%2F" path **/
3565
if (!strncmp(filename, "//", 2))
3570
** Act on a file or listing request, or try to figure out
3571
** which we're dealing with if we don't know yet. - FM
3573
if (!(type) || (type && *type != 'D')) {
3574
status = send_cmd_2("RETR", filename);
3575
#ifdef BROKEN_PROFTPD
3577
* ProFTPD 1.2.5rc1 gets confused when asked to RETR a directory.
3583
CTRACE((tfp, "{{reconnecting...\n"));
3584
close_connection(control);
3585
check = setup_connection(name, anchor);
3586
CTRACE((tfp, "...done }}reconnecting\n"));
3593
status = 5; /* Failed status set as flag. - FM */
3595
if (status != 1) { /* Failed : try to CWD to it */
3596
/** Clear any login messages if this isn't the login directory **/
3597
if (strcmp(filename, "/"))
3598
init_help_message_cache();
3600
status = send_cwd(filename);
3601
if (status == 2) { /* Succeeded : let's NAME LIST it */
3604
status = send_cmd_1("LIST");
3606
status = send_cmd_1("NLST");
3612
init_help_message_cache(); /* to free memory */
3613
NETCLOSE(control->socket);
3614
control->socket = -1;
3623
if(!ftp_local_passive) {
3624
/* Wait for the connection */
3626
struct sockaddr_storage soc_address;
3628
struct sockaddr_in soc_address;
3630
int soc_addrlen=sizeof(soc_address);
3633
status = Raccept(master_socket,
3634
(struct sockaddr *)&soc_address,
3635
(void *)&soc_addrlen);
3638
status = accept(master_socket,
3639
(struct sockaddr *)&soc_address,
3640
(void *)&soc_addrlen);
3642
init_help_message_cache(); /* to free memory */
3643
return HTInetStatus("accept");
3645
CTRACE((tfp, "TCP: Accepted new socket %d\n", status));
3647
} /* !ftp_local_passive */
3649
#if 0 /* no - this makes the data connection go away too soon (2.8.3dev.22) */
3650
if ((status = send_cmd_nowait("QUIT")) == 1)
3655
if (server_type == UNIX_SERVER && !unsure_type &&
3656
!strcmp(response_text,
3657
"150 Opening ASCII mode data connection for /bin/dl.\n")) {
3658
CTRACE((tfp, "HTFTP: Treating as \"dls\" server.\n"));
3659
server_type = DLS_SERVER;
3661
final_status = read_directory (anchor, name, format_out, sink);
3662
if (final_status > 0) {
3663
if (server_type != CMS_SERVER)
3664
if (outstanding-- > 0) {
3665
status = response(0);
3667
(status == 2 && !strncmp(response_text, "221", 3)))
3670
} else { /* HT_INTERRUPTED */
3671
/* User may have pressed 'z' to give up because no
3672
packets got through, so let's not make them wait
3677
if (data_soc != -1) { /* normally done in read_directory */
3678
CTRACE((tfp, "HTFTP: Closing data socket %d\n", data_soc));
3679
status = NETCLOSE(data_soc);
3681
HTInetStatus("close"); /* Comment only */
3683
status = final_status;
3686
char *FileName = HTParse(name, "", PARSE_PATH + PARSE_PUNCTUATION);
3688
/** Clear any login messages **/
3689
init_help_message_cache();
3691
/** Fake a Content-Encoding for compressed files. - FM **/
3692
HTUnEscape(FileName);
3693
if (!IsUnityEnc(encoding)) {
3695
* We already know from the call to HTFileFormat above that
3696
* this is a compressed file, no need to look at the filename
3699
StrAllocCopy(anchor->content_type, format->name);
3700
StrAllocCopy(anchor->content_encoding, HTAtom_name(encoding));
3701
format = HTAtom_for("www/compressed");
3705
CompressFileType cft = HTCompressFileType(FileName, "._-", &dot);
3707
if (cft != cftNone) {
3709
format = HTFileFormat(FileName, &encoding, NULL);
3710
format = HTCharsetFormat(format, anchor, -1);
3711
StrAllocCopy(anchor->content_type, format->name);
3712
format = HTAtom_for("www/compressed");
3716
StrAllocCopy(anchor->content_encoding, "x-compress");
3719
StrAllocCopy(anchor->content_encoding, "x-gzip");
3722
StrAllocCopy(anchor->content_encoding, "x-bzip2");
3731
_HTProgress (gettext("Receiving FTP file."));
3732
rv = HTParseSocket(format, format_out, anchor, data_soc, sink);
3734
HTInitInput(control->socket);
3735
/* Reset buffering to control connection DD 921208 */
3738
#if 0 /* any known servers where ABOR would work this way? */
3739
if (rv == HT_INTERRUPTED || rv == -501)
3740
if (send_cmd_nowait("ABOR") == 1) {
3742
CTRACE((tfp, "HTFTP: outstanding responses: %d\n", outstanding));
3745
if (rv == -2) /* weird error, don't expect much response */
3747
else if (rv == HT_INTERRUPTED || rv == -1)
3748
/* User may have pressed 'z' to give up because no
3749
packets got through, so let's not make them wait
3752
CTRACE((tfp, "HTFTP: Closing data socket %d\n", data_soc));
3753
status = NETCLOSE(data_soc);
3755
status = 2; /* data_soc already closed in HTCopy - kw */
3757
if (status < 0 && rv != HT_INTERRUPTED && rv != -1) {
3758
(void) HTInetStatus("close"); /* Comment only */
3760
if (rv != HT_LOADED && outstanding--) {
3761
status = response(0); /* Pick up final reply */
3762
if (status != 2 && rv != HT_INTERRUPTED && rv != -1) {
3763
data_soc = -1; /* invalidate it */
3764
init_help_message_cache(); /* to free memory */
3765
return HTLoadError(sink, 500, response_text);
3766
} else if (status <= 0) {
3768
} else if (status == 2 && !strncmp(response_text, "221", 3))
3772
final_status = HT_LOADED;
3774
while (outstanding-- > 0 &&
3776
status = response(0);
3777
if (status == 2 && !strncmp(response_text, "221", 3))
3780
data_soc = -1; /* invalidate it */
3781
CTRACE((tfp, "HTFTPLoad: normal end; "));
3782
if (control->socket < 0) {
3783
CTRACE((tfp, "control socket is %d\n", control->socket));
3785
CTRACE((tfp, "closing control socket %d\n", control->socket));
3786
status = NETCLOSE(control->socket);
3788
HTInetStatus("control connection close"); /* Comment only */
3790
control->socket = -1;
3791
init_help_message_cache(); /* to free memory */
3792
/* returns HT_LOADED (always for file if we get here) or error */
3793
return final_status;
3794
} /* open_file_read */
3797
** This function frees any user entered password, so that
3798
** it must be entered again for a future request. - FM
3800
PUBLIC void HTClearFTPPassword NOARGS
3803
** Need code to check cached documents from
3804
** non-anonymous ftp accounts and do something
3805
** to ensure that they no longer can be accessed
3806
** without a new retrieval. - FM
3810
** Now free the current user entered password,
3813
FREE(user_entered_password);
3816
#endif /* ifndef DISABLE_FTP */