4180 lines
106 KiB
C
4180 lines
106 KiB
C
/*
|
|
* $LynxId: HTFTP.c,v 1.149 2024/03/21 07:51:10 tom Exp $
|
|
*
|
|
* File Transfer Protocol (FTP) Client
|
|
* for a WorldWideWeb browser
|
|
* ===================================
|
|
*
|
|
* A cache of control connections is kept.
|
|
*
|
|
* Note: Port allocation
|
|
*
|
|
* It is essential that the port is allocated by the system, rather
|
|
* than chosen in rotation by us (POLL_PORTS), or the following
|
|
* problem occurs.
|
|
*
|
|
* It seems that an attempt by the server to connect to a port which has
|
|
* been used recently by a listen on the same socket, or by another
|
|
* socket this or another process causes a hangup of (almost exactly)
|
|
* one minute. Therefore, we have to use a rotating port number.
|
|
* The problem remains that if the application is run twice in quick
|
|
* succession, it will hang for what remains of a minute.
|
|
*
|
|
* Authors
|
|
* TBL Tim Berners-lee <timbl@info.cern.ch>
|
|
* DD Denis DeLaRoca 310 825-4580 <CSP1DWD@mvs.oac.ucla.edu>
|
|
* LM Lou Montulli <montulli@ukanaix.cc.ukans.edu>
|
|
* FM Foteos Macrides <macrides@sci.wfeb.edu>
|
|
* History:
|
|
* 2 May 91 Written TBL, as a part of the WorldWideWeb project.
|
|
* 15 Jan 92 Bug fix: close() was used for NETCLOSE for control soc
|
|
* 10 Feb 92 Retry if cached connection times out or breaks
|
|
* 8 Dec 92 Bug fix 921208 TBL after DD
|
|
* 17 Dec 92 Anon FTP password now just WWWuser@ suggested by DD
|
|
* fails on princeton.edu!
|
|
* 27 Dec 93 (FM) Fixed up so FTP now works with VMS hosts. Path
|
|
* must be Unix-style and cannot include the device
|
|
* or top directory.
|
|
* ?? ??? ?? (LM) Added code to prompt and send passwords for non
|
|
* anonymous FTP
|
|
* 25 Mar 94 (LM) Added code to recognize different ftp server types
|
|
* and code to parse dates and sizes on most hosts.
|
|
* 27 Mar 93 (FM) Added code for getting dates and sizes on VMS hosts.
|
|
*
|
|
* Notes:
|
|
* Portions Copyright 1994 Trustees of Dartmouth College
|
|
* Code for recognizing different FTP servers and
|
|
* parsing "ls -l" output taken from Macintosh Fetch
|
|
* program with permission from Jim Matthews,
|
|
* Dartmouth Software Development Team.
|
|
*/
|
|
|
|
/*
|
|
* BUGS: @@@ Limit connection cache size!
|
|
* Error reporting to user.
|
|
* 400 & 500 errors are ack'ed by user with windows.
|
|
* Use configuration file for user names
|
|
*
|
|
* Note for portability this version does not use select() and
|
|
* so does not watch the control and data channels at the
|
|
* same time.
|
|
*/
|
|
|
|
#include <HTUtils.h>
|
|
|
|
#include <HTAlert.h>
|
|
|
|
#include <HTFTP.h> /* Implemented here */
|
|
#include <HTTCP.h>
|
|
#include <HTTP.h>
|
|
#include <HTFont.h>
|
|
|
|
#define REPEAT_PORT /* Give the port number for each file */
|
|
#define REPEAT_LISTEN /* Close each listen socket and open a new one */
|
|
|
|
/* define POLL_PORTS If allocation does not work, poll ourselves.*/
|
|
#define LISTEN_BACKLOG 2 /* Number of pending connect requests (TCP) */
|
|
|
|
#define FIRST_TCP_PORT 1024 /* Region to try for a listening port */
|
|
#define LAST_TCP_PORT 5999
|
|
|
|
#define LINE_LENGTH 256
|
|
|
|
#include <HTParse.h>
|
|
#include <HTAnchor.h>
|
|
#include <HTFile.h> /* For HTFileFormat() */
|
|
#include <HTBTree.h>
|
|
#include <HTChunk.h>
|
|
#ifndef IPPORT_FTP
|
|
#define IPPORT_FTP 21
|
|
#endif /* !IPORT_FTP */
|
|
|
|
#include <LYUtils.h>
|
|
#include <LYGlobalDefs.h>
|
|
#include <LYStrings.h>
|
|
#include <LYLeaks.h>
|
|
|
|
typedef struct _connection {
|
|
struct _connection *next; /* Link on list */
|
|
int socket; /* Socket number for communication */
|
|
BOOL is_binary; /* Binary mode? */
|
|
} connection;
|
|
|
|
/* Hypertext object building machinery
|
|
*/
|
|
#include <HTML.h>
|
|
|
|
/*
|
|
* socklen_t is the standard, but there are many pre-standard variants.
|
|
* This ifdef works around a few of those cases.
|
|
*
|
|
* Information was obtained from header files on these platforms:
|
|
* AIX 4.3.2, 5.1
|
|
* HPUX 10.20, 11.00, 11.11
|
|
* IRIX64 6.5
|
|
* Tru64 4.0G, 4.0D, 5.1
|
|
*/
|
|
#if defined(SYS_IRIX64)
|
|
/* IRIX64 6.5 socket.h may use socklen_t if SGI_SOURCE is not defined */
|
|
# if _NO_XOPEN4 && _NO_XOPEN5
|
|
# define LY_SOCKLEN socklen_t
|
|
# elif _ABIAPI
|
|
# define LY_SOCKLEN int
|
|
# elif _XOPEN5
|
|
# if (_MIPS_SIM != _ABIO32)
|
|
# define LY_SOCKLEN socklen_t
|
|
# else
|
|
# define LY_SOCKLEN int
|
|
# endif
|
|
# else
|
|
# define LY_SOCKLEN size_t
|
|
# endif
|
|
#elif defined(SYS_HPUX)
|
|
# if defined(_XOPEN_SOURCE_EXTENDED) && defined(SO_PROTOTYPE)
|
|
# define LY_SOCKLEN socklen_t
|
|
# else /* HPUX 10.20, etc. */
|
|
# define LY_SOCKLEN int
|
|
# endif
|
|
#elif defined(SYS_TRU64)
|
|
# if defined(_POSIX_PII_SOCKET)
|
|
# define LY_SOCKLEN socklen_t
|
|
# elif defined(_XOPEN_SOURCE_EXTENDED)
|
|
# define LY_SOCKLEN size_t
|
|
# else
|
|
# define LY_SOCKLEN int
|
|
# endif
|
|
#else
|
|
# define LY_SOCKLEN socklen_t
|
|
#endif
|
|
|
|
#define PUTC(c) (*target->isa->put_character) (target, c)
|
|
#define PUTS(s) (*target->isa->put_string) (target, s)
|
|
#define START(e) (*target->isa->start_element) (target, e, 0, 0, -1, 0)
|
|
#define END(e) (*target->isa->end_element) (target, e, 0)
|
|
#define FREE_TARGET (*target->isa->_free) (target)
|
|
#define ABORT_TARGET (*target->isa->_free) (target)
|
|
|
|
#define TRACE_ENTRY(tag, entry_info) \
|
|
CTRACE((tfp, "HTFTP: %s filename: %s date: %s size: %" PRI_off_t "\n", \
|
|
tag, \
|
|
entry_info->filename, \
|
|
NonNull(entry_info->date), \
|
|
CAST_off_t(entry_info->size)))
|
|
|
|
struct _HTStructured {
|
|
const HTStructuredClass *isa;
|
|
/* ... */
|
|
};
|
|
|
|
/* Global Variables
|
|
* ---------------------
|
|
*/
|
|
int HTfileSortMethod = FILE_BY_NAME;
|
|
|
|
#ifndef DISABLE_FTP /*This disables everything to end-of-file */
|
|
static char ThisYear[8];
|
|
static char LastYear[8];
|
|
static int TheDate;
|
|
static BOOLEAN HaveYears = FALSE;
|
|
|
|
/* Module-Wide Variables
|
|
* ---------------------
|
|
*/
|
|
static connection *connections = NULL; /* Linked list of connections */
|
|
static char response_text[LINE_LENGTH + 1]; /* Last response from ftp host */
|
|
static connection *control = NULL; /* Current connection */
|
|
static int data_soc = -1; /* Socket for data transfer =invalid */
|
|
static char *user_entered_password = NULL;
|
|
static char *last_username_and_host = NULL;
|
|
|
|
/*
|
|
* Some ftp servers are known to have a broken implementation of RETR. If
|
|
* asked to retrieve a directory, they get confused and fail subsequent
|
|
* commands such as CWD and LIST.
|
|
*/
|
|
static int Broken_RETR = FALSE;
|
|
|
|
/*
|
|
* Some ftp servers are known to have a broken implementation of EPSV. The
|
|
* server will hang for a long time when we attempt to connect after issuing
|
|
* this command.
|
|
*/
|
|
#ifdef INET6
|
|
static int Broken_EPSV = FALSE;
|
|
#endif
|
|
|
|
typedef enum {
|
|
GENERIC_SERVER
|
|
,MACHTEN_SERVER
|
|
,UNIX_SERVER
|
|
,VMS_SERVER
|
|
,CMS_SERVER
|
|
,DCTS_SERVER
|
|
,TCPC_SERVER
|
|
,PETER_LEWIS_SERVER
|
|
,NCSA_SERVER
|
|
,WINDOWS_NT_SERVER
|
|
,WINDOWS_2K_SERVER
|
|
,MS_WINDOWS_SERVER
|
|
,MSDOS_SERVER
|
|
,APPLESHARE_SERVER
|
|
,NETPRESENZ_SERVER
|
|
,DLS_SERVER
|
|
} eServerType;
|
|
|
|
static eServerType server_type = GENERIC_SERVER; /* the type of ftp host */
|
|
static int unsure_type = FALSE; /* sure about the type? */
|
|
static BOOLEAN use_list = FALSE; /* use the LIST command? */
|
|
|
|
static int interrupted_in_next_data_char = FALSE;
|
|
|
|
#ifdef POLL_PORTS
|
|
static PortNumber port_number = FIRST_TCP_PORT;
|
|
#endif /* POLL_PORTS */
|
|
|
|
static BOOL have_socket = FALSE; /* true if master_socket is valid */
|
|
static LYNX_FD master_socket; /* Listening socket = invalid */
|
|
|
|
static char *port_command; /* Command for setting the port */
|
|
static fd_set open_sockets; /* Mask of active channels */
|
|
static LYNX_FD num_sockets; /* Number of sockets to scan */
|
|
static PortNumber passive_port; /* Port server specified for data */
|
|
|
|
#define NEXT_CHAR HTGetCharacter() /* Use function in HTFormat.c */
|
|
|
|
#define DATA_BUFFER_SIZE 2048
|
|
static char data_buffer[DATA_BUFFER_SIZE]; /* Input data buffer */
|
|
static char *data_read_pointer;
|
|
static char *data_write_pointer;
|
|
|
|
#define NEXT_DATA_CHAR next_data_char()
|
|
static int close_connection(connection * con);
|
|
|
|
#ifndef HAVE_ATOLL
|
|
off_t LYatoll(const char *value)
|
|
{
|
|
off_t result = 0;
|
|
|
|
while (*value != '\0') {
|
|
result = (result * 10) + (off_t) (*value++ - '0');
|
|
}
|
|
return result;
|
|
}
|
|
#endif
|
|
|
|
#ifdef LY_FIND_LEAKS
|
|
/*
|
|
* This function frees module globals. - FM
|
|
*/
|
|
static void free_FTPGlobals(void)
|
|
{
|
|
FREE(user_entered_password);
|
|
FREE(last_username_and_host);
|
|
if (control) {
|
|
if (control->socket != -1)
|
|
close_connection(control);
|
|
FREE(control);
|
|
}
|
|
}
|
|
#endif /* LY_FIND_LEAKS */
|
|
|
|
/* PUBLIC HTVMS_name()
|
|
* CONVERTS WWW name into a VMS name
|
|
* ON ENTRY:
|
|
* nn Node Name (optional)
|
|
* fn WWW file name
|
|
*
|
|
* ON EXIT:
|
|
* returns vms file specification
|
|
*
|
|
* Bug: Returns pointer to static -- non-reentrant
|
|
*/
|
|
char *HTVMS_name(const char *nn,
|
|
const char *fn)
|
|
{
|
|
/* We try converting the filename into Files-11 syntax. That is, we assume
|
|
* first that the file is, like us, on a VMS node. We try remote (or
|
|
* local) DECnet access. Files-11, VMS, VAX and DECnet are trademarks of
|
|
* Digital Equipment Corporation. The node is assumed to be local if the
|
|
* hostname WITHOUT DOMAIN matches the local one. @@@
|
|
*/
|
|
static char *vmsname;
|
|
char *filename = (char *) malloc(strlen(fn) + 1);
|
|
char *nodename = (char *) malloc(strlen(nn) + 2 + 1); /* Copies to hack */
|
|
char *second; /* 2nd slash */
|
|
char *last; /* last slash */
|
|
|
|
const char *hostname = HTHostName();
|
|
|
|
if (!filename || !nodename)
|
|
outofmem(__FILE__, "HTVMSname");
|
|
|
|
strcpy(filename, fn);
|
|
strcpy(nodename, ""); /* On same node? Yes if node names match */
|
|
if (StrNCmp(nn, "localhost", 9)) {
|
|
const char *p;
|
|
const char *q;
|
|
|
|
for (p = hostname, q = nn;
|
|
*p && *p != '.' && *q && *q != '.'; p++, q++) {
|
|
if (TOUPPER(*p) != TOUPPER(*q)) {
|
|
char *r;
|
|
|
|
strcpy(nodename, nn);
|
|
r = StrChr(nodename, '.'); /* Mismatch */
|
|
if (r)
|
|
*r = '\0'; /* Chop domain */
|
|
strcat(nodename, "::"); /* Try decnet anyway */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
second = StrChr(filename + 1, '/'); /* 2nd slash */
|
|
last = strrchr(filename, '/'); /* last slash */
|
|
|
|
if (!second) { /* Only one slash */
|
|
HTSprintf0(&vmsname, "%s%s", nodename, filename + 1);
|
|
} else if (second == last) { /* Exactly two slashes */
|
|
*second = '\0'; /* Split filename from disk */
|
|
HTSprintf0(&vmsname, "%s%s:%s", nodename, filename + 1, second + 1);
|
|
*second = '/'; /* restore */
|
|
} else { /* More than two slashes */
|
|
char *p;
|
|
|
|
*second = '\0'; /* Split disk from directories */
|
|
*last = '\0'; /* Split dir from filename */
|
|
HTSprintf0(&vmsname, "%s%s:[%s]%s",
|
|
nodename, filename + 1, second + 1, last + 1);
|
|
*second = *last = '/'; /* restore filename */
|
|
if ((p = StrChr(vmsname, '[')) != 0) {
|
|
while (*p != '\0' && *p != ']') {
|
|
if (*p == '/')
|
|
*p = '.'; /* Convert dir sep. to dots */
|
|
++p;
|
|
}
|
|
}
|
|
}
|
|
FREE(nodename);
|
|
FREE(filename);
|
|
return vmsname;
|
|
}
|
|
|
|
/* Procedure: Read a character from the data connection
|
|
* ----------------------------------------------------
|
|
*/
|
|
static int next_data_char(void)
|
|
{
|
|
int status;
|
|
|
|
if (data_read_pointer >= data_write_pointer) {
|
|
status = NETREAD(data_soc, data_buffer, DATA_BUFFER_SIZE);
|
|
if (status == HT_INTERRUPTED)
|
|
interrupted_in_next_data_char = 1;
|
|
if (status <= 0)
|
|
return EOF;
|
|
data_write_pointer = data_buffer + status;
|
|
data_read_pointer = data_buffer;
|
|
}
|
|
#ifdef NOT_ASCII
|
|
{
|
|
char c = *data_read_pointer++;
|
|
|
|
return FROMASCII(c);
|
|
}
|
|
#else
|
|
return UCH(*data_read_pointer++);
|
|
#endif /* NOT_ASCII */
|
|
}
|
|
|
|
/* Close an individual connection
|
|
*
|
|
*/
|
|
static int close_connection(connection * con)
|
|
{
|
|
connection *scan;
|
|
int status;
|
|
|
|
CTRACE((tfp, "HTFTP: Closing control socket %d\n", con->socket));
|
|
status = NETCLOSE(con->socket);
|
|
if (TRACE && status != 0) {
|
|
#ifdef UNIX
|
|
CTRACE((tfp, "HTFTP:close_connection: %s", LYStrerror(errno)));
|
|
#else
|
|
if (con->socket != INVSOC)
|
|
HTInetStatus("HTFTP:close_connection");
|
|
#endif
|
|
}
|
|
con->socket = -1;
|
|
if (connections == con) {
|
|
connections = con->next;
|
|
return status;
|
|
}
|
|
for (scan = connections; scan; scan = scan->next) {
|
|
if (scan->next == con) {
|
|
scan->next = con->next; /* Unlink */
|
|
if (control == con)
|
|
control = (connection *) 0;
|
|
return status;
|
|
} /*if */
|
|
} /* for */
|
|
return -1; /* very strange -- was not on list. */
|
|
}
|
|
|
|
static char *help_message_buffer = NULL; /* global :( */
|
|
|
|
static void init_help_message_cache(void)
|
|
{
|
|
FREE(help_message_buffer);
|
|
}
|
|
|
|
static void help_message_cache_add(char *string)
|
|
{
|
|
if (help_message_buffer)
|
|
StrAllocCat(help_message_buffer, string);
|
|
else
|
|
StrAllocCopy(help_message_buffer, string);
|
|
|
|
CTRACE((tfp, "Adding message to help cache: %s\n", string));
|
|
}
|
|
|
|
static char *help_message_cache_non_empty(void)
|
|
{
|
|
return (help_message_buffer);
|
|
}
|
|
|
|
static char *help_message_cache_contents(void)
|
|
{
|
|
return (help_message_buffer);
|
|
}
|
|
|
|
/* Send One Command
|
|
* ----------------
|
|
*
|
|
* This function checks whether we have a control connection, and sends
|
|
* one command if given.
|
|
*
|
|
* On entry,
|
|
* control points to the connection which is established.
|
|
* cmd points to a command, or is zero to just get the response.
|
|
*
|
|
* The command should already be terminated with the CRLF pair.
|
|
*
|
|
* On exit,
|
|
* returns: 1 for success,
|
|
* or negative for communication failure (in which case
|
|
* the control connection will be closed).
|
|
*/
|
|
static int write_cmd(const char *cmd)
|
|
{
|
|
int status;
|
|
|
|
if (!control) {
|
|
CTRACE((tfp, "HTFTP: No control connection set up!!\n"));
|
|
return HT_NO_CONNECTION;
|
|
}
|
|
|
|
if (cmd) {
|
|
CTRACE((tfp, " Tx: %s", cmd));
|
|
#ifdef NOT_ASCII
|
|
{
|
|
char *p;
|
|
|
|
for (p = cmd; *p; p++) {
|
|
*p = TOASCII(*p);
|
|
}
|
|
}
|
|
#endif /* NOT_ASCII */
|
|
status = (int) NETWRITE(control->socket, cmd, (unsigned) strlen(cmd));
|
|
if (status < 0) {
|
|
CTRACE((tfp,
|
|
"HTFTP: Error %d sending command: closing socket %d\n",
|
|
status, control->socket));
|
|
close_connection(control);
|
|
return status;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* For each string in the list, check if it is found in the response text.
|
|
* If so, return TRUE.
|
|
*/
|
|
static BOOL find_response(HTList *list)
|
|
{
|
|
BOOL result = FALSE;
|
|
HTList *p = list;
|
|
char *value;
|
|
|
|
while ((value = (char *) HTList_nextObject(p)) != NULL) {
|
|
if (LYstrstr(response_text, value)) {
|
|
result = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/* Execute Command and get Response
|
|
* --------------------------------
|
|
*
|
|
* See the state machine illustrated in RFC959, p57. This implements
|
|
* one command/reply sequence. It also interprets lines which are to
|
|
* be continued, which are marked with a "-" immediately after the
|
|
* status code.
|
|
*
|
|
* Continuation then goes on until a line with a matching reply code
|
|
* an a space after it.
|
|
*
|
|
* On entry,
|
|
* control points to the connection which is established.
|
|
* cmd points to a command, or is zero to just get the response.
|
|
*
|
|
* The command must already be terminated with the CRLF pair.
|
|
*
|
|
* On exit,
|
|
* returns: The first digit of the reply type,
|
|
* or negative for communication failure.
|
|
*/
|
|
static int response(const char *cmd)
|
|
{
|
|
int result; /* Three-digit decimal code */
|
|
int continuation_response = -1;
|
|
int status;
|
|
|
|
if ((status = write_cmd(cmd)) < 0)
|
|
return status;
|
|
|
|
do {
|
|
char *p = response_text;
|
|
|
|
for (;;) {
|
|
int ich = NEXT_CHAR;
|
|
|
|
if (((*p++ = (char) ich) == LF)
|
|
|| (p == &response_text[LINE_LENGTH])) {
|
|
|
|
char continuation;
|
|
|
|
if (interrupted_in_htgetcharacter) {
|
|
CTRACE((tfp,
|
|
"HTFTP: Interrupted in HTGetCharacter, apparently.\n"));
|
|
NETCLOSE(control->socket);
|
|
control->socket = -1;
|
|
return HT_INTERRUPTED;
|
|
}
|
|
|
|
*p = '\0'; /* Terminate the string */
|
|
CTRACE((tfp, " Rx: %s", response_text));
|
|
|
|
/* Check for login or help messages */
|
|
if (!StrNCmp(response_text, "230-", 4) ||
|
|
!StrNCmp(response_text, "250-", 4) ||
|
|
!StrNCmp(response_text, "220-", 4))
|
|
help_message_cache_add(response_text + 4);
|
|
|
|
sscanf(response_text, "%d%c", &result, &continuation);
|
|
if (continuation_response == -1) {
|
|
if (continuation == '-') /* start continuation */
|
|
continuation_response = result;
|
|
} else { /* continuing */
|
|
if (continuation_response == result &&
|
|
continuation == ' ')
|
|
continuation_response = -1; /* ended */
|
|
}
|
|
if (result == 220 && find_response(broken_ftp_retr)) {
|
|
Broken_RETR = TRUE;
|
|
CTRACE((tfp, "This server is broken (RETR)\n"));
|
|
}
|
|
#ifdef INET6
|
|
if (result == 220 && find_response(broken_ftp_epsv)) {
|
|
Broken_EPSV = TRUE;
|
|
CTRACE((tfp, "This server is broken (EPSV)\n"));
|
|
}
|
|
#endif
|
|
break;
|
|
}
|
|
/* if end of line */
|
|
if (interrupted_in_htgetcharacter) {
|
|
CTRACE((tfp,
|
|
"HTFTP: Interrupted in HTGetCharacter, apparently.\n"));
|
|
NETCLOSE(control->socket);
|
|
control->socket = -1;
|
|
return HT_INTERRUPTED;
|
|
}
|
|
|
|
if (ich == EOF) {
|
|
CTRACE((tfp, "Error on rx: closing socket %d\n",
|
|
control->socket));
|
|
strcpy(response_text, "000 *** TCP read error on response\n");
|
|
close_connection(control);
|
|
return -1; /* End of file on response */
|
|
}
|
|
} /* Loop over characters */
|
|
|
|
} while (continuation_response != -1);
|
|
|
|
if (result == 421) {
|
|
CTRACE((tfp, "HTFTP: They close so we close socket %d\n",
|
|
control->socket));
|
|
close_connection(control);
|
|
return -1;
|
|
}
|
|
if ((result == 255 && server_type == CMS_SERVER) &&
|
|
(0 == strncasecomp(cmd, "CWD", 3) ||
|
|
0 == strcasecomp(cmd, "CDUP"))) {
|
|
/*
|
|
* Alas, CMS returns 255 on failure to CWD to parent of root. - PG
|
|
*/
|
|
result = 555;
|
|
}
|
|
return result / 100;
|
|
}
|
|
|
|
static int send_cmd_1(const char *verb)
|
|
{
|
|
char command[80];
|
|
|
|
sprintf(command, "%.*s%c%c", (int) sizeof(command) - 4, verb, CR, LF);
|
|
return response(command);
|
|
}
|
|
|
|
static int send_cmd_2(const char *verb, const char *param)
|
|
{
|
|
char *command = 0;
|
|
int status;
|
|
|
|
HTSprintf0(&command, "%s %s%c%c", verb, param, CR, LF);
|
|
status = response(command);
|
|
FREE(command);
|
|
|
|
return status;
|
|
}
|
|
|
|
#define send_cwd(path) send_cmd_2("CWD", path)
|
|
|
|
/*
|
|
* This function should try to set the macintosh server into binary mode. Some
|
|
* servers need an additional letter after the MACB command.
|
|
*/
|
|
static int set_mac_binary(eServerType ServerType)
|
|
{
|
|
/* try to set mac binary mode */
|
|
if (ServerType == APPLESHARE_SERVER ||
|
|
ServerType == NETPRESENZ_SERVER) {
|
|
/*
|
|
* Presumably E means "Enable". - KW
|
|
*/
|
|
return (2 == response("MACB E\r\n"));
|
|
} else {
|
|
return (2 == response("MACB\r\n"));
|
|
}
|
|
}
|
|
|
|
/* This function gets the current working directory to help
|
|
* determine what kind of host it is
|
|
*/
|
|
|
|
static void get_ftp_pwd(eServerType *ServerType, BOOLEAN *UseList)
|
|
{
|
|
char *cp;
|
|
|
|
/* get the working directory (to see what it looks like) */
|
|
int status = response("PWD\r\n");
|
|
|
|
if (status < 0) {
|
|
return;
|
|
} else {
|
|
cp = StrChr(response_text + 5, '"');
|
|
if (cp)
|
|
*cp = '\0';
|
|
if (*ServerType == TCPC_SERVER) {
|
|
*ServerType = ((response_text[5] == '/') ?
|
|
NCSA_SERVER : TCPC_SERVER);
|
|
CTRACE((tfp, "HTFTP: Treating as %s server.\n",
|
|
((*ServerType == NCSA_SERVER) ?
|
|
"NCSA" : "TCPC")));
|
|
} else if (response_text[5] == '/') {
|
|
/* path names beginning with / imply Unix,
|
|
* right?
|
|
*/
|
|
if (set_mac_binary(*ServerType)) {
|
|
*ServerType = NCSA_SERVER;
|
|
CTRACE((tfp, "HTFTP: Treating as NCSA server.\n"));
|
|
} else {
|
|
*ServerType = UNIX_SERVER;
|
|
*UseList = TRUE;
|
|
CTRACE((tfp, "HTFTP: Treating as Unix server.\n"));
|
|
}
|
|
return;
|
|
} else if (response_text[strlen(response_text) - 1] == ']') {
|
|
/* path names ending with ] imply VMS, right? */
|
|
*ServerType = VMS_SERVER;
|
|
*UseList = TRUE;
|
|
CTRACE((tfp, "HTFTP: Treating as VMS server.\n"));
|
|
} else {
|
|
*ServerType = GENERIC_SERVER;
|
|
CTRACE((tfp, "HTFTP: Treating as Generic server.\n"));
|
|
}
|
|
|
|
if ((*ServerType == NCSA_SERVER) ||
|
|
(*ServerType == TCPC_SERVER) ||
|
|
(*ServerType == PETER_LEWIS_SERVER) ||
|
|
(*ServerType == NETPRESENZ_SERVER))
|
|
set_mac_binary(*ServerType);
|
|
}
|
|
}
|
|
|
|
/* This function turns MSDOS-like directory output off for
|
|
* Windows NT servers.
|
|
*/
|
|
|
|
static void set_unix_dirstyle(eServerType *ServerType, BOOLEAN *UseList)
|
|
{
|
|
char *cp;
|
|
|
|
/* This is a toggle. It seems we have to toggle in order to see
|
|
* the current state (after toggling), so we may end up toggling
|
|
* twice. - kw
|
|
*/
|
|
int status = response("SITE DIRSTYLE\r\n");
|
|
|
|
if (status != 2) {
|
|
*ServerType = GENERIC_SERVER;
|
|
CTRACE((tfp, "HTFTP: DIRSTYLE failed, treating as Generic server.\n"));
|
|
return;
|
|
} else {
|
|
*UseList = TRUE;
|
|
/* Expecting one of:
|
|
* 200 MSDOS-like directory output is off
|
|
* 200 MSDOS-like directory output is on
|
|
* The following code doesn't look for the full exact string -
|
|
* who knows how the wording may change in some future version.
|
|
* If the first response isn't recognized, we toggle again
|
|
* anyway, under the assumption that it's more likely that
|
|
* the MSDOS setting was "off" originally. - kw
|
|
*/
|
|
cp = strstr(response_text + 4, "MSDOS");
|
|
if (cp && strstr(cp, " off")) {
|
|
return; /* already off now. */
|
|
} else {
|
|
response("SITE DIRSTYLE\r\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
#define CheckForInterrupt(msg) \
|
|
if (status == HT_INTERRUPTED) { \
|
|
CTRACE((tfp, "HTFTP: Interrupted %s.\n", msg)); \
|
|
_HTProgress(CONNECTION_INTERRUPTED); \
|
|
NETCLOSE(control->socket); \
|
|
control->socket = -1; \
|
|
return HT_INTERRUPTED; \
|
|
}
|
|
|
|
/* Get a valid connection to the host
|
|
* ----------------------------------
|
|
*
|
|
* On entry,
|
|
* arg points to the name of the host in a hypertext address
|
|
* On exit,
|
|
* returns <0 if error
|
|
* socket number if success
|
|
*
|
|
* This routine takes care of managing timed-out connections, and
|
|
* limiting the number of connections in use at any one time.
|
|
*
|
|
* It ensures that all connections are logged in if they exist.
|
|
* It ensures they have the port number transferred.
|
|
*/
|
|
static int get_connection(const char *arg,
|
|
HTParentAnchor *anchor)
|
|
{
|
|
int status;
|
|
char *command = 0;
|
|
connection *con;
|
|
char *username = NULL;
|
|
char *password = NULL;
|
|
static BOOLEAN firstuse = TRUE;
|
|
|
|
if (firstuse) {
|
|
/*
|
|
* Set up freeing at exit. - FM
|
|
*/
|
|
#ifdef LY_FIND_LEAKS
|
|
atexit(free_FTPGlobals);
|
|
#endif
|
|
firstuse = FALSE;
|
|
}
|
|
|
|
if (control != 0) {
|
|
connection *next = control->next;
|
|
|
|
if (control->socket != -1) {
|
|
NETCLOSE(control->socket);
|
|
}
|
|
memset(con = control, 0, sizeof(*con));
|
|
con->next = next;
|
|
} else {
|
|
con = typecalloc(connection);
|
|
if (con == NULL)
|
|
outofmem(__FILE__, "get_connection");
|
|
}
|
|
con->socket = -1;
|
|
|
|
if (isEmpty(arg)) {
|
|
free(con);
|
|
return -1; /* Bad if no name specified */
|
|
}
|
|
|
|
/* Get node name:
|
|
*/
|
|
CTRACE((tfp, "get_connection(%s)\n", arg));
|
|
{
|
|
char *p1 = HTParse(arg, "", PARSE_HOST);
|
|
char *p2 = strrchr(p1, '@'); /* user? */
|
|
char *pw = NULL;
|
|
|
|
if (p2 != NULL) {
|
|
username = p1;
|
|
*p2 = '\0'; /* terminate */
|
|
p1 = p2 + 1; /* point to host */
|
|
pw = StrChr(username, ':');
|
|
if (pw != NULL) {
|
|
*pw++ = '\0';
|
|
password = HTUnEscape(pw);
|
|
}
|
|
if (*username)
|
|
HTUnEscape(username);
|
|
|
|
/*
|
|
* If the password doesn't exist then we are going to have to ask
|
|
* the user for it. The only problem is that we don't want to ask
|
|
* for it every time, so we will store away in a primitive fashion.
|
|
*/
|
|
if (!password) {
|
|
char *tmp = NULL;
|
|
|
|
HTSprintf0(&tmp, "%s@%s", username, p1);
|
|
/*
|
|
* If the user@host is not equal to the last time through or
|
|
* user_entered_password has no data then we need to ask the
|
|
* user for the password.
|
|
*/
|
|
if (!last_username_and_host ||
|
|
strcmp(tmp, last_username_and_host) ||
|
|
!user_entered_password) {
|
|
|
|
StrAllocCopy(last_username_and_host, tmp);
|
|
HTSprintf0(&tmp, gettext("Enter password for user %s@%s:"),
|
|
username, p1);
|
|
FREE(user_entered_password);
|
|
user_entered_password = HTPromptPassword(tmp, NULL);
|
|
|
|
} /* else we already know the password */
|
|
password = user_entered_password;
|
|
FREE(tmp);
|
|
}
|
|
}
|
|
|
|
if (!username)
|
|
FREE(p1);
|
|
} /* scope of p1 */
|
|
|
|
status = HTDoConnect(arg, "FTP", IPPORT_FTP, (int *) &con->socket);
|
|
|
|
if (status < 0) {
|
|
if (status == HT_INTERRUPTED) {
|
|
CTRACE((tfp, "HTFTP: Interrupted on connect\n"));
|
|
} else {
|
|
CTRACE((tfp, "HTFTP: Unable to connect to remote host for `%s'.\n",
|
|
arg));
|
|
}
|
|
if (status == HT_INTERRUPTED) {
|
|
_HTProgress(CONNECTION_INTERRUPTED);
|
|
status = HT_NOT_LOADED;
|
|
} else {
|
|
HTAlert(gettext("Unable to connect to FTP host."));
|
|
}
|
|
if (con->socket != -1) {
|
|
NETCLOSE(con->socket);
|
|
}
|
|
|
|
FREE(username);
|
|
if (control == con)
|
|
control = NULL;
|
|
FREE(con);
|
|
return status; /* Bad return */
|
|
}
|
|
|
|
CTRACE((tfp, "FTP connected, socket %d control %p\n",
|
|
con->socket, (void *) con));
|
|
control = con; /* Current control connection */
|
|
|
|
/* Initialise buffering for control connection */
|
|
HTInitInput(control->socket);
|
|
init_help_message_cache(); /* Clear the login message buffer. */
|
|
|
|
/* Now we log in Look up username, prompt for pw.
|
|
*/
|
|
status = response(NULL); /* Get greeting */
|
|
CheckForInterrupt("at beginning of login");
|
|
|
|
server_type = GENERIC_SERVER; /* reset */
|
|
if (status == 2) { /* Send username */
|
|
char *cp; /* look at greeting text */
|
|
|
|
/* don't gettext() this -- incoming text: */
|
|
if (strlen(response_text) > 4) {
|
|
if ((cp = strstr(response_text, " awaits your command")) ||
|
|
(cp = strstr(response_text, " ready."))) {
|
|
*cp = '\0';
|
|
}
|
|
cp = response_text + 4;
|
|
if (!strncasecomp(cp, "NetPresenz", 10))
|
|
server_type = NETPRESENZ_SERVER;
|
|
} else {
|
|
cp = response_text;
|
|
}
|
|
StrAllocCopy(anchor->server, cp);
|
|
|
|
status = send_cmd_2("USER", (username && *username)
|
|
? username
|
|
: "anonymous");
|
|
|
|
CheckForInterrupt("while sending username");
|
|
}
|
|
if (status == 3) { /* Send password */
|
|
if (non_empty(password)) {
|
|
HTSprintf0(&command, "PASS %s%c%c", password, CR, LF);
|
|
} else {
|
|
/*
|
|
* No password was given; use mail-address.
|
|
*/
|
|
const char *the_address;
|
|
char *user = NULL;
|
|
const char *host = NULL;
|
|
char *cp;
|
|
|
|
the_address = anonftp_password;
|
|
if (isEmpty(the_address))
|
|
the_address = personal_mail_address;
|
|
if (isEmpty(the_address))
|
|
the_address = LYGetEnv("USER");
|
|
if (isEmpty(the_address))
|
|
the_address = "WWWuser";
|
|
|
|
StrAllocCopy(user, the_address);
|
|
if ((cp = StrChr(user, '@')) != NULL) {
|
|
*cp++ = '\0';
|
|
if (*cp == '\0')
|
|
host = HTHostName();
|
|
else
|
|
host = cp;
|
|
} else {
|
|
host = HTHostName();
|
|
}
|
|
|
|
/*
|
|
* If host is not fully qualified, suppress it
|
|
* as ftp.uu.net prefers a blank to a bad name
|
|
*/
|
|
if (!(host) || StrChr(host, '.') == NULL)
|
|
host = "";
|
|
|
|
HTSprintf0(&command, "PASS %s@%s%c%c", user, host, CR, LF);
|
|
FREE(user);
|
|
}
|
|
status = response(command);
|
|
FREE(command);
|
|
CheckForInterrupt("while sending password");
|
|
}
|
|
FREE(username);
|
|
|
|
if (status == 3) {
|
|
status = send_cmd_1("ACCT noaccount");
|
|
CheckForInterrupt("while sending password");
|
|
}
|
|
if (status != 2) {
|
|
CTRACE((tfp, "HTFTP: Login fail: %s", response_text));
|
|
/* if (control->socket > 0) close_connection(control->socket); */
|
|
return -1; /* Bad return */
|
|
}
|
|
CTRACE((tfp, "HTFTP: Logged in.\n"));
|
|
|
|
/* Check for host type */
|
|
if (server_type != NETPRESENZ_SERVER)
|
|
server_type = GENERIC_SERVER; /* reset */
|
|
use_list = FALSE; /* reset */
|
|
if (response("SYST\r\n") == 2) {
|
|
/* we got a line -- what kind of server are we talking to? */
|
|
if (StrNCmp(response_text + 4,
|
|
"UNIX Type: L8 MAC-OS MachTen", 28) == 0) {
|
|
server_type = MACHTEN_SERVER;
|
|
use_list = TRUE;
|
|
CTRACE((tfp, "HTFTP: Treating as MachTen server.\n"));
|
|
|
|
} else if (strstr(response_text + 4, "UNIX") != NULL ||
|
|
strstr(response_text + 4, "Unix") != NULL) {
|
|
server_type = UNIX_SERVER;
|
|
unsure_type = FALSE; /* to the best of out knowledge... */
|
|
use_list = TRUE;
|
|
CTRACE((tfp, "HTFTP: Treating as Unix server.\n"));
|
|
|
|
} else if (strstr(response_text + 4, "MSDOS") != NULL) {
|
|
server_type = MSDOS_SERVER;
|
|
use_list = TRUE;
|
|
CTRACE((tfp, "HTFTP: Treating as MSDOS (Unix emulation) server.\n"));
|
|
|
|
} else if (StrNCmp(response_text + 4, "VMS", 3) == 0) {
|
|
char *tilde = strstr(arg, "/~");
|
|
|
|
use_list = TRUE;
|
|
if (tilde != 0
|
|
&& tilde[2] != 0
|
|
&& strstr(response_text + 4, "MadGoat") != 0) {
|
|
server_type = UNIX_SERVER;
|
|
CTRACE((tfp, "HTFTP: Treating VMS as UNIX server.\n"));
|
|
} else {
|
|
server_type = VMS_SERVER;
|
|
CTRACE((tfp, "HTFTP: Treating as VMS server.\n"));
|
|
}
|
|
|
|
} else if ((StrNCmp(response_text + 4, "VM/CMS", 6) == 0) ||
|
|
(StrNCmp(response_text + 4, "VM ", 3) == 0)) {
|
|
server_type = CMS_SERVER;
|
|
use_list = TRUE;
|
|
CTRACE((tfp, "HTFTP: Treating as CMS server.\n"));
|
|
|
|
} else if (StrNCmp(response_text + 4, "DCTS", 4) == 0) {
|
|
server_type = DCTS_SERVER;
|
|
CTRACE((tfp, "HTFTP: Treating as DCTS server.\n"));
|
|
|
|
} else if (strstr(response_text + 4, "MAC-OS TCP/Connect II") != NULL) {
|
|
server_type = TCPC_SERVER;
|
|
CTRACE((tfp, "HTFTP: Looks like a TCPC server.\n"));
|
|
get_ftp_pwd(&server_type, &use_list);
|
|
unsure_type = TRUE;
|
|
|
|
} else if (server_type == NETPRESENZ_SERVER) { /* already set above */
|
|
use_list = TRUE;
|
|
set_mac_binary(server_type);
|
|
CTRACE((tfp, "HTFTP: Treating as NetPresenz (MACOS) server.\n"));
|
|
|
|
} else if (StrNCmp(response_text + 4, "MACOS Peter's Server", 20) == 0) {
|
|
server_type = PETER_LEWIS_SERVER;
|
|
use_list = TRUE;
|
|
set_mac_binary(server_type);
|
|
CTRACE((tfp, "HTFTP: Treating as Peter Lewis (MACOS) server.\n"));
|
|
|
|
} else if (StrNCmp(response_text + 4, "Windows_NT", 10) == 0) {
|
|
server_type = WINDOWS_NT_SERVER;
|
|
CTRACE((tfp, "HTFTP: Treating as Window_NT server.\n"));
|
|
set_unix_dirstyle(&server_type, &use_list);
|
|
|
|
} else if (StrNCmp(response_text + 4, "Windows2000", 11) == 0) {
|
|
server_type = WINDOWS_2K_SERVER;
|
|
CTRACE((tfp, "HTFTP: Treating as Window_2K server.\n"));
|
|
set_unix_dirstyle(&server_type, &use_list);
|
|
|
|
} else if (StrNCmp(response_text + 4, "MS Windows", 10) == 0) {
|
|
server_type = MS_WINDOWS_SERVER;
|
|
use_list = TRUE;
|
|
CTRACE((tfp, "HTFTP: Treating as MS Windows server.\n"));
|
|
|
|
} else if (StrNCmp(response_text + 4,
|
|
"MACOS AppleShare IP FTP Server", 30) == 0) {
|
|
server_type = APPLESHARE_SERVER;
|
|
use_list = TRUE;
|
|
set_mac_binary(server_type);
|
|
CTRACE((tfp, "HTFTP: Treating as AppleShare server.\n"));
|
|
|
|
} else {
|
|
server_type = GENERIC_SERVER;
|
|
CTRACE((tfp, "HTFTP: Ugh! A Generic server.\n"));
|
|
get_ftp_pwd(&server_type, &use_list);
|
|
unsure_type = TRUE;
|
|
}
|
|
} else {
|
|
/* SYST fails :( try to get the type from the PWD command */
|
|
get_ftp_pwd(&server_type, &use_list);
|
|
}
|
|
|
|
return con->socket; /* Good return */
|
|
}
|
|
|
|
static void reset_master_socket(void)
|
|
{
|
|
have_socket = FALSE;
|
|
}
|
|
|
|
static void set_master_socket(int value)
|
|
{
|
|
have_socket = (BOOLEAN) (value >= 0);
|
|
if (have_socket)
|
|
master_socket = (LYNX_FD) value;
|
|
}
|
|
|
|
/* Close Master (listening) socket
|
|
* -------------------------------
|
|
*
|
|
*
|
|
*/
|
|
static int close_master_socket(void)
|
|
{
|
|
int status;
|
|
|
|
if (have_socket)
|
|
FD_CLR(master_socket, &open_sockets);
|
|
|
|
status = NETCLOSE((int) master_socket);
|
|
CTRACE((tfp, "HTFTP: Closed master socket %u\n", (unsigned) master_socket));
|
|
|
|
reset_master_socket();
|
|
|
|
if (status < 0)
|
|
return HTInetStatus(gettext("close master socket"));
|
|
else
|
|
return status;
|
|
}
|
|
|
|
/* Open a master socket for listening on
|
|
* -------------------------------------
|
|
*
|
|
* When data is transferred, we open a port, and wait for the server to
|
|
* connect with the data.
|
|
*
|
|
* On entry,
|
|
* have_socket Must be false, if master_socket is not setup already
|
|
* master_socket Must be negative if not set up already.
|
|
* On exit,
|
|
* Returns socket number if good
|
|
* less than zero if error.
|
|
* master_socket is socket number if good, else negative.
|
|
* port_number is valid if good.
|
|
*/
|
|
static int get_listen_socket(void)
|
|
{
|
|
LY_SOCKADDR soc_A;
|
|
|
|
#ifdef INET6
|
|
unsigned short af;
|
|
LY_SOCKLEN slen;
|
|
#endif /* INET6 */
|
|
int new_socket; /* Will be master_socket */
|
|
|
|
FD_ZERO(&open_sockets); /* Clear our record of open sockets */
|
|
num_sockets = 0;
|
|
|
|
FREE(port_command);
|
|
#ifndef REPEAT_LISTEN
|
|
if (have_socket)
|
|
return master_socket; /* Done already */
|
|
#endif /* !REPEAT_LISTEN */
|
|
|
|
#ifdef INET6
|
|
/* query address family of control connection */
|
|
memset(&soc_A, 0, sizeof(soc_A));
|
|
slen = (LY_SOCKLEN) sizeof(soc_A);
|
|
if (getsockname(control->socket, SOCKADDR_OF(soc_A), &slen) < 0) {
|
|
return HTInetStatus("getsockname failed");
|
|
}
|
|
af = SOCKADDR_OF(soc_A)->sa_family;
|
|
#endif /* INET6 */
|
|
|
|
/* Create internet socket
|
|
*/
|
|
#ifdef INET6
|
|
new_socket = socket(af, SOCK_STREAM, IPPROTO_TCP);
|
|
#else
|
|
new_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
#endif /* INET6 */
|
|
|
|
if (new_socket < 0)
|
|
return HTInetStatus(gettext("socket for master socket"));
|
|
|
|
CTRACE((tfp, "HTFTP: Opened master socket number %d\n", new_socket));
|
|
|
|
/* Search for a free port.
|
|
*/
|
|
#ifdef INET6
|
|
memset(&soc_A, 0, sizeof(soc_A));
|
|
SOCKADDR_OF(soc_A)->sa_family = (unsigned short) af;
|
|
switch (af) {
|
|
case AF_INET:
|
|
#ifdef SIN6_LEN
|
|
SOCKADDR_OF(soc_A)->sa_len = sizeof(struct sockaddr_in);
|
|
#endif /* SIN6_LEN */
|
|
break;
|
|
case AF_INET6:
|
|
#ifdef SIN6_LEN
|
|
SOCKADDR_OF(soc_A)->sa_len = sizeof(struct sockaddr_in6);
|
|
#endif /* SIN6_LEN */
|
|
break;
|
|
default:
|
|
HTInetStatus("AF");
|
|
}
|
|
#else
|
|
soc_A.soc_in.sin_family = AF_INET; /* Family = internet, host order */
|
|
soc_A.soc_in.sin_addr.s_addr = INADDR_ANY; /* Any peer address */
|
|
#endif /* INET6 */
|
|
#ifdef POLL_PORTS
|
|
{
|
|
PortNumber old_port_number = port_number;
|
|
|
|
for (port_number = (old_port_number + 1);; port_number++) {
|
|
int status;
|
|
|
|
if (port_number > LAST_TCP_PORT)
|
|
port_number = FIRST_TCP_PORT;
|
|
if (port_number == old_port_number) {
|
|
return HTInetStatus("bind");
|
|
}
|
|
#ifdef INET6
|
|
soc_A.soc_in.sin_port = htons(port_number);
|
|
#else
|
|
soc_A.sin_port = htons(port_number);
|
|
#endif /* INET6 */
|
|
#ifdef SOCKS
|
|
if (socks_flag)
|
|
if ((status = Rbind(new_socket,
|
|
SOCKADDR_OF(soc_A),
|
|
SOCKADDR_LEN(soc_A))) == 0) {
|
|
break;
|
|
} else
|
|
#endif /* SOCKS */
|
|
if ((status = bind(new_socket,
|
|
SOCKADDR_OF(soc_A),
|
|
SOCKADDR_LEN(soc_A)
|
|
)) == 0) {
|
|
break;
|
|
}
|
|
CTRACE((tfp, "TCP bind attempt to port %d yields %d, errno=%d\n",
|
|
port_number, status, SOCKET_ERRNO));
|
|
} /* for */
|
|
}
|
|
#else
|
|
{
|
|
int status;
|
|
LY_SOCKLEN address_length = (LY_SOCKLEN) sizeof(soc_A);
|
|
|
|
#ifdef SOCKS
|
|
if (socks_flag)
|
|
status = Rgetsockname(control->socket,
|
|
SOCKADDR_OF(soc_A),
|
|
&address_length);
|
|
else
|
|
#endif /* SOCKS */
|
|
status = getsockname(control->socket,
|
|
SOCKADDR_OF(soc_A),
|
|
&address_length);
|
|
if (status < 0) {
|
|
close(new_socket);
|
|
return HTInetStatus("getsockname");
|
|
}
|
|
CTRACE((tfp, "HTFTP: This host is %s\n",
|
|
HTInetString((void *) &soc_A.soc_in)));
|
|
|
|
soc_A.soc_in.sin_port = 0; /* Unspecified: please allocate */
|
|
#ifdef SOCKS
|
|
if (socks_flag)
|
|
status = Rbind(new_socket,
|
|
SOCKADDR_OF(soc_A),
|
|
sizeof(soc_A));
|
|
else
|
|
#endif /* SOCKS */
|
|
status = bind(new_socket,
|
|
SOCKADDR_OF(soc_A),
|
|
SOCKADDR_LEN(soc_A));
|
|
if (status < 0) {
|
|
close(new_socket);
|
|
return HTInetStatus("bind");
|
|
}
|
|
|
|
address_length = sizeof(soc_A);
|
|
#ifdef SOCKS
|
|
if (socks_flag)
|
|
status = Rgetsockname(new_socket,
|
|
SOCKADDR_OF(soc_A),
|
|
&address_length);
|
|
else
|
|
#endif /* SOCKS */
|
|
status = getsockname(new_socket,
|
|
SOCKADDR_OF(soc_A),
|
|
&address_length);
|
|
if (status < 0) {
|
|
close(new_socket);
|
|
return HTInetStatus("getsockname");
|
|
}
|
|
}
|
|
#endif /* POLL_PORTS */
|
|
|
|
CTRACE((tfp, "HTFTP: bound to port %d on %s\n",
|
|
(int) ntohs(soc_A.soc_in.sin_port),
|
|
HTInetString((void *) &soc_A.soc_in)));
|
|
|
|
#ifdef REPEAT_LISTEN
|
|
if (have_socket)
|
|
(void) close_master_socket();
|
|
#endif /* REPEAT_LISTEN */
|
|
|
|
set_master_socket(new_socket);
|
|
|
|
/* Now we must find out who we are to tell the other guy
|
|
*/
|
|
(void) HTHostName(); /* Make address valid - doesn't work */
|
|
#ifdef INET6
|
|
switch (SOCKADDR_OF(soc_A)->sa_family) {
|
|
case AF_INET:
|
|
#endif /* INET6 */
|
|
HTSprintf0(&port_command, "PORT %d,%d,%d,%d,%d,%d%c%c",
|
|
(int) *((unsigned char *) (&soc_A.soc_in.sin_addr) + 0),
|
|
(int) *((unsigned char *) (&soc_A.soc_in.sin_addr) + 1),
|
|
(int) *((unsigned char *) (&soc_A.soc_in.sin_addr) + 2),
|
|
(int) *((unsigned char *) (&soc_A.soc_in.sin_addr) + 3),
|
|
(int) *((unsigned char *) (&soc_A.soc_in.sin_port) + 0),
|
|
(int) *((unsigned char *) (&soc_A.soc_in.sin_port) + 1),
|
|
CR, LF);
|
|
|
|
#ifdef INET6
|
|
break;
|
|
|
|
case AF_INET6:
|
|
{
|
|
char hostbuf[MAXHOSTNAMELEN];
|
|
char portbuf[MAXHOSTNAMELEN];
|
|
|
|
getnameinfo(SOCKADDR_OF(soc_A),
|
|
SOCKADDR_LEN(soc_A),
|
|
hostbuf,
|
|
(socklen_t) sizeof(hostbuf),
|
|
portbuf,
|
|
(socklen_t) sizeof(portbuf),
|
|
NI_NUMERICHOST | NI_NUMERICSERV);
|
|
HTSprintf0(&port_command, "EPRT |%d|%s|%s|%c%c", 2, hostbuf, portbuf,
|
|
CR, LF);
|
|
break;
|
|
}
|
|
default:
|
|
HTSprintf0(&port_command, "JUNK%c%c", CR, LF);
|
|
break;
|
|
}
|
|
#endif /* INET6 */
|
|
if (port_command == NULL)
|
|
return -1;
|
|
|
|
/* Inform TCP that we will accept connections
|
|
*/
|
|
{
|
|
int status;
|
|
|
|
#ifdef SOCKS
|
|
if (socks_flag)
|
|
status = Rlisten((int) master_socket, 1);
|
|
else
|
|
#endif /* SOCKS */
|
|
status = listen((int) master_socket, 1);
|
|
if (status < 0) {
|
|
reset_master_socket();
|
|
return HTInetStatus("listen");
|
|
}
|
|
}
|
|
CTRACE((tfp, "TCP: Master socket(), bind() and listen() all OK\n"));
|
|
FD_SET(master_socket, &open_sockets);
|
|
if ((master_socket + 1) > num_sockets)
|
|
num_sockets = master_socket + 1;
|
|
|
|
return (int) master_socket; /* Good */
|
|
|
|
} /* get_listen_socket */
|
|
|
|
static const char *months[12] =
|
|
{
|
|
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
|
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
|
|
};
|
|
|
|
/* Procedure: Set the current and last year strings and date integer
|
|
* -----------------------------------------------------------------
|
|
*
|
|
* Bug:
|
|
* This code is for sorting listings by date, if that option
|
|
* is selected in Lynx, and doesn't take into account time
|
|
* zones or ensure resetting at midnight, so the sort may not
|
|
* be perfect, but the actual date isn't changed in the display,
|
|
* i.e., the date is still correct. - FM
|
|
*/
|
|
static void set_years_and_date(void)
|
|
{
|
|
char day[8], month[8], date[12];
|
|
time_t NowTime;
|
|
int i;
|
|
char *printable;
|
|
|
|
NowTime = time(NULL);
|
|
printable = ctime(&NowTime);
|
|
LYStrNCpy(day, printable + 8, 2);
|
|
if (day[0] == ' ') {
|
|
day[0] = '0';
|
|
}
|
|
LYStrNCpy(month, printable + 4, 3);
|
|
for (i = 0; i < 12; i++) {
|
|
if (!strcasecomp(month, months[i])) {
|
|
break;
|
|
}
|
|
}
|
|
i++;
|
|
sprintf(date, "9999%02d%.2s", i % 100, day);
|
|
TheDate = atoi(date);
|
|
LYStrNCpy(ThisYear, printable + 20, 4);
|
|
sprintf(LastYear, "%d", (atoi(ThisYear) - 1) % 10000);
|
|
HaveYears = TRUE;
|
|
}
|
|
|
|
typedef struct _EntryInfo {
|
|
char *filename;
|
|
char *linkname; /* symbolic link, if any */
|
|
char *type;
|
|
char *date;
|
|
off_t size;
|
|
BOOLEAN display; /* show this entry? */
|
|
#ifdef LONG_LIST
|
|
unsigned long file_links;
|
|
char *file_mode;
|
|
char *file_user;
|
|
char *file_group;
|
|
#endif
|
|
} EntryInfo;
|
|
|
|
static void free_entryinfo_struct_contents(EntryInfo *entry_info)
|
|
{
|
|
if (entry_info) {
|
|
#ifdef LONG_LIST
|
|
FREE(entry_info->file_mode);
|
|
FREE(entry_info->file_user);
|
|
FREE(entry_info->file_group);
|
|
#endif
|
|
FREE(entry_info->filename);
|
|
FREE(entry_info->linkname);
|
|
FREE(entry_info->type);
|
|
FREE(entry_info->date);
|
|
}
|
|
/* don't free the struct */
|
|
}
|
|
|
|
/*
|
|
* is_ls_date() --
|
|
* Return TRUE if s points to a string of the form:
|
|
* "Sep 1 1990 " or
|
|
* "Sep 11 11:59 " or
|
|
* "Dec 12 1989 " or
|
|
* "FCv 23 1990 " ...
|
|
*/
|
|
static BOOLEAN is_ls_date(char *s)
|
|
{
|
|
/* must start with three alpha characters */
|
|
if (!isalpha(UCH(*s++)) || !isalpha(UCH(*s++)) || !isalpha(UCH(*s++)))
|
|
return FALSE;
|
|
|
|
/* space or HT_NON_BREAK_SPACE */
|
|
if (!(*s == ' ' || *s == HT_NON_BREAK_SPACE)) {
|
|
return FALSE;
|
|
}
|
|
s++;
|
|
|
|
/* space or digit */
|
|
if (!(*s == ' ' || isdigit(UCH(*s)))) {
|
|
return FALSE;
|
|
}
|
|
s++;
|
|
|
|
/* digit */
|
|
if (!isdigit(UCH(*s++)))
|
|
return FALSE;
|
|
|
|
/* space */
|
|
if (*s++ != ' ')
|
|
return FALSE;
|
|
|
|
/* space or digit */
|
|
if (!(*s == ' ' || isdigit(UCH(*s)))) {
|
|
return FALSE;
|
|
}
|
|
s++;
|
|
|
|
/* digit */
|
|
if (!isdigit(UCH(*s++)))
|
|
return FALSE;
|
|
|
|
/* colon or digit */
|
|
if (!(*s == ':' || isdigit(UCH(*s)))) {
|
|
return FALSE;
|
|
}
|
|
s++;
|
|
|
|
/* digit */
|
|
if (!isdigit(UCH(*s++)))
|
|
return FALSE;
|
|
|
|
/* space or digit */
|
|
if (!(*s == ' ' || isdigit(UCH(*s)))) {
|
|
return FALSE;
|
|
}
|
|
s++;
|
|
|
|
/* space */
|
|
if (*s != ' ')
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
} /* is_ls_date() */
|
|
|
|
/*
|
|
* Extract the name, size, and date from an EPLF line. - 08-06-96 DJB
|
|
*/
|
|
static void parse_eplf_line(char *line,
|
|
EntryInfo *info)
|
|
{
|
|
char *cp = line;
|
|
char ct[26];
|
|
off_t size;
|
|
time_t secs;
|
|
static time_t base; /* time() value on this OS in 1970 */
|
|
static int flagbase = 0;
|
|
|
|
if (!flagbase) {
|
|
struct tm t;
|
|
|
|
t.tm_year = 70;
|
|
t.tm_mon = 0;
|
|
t.tm_mday = 0;
|
|
t.tm_hour = 0;
|
|
t.tm_min = 0;
|
|
t.tm_sec = 0;
|
|
t.tm_isdst = -1;
|
|
base = mktime(&t); /* could return -1 */
|
|
flagbase = 1;
|
|
}
|
|
|
|
while (*cp) {
|
|
switch (*cp) {
|
|
case '\t':
|
|
StrAllocCopy(info->filename, cp + 1);
|
|
return;
|
|
case 's':
|
|
size = 0;
|
|
while (*(++cp) && (*cp != ','))
|
|
size = (size * 10) + (off_t) (*cp - '0');
|
|
info->size = size;
|
|
break;
|
|
case 'm':
|
|
secs = 0;
|
|
while (*(++cp) && (*cp != ','))
|
|
secs = (secs * 10) + (*cp - '0');
|
|
secs += base; /* assumes that time_t is #seconds */
|
|
LYStrNCpy(ct, ctime(&secs), 24);
|
|
StrAllocCopy(info->date, ct);
|
|
break;
|
|
case '/':
|
|
StrAllocCopy(info->type, ENTRY_IS_DIRECTORY);
|
|
/* FALLTHRU */
|
|
default:
|
|
while (*cp) {
|
|
if (*cp++ == ',')
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
} /* parse_eplf_line */
|
|
|
|
/*
|
|
* Extract the name, size, and date from an ls -l line.
|
|
*/
|
|
static void parse_ls_line(char *line,
|
|
EntryInfo *entry)
|
|
{
|
|
#ifdef LONG_LIST
|
|
char *next;
|
|
char *cp;
|
|
#endif
|
|
int i, j;
|
|
off_t base = 1;
|
|
off_t size_num = 0;
|
|
|
|
for (i = (int) strlen(line) - 1;
|
|
(i > 13) && (!isspace(UCH(line[i])) || !is_ls_date(&line[i - 12]));
|
|
i--) {
|
|
; /* null body */
|
|
}
|
|
line[i] = '\0';
|
|
if (i > 13) {
|
|
StrAllocCopy(entry->date, &line[i - 12]);
|
|
/* replace the 4th location with nbsp if it is a space or zero */
|
|
if (entry->date[4] == ' ' || entry->date[4] == '0')
|
|
entry->date[4] = HT_NON_BREAK_SPACE;
|
|
/* make sure year or time is flush right */
|
|
if (entry->date[11] == ' ') {
|
|
for (j = 11; j > 6; j--) {
|
|
entry->date[j] = entry->date[j - 1];
|
|
}
|
|
}
|
|
}
|
|
j = i - 14;
|
|
while (isdigit(UCH(line[j]))) {
|
|
size_num += ((off_t) (line[j] - '0') * base);
|
|
base *= 10;
|
|
j--;
|
|
}
|
|
entry->size = size_num;
|
|
StrAllocCopy(entry->filename, &line[i + 1]);
|
|
|
|
#ifdef LONG_LIST
|
|
line[j] = '\0';
|
|
|
|
/*
|
|
* Extract the file-permissions, as a string.
|
|
*/
|
|
if ((cp = StrChr(line, ' ')) != 0) {
|
|
if ((cp - line) == 10) {
|
|
*cp = '\0';
|
|
StrAllocCopy(entry->file_mode, line);
|
|
*cp = ' ';
|
|
}
|
|
|
|
/*
|
|
* Next is the link-count.
|
|
*/
|
|
next = 0;
|
|
entry->file_links = (unsigned long) strtol(cp, &next, 10);
|
|
if (next == 0 || *next != ' ') {
|
|
entry->file_links = 0;
|
|
next = cp;
|
|
} else {
|
|
cp = next;
|
|
}
|
|
/*
|
|
* Next is the user-name.
|
|
*/
|
|
while (isspace(UCH(*cp)))
|
|
++cp;
|
|
if ((next = StrChr(cp, ' ')) != 0)
|
|
*next = '\0';
|
|
if (*cp != '\0')
|
|
StrAllocCopy(entry->file_user, cp);
|
|
/*
|
|
* Next is the group-name (perhaps).
|
|
*/
|
|
if (next != NULL) {
|
|
cp = (next + 1);
|
|
while (isspace(UCH(*cp)))
|
|
++cp;
|
|
if ((next = StrChr(cp, ' ')) != 0)
|
|
*next = '\0';
|
|
if (*cp != '\0')
|
|
StrAllocCopy(entry->file_group, cp);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Extract the name and size info and whether it refers to a directory from a
|
|
* LIST line in "dls" format.
|
|
*/
|
|
static void parse_dls_line(char *line,
|
|
EntryInfo *entry_info,
|
|
char **pspilledname)
|
|
{
|
|
short j;
|
|
int base = 1;
|
|
off_t size_num = 0;
|
|
int len;
|
|
char *cps = NULL;
|
|
|
|
/* README 763 Information about this server\0
|
|
bin/ - \0
|
|
etc/ = \0
|
|
ls-lR 0 \0
|
|
ls-lR.Z 3 \0
|
|
pub/ = Public area\0
|
|
usr/ - \0
|
|
morgan 14 -> ../real/morgan\0
|
|
TIMIT.mostlikely.Z\0
|
|
79215 \0
|
|
*/
|
|
|
|
len = (int) strlen(line);
|
|
if (len == 0) {
|
|
FREE(*pspilledname);
|
|
entry_info->display = FALSE;
|
|
return;
|
|
}
|
|
cps = LYSkipNonBlanks(line);
|
|
if (*cps == '\0') { /* only a filename, save it and return. */
|
|
StrAllocCopy(*pspilledname, line);
|
|
entry_info->display = FALSE;
|
|
return;
|
|
}
|
|
if (len < 24 || line[23] != ' ' ||
|
|
(isspace(UCH(line[0])) && !*pspilledname)) {
|
|
/* this isn't the expected "dls" format! */
|
|
if (!isspace(UCH(line[0])))
|
|
*cps = '\0';
|
|
if (*pspilledname && !*line) {
|
|
entry_info->filename = *pspilledname;
|
|
*pspilledname = NULL;
|
|
if (entry_info->filename[strlen(entry_info->filename) - 1] == '/')
|
|
StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
|
|
else
|
|
StrAllocCopy(entry_info->type, "");
|
|
} else {
|
|
StrAllocCopy(entry_info->filename, line);
|
|
if (cps != line && *(cps - 1) == '/')
|
|
StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
|
|
else
|
|
StrAllocCopy(entry_info->type, "");
|
|
FREE(*pspilledname);
|
|
}
|
|
return;
|
|
}
|
|
|
|
j = 22;
|
|
if (line[j] == '=' || line[j] == '-') {
|
|
StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
|
|
} else {
|
|
while (isdigit(UCH(line[j]))) {
|
|
size_num += (line[j] - '0') * base;
|
|
base *= 10;
|
|
j--;
|
|
}
|
|
}
|
|
entry_info->size = size_num;
|
|
|
|
cps = LYSkipBlanks(&line[23]);
|
|
if (!StrNCmp(cps, "-> ", 3) && cps[3] != '\0' && cps[3] != ' ') {
|
|
StrAllocCopy(entry_info->type, ENTRY_IS_SYMBOLIC_LINK);
|
|
StrAllocCopy(entry_info->linkname, LYSkipBlanks(cps + 3));
|
|
entry_info->size = 0; /* don't display size */
|
|
}
|
|
|
|
if (j > 0)
|
|
line[j] = '\0';
|
|
|
|
LYTrimTrailing(line);
|
|
|
|
len = (int) strlen(line);
|
|
if (len == 0 && *pspilledname && **pspilledname) {
|
|
line = *pspilledname;
|
|
len = (int) strlen(*pspilledname);
|
|
}
|
|
if (len > 0 && line[len - 1] == '/') {
|
|
/*
|
|
* It's a dir, remove / and mark it as such.
|
|
*/
|
|
if (len > 1)
|
|
line[len - 1] = '\0';
|
|
if (!entry_info->type)
|
|
StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
|
|
}
|
|
|
|
StrAllocCopy(entry_info->filename, line);
|
|
FREE(*pspilledname);
|
|
} /* parse_dls_line() */
|
|
|
|
/*
|
|
* parse_vms_dir_entry()
|
|
* Format the name, date, and size from a VMS LIST line
|
|
* into the EntryInfo structure - FM
|
|
*/
|
|
static void parse_vms_dir_entry(char *line,
|
|
EntryInfo *entry_info)
|
|
{
|
|
int i, j;
|
|
off_t ialloc;
|
|
char *cp, *cpd, *cps, date[16];
|
|
const char *sp = " ";
|
|
|
|
/* Get rid of blank lines, and information lines. Valid lines have the ';'
|
|
* version number token.
|
|
*/
|
|
if (!strlen(line) || (cp = StrChr(line, ';')) == NULL) {
|
|
entry_info->display = FALSE;
|
|
return;
|
|
}
|
|
|
|
/* Cut out file or directory name at VMS version number. */
|
|
*cp++ = '\0';
|
|
StrAllocCopy(entry_info->filename, line);
|
|
|
|
/* Cast VMS non-README file and directory names to lowercase. */
|
|
if (strstr(entry_info->filename, "READ") == NULL) {
|
|
LYLowerCase(entry_info->filename);
|
|
i = (int) strlen(entry_info->filename);
|
|
} else {
|
|
i = (int) ((strstr(entry_info->filename, "READ")
|
|
- entry_info->filename)
|
|
+ 4);
|
|
if (!StrNCmp(&entry_info->filename[i], "ME", 2)) {
|
|
i += 2;
|
|
while (entry_info->filename[i] && entry_info->filename[i] != '.') {
|
|
i++;
|
|
}
|
|
} else if (!StrNCmp(&entry_info->filename[i], ".ME", 3)) {
|
|
i = (int) strlen(entry_info->filename);
|
|
} else {
|
|
i = 0;
|
|
}
|
|
LYLowerCase(entry_info->filename + i);
|
|
}
|
|
|
|
/* Uppercase terminal .z's or _z's. */
|
|
if ((--i > 2) &&
|
|
entry_info->filename[i] == 'z' &&
|
|
(entry_info->filename[i - 1] == '.' ||
|
|
entry_info->filename[i - 1] == '_'))
|
|
entry_info->filename[i] = 'Z';
|
|
|
|
/* Convert any tabs in rest of line to spaces. */
|
|
cps = cp - 1;
|
|
while ((cps = StrChr(cps + 1, '\t')) != NULL)
|
|
*cps = ' ';
|
|
|
|
/* Collapse serial spaces. */
|
|
i = 0;
|
|
j = 1;
|
|
cps = cp;
|
|
while (cps[j] != '\0') {
|
|
if (cps[i] == ' ' && cps[j] == ' ')
|
|
j++;
|
|
else
|
|
cps[++i] = cps[j++];
|
|
}
|
|
cps[++i] = '\0';
|
|
|
|
/* Set the years and date, if we don't have them yet. * */
|
|
if (!HaveYears) {
|
|
set_years_and_date();
|
|
}
|
|
|
|
/* Track down the date. */
|
|
if ((cpd = StrChr(cp, '-')) != NULL &&
|
|
strlen(cpd) > 9 && isdigit(UCH(*(cpd - 1))) &&
|
|
isalpha(UCH(*(cpd + 1))) && *(cpd + 4) == '-') {
|
|
|
|
/* Month */
|
|
*(cpd + 2) = (char) TOLOWER(*(cpd + 2));
|
|
*(cpd + 3) = (char) TOLOWER(*(cpd + 3));
|
|
sprintf(date, "%.3s ", cpd + 1);
|
|
|
|
/* Day */
|
|
if (isdigit(UCH(*(cpd - 2))))
|
|
sprintf(date + 4, "%.2s ", cpd - 2);
|
|
else
|
|
sprintf(date + 4, "%c%.1s ", HT_NON_BREAK_SPACE, cpd - 1);
|
|
|
|
/* Time or Year */
|
|
if (!StrNCmp(ThisYear, cpd + 5, 4) &&
|
|
strlen(cpd) > 15 && *(cpd + 12) == ':') {
|
|
sprintf(date + 7, "%.5s", cpd + 10);
|
|
} else {
|
|
sprintf(date + 7, " %.4s", cpd + 5);
|
|
}
|
|
|
|
StrAllocCopy(entry_info->date, date);
|
|
}
|
|
|
|
/* Track down the size */
|
|
if ((cpd = StrChr(cp, '/')) != NULL) {
|
|
/* Appears be in used/allocated format */
|
|
cps = cpd;
|
|
while (isdigit(UCH(*(cps - 1))))
|
|
cps--;
|
|
if (cps < cpd)
|
|
*cpd = '\0';
|
|
entry_info->size = LYatoll(cps);
|
|
cps = cpd + 1;
|
|
while (isdigit(UCH(*cps)))
|
|
cps++;
|
|
*cps = '\0';
|
|
ialloc = LYatoll(cpd + 1);
|
|
/* Check if used is in blocks or bytes */
|
|
if (entry_info->size <= ialloc)
|
|
entry_info->size *= 512;
|
|
|
|
} else if (strtok(cp, sp) != NULL) {
|
|
/* We just initialized on the version number */
|
|
/* Now let's hunt for a lone, size number */
|
|
while ((cps = strtok(NULL, sp)) != NULL) {
|
|
cpd = cps;
|
|
while (isdigit(UCH(*cpd)))
|
|
cpd++;
|
|
if (*cpd == '\0') {
|
|
/* Assume it's blocks */
|
|
entry_info->size = (LYatoll(cps) * 512);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
TRACE_ENTRY("VMS", entry_info);
|
|
return;
|
|
} /* parse_vms_dir_entry() */
|
|
|
|
/*
|
|
* parse_ms_windows_dir_entry() --
|
|
* Format the name, date, and size from an MS_WINDOWS LIST line into
|
|
* the EntryInfo structure (assumes Chameleon NEWT format). - FM
|
|
*/
|
|
static void parse_ms_windows_dir_entry(char *line,
|
|
EntryInfo *entry_info)
|
|
{
|
|
char *cp = line;
|
|
char *cps, *cpd, date[16];
|
|
char *end = line + strlen(line);
|
|
|
|
/* Get rid of blank or junk lines. */
|
|
cp = LYSkipBlanks(cp);
|
|
if (!(*cp)) {
|
|
entry_info->display = FALSE;
|
|
return;
|
|
}
|
|
|
|
/* Cut out file or directory name. */
|
|
cps = LYSkipNonBlanks(cp);
|
|
*cps++ = '\0';
|
|
cpd = cps;
|
|
StrAllocCopy(entry_info->filename, cp);
|
|
|
|
/* Track down the size */
|
|
if (cps < end) {
|
|
cps = LYSkipBlanks(cps);
|
|
cpd = LYSkipNonBlanks(cps);
|
|
*cpd++ = '\0';
|
|
if (isdigit(UCH(*cps))) {
|
|
entry_info->size = LYatoll(cps);
|
|
} else {
|
|
StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
|
|
}
|
|
} else {
|
|
StrAllocCopy(entry_info->type, "");
|
|
}
|
|
|
|
/* Set the years and date, if we don't have them yet. * */
|
|
if (!HaveYears) {
|
|
set_years_and_date();
|
|
}
|
|
|
|
/* Track down the date. */
|
|
if (cpd < end) {
|
|
cpd = LYSkipBlanks(cpd);
|
|
if (strlen(cpd) > 17) {
|
|
*(cpd + 6) = '\0'; /* Month and Day */
|
|
*(cpd + 11) = '\0'; /* Year */
|
|
*(cpd + 17) = '\0'; /* Time */
|
|
if (strcmp(ThisYear, cpd + 7))
|
|
/* Not this year, so show the year */
|
|
sprintf(date, "%.6s %.4s", cpd, (cpd + 7));
|
|
else
|
|
/* Is this year, so show the time */
|
|
sprintf(date, "%.6s %.5s", cpd, (cpd + 12));
|
|
StrAllocCopy(entry_info->date, date);
|
|
if (entry_info->date[4] == ' ' || entry_info->date[4] == '0') {
|
|
entry_info->date[4] = HT_NON_BREAK_SPACE;
|
|
}
|
|
}
|
|
}
|
|
|
|
TRACE_ENTRY("MS Windows", entry_info);
|
|
return;
|
|
} /* parse_ms_windows_dir_entry */
|
|
|
|
/*
|
|
* parse_windows_nt_dir_entry() --
|
|
* Format the name, date, and size from a WINDOWS_NT LIST line into
|
|
* the EntryInfo structure (assumes Chameleon NEWT format). - FM
|
|
*/
|
|
#ifdef NOTDEFINED
|
|
static void parse_windows_nt_dir_entry(char *line,
|
|
EntryInfo *entry_info)
|
|
{
|
|
char *cp = line;
|
|
char *cps, *cpd, date[16];
|
|
char *end = line + strlen(line);
|
|
int i;
|
|
|
|
/* Get rid of blank or junk lines. */
|
|
cp = LYSkipBlanks(cp);
|
|
if (!(*cp)) {
|
|
entry_info->display = FALSE;
|
|
return;
|
|
}
|
|
|
|
/* Cut out file or directory name. */
|
|
cpd = cp;
|
|
cps = LYSkipNonBlanks(end - 1);
|
|
cp = (cps + 1);
|
|
if (!strcmp(cp, ".") || !strcmp(cp, "..")) {
|
|
entry_info->display = FALSE;
|
|
return;
|
|
}
|
|
StrAllocCopy(entry_info->filename, cp);
|
|
if (cps < cpd)
|
|
return;
|
|
*cp = '\0';
|
|
end = cp;
|
|
|
|
/* Set the years and date, if we don't have them yet. * */
|
|
if (!HaveYears) {
|
|
set_years_and_date();
|
|
}
|
|
|
|
/* Cut out the date. */
|
|
cp = cps = cpd;
|
|
cps = LYSkipNonBlanks(cps);
|
|
*cps++ = '\0';
|
|
if (cps > end) {
|
|
entry_info->display = FALSE;
|
|
return;
|
|
}
|
|
cps = LYSkipBlanks(cps);
|
|
cpd = LYSkipNonBlanks(cps);
|
|
*cps++ = '\0';
|
|
if (cps > end || cpd == cps || strlen(cpd) < 7) {
|
|
entry_info->display = FALSE;
|
|
return;
|
|
}
|
|
if (strlen(cp) == 8 &&
|
|
isdigit(*cp) && isdigit(*(cp + 1)) && *(cp + 2) == '-' &&
|
|
isdigit(*(cp + 3)) && isdigit(*(cp + 4)) && *(cp + 5) == '-') {
|
|
*(cp + 2) = '\0'; /* Month */
|
|
i = atoi(cp) - 1;
|
|
*(cp + 5) = '\0'; /* Day */
|
|
sprintf(date, "%.3s %.2s", months[i], (cp + 3));
|
|
if (date[4] == '0')
|
|
date[4] = ' ';
|
|
cp += 6; /* Year */
|
|
if (strcmp((ThisYear + 2), cp)) {
|
|
/* Not this year, so show the year */
|
|
if (atoi(cp) < 70) {
|
|
sprintf(&date[6], " 20%.2s", cp);
|
|
} else {
|
|
sprintf(&date[6], " 19%.2s", cp);
|
|
}
|
|
} else {
|
|
/* Is this year, so show the time */
|
|
*(cpd + 2) = '\0'; /* Hour */
|
|
i = atoi(cpd);
|
|
if (*(cpd + 5) == 'P' || *(cpd + 5) == 'p')
|
|
i += 12;
|
|
sprintf(&date[6], " %02d:%.2s", i, (cpd + 3));
|
|
}
|
|
StrAllocCopy(entry_info->date, date);
|
|
if (entry_info->date[4] == ' ' || entry_info->date[4] == '0') {
|
|
entry_info->date[4] = HT_NON_BREAK_SPACE;
|
|
}
|
|
}
|
|
|
|
/* Track down the size */
|
|
if (cps < end) {
|
|
cps = LYSkipBlanks(cps);
|
|
cpd = LYSkipNonBlanks(cps);
|
|
*cpd = '\0';
|
|
if (isdigit(*cps)) {
|
|
entry_info->size = LYatoll(cps);
|
|
} else {
|
|
StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
|
|
}
|
|
} else {
|
|
StrAllocCopy(entry_info->type, "");
|
|
}
|
|
|
|
/* Wrap it up */
|
|
CTRACE((tfp, "HTFTP: Windows NT filename: %s date: %s size: %d\n",
|
|
entry_info->filename,
|
|
NonNull(entry_info->date),
|
|
entry_info->size));
|
|
return;
|
|
} /* parse_windows_nt_dir_entry */
|
|
#endif /* NOTDEFINED */
|
|
|
|
/*
|
|
* parse_cms_dir_entry() --
|
|
* Format the name, date, and size from a VM/CMS line into
|
|
* the EntryInfo structure. - FM
|
|
*/
|
|
static void parse_cms_dir_entry(char *line,
|
|
EntryInfo *entry_info)
|
|
{
|
|
char *cp = line;
|
|
char *cps, *cpd, date[16];
|
|
char *end = line + strlen(line);
|
|
int RecordLength = 0;
|
|
int Records = 0;
|
|
int i;
|
|
|
|
/* Get rid of blank or junk lines. */
|
|
cp = LYSkipBlanks(cp);
|
|
if (!(*cp)) {
|
|
entry_info->display = FALSE;
|
|
return;
|
|
}
|
|
|
|
/* Cut out file or directory name. */
|
|
cps = LYSkipNonBlanks(cp);
|
|
*cps++ = '\0';
|
|
StrAllocCopy(entry_info->filename, cp);
|
|
if (StrChr(entry_info->filename, '.') != NULL)
|
|
/* If we already have a dot, we did an NLST. */
|
|
return;
|
|
cp = LYSkipBlanks(cps);
|
|
if (!(*cp)) {
|
|
/* If we don't have more, we've misparsed. */
|
|
FREE(entry_info->filename);
|
|
FREE(entry_info->type);
|
|
entry_info->display = FALSE;
|
|
return;
|
|
}
|
|
cps = LYSkipNonBlanks(cp);
|
|
*cps++ = '\0';
|
|
if ((0 == strcasecomp(cp, "DIR")) && (cp - line) > 17) {
|
|
/* It's an SFS directory. */
|
|
StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
|
|
entry_info->size = 0;
|
|
} else {
|
|
/* It's a file. */
|
|
cp--;
|
|
*cp = '.';
|
|
StrAllocCat(entry_info->filename, cp);
|
|
|
|
/* Track down the VM/CMS RECFM or type. */
|
|
cp = cps;
|
|
if (cp < end) {
|
|
cp = LYSkipBlanks(cp);
|
|
cps = LYSkipNonBlanks(cp);
|
|
*cps++ = '\0';
|
|
/* Check cp here, if it's relevant someday. */
|
|
}
|
|
}
|
|
|
|
/* Track down the record length or dash. */
|
|
cp = cps;
|
|
if (cp < end) {
|
|
cp = LYSkipBlanks(cp);
|
|
cps = LYSkipNonBlanks(cp);
|
|
*cps++ = '\0';
|
|
if (isdigit(UCH(*cp))) {
|
|
RecordLength = atoi(cp);
|
|
}
|
|
}
|
|
|
|
/* Track down the number of records or the dash. */
|
|
cp = cps;
|
|
if (cps < end) {
|
|
cp = LYSkipBlanks(cp);
|
|
cps = LYSkipNonBlanks(cp);
|
|
*cps++ = '\0';
|
|
if (isdigit(UCH(*cp))) {
|
|
Records = atoi(cp);
|
|
}
|
|
if (Records > 0 && RecordLength > 0) {
|
|
/* Compute an approximate size. */
|
|
entry_info->size = ((off_t) Records * (off_t) RecordLength);
|
|
}
|
|
}
|
|
|
|
/* Set the years and date, if we don't have them yet. */
|
|
if (!HaveYears) {
|
|
set_years_and_date();
|
|
}
|
|
|
|
/* Track down the date using the ":" separating hours/minutes:
|
|
* mm/dd/yy hh:mm
|
|
* 01234567890123
|
|
*/
|
|
cpd = cps;
|
|
if (((cps < end) &&
|
|
(cps = StrChr(cpd, ':')) != NULL) &&
|
|
(cps < (end - 3) &&
|
|
isdigit(UCH(*(cps + 1))) && isdigit(UCH(*(cps + 2))) && *(cps + 3) == ':')) {
|
|
cps += 3;
|
|
*cps = '\0';
|
|
if ((cps - cpd) >= 14) {
|
|
cpd = (cps - 14);
|
|
*(cpd + 2) = '\0'; /* Month */
|
|
*(cpd + 5) = '\0'; /* Day */
|
|
*(cpd + 8) = '\0'; /* Year */
|
|
cps -= 5; /* Time */
|
|
if (*cpd == ' ')
|
|
*cpd = '0';
|
|
i = atoi(cpd) - 1;
|
|
sprintf(date, "%.3s %.2s", months[i], (cpd + 3));
|
|
if (date[4] == '0')
|
|
date[4] = ' ';
|
|
cpd += 6; /* Year */
|
|
if (strcmp((ThisYear + 2), cpd)) {
|
|
/* Not this year, so show the year. */
|
|
if (atoi(cpd) < 70) {
|
|
sprintf(&date[6], " 20%.2s", cpd);
|
|
} else {
|
|
sprintf(&date[6], " 19%.2s", cpd);
|
|
}
|
|
} else {
|
|
/* Is this year, so show the time. */
|
|
*(cps + 2) = '\0'; /* Hour */
|
|
i = atoi(cps);
|
|
sprintf(&date[6], " %02d:%.2s", i, (cps + 3));
|
|
}
|
|
StrAllocCopy(entry_info->date, date);
|
|
if (entry_info->date[4] == ' ' || entry_info->date[4] == '0') {
|
|
entry_info->date[4] = HT_NON_BREAK_SPACE;
|
|
}
|
|
}
|
|
}
|
|
|
|
TRACE_ENTRY("VM/CMS", entry_info);
|
|
return;
|
|
} /* parse_cms_dir_entry */
|
|
|
|
/*
|
|
* Given a line of LIST/NLST output in entry, return results and a file/dir
|
|
* name in entry_info struct
|
|
*
|
|
* If first is true, this is the first name in a directory.
|
|
*/
|
|
static EntryInfo *parse_dir_entry(char *entry,
|
|
BOOLEAN *first,
|
|
char **pspilledname)
|
|
{
|
|
EntryInfo *entry_info;
|
|
int i;
|
|
int len;
|
|
BOOLEAN remove_size = FALSE;
|
|
char *cp;
|
|
|
|
entry_info = typecalloc(EntryInfo);
|
|
|
|
if (entry_info == NULL)
|
|
outofmem(__FILE__, "parse_dir_entry");
|
|
|
|
entry_info->display = TRUE;
|
|
|
|
switch (server_type) {
|
|
case DLS_SERVER:
|
|
|
|
/*
|
|
* Interpret and edit LIST output from a Unix server in "dls" format.
|
|
* This one must have claimed to be Unix in order to get here; if the
|
|
* first line looks fishy, we revert to Unix and hope that fits better
|
|
* (this recovery is untested). - kw
|
|
*/
|
|
|
|
if (*first) {
|
|
len = (int) strlen(entry);
|
|
if (!len || entry[0] == ' ' ||
|
|
(len >= 24 && entry[23] != ' ') ||
|
|
(len < 24 && StrChr(entry, ' '))) {
|
|
server_type = UNIX_SERVER;
|
|
CTRACE((tfp,
|
|
"HTFTP: Falling back to treating as Unix server.\n"));
|
|
} else {
|
|
*first = FALSE;
|
|
}
|
|
}
|
|
|
|
if (server_type == DLS_SERVER) {
|
|
/* if still unchanged... */
|
|
parse_dls_line(entry, entry_info, pspilledname);
|
|
|
|
if (isEmpty(entry_info->filename)) {
|
|
entry_info->display = FALSE;
|
|
return (entry_info);
|
|
}
|
|
if (!strcmp(entry_info->filename, "..") ||
|
|
!strcmp(entry_info->filename, "."))
|
|
entry_info->display = FALSE;
|
|
if (entry_info->type && *entry_info->type == '\0') {
|
|
FREE(entry_info->type);
|
|
return (entry_info);
|
|
}
|
|
/*
|
|
* Goto the bottom and get real type.
|
|
*/
|
|
break;
|
|
}
|
|
/* fall through if server_type changed for *first == TRUE ! */
|
|
/* FALLTHRU */
|
|
case UNIX_SERVER:
|
|
case PETER_LEWIS_SERVER:
|
|
case MACHTEN_SERVER:
|
|
case MSDOS_SERVER:
|
|
case WINDOWS_NT_SERVER:
|
|
case WINDOWS_2K_SERVER:
|
|
case APPLESHARE_SERVER:
|
|
case NETPRESENZ_SERVER:
|
|
/*
|
|
* Check for EPLF output (local times).
|
|
*/
|
|
if (*entry == '+') {
|
|
parse_eplf_line(entry, entry_info);
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Interpret and edit LIST output from Unix server.
|
|
*/
|
|
len = (int) strlen(entry);
|
|
if (*first) {
|
|
/* don't gettext() this -- incoming text: */
|
|
if (!strcmp(entry, "can not access directory .")) {
|
|
/*
|
|
* Don't reset *first, nothing real will follow. - KW
|
|
*/
|
|
entry_info->display = FALSE;
|
|
return (entry_info);
|
|
}
|
|
*first = FALSE;
|
|
if (!StrNCmp(entry, "total ", 6) ||
|
|
strstr(entry, "not available") != NULL) {
|
|
entry_info->display = FALSE;
|
|
return (entry_info);
|
|
} else if (unsure_type) {
|
|
/* this isn't really a unix server! */
|
|
server_type = GENERIC_SERVER;
|
|
entry_info->display = FALSE;
|
|
return (entry_info);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check first character of ls -l output.
|
|
*/
|
|
if (TOUPPER(entry[0]) == 'D') {
|
|
/*
|
|
* It's a directory.
|
|
*/
|
|
StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
|
|
remove_size = TRUE; /* size is not useful */
|
|
} else if (entry[0] == 'l') {
|
|
/*
|
|
* It's a symbolic link, does the user care about knowing if it is
|
|
* symbolic? I think so since it might be a directory.
|
|
*/
|
|
StrAllocCopy(entry_info->type, ENTRY_IS_SYMBOLIC_LINK);
|
|
remove_size = TRUE; /* size is not useful */
|
|
|
|
/*
|
|
* Strip off " -> pathname".
|
|
*/
|
|
for (i = len - 1; (i > 3) &&
|
|
(!isspace(UCH(entry[i])) ||
|
|
(entry[i - 1] != '>') ||
|
|
(entry[i - 2] != '-') ||
|
|
(entry[i - 3] != ' ')); i--) ; /* null body */
|
|
if (i > 3) {
|
|
entry[i - 3] = '\0';
|
|
StrAllocCopy(entry_info->linkname, LYSkipBlanks(entry + i));
|
|
}
|
|
}
|
|
/* link */
|
|
parse_ls_line(entry, entry_info);
|
|
|
|
if (!strcmp(entry_info->filename, "..") ||
|
|
!strcmp(entry_info->filename, "."))
|
|
entry_info->display = FALSE;
|
|
/*
|
|
* Goto the bottom and get real type.
|
|
*/
|
|
break;
|
|
|
|
case VMS_SERVER:
|
|
/*
|
|
* Interpret and edit LIST output from VMS server and convert
|
|
* information lines to zero length.
|
|
*/
|
|
parse_vms_dir_entry(entry, entry_info);
|
|
|
|
/*
|
|
* Get rid of any junk lines.
|
|
*/
|
|
if (!entry_info->display)
|
|
return (entry_info);
|
|
|
|
/*
|
|
* Trim off VMS directory extensions.
|
|
*/
|
|
len = (int) strlen(entry_info->filename);
|
|
if ((len > 4) && !strcmp(&entry_info->filename[len - 4], ".dir")) {
|
|
entry_info->filename[len - 4] = '\0';
|
|
StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
|
|
remove_size = TRUE; /* size is not useful */
|
|
}
|
|
/*
|
|
* Goto the bottom and get real type.
|
|
*/
|
|
break;
|
|
|
|
case MS_WINDOWS_SERVER:
|
|
/*
|
|
* Interpret and edit LIST output from MS_WINDOWS server and convert
|
|
* information lines to zero length.
|
|
*/
|
|
parse_ms_windows_dir_entry(entry, entry_info);
|
|
|
|
/*
|
|
* Get rid of any junk lines.
|
|
*/
|
|
if (!entry_info->display)
|
|
return (entry_info);
|
|
if (entry_info->type && *entry_info->type == '\0') {
|
|
FREE(entry_info->type);
|
|
return (entry_info);
|
|
}
|
|
/*
|
|
* Goto the bottom and get real type.
|
|
*/
|
|
break;
|
|
|
|
#ifdef NOTDEFINED
|
|
case WINDOWS_NT_SERVER:
|
|
/*
|
|
* Interpret and edit LIST output from MS_WINDOWS server and convert
|
|
* information lines to zero length.
|
|
*/
|
|
parse_windows_nt_dir_entry(entry, entry_info);
|
|
|
|
/*
|
|
* Get rid of any junk lines.
|
|
*/
|
|
if (!entry_info->display)
|
|
return (entry_info);
|
|
if (entry_info->type && *entry_info->type == '\0') {
|
|
FREE(entry_info->type);
|
|
return (entry_info);
|
|
}
|
|
/*
|
|
* Goto the bottom and get real type.
|
|
*/
|
|
break;
|
|
#endif /* NOTDEFINED */
|
|
|
|
case CMS_SERVER:
|
|
{
|
|
/*
|
|
* Interpret and edit LIST output from VM/CMS server and convert
|
|
* any information lines to zero length.
|
|
*/
|
|
parse_cms_dir_entry(entry, entry_info);
|
|
|
|
/*
|
|
* Get rid of any junk lines.
|
|
*/
|
|
if (!entry_info->display)
|
|
return (entry_info);
|
|
if (entry_info->type && *entry_info->type == '\0') {
|
|
FREE(entry_info->type);
|
|
return (entry_info);
|
|
}
|
|
/*
|
|
* Goto the bottom and get real type.
|
|
*/
|
|
break;
|
|
}
|
|
|
|
case NCSA_SERVER:
|
|
case TCPC_SERVER:
|
|
/*
|
|
* Directories identified by trailing "/" characters.
|
|
*/
|
|
StrAllocCopy(entry_info->filename, entry);
|
|
len = (int) strlen(entry);
|
|
if (entry[len - 1] == '/') {
|
|
/*
|
|
* It's a dir, remove / and mark it as such.
|
|
*/
|
|
entry[len - 1] = '\0';
|
|
StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
|
|
remove_size = TRUE; /* size is not useful */
|
|
}
|
|
/*
|
|
* Goto the bottom and get real type.
|
|
*/
|
|
break;
|
|
|
|
default:
|
|
/*
|
|
* We can't tell if it is a directory since we only did an NLST :( List
|
|
* bad file types anyways? NOT!
|
|
*/
|
|
StrAllocCopy(entry_info->filename, entry);
|
|
return (entry_info); /* mostly empty info */
|
|
|
|
} /* switch (server_type) */
|
|
|
|
#ifdef LONG_LIST
|
|
(void) remove_size;
|
|
#else
|
|
if (remove_size && entry_info->size) {
|
|
entry_info->size = 0;
|
|
}
|
|
#endif
|
|
|
|
if (isEmpty(entry_info->filename)) {
|
|
entry_info->display = FALSE;
|
|
return (entry_info);
|
|
}
|
|
if (strlen(entry_info->filename) > 3) {
|
|
if (((cp = strrchr(entry_info->filename, '.')) != NULL &&
|
|
0 == strncasecomp(cp, ".me", 3)) &&
|
|
(cp[3] == '\0' || cp[3] == ';')) {
|
|
/*
|
|
* Don't treat this as application/x-Troff-me if it's a Unix server
|
|
* but has the string "read.me", or if it's not a Unix server. -
|
|
* FM
|
|
*/
|
|
if ((server_type != UNIX_SERVER) ||
|
|
(cp > (entry_info->filename + 3) &&
|
|
0 == strncasecomp((cp - 4), "read.me", 7))) {
|
|
StrAllocCopy(entry_info->type, STR_PLAINTEXT);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Get real types eventually.
|
|
*/
|
|
if (!entry_info->type) {
|
|
const char *cp2;
|
|
HTFormat format;
|
|
HTAtom *encoding; /* @@ not used at all */
|
|
|
|
format = HTFileFormat(entry_info->filename, &encoding, &cp2);
|
|
|
|
if (cp2 == NULL) {
|
|
if (!StrNCmp(HTAtom_name(format), "application", 11)) {
|
|
cp2 = HTAtom_name(format) + 12;
|
|
if (!StrNCmp(cp2, "x-", 2))
|
|
cp2 += 2;
|
|
} else {
|
|
cp2 = HTAtom_name(format);
|
|
}
|
|
}
|
|
|
|
StrAllocCopy(entry_info->type, cp2);
|
|
}
|
|
|
|
return (entry_info);
|
|
}
|
|
|
|
static void formatDate(char target[16], EntryInfo *entry)
|
|
{
|
|
char temp[8], month[4];
|
|
int i;
|
|
|
|
/*
|
|
* Set up for sorting in reverse chronological order. - FM
|
|
*/
|
|
if (entry->date[9] == ':') {
|
|
strcpy(target, "9999");
|
|
LYStrNCpy(temp, &entry->date[7], 5);
|
|
if (temp[0] == ' ') {
|
|
temp[0] = '0';
|
|
}
|
|
} else {
|
|
LYStrNCpy(target, &entry->date[8], 4);
|
|
strcpy(temp, "00:00");
|
|
}
|
|
LYStrNCpy(month, entry->date, 3);
|
|
for (i = 0; i < 12; i++) {
|
|
if (!strcasecomp(month, months[i])) {
|
|
break;
|
|
}
|
|
}
|
|
i++;
|
|
sprintf(month, "%02d", i % 100);
|
|
strcat(target, month);
|
|
StrNCat(target, &entry->date[4], 2);
|
|
if (target[6] == ' ' || target[6] == HT_NON_BREAK_SPACE) {
|
|
target[6] = '0';
|
|
}
|
|
|
|
/* If no year given, assume last year if it would otherwise be in the
|
|
* future by more than one day. The one day tolerance is to account for a
|
|
* possible timezone difference. - kw
|
|
*/
|
|
if (target[0] == '9' && atoi(target) > TheDate + 1) {
|
|
for (i = 0; i < 4; i++) {
|
|
target[i] = LastYear[i];
|
|
}
|
|
}
|
|
strcat(target, temp);
|
|
}
|
|
|
|
static int compare_EntryInfo_structs(EntryInfo *entry1, EntryInfo *entry2)
|
|
{
|
|
int status;
|
|
char date1[16], date2[16];
|
|
int result = strcmp(entry1->filename, entry2->filename);
|
|
|
|
switch (HTfileSortMethod) {
|
|
case FILE_BY_SIZE:
|
|
/* both equal or both 0 */
|
|
if (entry1->size > entry2->size)
|
|
result = 1;
|
|
else if (entry1->size < entry2->size)
|
|
result = -1;
|
|
break;
|
|
|
|
case FILE_BY_TYPE:
|
|
if (entry1->type && entry2->type) {
|
|
status = strcasecomp(entry1->type, entry2->type);
|
|
if (status)
|
|
result = status;
|
|
}
|
|
break;
|
|
|
|
case FILE_BY_DATE:
|
|
if (entry1->date && entry2->date &&
|
|
strlen(entry1->date) == 12 &&
|
|
strlen(entry2->date) == 12) {
|
|
/*
|
|
* Set the years and date, if we don't have them yet.
|
|
*/
|
|
if (!HaveYears) {
|
|
set_years_and_date();
|
|
}
|
|
formatDate(date1, entry1);
|
|
formatDate(date2, entry2);
|
|
/*
|
|
* Do the comparison. - FM
|
|
*/
|
|
status = strcasecomp(date2, date1);
|
|
if (status)
|
|
result = status;
|
|
}
|
|
break;
|
|
|
|
case FILE_BY_NAME:
|
|
default:
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#ifdef LONG_LIST
|
|
static char *FormatStr(char **bufp,
|
|
char *start,
|
|
const char *value)
|
|
{
|
|
char fmt[512];
|
|
|
|
if (*start) {
|
|
sprintf(fmt, "%%%.*ss", (int) sizeof(fmt) - 3, start);
|
|
HTSprintf(bufp, fmt, value);
|
|
} else if (*bufp && !(value && *value)) {
|
|
;
|
|
} else if (value) {
|
|
StrAllocCat(*bufp, value);
|
|
}
|
|
return *bufp;
|
|
}
|
|
|
|
static char *FormatSize(char **bufp,
|
|
char *start,
|
|
off_t value)
|
|
{
|
|
char fmt[512];
|
|
|
|
if (*start) {
|
|
sprintf(fmt, "%%%.*s" PRI_off_t,
|
|
(int) sizeof(fmt) - DigitsOf(start) - 3, start);
|
|
|
|
HTSprintf(bufp, fmt, value);
|
|
} else {
|
|
sprintf(fmt, "%" PRI_off_t, CAST_off_t (value));
|
|
|
|
StrAllocCat(*bufp, fmt);
|
|
}
|
|
return *bufp;
|
|
}
|
|
|
|
static char *FormatNum(char **bufp,
|
|
char *start,
|
|
unsigned long value)
|
|
{
|
|
char fmt[512];
|
|
|
|
if (*start) {
|
|
sprintf(fmt, "%%%.*sld",
|
|
(int) sizeof(fmt) - DigitsOf(start) - 3, start);
|
|
HTSprintf(bufp, fmt, value);
|
|
} else {
|
|
sprintf(fmt, "%lu", value);
|
|
StrAllocCat(*bufp, fmt);
|
|
}
|
|
return *bufp;
|
|
}
|
|
|
|
static void FlushParse(HTStructured * target, char **buf)
|
|
{
|
|
if (*buf && **buf) {
|
|
PUTS(*buf);
|
|
**buf = '\0';
|
|
}
|
|
}
|
|
|
|
static void LYListFmtParse(const char *fmtstr,
|
|
EntryInfo *data,
|
|
HTStructured * target,
|
|
char *tail)
|
|
{
|
|
char c;
|
|
char *s;
|
|
char *end;
|
|
char *start;
|
|
char *str = NULL;
|
|
char *buf = NULL;
|
|
BOOL is_directory = (BOOL) (data->file_mode != 0 &&
|
|
(TOUPPER(data->file_mode[0]) == 'D'));
|
|
BOOL is_symlinked = (BOOL) (data->file_mode != 0 &&
|
|
(TOUPPER(data->file_mode[0]) == 'L'));
|
|
BOOL remove_size = (BOOL) (is_directory || is_symlinked);
|
|
|
|
StrAllocCopy(str, fmtstr);
|
|
s = str;
|
|
end = str + strlen(str);
|
|
while (*s) {
|
|
start = s;
|
|
while (*s) {
|
|
if (*s == '%') {
|
|
if (*(s + 1) == '%') /* literal % */
|
|
s++;
|
|
else
|
|
break;
|
|
}
|
|
s++;
|
|
}
|
|
/* s is positioned either at a % or at \0 */
|
|
*s = '\0';
|
|
if (s > start) { /* some literal chars. */
|
|
StrAllocCat(buf, start);
|
|
}
|
|
if (s == end)
|
|
break;
|
|
start = ++s;
|
|
while (isdigit(UCH(*s)) || *s == '.' || *s == '-' || *s == ' ' ||
|
|
*s == '#' || *s == '+' || *s == '\'')
|
|
s++;
|
|
c = *s; /* the format char. or \0 */
|
|
*s = '\0';
|
|
|
|
switch (c) {
|
|
case '\0':
|
|
StrAllocCat(buf, start);
|
|
continue;
|
|
|
|
case 'A':
|
|
case 'a': /* anchor */
|
|
FlushParse(target, &buf);
|
|
HTDirEntry(target, tail, data->filename);
|
|
FormatStr(&buf, start, data->filename);
|
|
PUTS(buf);
|
|
END(HTML_A);
|
|
if (buf != 0)
|
|
*buf = '\0';
|
|
if (c != 'A' && data->linkname != 0) {
|
|
PUTS(" -> ");
|
|
PUTS(data->linkname);
|
|
}
|
|
break;
|
|
|
|
case 'T': /* MIME type */
|
|
case 't': /* MIME type description */
|
|
if (is_directory) {
|
|
if (c != 'T') {
|
|
FormatStr(&buf, start, ENTRY_IS_DIRECTORY);
|
|
} else {
|
|
FormatStr(&buf, start, "");
|
|
}
|
|
} else if (is_symlinked) {
|
|
if (c != 'T') {
|
|
FormatStr(&buf, start, ENTRY_IS_SYMBOLIC_LINK);
|
|
} else {
|
|
FormatStr(&buf, start, "");
|
|
}
|
|
} else {
|
|
const char *cp2;
|
|
HTFormat format;
|
|
|
|
format = HTFileFormat(data->filename, NULL, &cp2);
|
|
|
|
if (c != 'T') {
|
|
if (cp2 == NULL) {
|
|
if (!StrNCmp(HTAtom_name(format),
|
|
"application", 11)) {
|
|
cp2 = HTAtom_name(format) + 12;
|
|
if (!StrNCmp(cp2, "x-", 2))
|
|
cp2 += 2;
|
|
} else {
|
|
cp2 = HTAtom_name(format);
|
|
}
|
|
}
|
|
FormatStr(&buf, start, cp2);
|
|
} else {
|
|
FormatStr(&buf, start, HTAtom_name(format));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'd': /* date */
|
|
if (data->date) {
|
|
FormatStr(&buf, start, data->date);
|
|
} else {
|
|
FormatStr(&buf, start, " * ");
|
|
}
|
|
break;
|
|
|
|
case 's': /* size in bytes */
|
|
FormatSize(&buf, start, data->size);
|
|
break;
|
|
|
|
case 'K': /* size in Kilobytes but not for directories */
|
|
if (remove_size) {
|
|
FormatStr(&buf, start, "");
|
|
StrAllocCat(buf, " ");
|
|
break;
|
|
}
|
|
/* FALL THROUGH */
|
|
case 'k': /* size in Kilobytes */
|
|
/* FIXME - this is inconsistent with HTFile.c, but historical */
|
|
if (data->size < 1024) {
|
|
FormatSize(&buf, start, data->size);
|
|
StrAllocCat(buf, " bytes");
|
|
} else {
|
|
FormatSize(&buf, start, data->size / 1024);
|
|
StrAllocCat(buf, "Kb");
|
|
}
|
|
break;
|
|
|
|
#ifdef LONG_LIST
|
|
case 'p': /* unix-style permission bits */
|
|
FormatStr(&buf, start, NonNull(data->file_mode));
|
|
break;
|
|
|
|
case 'o': /* owner */
|
|
FormatStr(&buf, start, NonNull(data->file_user));
|
|
break;
|
|
|
|
case 'g': /* group */
|
|
FormatStr(&buf, start, NonNull(data->file_group));
|
|
break;
|
|
|
|
case 'l': /* link count */
|
|
FormatNum(&buf, start, data->file_links);
|
|
break;
|
|
#endif
|
|
|
|
case '%': /* literal % with flags/width */
|
|
FormatStr(&buf, start, "%");
|
|
break;
|
|
|
|
default:
|
|
fprintf(stderr,
|
|
"Unknown format character `%c' in list format\n", c);
|
|
break;
|
|
}
|
|
|
|
s++;
|
|
}
|
|
if (buf) {
|
|
LYTrimTrailing(buf);
|
|
FlushParse(target, &buf);
|
|
FREE(buf);
|
|
}
|
|
PUTC('\n');
|
|
FREE(str);
|
|
}
|
|
#endif /* LONG_LIST */
|
|
|
|
/* Read a directory into an hypertext object from the data socket
|
|
* --------------------------------------------------------------
|
|
*
|
|
* On entry,
|
|
* anchor Parent anchor to link the this node to
|
|
* address Address of the directory
|
|
* On exit,
|
|
* returns HT_LOADED if OK
|
|
* <0 if error.
|
|
*/
|
|
static int read_directory(HTParentAnchor *parent,
|
|
const char *address,
|
|
HTFormat format_out,
|
|
HTStream *sink)
|
|
{
|
|
int status;
|
|
BOOLEAN WasInterrupted = FALSE;
|
|
HTStructured *target = HTML_new(parent, format_out, sink);
|
|
char *filename = HTParse(address, "", PARSE_PATH + PARSE_PUNCTUATION);
|
|
EntryInfo *entry_info;
|
|
BOOLEAN first = TRUE;
|
|
char *lastpath = NULL; /* prefix for link, either "" (for root) or xxx */
|
|
BOOL tildeIsTop = FALSE;
|
|
|
|
#ifndef LONG_LIST
|
|
char string_buffer[64];
|
|
#endif
|
|
|
|
_HTProgress(gettext("Receiving FTP directory."));
|
|
|
|
/*
|
|
* Force the current Date and Year (TheDate, ThisYear, and LastYear) to be
|
|
* recalculated for each directory request. Otherwise we have a problem
|
|
* with long-running sessions assuming the wrong date for today. - kw
|
|
*/
|
|
HaveYears = FALSE;
|
|
/*
|
|
* Check whether we always want the home directory treated as Welcome. -
|
|
* FM
|
|
*/
|
|
if (server_type == VMS_SERVER)
|
|
tildeIsTop = TRUE;
|
|
|
|
/*
|
|
* This should always come back FALSE, since the flag is set only for local
|
|
* directory listings if LONG_LIST was defined on compilation, but we could
|
|
* someday set up an equivalent listing for Unix ftp servers. - FM
|
|
*/
|
|
(void) HTDirTitles(target, parent, format_out, tildeIsTop);
|
|
|
|
data_read_pointer = data_write_pointer = data_buffer;
|
|
|
|
if (*filename == '\0') { /* Empty filename: use root. */
|
|
StrAllocCopy(lastpath, "/");
|
|
} else if (!strcmp(filename, "/")) { /* Root path. */
|
|
StrAllocCopy(lastpath, "/foo/..");
|
|
} else {
|
|
char *p = strrchr(filename, '/'); /* Find the lastslash. */
|
|
char *cp;
|
|
|
|
if (server_type == CMS_SERVER) {
|
|
StrAllocCopy(lastpath, filename); /* Use absolute path for CMS. */
|
|
} else {
|
|
StrAllocCopy(lastpath, p + 1); /* Take slash off the beginning. */
|
|
}
|
|
if ((cp = strrchr(lastpath, ';')) != NULL) { /* Trim type= param. */
|
|
if (!strncasecomp((cp + 1), "type=", 5)) {
|
|
if (TOUPPER(*(cp + 6)) == 'D' ||
|
|
TOUPPER(*(cp + 6)) == 'A' ||
|
|
TOUPPER(*(cp + 6)) == 'I')
|
|
*cp = '\0';
|
|
}
|
|
}
|
|
}
|
|
FREE(filename);
|
|
|
|
{
|
|
HTBTree *bt = HTBTree_new((HTComparer) compare_EntryInfo_structs);
|
|
int ic;
|
|
HTChunk *chunk = HTChunkCreate(128);
|
|
int BytesReceived = 0;
|
|
int BytesReported = 0;
|
|
char NumBytes[64];
|
|
char *spilledname = NULL;
|
|
|
|
PUTC('\n'); /* prettier LJM */
|
|
for (ic = 0; ic != EOF;) { /* For each entry in the directory */
|
|
HTChunkClear(chunk);
|
|
|
|
if (HTCheckForInterrupt()) {
|
|
CTRACE((tfp,
|
|
"read_directory: interrupted after %d bytes\n",
|
|
BytesReceived));
|
|
WasInterrupted = TRUE;
|
|
if (BytesReceived) {
|
|
goto unload_btree; /* unload btree */
|
|
} else {
|
|
ABORT_TARGET;
|
|
HTBTreeAndObject_free(bt);
|
|
FREE(spilledname);
|
|
HTChunkFree(chunk);
|
|
return HT_INTERRUPTED;
|
|
}
|
|
}
|
|
|
|
/* read directory entry
|
|
*/
|
|
interrupted_in_next_data_char = FALSE;
|
|
for (;;) { /* Read in one line as filename */
|
|
ic = NEXT_DATA_CHAR;
|
|
AgainForMultiNet:
|
|
if (interrupted_in_next_data_char) {
|
|
CTRACE((tfp,
|
|
"read_directory: interrupted_in_next_data_char after %d bytes\n",
|
|
BytesReceived));
|
|
WasInterrupted = TRUE;
|
|
if (BytesReceived) {
|
|
goto unload_btree; /* unload btree */
|
|
} else {
|
|
ABORT_TARGET;
|
|
HTBTreeAndObject_free(bt);
|
|
FREE(spilledname);
|
|
HTChunkFree(chunk);
|
|
return HT_INTERRUPTED;
|
|
}
|
|
} else if ((char) ic == CR || (char) ic == LF) { /* Terminator? */
|
|
if (chunk->size != 0) { /* got some text */
|
|
/* Deal with MultiNet's wrapping of long lines */
|
|
if (server_type == VMS_SERVER) {
|
|
/* Deal with MultiNet's wrapping of long lines - F.M. */
|
|
if (data_read_pointer < data_write_pointer &&
|
|
*(data_read_pointer + 1) == ' ')
|
|
data_read_pointer++;
|
|
else if (data_read_pointer >= data_write_pointer) {
|
|
status = NETREAD(data_soc, data_buffer,
|
|
DATA_BUFFER_SIZE);
|
|
if (status == HT_INTERRUPTED) {
|
|
interrupted_in_next_data_char = 1;
|
|
goto AgainForMultiNet;
|
|
}
|
|
if (status <= 0) {
|
|
ic = EOF;
|
|
break;
|
|
}
|
|
data_write_pointer = data_buffer + status;
|
|
data_read_pointer = data_buffer;
|
|
if (*data_read_pointer == ' ')
|
|
data_read_pointer++;
|
|
else
|
|
break;
|
|
} else
|
|
break;
|
|
} else
|
|
break; /* finish getting one entry */
|
|
}
|
|
} else if (ic == EOF) {
|
|
break; /* End of file */
|
|
} else {
|
|
HTChunkPutc(chunk, UCH(ic));
|
|
}
|
|
}
|
|
HTChunkTerminate(chunk);
|
|
|
|
BytesReceived += chunk->size;
|
|
if (BytesReceived > BytesReported + 1024) {
|
|
#ifdef _WINDOWS
|
|
sprintf(NumBytes, gettext("Transferred %d bytes (%5d)"),
|
|
BytesReceived, ws_read_per_sec);
|
|
#else
|
|
sprintf(NumBytes, TRANSFERRED_X_BYTES, BytesReceived);
|
|
#endif
|
|
HTProgress(NumBytes);
|
|
BytesReported = BytesReceived;
|
|
}
|
|
|
|
if (ic == EOF && chunk->size == 1)
|
|
/* 1 means empty: includes terminating 0 */
|
|
break;
|
|
CTRACE((tfp, "HTFTP: Line in %s is %s\n",
|
|
lastpath, chunk->data));
|
|
|
|
entry_info = parse_dir_entry(chunk->data, &first, &spilledname);
|
|
if (entry_info->display) {
|
|
FREE(spilledname);
|
|
CTRACE((tfp, "Adding file to BTree: %s\n",
|
|
entry_info->filename));
|
|
HTBTree_add(bt, entry_info);
|
|
} else {
|
|
free_entryinfo_struct_contents(entry_info);
|
|
FREE(entry_info);
|
|
}
|
|
|
|
} /* next entry */
|
|
|
|
unload_btree:
|
|
|
|
HTChunkFree(chunk);
|
|
FREE(spilledname);
|
|
|
|
/* print out the handy help message if it exists :) */
|
|
if (help_message_cache_non_empty()) {
|
|
START(HTML_PRE);
|
|
START(HTML_HR);
|
|
PUTC('\n');
|
|
PUTS(help_message_cache_contents());
|
|
init_help_message_cache(); /* to free memory */
|
|
START(HTML_HR);
|
|
PUTC('\n');
|
|
} else {
|
|
START(HTML_PRE);
|
|
PUTC('\n');
|
|
}
|
|
|
|
/* Run through tree printing out in order
|
|
*/
|
|
{
|
|
#ifndef LONG_LIST
|
|
#ifdef SH_EX /* 1997/10/18 (Sat) 14:14:28 */
|
|
char *p, name_buff[256];
|
|
int name_len, dot_len;
|
|
|
|
#define FNAME_WIDTH 30
|
|
#define FILE_GAP 1
|
|
|
|
#endif
|
|
int i;
|
|
#endif
|
|
HTBTElement *ele;
|
|
|
|
for (ele = HTBTree_next(bt, NULL);
|
|
ele != NULL;
|
|
ele = HTBTree_next(bt, ele)) {
|
|
entry_info = (EntryInfo *) HTBTree_object(ele);
|
|
|
|
#ifdef LONG_LIST
|
|
LYListFmtParse(ftp_format,
|
|
entry_info,
|
|
target,
|
|
lastpath);
|
|
#else
|
|
if (entry_info->date) {
|
|
PUTS(entry_info->date);
|
|
PUTS(" ");
|
|
} else {
|
|
PUTS(" * ");
|
|
}
|
|
|
|
if (entry_info->type) {
|
|
for (i = 0; entry_info->type[i] != '\0' && i < 16; i++)
|
|
PUTC(entry_info->type[i]);
|
|
for (; i < 17; i++)
|
|
PUTC(' ');
|
|
}
|
|
/* start the anchor */
|
|
HTDirEntry(target, lastpath, entry_info->filename);
|
|
#ifdef SH_EX /* 1997/10/18 (Sat) 16:00 */
|
|
name_len = strlen(entry_info->filename);
|
|
|
|
sprintf(name_buff, "%-*s", FNAME_WIDTH, entry_info->filename);
|
|
|
|
if (name_len < FNAME_WIDTH) {
|
|
dot_len = FNAME_WIDTH - FILE_GAP - name_len;
|
|
if (dot_len > 0) {
|
|
p = name_buff + name_len + 1;
|
|
while (dot_len-- > 0)
|
|
*p++ = '.';
|
|
}
|
|
} else {
|
|
name_buff[FNAME_WIDTH] = '\0';
|
|
}
|
|
|
|
PUTS(name_buff);
|
|
#else
|
|
PUTS(entry_info->filename);
|
|
#endif
|
|
END(HTML_A);
|
|
|
|
if (entry_info->size) {
|
|
#ifdef SH_EX /* 1998/02/02 (Mon) 16:34:52 */
|
|
if (entry_info->size < 1024)
|
|
sprintf(string_buffer, "%6ld bytes",
|
|
entry_info->size);
|
|
else
|
|
sprintf(string_buffer, "%6ld Kb",
|
|
entry_info->size / 1024);
|
|
#else
|
|
if (entry_info->size < 1024)
|
|
sprintf(string_buffer, " %lu bytes",
|
|
(unsigned long) entry_info->size);
|
|
else
|
|
sprintf(string_buffer, " %luKb",
|
|
(unsigned long) entry_info->size / 1024);
|
|
#endif
|
|
PUTS(string_buffer);
|
|
} else if (entry_info->linkname != 0) {
|
|
PUTS(" -> ");
|
|
PUTS(entry_info->linkname);
|
|
}
|
|
|
|
PUTC('\n'); /* end of this entry */
|
|
#endif
|
|
|
|
free_entryinfo_struct_contents(entry_info);
|
|
}
|
|
}
|
|
END(HTML_PRE);
|
|
END(HTML_BODY);
|
|
FREE_TARGET;
|
|
HTBTreeAndObject_free(bt);
|
|
}
|
|
|
|
FREE(lastpath);
|
|
|
|
if (WasInterrupted || data_soc != -1) { /* should always be true */
|
|
/*
|
|
* Without closing the data socket first, the response(NULL) later may
|
|
* hang. Some servers expect the client to fin/ack the close of the
|
|
* data connection before proceeding with the conversation on the
|
|
* control connection. - kw
|
|
*/
|
|
CTRACE((tfp, "HTFTP: Closing data socket %d\n", data_soc));
|
|
status = NETCLOSE(data_soc);
|
|
if (status == -1)
|
|
HTInetStatus("close"); /* Comment only */
|
|
data_soc = -1;
|
|
}
|
|
|
|
if (WasInterrupted || HTCheckForInterrupt()) {
|
|
_HTProgress(TRANSFER_INTERRUPTED);
|
|
}
|
|
return HT_LOADED;
|
|
}
|
|
|
|
/*
|
|
* Setup an FTP connection.
|
|
*/
|
|
static int setup_connection(const char *name,
|
|
HTParentAnchor *anchor)
|
|
{
|
|
int retry; /* How many times tried? */
|
|
int status = HT_NO_CONNECTION;
|
|
|
|
CTRACE((tfp, "setup_connection(%s)\n", name));
|
|
|
|
/* set use_list to NOT since we don't know what kind of server
|
|
* this is yet. And set the type to GENERIC
|
|
*/
|
|
use_list = FALSE;
|
|
server_type = GENERIC_SERVER;
|
|
Broken_RETR = FALSE;
|
|
|
|
#ifdef INET6
|
|
Broken_EPSV = FALSE;
|
|
#endif
|
|
|
|
for (retry = 0; retry < 2; retry++) { /* For timed out/broken connections */
|
|
status = get_connection(name, anchor);
|
|
if (status < 0) {
|
|
break;
|
|
}
|
|
|
|
if (!ftp_local_passive) {
|
|
status = get_listen_socket();
|
|
if (status < 0) {
|
|
NETCLOSE(control->socket);
|
|
control->socket = -1;
|
|
#ifdef INET6
|
|
if (have_socket)
|
|
(void) close_master_socket();
|
|
#else
|
|
close_master_socket();
|
|
#endif /* INET6 */
|
|
/* HT_INTERRUPTED would fall through, if we could interrupt
|
|
somehow in the middle of it, which we currently can't. */
|
|
break;
|
|
}
|
|
#ifdef REPEAT_PORT
|
|
/* Inform the server of the port number we will listen on
|
|
*/
|
|
status = response(port_command);
|
|
FREE(port_command);
|
|
if (status == HT_INTERRUPTED) {
|
|
CTRACE((tfp, "HTFTP: Interrupted in response (port_command)\n"));
|
|
_HTProgress(CONNECTION_INTERRUPTED);
|
|
NETCLOSE(control->socket);
|
|
control->socket = -1;
|
|
close_master_socket();
|
|
status = HT_INTERRUPTED;
|
|
break;
|
|
}
|
|
if (status != 2) { /* Could have timed out */
|
|
if (status < 0)
|
|
continue; /* try again - net error */
|
|
status = -status; /* bad reply */
|
|
break;
|
|
}
|
|
CTRACE((tfp, "HTFTP: Port defined.\n"));
|
|
#endif /* REPEAT_PORT */
|
|
} else { /* Tell the server to be passive */
|
|
char *command = NULL;
|
|
const char *p = "?";
|
|
int h0, h1, h2, h3, p0, p1; /* Parts of reply */
|
|
|
|
#ifdef INET6
|
|
char dst[LINE_LENGTH + 1];
|
|
#endif
|
|
|
|
data_soc = status;
|
|
|
|
#ifdef INET6
|
|
/* see RFC 2428 */
|
|
if (Broken_EPSV)
|
|
status = 1;
|
|
else
|
|
status = send_cmd_1(p = "EPSV");
|
|
if (status < 0) /* retry or Bad return */
|
|
continue;
|
|
else if (status != 2) {
|
|
status = send_cmd_1(p = "PASV");
|
|
if (status < 0) { /* retry or Bad return */
|
|
continue;
|
|
} else if (status != 2) {
|
|
status = -status; /* bad reply */
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (strcmp(p, "PASV") == 0) {
|
|
for (p = response_text; *p && *p != ','; p++) {
|
|
; /* null body */
|
|
}
|
|
|
|
while (--p > response_text && '0' <= *p && *p <= '9') {
|
|
; /* null body */
|
|
}
|
|
status = sscanf(p + 1, "%d,%d,%d,%d,%d,%d",
|
|
&h0, &h1, &h2, &h3, &p0, &p1);
|
|
if (status < 4) {
|
|
fprintf(tfp, "HTFTP: PASV reply has no inet address!\n");
|
|
status = HT_NO_CONNECTION;
|
|
break;
|
|
}
|
|
passive_port = (PortNumber) ((p0 << 8) + p1);
|
|
sprintf(dst, "%d.%d.%d.%d", h0, h1, h2, h3);
|
|
} else if (strcmp(p, "EPSV") == 0) {
|
|
char c0, c1, c2, c3;
|
|
LY_SOCKADDR ss;
|
|
LY_SOCKLEN sslen;
|
|
|
|
/*
|
|
* EPSV bla (|||port|)
|
|
*/
|
|
for (p = response_text; *p && !isspace(UCH(*p)); p++) {
|
|
; /* null body */
|
|
}
|
|
for ( /*nothing */ ;
|
|
*p && *p != '(';
|
|
p++) { /*) */
|
|
; /* null body */
|
|
}
|
|
status = sscanf(p, "(%c%c%c%d%c)", &c0, &c1, &c2, &p0, &c3);
|
|
if (status != 5) {
|
|
fprintf(tfp, "HTFTP: EPSV reply has invalid format!\n");
|
|
status = HT_NO_CONNECTION;
|
|
break;
|
|
}
|
|
passive_port = (PortNumber) p0;
|
|
|
|
sslen = (LY_SOCKLEN) sizeof(ss);
|
|
if (getpeername(control->socket, SOCKADDR_OF(ss), &sslen) < 0) {
|
|
fprintf(tfp, "HTFTP: getpeername(control) failed\n");
|
|
status = HT_NO_CONNECTION;
|
|
break;
|
|
}
|
|
if (getnameinfo(SOCKADDR_OF(ss),
|
|
sslen,
|
|
dst,
|
|
(socklen_t) sizeof(dst),
|
|
NULL, 0, NI_NUMERICHOST)) {
|
|
fprintf(tfp, "HTFTP: getnameinfo failed\n");
|
|
status = HT_NO_CONNECTION;
|
|
break;
|
|
}
|
|
}
|
|
#else
|
|
status = send_cmd_1("PASV");
|
|
if (status != 2) {
|
|
if (status < 0)
|
|
continue; /* retry or Bad return */
|
|
status = -status; /* bad reply */
|
|
break;
|
|
}
|
|
for (p = response_text; *p && *p != ','; p++) {
|
|
; /* null body */
|
|
}
|
|
|
|
while (--p > response_text && '0' <= *p && *p <= '9') {
|
|
; /* null body */
|
|
}
|
|
|
|
status = sscanf(p + 1, "%d,%d,%d,%d,%d,%d",
|
|
&h0, &h1, &h2, &h3, &p0, &p1);
|
|
if (status < 4) {
|
|
fprintf(tfp, "HTFTP: PASV reply has no inet address!\n");
|
|
status = HT_NO_CONNECTION;
|
|
break;
|
|
}
|
|
passive_port = (PortNumber) ((p0 << 8) + p1);
|
|
#endif /* INET6 */
|
|
CTRACE((tfp, "HTFTP: Server is listening on port %d\n",
|
|
passive_port));
|
|
|
|
/* Open connection for data: */
|
|
|
|
#ifdef INET6
|
|
HTSprintf0(&command, "%s//%s:%d/", STR_FTP_URL, dst, passive_port);
|
|
#else
|
|
HTSprintf0(&command, "%s//%d.%d.%d.%d:%d/",
|
|
STR_FTP_URL, h0, h1, h2, h3, passive_port);
|
|
#endif
|
|
status = HTDoConnect(command, "FTP data", passive_port, &data_soc);
|
|
FREE(command);
|
|
|
|
if (status < 0) {
|
|
(void) HTInetStatus(gettext("connect for data"));
|
|
NETCLOSE(data_soc);
|
|
break;
|
|
}
|
|
|
|
CTRACE((tfp, "FTP data connected, socket %d\n", data_soc));
|
|
}
|
|
status = 0;
|
|
break; /* No more retries */
|
|
|
|
} /* for retries */
|
|
CTRACE((tfp, "setup_connection returns %d\n", status));
|
|
return status;
|
|
}
|
|
|
|
/* Retrieve File from Server
|
|
* -------------------------
|
|
*
|
|
* On entry,
|
|
* name WWW address of a file: document, including hostname
|
|
* On exit,
|
|
* returns Socket number for file if good.
|
|
* <0 if bad.
|
|
*/
|
|
int HTFTPLoad(const char *name,
|
|
HTParentAnchor *anchor,
|
|
HTFormat format_out,
|
|
HTStream *sink)
|
|
{
|
|
BOOL isDirectory = NO;
|
|
HTAtom *encoding = NULL;
|
|
int status, final_status;
|
|
int outstanding = 1; /* outstanding control connection responses
|
|
|
|
that we are willing to wait for, if we
|
|
get to the point of reading data - kw */
|
|
HTFormat format;
|
|
|
|
CTRACE((tfp, "HTFTPLoad(%s) %s connection\n",
|
|
name,
|
|
(ftp_local_passive
|
|
? "passive"
|
|
: "normal")));
|
|
|
|
HTReadProgress((off_t) 0, (off_t) 0);
|
|
|
|
status = setup_connection(name, anchor);
|
|
if (status < 0)
|
|
return status; /* Failed with this code */
|
|
|
|
/* Ask for the file:
|
|
*/
|
|
{
|
|
char *filename = HTParse(name, "", PARSE_PATH + PARSE_PUNCTUATION);
|
|
char *fname = filename; /* Save for subsequent free() */
|
|
char *vmsname = NULL;
|
|
BOOL binary;
|
|
const char *type = NULL;
|
|
char *types = NULL;
|
|
char *cp;
|
|
|
|
if (server_type == CMS_SERVER) {
|
|
/* If the unescaped path has a %2f, reject it as illegal. - FM */
|
|
if (((cp = strstr(filename, "%2")) != NULL) &&
|
|
TOUPPER(cp[2]) == 'F') {
|
|
FREE(fname);
|
|
init_help_message_cache(); /* to free memory */
|
|
NETCLOSE(control->socket);
|
|
control->socket = -1;
|
|
CTRACE((tfp,
|
|
"HTFTP: Rejecting path due to illegal escaped slash.\n"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (!*filename) {
|
|
StrAllocCopy(filename, "/");
|
|
type = "D";
|
|
} else if ((type = types = strrchr(filename, ';')) != NULL) {
|
|
/*
|
|
* Check and trim the type= parameter. - FM
|
|
*/
|
|
if (!strncasecomp((type + 1), "type=", 5)) {
|
|
switch (TOUPPER(*(type + 6))) {
|
|
case 'D':
|
|
*types = '\0';
|
|
type = "D";
|
|
break;
|
|
case 'A':
|
|
*types = '\0';
|
|
type = "A";
|
|
break;
|
|
case 'I':
|
|
*types = '\0';
|
|
type = "I";
|
|
break;
|
|
default:
|
|
type = "";
|
|
break;
|
|
}
|
|
if (!*filename) {
|
|
*filename = '/';
|
|
*(filename + 1) = '\0';
|
|
}
|
|
}
|
|
if (*type != '\0') {
|
|
CTRACE((tfp, "HTFTP: type=%s\n", type));
|
|
}
|
|
}
|
|
HTUnEscape(filename);
|
|
CTRACE((tfp, "HTFTP: UnEscaped %s\n", filename));
|
|
if (filename[1] == '~') {
|
|
/*
|
|
* Check if translation of HOME as tilde is supported,
|
|
* and adjust filename if so. - FM
|
|
*/
|
|
char *cp2 = NULL;
|
|
char *fn = NULL;
|
|
|
|
if ((cp2 = StrChr((filename + 1), '/')) != NULL) {
|
|
*cp2 = '\0';
|
|
}
|
|
status = send_cmd_1("PWD");
|
|
if (status == 2 && response_text[5] == '/') {
|
|
status = send_cwd(filename + 1);
|
|
if (status == 2) {
|
|
StrAllocCopy(fn, (filename + 1));
|
|
if (cp2) {
|
|
*cp2 = '/';
|
|
if (fn[strlen(fn) - 1] != '/') {
|
|
StrAllocCat(fn, cp2);
|
|
} else {
|
|
StrAllocCat(fn, (cp2 + 1));
|
|
}
|
|
cp2 = NULL;
|
|
}
|
|
FREE(fname);
|
|
fname = filename = fn;
|
|
}
|
|
}
|
|
if (cp2) {
|
|
*cp2 = '/';
|
|
}
|
|
}
|
|
if (strlen(filename) > 3) {
|
|
char *cp2;
|
|
|
|
if (((cp2 = strrchr(filename, '.')) != NULL &&
|
|
0 == strncasecomp(cp2, ".me", 3)) &&
|
|
(cp2[3] == '\0' || cp2[3] == ';')) {
|
|
/*
|
|
* Don't treat this as application/x-Troff-me if it's a Unix
|
|
* server but has the string "read.me", or if it's not a Unix
|
|
* server. - FM
|
|
*/
|
|
if ((server_type != UNIX_SERVER) ||
|
|
(cp2 > (filename + 3) &&
|
|
0 == strncasecomp((cp2 - 4), "read.me", 7))) {
|
|
*cp2 = '\0';
|
|
format = HTFileFormat(filename, &encoding, NULL);
|
|
*cp2 = '.';
|
|
} else {
|
|
format = HTFileFormat(filename, &encoding, NULL);
|
|
}
|
|
} else {
|
|
format = HTFileFormat(filename, &encoding, NULL);
|
|
}
|
|
} else {
|
|
format = HTFileFormat(filename, &encoding, NULL);
|
|
}
|
|
format = HTCharsetFormat(format, anchor, -1);
|
|
binary = (BOOL) (encoding != WWW_ENC_8BIT &&
|
|
encoding != WWW_ENC_7BIT);
|
|
if (!binary &&
|
|
/*
|
|
* Force binary if we're in source, download or dump mode and this is
|
|
* not a VM/CMS server, so we don't get CRLF instead of LF (or CR) for
|
|
* newlines in text files. Can't do this for VM/CMS or we'll get raw
|
|
* EBCDIC. - FM
|
|
*/
|
|
(format_out == WWW_SOURCE ||
|
|
format_out == WWW_DOWNLOAD ||
|
|
format_out == WWW_DUMP) &&
|
|
(server_type != CMS_SERVER))
|
|
binary = TRUE;
|
|
if (!binary && type && *type == 'I') {
|
|
/*
|
|
* Force binary if we had ;type=I - FM
|
|
*/
|
|
binary = TRUE;
|
|
} else if (binary && type && *type == 'A') {
|
|
/*
|
|
* Force ASCII if we had ;type=A - FM
|
|
*/
|
|
binary = FALSE;
|
|
}
|
|
if (binary != control->is_binary) {
|
|
/*
|
|
* Act on our setting if not already set. - FM
|
|
*/
|
|
const char *mode = binary ? "I" : "A";
|
|
|
|
status = send_cmd_2("TYPE", mode);
|
|
if (status != 2) {
|
|
init_help_message_cache(); /* to free memory */
|
|
return ((status < 0) ? status : -status);
|
|
}
|
|
control->is_binary = binary;
|
|
}
|
|
switch (server_type) {
|
|
/*
|
|
* Handle what for Lynx are special case servers, e.g., for which
|
|
* we respect RFC 1738, or which have known conflicts in suffix
|
|
* mappings. - FM
|
|
*/
|
|
case VMS_SERVER:
|
|
{
|
|
char *cp1, *cp2;
|
|
BOOL included_device = FALSE;
|
|
BOOL found_tilde = FALSE;
|
|
|
|
/* Accept only Unix-style filename */
|
|
if (StrChr(filename, ':') != NULL ||
|
|
StrChr(filename, '[') != NULL) {
|
|
FREE(fname);
|
|
init_help_message_cache(); /* to free memory */
|
|
NETCLOSE(control->socket);
|
|
control->socket = -1;
|
|
CTRACE((tfp,
|
|
"HTFTP: Rejecting path due to non-Unix-style syntax.\n"));
|
|
return -1;
|
|
}
|
|
/* Handle any unescaped "/%2F" path */
|
|
if (!StrNCmp(filename, "//", 2)) {
|
|
int i;
|
|
|
|
included_device = TRUE;
|
|
for (i = 0; filename[(i + 1)]; i++)
|
|
filename[i] = filename[(i + 1)];
|
|
filename[i] = '\0';
|
|
CTRACE((tfp, "HTFTP: Trimmed '%s'\n", filename));
|
|
cp = HTVMS_name("", filename);
|
|
CTRACE((tfp, "HTFTP: VMSized '%s'\n", cp));
|
|
if ((cp1 = strrchr(cp, ']')) != NULL) {
|
|
strcpy(filename, ++cp1);
|
|
CTRACE((tfp, "HTFTP: Filename '%s'\n", filename));
|
|
*cp1 = '\0';
|
|
status = send_cwd(cp);
|
|
if (status != 2) {
|
|
char *dotslash = 0;
|
|
|
|
if ((cp1 = StrChr(cp, '[')) != NULL) {
|
|
*cp1++ = '\0';
|
|
status = send_cwd(cp);
|
|
if (status != 2) {
|
|
FREE(fname);
|
|
init_help_message_cache(); /* to free memory */
|
|
NETCLOSE(control->socket);
|
|
control->socket = -1;
|
|
return ((status < 0) ? status : -status);
|
|
}
|
|
HTSprintf0(&dotslash, "[.%s", cp1);
|
|
status = send_cwd(dotslash);
|
|
FREE(dotslash);
|
|
if (status != 2) {
|
|
FREE(fname);
|
|
init_help_message_cache(); /* to free memory */
|
|
NETCLOSE(control->socket);
|
|
control->socket = -1;
|
|
return ((status < 0) ? status : -status);
|
|
}
|
|
} else {
|
|
FREE(fname);
|
|
init_help_message_cache(); /* to free memory */
|
|
NETCLOSE(control->socket);
|
|
control->socket = -1;
|
|
return ((status < 0) ? status : -status);
|
|
}
|
|
}
|
|
} else if ((cp1 = StrChr(cp, ':')) != NULL &&
|
|
StrChr(cp, '[') == NULL &&
|
|
StrChr(cp, ']') == NULL) {
|
|
cp1++;
|
|
if (*cp1 != '\0') {
|
|
int cplen = (int) (cp1 - cp);
|
|
|
|
strcpy(filename, cp1);
|
|
CTRACE((tfp, "HTFTP: Filename '%s'\n", filename));
|
|
HTSprintf0(&vmsname, "%.*s[%s]", cplen, cp, filename);
|
|
status = send_cwd(vmsname);
|
|
if (status != 2) {
|
|
HTSprintf(&vmsname, "%.*s[000000]", cplen, cp);
|
|
status = send_cwd(vmsname);
|
|
if (status != 2) {
|
|
HTSprintf(&vmsname, "%.*s", cplen, cp);
|
|
status = send_cwd(vmsname);
|
|
if (status != 2) {
|
|
FREE(fname);
|
|
init_help_message_cache();
|
|
NETCLOSE(control->socket);
|
|
control->socket = -1;
|
|
return ((status < 0) ? status : -status);
|
|
}
|
|
}
|
|
} else {
|
|
HTSprintf0(&vmsname, "000000");
|
|
filename = vmsname;
|
|
}
|
|
}
|
|
} else if (0 == strcmp(cp, (filename + 1))) {
|
|
status = send_cwd(cp);
|
|
if (status != 2) {
|
|
HTSprintf0(&vmsname, "%s:", cp);
|
|
status = send_cwd(vmsname);
|
|
if (status != 2) {
|
|
FREE(fname);
|
|
init_help_message_cache(); /* to free memory */
|
|
NETCLOSE(control->socket);
|
|
control->socket = -1;
|
|
return ((status < 0) ? status : -status);
|
|
}
|
|
}
|
|
HTSprintf0(&vmsname, "000000");
|
|
filename = vmsname;
|
|
}
|
|
}
|
|
/* Trim trailing slash if filename is not the top directory */
|
|
if (strlen(filename) > 1 && filename[strlen(filename) - 1] == '/')
|
|
filename[strlen(filename) - 1] = '\0';
|
|
|
|
#ifdef MAINTAIN_CONNECTION /* Don't need this if always new connection - F.M. */
|
|
if (!included_device) {
|
|
/* Get the current default VMS device:[directory] */
|
|
status = send_cmd_1("PWD");
|
|
if (status != 2) {
|
|
FREE(fname);
|
|
init_help_message_cache(); /* to free memory */
|
|
NETCLOSE(control->socket);
|
|
control->socket = -1;
|
|
return ((status < 0) ? status : -status);
|
|
}
|
|
/* Go to the VMS account's top directory */
|
|
if ((cp = StrChr(response_text, '[')) != NULL &&
|
|
(cp1 = strrchr(response_text, ']')) != NULL) {
|
|
char *tmp = 0;
|
|
unsigned len = 4;
|
|
|
|
StrAllocCopy(tmp, cp);
|
|
if ((cp2 = StrChr(cp, '.')) != NULL && cp2 < cp1) {
|
|
len += (cp2 - cp);
|
|
} else {
|
|
len += (cp1 - cp);
|
|
}
|
|
tmp[len] = 0;
|
|
StrAllocCat(tmp, "]");
|
|
|
|
status = send_cwd(tmp);
|
|
FREE(tmp);
|
|
|
|
if (status != 2) {
|
|
FREE(fname);
|
|
init_help_message_cache(); /* to free memory */
|
|
NETCLOSE(control->socket);
|
|
control->socket = -1;
|
|
return ((status < 0) ? status : -status);
|
|
}
|
|
}
|
|
}
|
|
#endif /* MAINTAIN_CONNECTION */
|
|
|
|
/* If we want the VMS account's top directory, list it now */
|
|
if (!(strcmp(filename, "/~")) ||
|
|
(included_device && 0 == strcmp(filename, "000000")) ||
|
|
(strlen(filename) == 1 && *filename == '/')) {
|
|
isDirectory = YES;
|
|
status = send_cmd_1("LIST");
|
|
FREE(fname);
|
|
if (status != 1) {
|
|
/* Action not started */
|
|
init_help_message_cache(); /* to free memory */
|
|
NETCLOSE(control->socket);
|
|
control->socket = -1;
|
|
return ((status < 0) ? status : -status);
|
|
}
|
|
/* Big goto! */
|
|
goto listen;
|
|
}
|
|
/* Otherwise, go to appropriate directory and doctor filename */
|
|
if (!StrNCmp(filename, "/~", 2)) {
|
|
filename += 2;
|
|
found_tilde = TRUE;
|
|
}
|
|
CTRACE((tfp, "check '%s' to translate x/y/ to [.x.y]\n", filename));
|
|
if (!included_device &&
|
|
(cp = StrChr(filename, '/')) != NULL &&
|
|
(cp1 = strrchr(cp, '/')) != NULL &&
|
|
(cp1 - cp) > 1) {
|
|
char *tmp = 0;
|
|
|
|
HTSprintf0(&tmp, "[.%.*s]", (int) (cp1 - cp - 1), cp + 1);
|
|
|
|
CTRACE((tfp, "change path '%s'\n", tmp));
|
|
while ((cp2 = strrchr(tmp, '/')) != NULL)
|
|
*cp2 = '.';
|
|
CTRACE((tfp, "...to path '%s'\n", tmp));
|
|
|
|
status = send_cwd(tmp);
|
|
FREE(tmp);
|
|
|
|
if (status != 2) {
|
|
FREE(fname);
|
|
init_help_message_cache(); /* to free memory */
|
|
NETCLOSE(control->socket);
|
|
control->socket = -1;
|
|
return ((status < 0) ? status : -status);
|
|
}
|
|
filename = cp1 + 1;
|
|
} else {
|
|
if (!included_device && !found_tilde) {
|
|
filename += 1;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case CMS_SERVER:
|
|
{
|
|
/*
|
|
* If we want the CMS account's top directory, or a base SFS or
|
|
* anonymous directory path (i.e., without a slash), list it
|
|
* now. FM
|
|
*/
|
|
if ((strlen(filename) == 1 && *filename == '/') ||
|
|
((0 == strncasecomp((filename + 1), "vmsysu:", 7)) &&
|
|
(cp = StrChr((filename + 1), '.')) != NULL &&
|
|
StrChr(cp, '/') == NULL) ||
|
|
(0 == strncasecomp(filename + 1, "anonymou.", 9) &&
|
|
StrChr(filename + 1, '/') == NULL)) {
|
|
if (filename[1] != '\0') {
|
|
status = send_cwd(filename + 1);
|
|
if (status != 2) {
|
|
/* Action not started */
|
|
init_help_message_cache(); /* to free memory */
|
|
NETCLOSE(control->socket);
|
|
control->socket = -1;
|
|
return ((status < 0) ? status : -status);
|
|
}
|
|
}
|
|
isDirectory = YES;
|
|
if (use_list)
|
|
status = send_cmd_1("LIST");
|
|
else
|
|
status = send_cmd_1("NLST");
|
|
FREE(fname);
|
|
if (status != 1) {
|
|
/* Action not started */
|
|
init_help_message_cache(); /* to free memory */
|
|
NETCLOSE(control->socket);
|
|
control->socket = -1;
|
|
return ((status < 0) ? status : -status);
|
|
}
|
|
/* Big goto! */
|
|
goto listen;
|
|
}
|
|
filename++;
|
|
|
|
/* Otherwise, go to appropriate directory and adjust filename */
|
|
while ((cp = StrChr(filename, '/')) != NULL) {
|
|
*cp++ = '\0';
|
|
status = send_cwd(filename);
|
|
if (status == 2) {
|
|
if (*cp == '\0') {
|
|
isDirectory = YES;
|
|
if (use_list)
|
|
status = send_cmd_1("LIST");
|
|
else
|
|
status = send_cmd_1("NLST");
|
|
FREE(fname);
|
|
if (status != 1) {
|
|
/* Action not started */
|
|
init_help_message_cache(); /* to free memory */
|
|
NETCLOSE(control->socket);
|
|
control->socket = -1;
|
|
return ((status < 0) ? status : -status);
|
|
}
|
|
/* Clear any messages from the login directory */
|
|
init_help_message_cache();
|
|
/* Big goto! */
|
|
goto listen;
|
|
}
|
|
filename = cp;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
/* Shift for any unescaped "/%2F" path */
|
|
if (!StrNCmp(filename, "//", 2))
|
|
filename++;
|
|
break;
|
|
}
|
|
/*
|
|
* Act on a file or listing request, or try to figure out which we're
|
|
* dealing with if we don't know yet. - FM
|
|
*/
|
|
if (!(type) || (type && *type != 'D')) {
|
|
/*
|
|
* If we are retrieving a file we will (except for CMS) use
|
|
* binary mode, which lets us use the size command supported by
|
|
* ftp servers which implement RFC 3659. Knowing the size lets
|
|
* us in turn display ETA in the progress message -TD
|
|
*/
|
|
if (control->is_binary) {
|
|
int code;
|
|
|
|
status = send_cmd_2("SIZE", filename);
|
|
if (status == 2) {
|
|
#if !defined(HAVE_LONG_LONG) && defined(GUESS_PRI_off_t)
|
|
long size;
|
|
|
|
if (sscanf(response_text, "%d %ld", &code, &size) == 2) {
|
|
anchor->content_length = (off_t) size;
|
|
}
|
|
#else
|
|
off_t size;
|
|
if (sscanf(response_text, "%d %" SCN_off_t, &code, &size)
|
|
== 2) {
|
|
anchor->content_length = size;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
status = send_cmd_2("RETR", filename);
|
|
if (status >= 5) {
|
|
int check;
|
|
|
|
if (Broken_RETR) {
|
|
CTRACE((tfp, "{{reconnecting...\n"));
|
|
close_connection(control);
|
|
check = setup_connection(name, anchor);
|
|
CTRACE((tfp, "...done }}reconnecting\n"));
|
|
if (check < 0)
|
|
return check;
|
|
}
|
|
}
|
|
} else {
|
|
status = 5; /* Failed status set as flag. - FM */
|
|
}
|
|
if (status != 1) { /* Failed : try to CWD to it */
|
|
/* Clear any login messages if this isn't the login directory */
|
|
if (strcmp(filename, "/"))
|
|
init_help_message_cache();
|
|
|
|
status = send_cwd(filename);
|
|
if (status == 2) { /* Succeeded : let's NAME LIST it */
|
|
isDirectory = YES;
|
|
if (use_list)
|
|
status = send_cmd_1("LIST");
|
|
else
|
|
status = send_cmd_1("NLST");
|
|
}
|
|
}
|
|
FREE(fname);
|
|
FREE(vmsname);
|
|
if (status != 1) {
|
|
init_help_message_cache(); /* to free memory */
|
|
NETCLOSE(control->socket);
|
|
control->socket = -1;
|
|
if (status < 0)
|
|
return status;
|
|
else
|
|
return -status;
|
|
}
|
|
}
|
|
|
|
listen:
|
|
if (!ftp_local_passive) {
|
|
/* Wait for the connection */
|
|
LY_SOCKADDR soc_A;
|
|
LY_SOCKLEN soc_addrlen = (LY_SOCKLEN) sizeof(soc_A);
|
|
|
|
#ifdef SOCKS
|
|
if (socks_flag)
|
|
status = Raccept((int) master_socket,
|
|
SOCKADDR_OF(soc_A),
|
|
&soc_addrlen);
|
|
else
|
|
#endif /* SOCKS */
|
|
status = accept((int) master_socket,
|
|
SOCKADDR_OF(soc_A),
|
|
&soc_addrlen);
|
|
if (status < 0) {
|
|
init_help_message_cache(); /* to free memory */
|
|
return HTInetStatus("accept");
|
|
}
|
|
CTRACE((tfp, "TCP: Accepted new socket %d\n", status));
|
|
data_soc = status;
|
|
}
|
|
|
|
if (isDirectory) {
|
|
if (server_type == UNIX_SERVER && !unsure_type &&
|
|
!strcmp(response_text,
|
|
"150 Opening ASCII mode data connection for /bin/dl.\n")) {
|
|
CTRACE((tfp, "HTFTP: Treating as \"dls\" server.\n"));
|
|
server_type = DLS_SERVER;
|
|
}
|
|
final_status = read_directory(anchor, name, format_out, sink);
|
|
if (final_status > 0) {
|
|
if (server_type != CMS_SERVER)
|
|
if (outstanding-- > 0) {
|
|
status = response(NULL);
|
|
if (status < 0 ||
|
|
(status == 2 && !StrNCmp(response_text, "221", 3)))
|
|
outstanding = 0;
|
|
}
|
|
} else { /* HT_INTERRUPTED */
|
|
/* User may have pressed 'z' to give up because no
|
|
packets got through, so let's not make them wait
|
|
any longer - kw */
|
|
outstanding = 0;
|
|
}
|
|
|
|
if (data_soc != -1) { /* normally done in read_directory */
|
|
CTRACE((tfp, "HTFTP: Closing data socket %d\n", data_soc));
|
|
status = NETCLOSE(data_soc);
|
|
if (status == -1)
|
|
HTInetStatus("close"); /* Comment only */
|
|
}
|
|
status = final_status;
|
|
} else {
|
|
int rv;
|
|
char *FileName = HTParse(name, "", PARSE_PATH + PARSE_PUNCTUATION);
|
|
|
|
/* Clear any login messages */
|
|
init_help_message_cache();
|
|
|
|
/* Fake a Content-Encoding for compressed files. - FM */
|
|
HTUnEscape(FileName);
|
|
if (!IsUnityEnc(encoding)) {
|
|
/*
|
|
* We already know from the call to HTFileFormat above that this is
|
|
* a compressed file, no need to look at the filename again. - kw
|
|
*/
|
|
StrAllocCopy(anchor->content_type, format->name);
|
|
StrAllocCopy(anchor->content_encoding, HTAtom_name(encoding));
|
|
format = HTAtom_for("www/compressed");
|
|
|
|
} else {
|
|
int rootlen;
|
|
CompressFileType cft = HTCompressFileType(FileName, "._-", &rootlen);
|
|
|
|
if (cft != cftNone) {
|
|
FileName[rootlen] = '\0';
|
|
format = HTFileFormat(FileName, &encoding, NULL);
|
|
format = HTCharsetFormat(format, anchor, -1);
|
|
StrAllocCopy(anchor->content_type, format->name);
|
|
format = HTAtom_for("www/compressed");
|
|
}
|
|
|
|
switch (cft) {
|
|
case cftCompress:
|
|
StrAllocCopy(anchor->content_encoding, "x-compress");
|
|
break;
|
|
case cftGzip:
|
|
StrAllocCopy(anchor->content_encoding, "x-gzip");
|
|
break;
|
|
case cftDeflate:
|
|
StrAllocCopy(anchor->content_encoding, "x-deflate");
|
|
break;
|
|
case cftBzip2:
|
|
StrAllocCopy(anchor->content_encoding, "x-bzip2");
|
|
break;
|
|
case cftBrotli:
|
|
StrAllocCopy(anchor->content_encoding, "x-brotli");
|
|
break;
|
|
case cftNone:
|
|
break;
|
|
}
|
|
}
|
|
FREE(FileName);
|
|
|
|
_HTProgress(gettext("Receiving FTP file."));
|
|
rv = HTParseSocket(format, format_out, anchor, data_soc, sink);
|
|
|
|
HTInitInput(control->socket);
|
|
/* Reset buffering to control connection DD 921208 */
|
|
|
|
if (rv < 0) {
|
|
if (rv == -2) /* weird error, don't expect much response */
|
|
outstanding--;
|
|
else if (rv == HT_INTERRUPTED || rv == -1)
|
|
/* User may have pressed 'z' to give up because no
|
|
packets got through, so let's not make them wait
|
|
longer - kw */
|
|
outstanding = 0;
|
|
CTRACE((tfp, "HTFTP: Closing data socket %d\n", data_soc));
|
|
status = NETCLOSE(data_soc);
|
|
} else {
|
|
status = 2; /* data_soc already closed in HTCopy - kw */
|
|
}
|
|
|
|
if (status < 0 && rv != HT_INTERRUPTED && rv != -1) {
|
|
(void) HTInetStatus("close"); /* Comment only */
|
|
} else {
|
|
if (rv != HT_LOADED && outstanding--) {
|
|
status = response(NULL); /* Pick up final reply */
|
|
if (status != 2 && rv != HT_INTERRUPTED && rv != -1) {
|
|
data_soc = -1; /* invalidate it */
|
|
init_help_message_cache(); /* to free memory */
|
|
return HTLoadError(sink, 500, response_text);
|
|
} else if (status == 2 && !StrNCmp(response_text, "221", 3)) {
|
|
outstanding = 0;
|
|
}
|
|
}
|
|
}
|
|
final_status = HT_LOADED;
|
|
}
|
|
while (outstanding-- > 0 &&
|
|
(status > 0)) {
|
|
status = response(NULL);
|
|
if (status == 2 && !StrNCmp(response_text, "221", 3))
|
|
break;
|
|
}
|
|
data_soc = -1; /* invalidate it */
|
|
CTRACE((tfp, "HTFTPLoad: normal end; "));
|
|
if (control->socket < 0) {
|
|
CTRACE((tfp, "control socket is %d\n", control->socket));
|
|
} else {
|
|
CTRACE((tfp, "closing control socket %d\n", control->socket));
|
|
status = NETCLOSE(control->socket);
|
|
if (status == -1)
|
|
HTInetStatus("control connection close"); /* Comment only */
|
|
}
|
|
control->socket = -1;
|
|
init_help_message_cache(); /* to free memory */
|
|
/* returns HT_LOADED (always for file if we get here) or error */
|
|
return final_status;
|
|
} /* open_file_read */
|
|
|
|
/*
|
|
* This function frees any user entered password, so that
|
|
* it must be entered again for a future request. - FM
|
|
*/
|
|
void HTClearFTPPassword(void)
|
|
{
|
|
/*
|
|
* Need code to check cached documents from non-anonymous ftp accounts and
|
|
* do something to ensure that they no longer can be accessed without a new
|
|
* retrieval. - FM
|
|
*/
|
|
|
|
/*
|
|
* Now free the current user entered password, if any. - FM
|
|
*/
|
|
FREE(user_entered_password);
|
|
}
|
|
|
|
#endif /* ifndef DISABLE_FTP */
|