home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Magazyn Amiga Shareware Floppies
/
ma64.dms
/
ma64.adf
/
FTPMount-1.0
/
Source
/
connect.c
next >
Wrap
C/C++ Source or Header
|
1996-04-12
|
58KB
|
2,617 lines
/*
* This source file is Copyright 1995 and 1996 by Evan Scott, apart from those sections which are
* explicitly described as otherwise.
* All rights reserved.
* Permission is granted to distribute this file provided no
* fees beyond distribution costs are levied.
*/
#include <exec/types.h>
#include <exec/memory.h>
#include <exec/alerts.h>
#include <devices/timer.h>
#include <dos/dos.h>
#include <dos/dosextens.h>
#include <dos/dostags.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/intuition.h>
#include <proto/gadtools.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h> // for isdigit() (RJF)
#include "evtypes.h"
#include "verify.h"
#include "tcp.h"
#include "site.h"
#include "ftp.h"
#include "split.h"
#include "ftpinfo.h"
#include "connect.h"
#include "request.h"
#include "globals.h"
#include "strings.h"
#define ERROR_GARBAGE_RECEIVED 15
extern ftpinfo *get_info(site *, b8 *);
b8 *grow_info(b8 *a, b8 *b, int n)
/*
* concatenates a and b (not null terminated of length n) and returns
* an allocated string with the result. a is freed.
* a may be nil
* b may not
*/
{
b8 *c, *d;
int len;
if (a) len = strlen(a);
else len = 0;
len += n + 1;
c = (b8 *)allocate(len, V_cstr);
if (!c) {
if (a) deallocate(a, V_cstr);
return nil;
}
if (a) {
strcpy(c, a);
d = c + strlen(c);
} else {
d = c;
}
if (n)
memcpy(d, b, n);
d[n] = 0;
if (a) {
deallocate(a, V_cstr);
}
a = c;
while (*a) {
if (*a == '\t') *a = ' '; /* hmmm */
if (*a == '\r') *a = '\n';
a++;
}
return c;
}
boolean substr(b8 *s, b8 *ss)
/*
* returns true if s contains ss (non-case-sensitive)
* s may be nil, in which case false is returned
*/
{
int len;
if (s == nil) return false;
len = strlen(ss);
while (*s) {
if (strnicmp(s, ss, len) == 0) return true;
s++;
}
return false;
}
void inform(struct IntuitionBase *IntuitionBase, b8 *title, b8 *text, b8 *site, b32 errno)
{
struct EasyStruct es;
es.es_StructSize = sizeof(struct EasyStruct);
es.es_Flags = 0;
es.es_Title = title;
es.es_GadgetFormat = strings[MSG_OK];
es.es_TextFormat = text;
EasyRequest(nil, &es, nil, site, errno);
}
tcpmessage *new_message(site *sp)
/*
* get a new tcpmessage
*/
{
tcpmessage *intr;
struct MsgPort *sync;
verify(sp, V_site);
intr = sp->intr;
verify(intr, V_tcpmessage);
sync = sp->sync;
/* "re-use" intr to get a new tcpmessage */
intr->command = TCP_NEWMESSAGE;
intr->header.mn_ReplyPort = sync;
PutMsg(tcp, &intr->header);
WaitPort(sync); GetMsg(sync); /* this _should_ never block */
return (tcpmessage *)intr->data;
}
void interrupt_message(site *sp, tcpmessage *tm)
/*
* interrupt tm
*/
{
tcpmessage *intr;
struct MsgPort *sync;
verify(sp, V_site);
verify(tm, V_tcpmessage);
intr = sp->intr;
verify(intr, V_tcpmessage);
sync = sp->sync;
intr->command = TCP_INTERRUPT;
intr->header.mn_ReplyPort = sync;
intr->interrupt = tm;
PutMsg(tcp, &intr->header);
/*
* NB: I could probably assume tm->header.mn_ReplyPort == sync safely, but
* this seems a bit more "correctly generic" (although potentially more buggy)
*/
WaitPort(tm->header.mn_ReplyPort); GetMsg(tm->header.mn_ReplyPort); /* aborted tm coming back */
WaitPort(sync); GetMsg(sync); /* intr coming back successful */
return;
}
b32 control_write(site *sp, b8 *command, b32 csig)
/*
* writes the string command to the control connection
* Inputs:
* sp : site pointer
* command : null terminated command string
* csig : additional cancel signals, 0 is ok
*
* returns the tcp error
*/
{
tcpmessage *tm;
struct MsgPort *sync;
b32 signals, rsigs;
verify(sp, V_site);
truth(command != nil);
// truth(sp->connected);
tm = sp->control;
verify(tm, V_tcpmessage);
sync = sp->sync;
tm->command = TCP_WRITE;
tm->length = strlen(command);
tm->flags = 0;
tm->data = command;
tm->header.mn_ReplyPort = sync;
csig |= sp->abort_signals | sp->disconnect_signals;
signals = (1 << sync->mp_SigBit) | csig;
PutMsg(tcp, &tm->header);
do {
rsigs = Wait(signals);
if (rsigs & csig) {
interrupt_message(sp, tm);
if (rsigs & sp->disconnect_signals) {
disconnect(sp);
}
return ERROR_INTERRUPTED;
}
} while (!GetMsg(sync));
return tm->error;
}
b32 make_connection(site *sp, tcpmessage *tm, b8 *addr, b16 port, b32 csig)
/*
* make a connection to a remote host
* Inputs:
* sp : site pointer
* tm : an unused tcpmessage
* addr : null terminated string address
* port : port number to connect to
* csig : additional cancel signals (may be 0)
*
* Returns:
* standard tcp error
*/
{
struct MsgPort *sync;
b32 signals, rsigs, asigs;
verify(sp, V_site);
verify(tm, V_tcpmessage);
truth(addr != nil);
sync = sp->sync;
tm->header.mn_ReplyPort = sync;
tm->command = TCP_CONNECT;
tm->data = addr;
tm->port.w = port;
tm->flags = 0;
asigs = csig | sp->abort_signals | sp->disconnect_signals;
signals = asigs | (1 << sync->mp_SigBit);
PutMsg(tcp, &tm->header);
do {
rsigs = Wait(signals);
if (rsigs & asigs) {
interrupt_message(sp, tm);
if (rsigs & sp->disconnect_signals) {
disconnect(sp);
}
return ERROR_INTERRUPTED;
}
} while (!GetMsg(sync));
return tm->error;
}
void break_connection(site *sp, tcpmessage *tm)
/*
* do a TCP_CLOSE on tm
*/
{
struct MsgPort *sync;
verify(sp, V_site);
verify(tm, V_tcpmessage);
sync = sp->sync;
tm->command = TCP_CLOSE;
tm->header.mn_ReplyPort = sync;
PutMsg(tcp, &tm->header);
WaitPort(sync); GetMsg(sync);
return;
}
boolean passive_response(b8 *s, b8 *addr, b16 *portp)
/*
* parse the response to a PASV command
* Inputs:
* s : the response to the PASV (null terminated)
* addr : a buffer to hold the address (should be as long as s)
* portp : where to put the port number
*
* Returns:
* true if it was a valid PASV response
*/
{
b8 *t;
b16 ncommas, portn;
truth(s != nil);
truth(addr != nil);
truth(portp != nil);
while (*s && *s != '(') s++;
if (!*s) return false;
/* first calculate port number ... skip the first 4 commas */
ncommas = 0;
t = s;
while (*t && *t != ')' && ncommas < 4) {
if (*t == ',') ncommas++;
t++;
}
portn = atoi(t) * 256; /* possibly a more thorough check of whether these are legit numbers */
while (*t && *t != ',' && *t != ')') t++;
if (*t == ',') portn += atoi(t+1);
/*
* now copy the first 4 fields to addr, changing commas to periods
* (hopefully making a legitimate ip address)
*/
ncommas = 0;
s++; /* move s past the '(' */
while (*s && ncommas < 4) {
if (*s == ',') {
ncommas++;
if (ncommas == 4) *addr = 0;
else *addr++ = '.';
s++;
} else {
*addr++ = *s++;
}
}
*portp = portn;
return true;
}
b32 response(site *sp, b32 csig, b8 **infop, b8 *code)
/*
* reads response from remote server on sp->control
* Inputs:
* sp : site pointer
* csig : cancel signals (in addition to standard sp->abort etc ... usually a window) 0 is ok.
* infop : pointer to a string pointer to store the servers response message
* code : 3 byte array for the response code (eg 257 "/usr/dm/pathname" directory created)
*
* Returns:
* standard tcp error code with the additional error ERROR_GARBAGE_RECEIVED
*/
{
tcpmessage *tm;
struct MsgPort *sync;
b32 signals, rsigs, asigs;
b8 *info, *z;
verify(sp, V_site);
truth(code != nil);
truth(infop != nil);
*infop = nil;
tm = sp->control;
sync = sp->sync;
verify(tm, V_tcpmessage);
asigs = csig | sp->disconnect_signals | sp->abort_signals; /* abort signals */
signals = asigs | (1 << sync->mp_SigBit);
tm->command = TCP_READ;
tm->flags = FLAG_READLINE;
tm->data = sp->read_buffer;
tm->length = READ_BUFFER_LENGTH;
tm->header.mn_ReplyPort = sync;
PutMsg(tcp, &tm->header);
do {
rsigs = Wait(signals);
if (rsigs & asigs) {
state_change(sp, SS_ABORTING);
interrupt_message(sp, tm);
/* cancelling the read of the response is guaranteed fatal anyway ... but ... */
if (rsigs & sp->disconnect_signals) {
disconnect(sp);
}
return ERROR_INTERRUPTED;
}
} while (!GetMsg(sync));
if (tm->error != NO_ERROR) {
show_int(tm->error);
return tm->error;
}
z = sp->read_buffer;
if (tm->result < 4 ||
z[0] < '0' || z[0] > '9' ||
z[1] < '0' || z[1] > '9' ||
z[2] < '0' || z[2] > '9') {
show_int(tm->result);
return ERROR_GARBAGE_RECEIVED;
}
code[0] = z[0];
code[1] = z[1];
code[2] = z[2];
info = grow_info(nil, &z[4], tm->result - 4);
if (z[3] == '-') { /* we have a continuation message */
while (1) {
PutMsg(tcp, &tm->header);
do {
rsigs = Wait(signals);
if (rsigs & asigs) {
state_change(sp, SS_ABORTING);
if (info)
deallocate(info, V_cstr);
interrupt_message(sp, tm);
if (rsigs & sp->disconnect_signals) {
disconnect(sp);
}
return ERROR_INTERRUPTED;
}
} while (!GetMsg(sync));
if (tm->error != NO_ERROR) { /* tell them about the error */
if (info)
deallocate(info, V_cstr);
return tm->error;
}
if (tm->result < 4) { /* not enough to even check if codes are equal */
info = grow_info(info, z, tm->result);
continue;
}
if (z[0] == code[0] &&
z[1] == code[1] &&
z[2] == code[2]) {
info = grow_info(info, &z[4], tm->result - 4);
if (z[3] == ' ') break; /* end of continuation */
} else {
info = grow_info(info, z, tm->result);
}
}
}
*infop = info;
return NO_ERROR;
}
b16 numeric_reply(b8 *s)
{
return (b16)((s[0] - '0') * 100 + (s[1] - '0') * 10 + (s[2] - '0'));
}
boolean retry_cancel(struct IntuitionBase *IntuitionBase, b8 *title, b8 *info)
/*
* paged information with retry/cancel buttons
* returns true for retry
* info may be nil (well, sortof)
*/
{
b8 *z, *s, tmp;
struct EasyStruct es;
int nlines;
es.es_StructSize = sizeof(struct EasyStruct);
es.es_Flags = 0;
es.es_Title = title;
es.es_TextFormat = "%s";
if (info)
z = info;
else
z = strings[MSG_UNKNOWN];
more:
s = z;
nlines = 0;
while (*z && nlines < MORE_LINES) {
if (*z == '\n') nlines++;
z++;
}
if (*z) {
es.es_GadgetFormat = strings[MSG_RETRY_MORE_CANCEL];
} else {
es.es_GadgetFormat = strings[MSG_RETRY_CANCEL];
}
tmp = *z;
*z = 0;
switch (EasyRequest(nil, &es, nil, s)) {
case 0: /* cancel */
*z = tmp;
return false;
case 1: /* retry */
*z = tmp;
return true;
case 2: /* more */
*z = tmp;
if (!*z) return true;
goto more;
}
}
void ok(struct IntuitionBase *IntuitionBase, b8 *title, b8 *info)
/*
* paged information with ok button
* info may be nil (sortof)
*/
{
b8 *z, *s, tmp;
struct EasyStruct es;
int nlines;
es.es_StructSize = sizeof(struct EasyStruct);
es.es_Flags = 0;
es.es_Title = title;
es.es_TextFormat = "%s";
if (info)
z = info;
else
z = strings[MSG_UNKNOWN];
more:
s = z;
nlines = 0;
while (*z && nlines < MORE_LINES) {
if (*z == '\n') nlines++;
z++;
}
if (*z) {
es.es_GadgetFormat = strings[MSG_MORE_OK];
} else {
es.es_GadgetFormat = strings[MSG_OK];
}
tmp = *z;
*z = 0;
if (EasyRequest(nil, &es, nil, s) && tmp) {
*z = tmp;
goto more;
}
*z = tmp;
return;
}
void disconnect(site *sp)
/*
* rudely close control connection and clean up state information on site sp
*/
{
tcpmessage *tm;
struct MsgPort *sync;
verify(sp, V_site);
if (!sp->connected) return;
sync = sp->sync;
state_change(sp, SS_DISCONNECTING);
tm = sp->cfile; /* file open, have to close it */
if (tm) {
verify(tm, V_tcpmessage);
verify(sp->file_list, V_file_info);
tm->command = TCP_CLOSE;
tm->header.mn_ReplyPort = sync;
PutMsg(tcp, &tm->header); /* send CLOSE on file tm */
WaitPort(sync); GetMsg(sync);
tm->command = TCP_DISPOSE;
PutMsg(tcp, &tm->header);
sp->cfile = nil;
deallocate(sp->file_list, V_file_info);
sp->file_list = nil;
}
tm = sp->control;
verify(tm, V_tcpmessage);
tm->command = TCP_CLOSE;
tm->header.mn_ReplyPort = sync;
PutMsg(tcp, &tm->header); /* send CLOSE */
sp->connected = false;
if (sp->cwd) {
deallocate(sp->cwd, V_cstr);
sp->cwd = nil;
}
while (sp->infos) free_info_header(sp->infos);
WaitPort(sync); GetMsg(sync); /* wait for CLOSE to come back */
/* it shouldn't really fail ... not sure if we can do anything if it has */
state_change(sp, SS_DISCONNECTED);
return;
}
b32 read_file(site *sp, b8 *s, b32 *length)
/*
* read *length bytes from open file
* Inputs:
* sp : site pointer
* s : data buffer
* length : pointer to length to read, changed to length actually read
*
* Result:
* tcp error is returned, *length is modified to be actual length read
*/
{
tcpmessage *tm;
struct MsgPort *sync;
b32 signals, asigs, rsigs;
verify(sp, V_site);
truth(s != nil);
truth(length != nil);
tm = sp->cfile;
verify(tm, V_tcpmessage);
sync = sp->sync;
tm->command = TCP_READ;
tm->flags = 0;
tm->data = s;
tm->length = *length;
tm->header.mn_ReplyPort = sync;
asigs = sp->abort_signals | sp->disconnect_signals;
signals = asigs | (1 << sync->mp_SigBit);
PutMsg(tcp, &tm->header);
do {
rsigs = Wait(signals);
if (rsigs & asigs) {
state_change(sp, SS_ABORTING);
interrupt_message(sp, tm);
if (rsigs & sp->disconnect_signals) {
disconnect(sp);
} else {
close_file(sp, false);
}
*length = 0;
return ERROR_INTERRUPTED;
}
} while (!GetMsg(sync));
if (tm->result > 0) {
*length = tm->result;
return NO_ERROR;
} else {
*length = 0;
return tm->error;
}
}
b32 write_file(site *sp, b8 *s, b32 *length)
/*
* write *length bytes to an open file (almost identical copy to read_file above)
* Inputs:
* sp : site pointer
* s : data buffer
* length : pointer to length to write, changed to length actually written
*
* Result:
* tcp error is returned, *length is modified to be actual length written
*/
{
tcpmessage *tm;
struct MsgPort *sync;
b32 signals, asigs, rsigs;
verify(sp, V_site);
truth(s != nil);
truth(length != nil);
tm = sp->cfile;
verify(tm, V_tcpmessage);
sync = sp->sync;
tm->command = TCP_WRITE;
tm->flags = 0;
tm->data = s;
tm->length = *length;
tm->header.mn_ReplyPort = sync;
asigs = sp->abort_signals | sp->disconnect_signals;
signals = asigs | (1 << sync->mp_SigBit);
PutMsg(tcp, &tm->header);
do {
rsigs = Wait(signals);
if (rsigs & asigs) {
state_change(sp, SS_ABORTING);
interrupt_message(sp, tm);
if (rsigs & sp->disconnect_signals) {
disconnect(sp);
} else {
close_file(sp, false);
}
*length = 0;
return ERROR_INTERRUPTED;
}
} while (!GetMsg(sync));
if (tm->result > 0) {
*length = tm->result;
return NO_ERROR;
} else {
*length = 0;
return tm->error;
}
}
b32 open_file(site *sp, b8 *s, boolean writing, b8 *leaf_name)
/*
* open file with name in s
* Inputs:
* sp : site pointer
* s : file name
* writing : true if opened for writing, false if for reading
*
* Returns:
* 0 : no error
* non-0 : standard file system errors (ERROR_OBJECT_NOT_FOUND etc)
*/
{
tcpmessage *tm, *newtm;
struct MsgPort *sync;
b8 reply[4], *info;
b8 *leaf;
b32 error;
b16 port_number;
file_info *fi;
verify(sp, V_site);
truth(s != nil);
/* a few conditions we are assuming */
truth(sp->connected);
truth(sp->cfile == nil);
truth(sp->file_list == nil);
if (s[0] == 0) {
/* they are trying to open the root of this site as a file ... */
return ERROR_OBJECT_WRONG_TYPE;
}
tm = sp->control;
verify(tm, V_tcpmessage);
sync = sp->sync;
tm->header.mn_ReplyPort = sync;
leaf = cd_parent(sp, s);
if (!leaf) {
/* there are other possible reasons here, but I'm being lazy ... */
return ERROR_DIR_NOT_FOUND;
}
if (leaf_name) leaf = leaf_name;
state_change(sp, SS_OPENING);
newtm = new_message(sp);
if (newtm) {
fi = (file_info *)allocate(sizeof(*fi) + strlen(s) + 1, V_file_info);
if (fi) {
strcpy(fi->fname, s);
if (control_write(sp, "PASV\r\n", 0) == NO_ERROR) {
if (writing) { /* yes, I do this twice :( */
sprintf(sp->read_buffer, "STOR %s\r\n", leaf);
} else {
sprintf(sp->read_buffer, "RETR %s\r\n", leaf);
}
if (!sp->quick || control_write(sp, sp->read_buffer, 0) == NO_ERROR) {
if (response(sp, 0, &info, reply) == NO_ERROR) {
if (reply[0] == '2') {
if (info) {
if (passive_response(info, sp->read_buffer, &port_number)) {
deallocate(info, V_cstr);
if (make_connection(sp, newtm, sp->read_buffer, port_number, 0) == NO_ERROR) {
if (writing) { /* and again */
sprintf(sp->read_buffer, "STOR %s\r\n", leaf);
} else {
sprintf(sp->read_buffer, "RETR %s\r\n", leaf);
}
if (sp->quick || control_write(sp, sp->read_buffer, 0) == NO_ERROR) {
/* this next response will be to the RETR/STOR */
if (response(sp, 0, &info, reply) == NO_ERROR) {
if (info) {
#ifdef VERIFY
if (reply[0] != '1') {
reply[3] = 0;
show_string(reply);
show_string(info);
}
#endif
deallocate(info, V_cstr);
}
if (reply[0] == '1') {
ensure(fi, V_file_info);
fi->rfarg = 0;
fi->rpos = 0;
fi->vpos = 0;
fi->end = 0;
fi->closed = false;
fi->seek_end = false;
fi->eof = false;
fi->port = nil;
fi->type = (writing) ? ACTION_FINDOUTPUT : ACTION_FINDINPUT;
sp->cfile = newtm;
sp->file_list = fi;
fi->next = nil;
return 0;
} else {
switch (numeric_reply(reply)) {
case 450:
case 520:
case 550:
if (writing) {
error = ERROR_INVALID_COMPONENT_NAME;
} else {
error = ERROR_OBJECT_NOT_FOUND;
}
break;
case 521:
case 532:
case 533:
if (writing) {
error = ERROR_WRITE_PROTECTED;
} else {
error = ERROR_READ_PROTECTED;
}
break;
case 452:
case 552:
error = ERROR_DISK_FULL;
break;
case 553:
if (writing) {
error = ERROR_WRITE_PROTECTED;
} else {
error = ERROR_INVALID_COMPONENT_NAME;
}
break;
default:
error = ERROR_OBJECT_NOT_FOUND;
break;
}
}
/* no need to disconnect sp */
deallocate(fi, V_file_info);
break_connection(sp, newtm);
newtm->command = TCP_DISPOSE;
PutMsg(tcp, &newtm->header);
return error;
} else {
show_string("Error reading response to RETR/STOR");
error = ERROR_OBJECT_NOT_FOUND;
}
break_connection(sp, newtm);
} else {
show_string("error writing RETR/STOR");
error = ERROR_OBJECT_NOT_FOUND;
}
} else {
show_string("Error making connection");
error = ERROR_OBJECT_NOT_FOUND;
}
} else {
show_string("Bad PASV response");
deallocate(info, V_cstr);
error = ERROR_OBJECT_NOT_FOUND;
}
} else {
show_string("no info");
error = ERROR_NO_FREE_STORE;
}
} else {
show_string("non-'2' response to PASV");
error = ERROR_OBJECT_NOT_FOUND;
}
} else {
show_string("error reading response to PASV");
error = ERROR_OBJECT_NOT_FOUND;
}
} else {
show_string("error writing RETR/STOR");
error = ERROR_OBJECT_NOT_FOUND;
}
} else {
show_string("error writing PASV");
error = ERROR_OBJECT_NOT_FOUND;
}
deallocate(fi, V_file_info);
} else error = ERROR_NO_FREE_STORE;
newtm->command = TCP_DISPOSE;
PutMsg(tcp, &newtm->header);
disconnect(sp);
} else error = ERROR_NO_FREE_STORE;
return error;
}
/* this is how large our flushing buffer is when attempting an abort */
#define FLUSH_SIZE 100
void close_file(site *sp, boolean normal_close)
/*
* close currently open file for site
* Inputs:
* sp : site pointer
* normal_close : true if closed normally, false if closed by async abort
*/
{
tcpmessage *tm, *filetm, *ret;
file_info *fi;
struct MsgPort *sync;
b8 *info, reply[4], flush[FLUSH_SIZE];
b32 signals, asigs, rsigs;
verify(sp, V_site);
tm = sp->control;
filetm = sp->cfile;
fi = sp->file_list;
verify(tm, V_tcpmessage);
verify(filetm, V_tcpmessage);
verify(fi, V_file_info);
if (normal_close) {
sp->cfile = nil;
sp->cfile_type = 0;
sp->file_list = 0;
}
state_change(sp, SS_CLOSING);
sync = sp->sync;
signals = (1 << sync->mp_SigBit) | sp->disconnect_signals | sp->abort_signals;
asigs = sp->disconnect_signals | sp->abort_signals;
#ifdef VERIFY
if (fi->eof && fi->rpos < fi->end) {
show_string("Closing : EOF before fi->end");
show_int(fi->rpos);
show_int(fi->end);
}
#endif
if (fi->type == ACTION_FINDINPUT && fi->rpos < fi->end && !fi->eof) {
if (normal_close) {
deallocate(fi, V_file_info);
} else {
fi->eof = true;
}
/* have to ABOR :( */
show_string("Attempting ABOR");
if (control_write(sp, "ABOR\r\n", 0) != NO_ERROR) {
show_string("close file failed X1");
break_connection(sp, filetm);
disconnect(sp);
filetm->command = TCP_DISPOSE;
PutMsg(tcp, &filetm->header);
return;
}
/* can't use response because we need to flush filetm at the same time */
filetm->command = TCP_READ;
filetm->flags = 0;
filetm->data = flush;
filetm->length = FLUSH_SIZE;
filetm->header.mn_ReplyPort = sync;
tm->command = TCP_READ;
tm->flags = FLAG_READLINE;
tm->data = sp->read_buffer;
tm->length = READ_BUFFER_LENGTH;
tm->header.mn_ReplyPort = sync;
PutMsg(tcp, &tm->header);
PutMsg(tcp, &filetm->header);
while (1) {
rsigs = Wait(signals);
if (rsigs & asigs) {
state_change(sp, SS_ABORTING);
interrupt_message(sp, tm);
interrupt_message(sp, filetm);
break;
}
ret = (tcpmessage *)GetMsg(sync);
if (ret == tm) {
/* wait for filetm */
WaitPort(sync); GetMsg(sync);
break;
}
if (ret->error != NO_ERROR) { /* filetm is done */
/* wait for tm */
WaitPort(sync); GetMsg(sync);
break;
}
PutMsg(tcp, &filetm->header);
}
break_connection(sp, filetm);
if (normal_close) {
filetm->command = TCP_DISPOSE;
PutMsg(tcp, &filetm->header);
}
if (tm->error != NO_ERROR) {
show_string("close file failed X2");
disconnect(sp);
return;
}
show_string("First ABOR response");
#ifdef VERIFY
if (sp->read_buffer[3] == '-') show_string("continuation reply on ABOR");
#endif
if (!normal_close) {
/* leave the close response until we do the real close later */
return;
}
switch (response(sp, 0, &info, reply)) {
case NO_ERROR:
break;
default:
show_string("close file failed X3");
disconnect(sp);
return;
}
show_string("Second ABOR response");
#ifdef VERIFY
if (reply[0] != '2') {
reply[3] = 0;
show_string(reply);
show_string(info);
}
#endif
if (info) deallocate(info, V_cstr);
show_string("ABOR completed");
return;
}
if (normal_close) {
deallocate(fi, V_file_info);
} else {
fi->eof = true;
}
break_connection(sp, filetm);
if (!normal_close) {
/* leave the final close response until we do the real close later */
return;
}
filetm->command = TCP_DISPOSE;
PutMsg(tcp, &filetm->header);
switch (response(sp, 0, &info, reply)) {
case NO_ERROR:
break;
default:
show_string("close failed 1");
disconnect(sp);
return;
}
if (info) deallocate(info, V_cstr);
/* we don't really care what they returned here */
#ifdef VERIFY
if (reply[0] != '2') {
show_string("Non '2' close of file");
sp->read_buffer[0] = reply[0];
sp->read_buffer[1] = reply[1];
sp->read_buffer[2] = reply[2];
sp->read_buffer[3] = 0;
show_string(sp->read_buffer);
}
#endif
return;
}
b32 delete_file(site *sp, b8 *s)
/*
* delete file with name in s
* Inputs:
* sp : site pointer
* s : full path name
*
* Returns:
* standard file system errors
*/
{
b8 *leaf;
b32 error;
boolean perm;
b8 *info, reply[3];
ftpinfo *fi;
verify(sp, V_site);
truth(s != nil);
if (s[0] == 0) {
/* trying to delete root */
return ERROR_OBJECT_WRONG_TYPE;
}
leaf = cd_parent(sp, s);
if (!leaf) {
/* again, being lazy here */
return ERROR_DIR_NOT_FOUND;
}
state_change(sp, SS_DELETING);
fi = get_info(sp, s);
if (fi) {
leaf = fi->name;
}
sprintf(sp->read_buffer, "DELE %s\r\n", leaf);
if (control_write(sp, sp->read_buffer, 0) == NO_ERROR) {
if (response(sp, 0, &info, reply) == NO_ERROR) {
perm = substr(info, "perm");
if (info) deallocate(info, V_cstr);
switch (reply[0]) {
case '2':
return 0; /* success */
case '4':
/* temp failure ... */
/* most likely reason */
return ERROR_OBJECT_IN_USE;
default:
if (numeric_reply(reply) == 502) return ERROR_ACTION_NOT_KNOWN;
if (perm)
return ERROR_DELETE_PROTECTED;
else
return ERROR_OBJECT_NOT_FOUND;
}
} else {
disconnect(sp);
error = ERROR_OBJECT_NOT_FOUND;
}
} else {
disconnect(sp);
error = ERROR_OBJECT_NOT_FOUND;
}
return error;
}
b32 delete_directory(site *sp, b8 *s)
/*
* delete directory with name in s
* Inputs:
* sp : site pointer
* s : full path name
*
* Returns:
* standard file system errors
*/
{
b8 *leaf;
b32 error;
boolean perm, no_such;
b8 *info, reply[3];
ftpinfo *fi;
verify(sp, V_site);
truth(s != nil);
if (s[0] == 0) {
/* trying to delete root */
return ERROR_OBJECT_WRONG_TYPE;
}
leaf = cd_parent(sp, s);
if (!leaf) {
/* again, being lazy here */
return ERROR_DIR_NOT_FOUND;
}
state_change(sp, SS_DELETING);
fi = get_info(sp, s);
if (fi) {
leaf = fi->name;
}
sprintf(sp->read_buffer, "RMD %s\r\n", leaf);
if (control_write(sp, sp->read_buffer, 0) == NO_ERROR) {
if (response(sp, 0, &info, reply) == NO_ERROR) {
perm = substr(info, "perm");
no_such = substr(info, "no such");
if (info) deallocate(info, V_cstr);
switch (reply[0]) {
case '2':
return 0; /* success */
case '4':
/* temp failure ... */
/* most likely reason */
return ERROR_OBJECT_IN_USE;
default:
if (numeric_reply(reply) == 502) return ERROR_ACTION_NOT_KNOWN;
if (perm) {
return ERROR_DELETE_PROTECTED;
} else if (no_such) {
return ERROR_OBJECT_NOT_FOUND;
} else {
return ERROR_DIRECTORY_NOT_EMPTY;
}
}
} else {
disconnect(sp);
error = ERROR_OBJECT_NOT_FOUND;
}
} else {
disconnect(sp);
error = ERROR_OBJECT_NOT_FOUND;
}
return error;
}
b32 make_directory(site *sp, b8 *s)
/*
* make directory with name in s
* Inputs:
* sp : site pointer
* s : full path name
*
* Returns:
* standard file system errors
*/
{
b8 *leaf;
b32 error;
boolean exists;
b8 *info, reply[3];
verify(sp, V_site);
truth(s != nil);
if (s[0] == 0) {
/* trying to mkd root */
return ERROR_OBJECT_WRONG_TYPE;
}
leaf = cd_parent(sp, s);
if (!leaf) {
/* again, being lazy here */
return ERROR_DIR_NOT_FOUND;
}
state_change(sp, SS_MAKEDIR);
sprintf(sp->read_buffer, "MKD %s\r\n", leaf);
if (control_write(sp, sp->read_buffer, 0) == NO_ERROR) {
if (response(sp, 0, &info, reply) == NO_ERROR) {
exists = substr(info, "exist");
if (info) deallocate(info, V_cstr);
switch (reply[0]) {
case '2':
return 0; /* success */
case '4':
/* temp failure ... */
/* most likely reason */
return ERROR_OBJECT_IN_USE;
default:
if (numeric_reply(reply) == 502) return ERROR_ACTION_NOT_KNOWN;
if (exists)
return ERROR_OBJECT_EXISTS;
else
return ERROR_WRITE_PROTECTED;
}
} else {
disconnect(sp);
error = ERROR_OBJECT_NOT_FOUND;
}
} else {
disconnect(sp);
error = ERROR_OBJECT_NOT_FOUND;
}
return error;
}
b32 rename_object(site *sp, b8 *from, b8 *to)
/*
* renames file 'from' to 'to'
* Inputs:
* sp : site pointer
* from, to: null terminated file names
*
* Returns:
* file system error, or 0 indicating success
*/
{
b8 *leaf1, *leaf2;
b8 *info, reply[3];
boolean perm, exist;
if (sp->unix_paths) {
if (!change_dir(sp, "")) {
return ERROR_DIR_NOT_FOUND;
}
leaf1 = from;
leaf2 = to;
} else {
leaf1 = cd_parent(sp, from);
if (!leaf1) {
return ERROR_DIR_NOT_FOUND;
}
leaf2 = to + strlen(to) - 1;
while (leaf2 > to && *leaf2 != '/') leaf2--;
if (leaf2 > to) *leaf2 = 0;
if (strcmp(to, sp->cwd) == 0) { /* they are in the same directory, we can do it */
if (leaf2 > to) *leaf2 = '/';
} else {
return ERROR_ACTION_NOT_KNOWN;
}
}
state_change(sp, SS_RENAMING);
sprintf(sp->read_buffer, "RNFR %s\r\n", leaf1);
if (control_write(sp, sp->read_buffer, 0) != NO_ERROR) {
disconnect(sp);
return ERROR_OBJECT_NOT_FOUND;
}
if (sp->quick) {
sprintf(sp->read_buffer, "RNTO %s\r\n", leaf2);
if (control_write(sp, sp->read_buffer, 0) != NO_ERROR) {
disconnect(sp);
return ERROR_OBJECT_NOT_FOUND;
}
}
/* response to RNFR */
if (response(sp, 0, &info, reply) != NO_ERROR) {
disconnect(sp);
return ERROR_OBJECT_NOT_FOUND;
}
perm = substr(info, "perm");
if (info) deallocate(info, V_cstr);
if (reply[0] != '3') {
if (perm) {
return ERROR_WRITE_PROTECTED;
}
return ERROR_OBJECT_NOT_FOUND;
}
if (!sp->quick) {
sprintf(sp->read_buffer, "RNTO %s\r\n", leaf2);
if (control_write(sp, sp->read_buffer, 0) != NO_ERROR) {
disconnect(sp);
return ERROR_OBJECT_NOT_FOUND;
}
}
/* response to RNTO */
if (response(sp, 0, &info, reply) != NO_ERROR) {
disconnect(sp);
return ERROR_OBJECT_NOT_FOUND;
}
perm = substr(info, "perm");
exist = substr(info, "exist");
if (info) deallocate(info, V_cstr);
if (reply[0] != '2') {
if (perm) {
return ERROR_WRITE_PROTECTED;
}
if (exist) {
return ERROR_OBJECT_EXISTS;
}
return ERROR_INVALID_COMPONENT_NAME;
}
return 0;
}
boolean change_dir(site *sp, b8 *new_dir)
/*
* change directory to new_dir
* Inputs:
* sp : site pointer
* new_dir : null terminated path name
*
* Returns:
* true if change_dir was successful
*/
{
tcpmessage *tm;
struct MsgPort *sync;
b8 *info, reply[4];
b8 *z, *s;
verify(sp, V_site);
truth(new_dir != nil);
tm = sp->control;
sync = sp->sync;
verify(tm, V_tcpmessage);
/* check to see if we are already there */
if (!sp->cwd && new_dir[0] == 0) return true;
if (sp->cwd && strcmp(sp->cwd, new_dir) == 0) return true;
/* have to explicitly change there */
if (sp->cfile) { /* can't do _anything_ while we have a file opened */
if (sp->file_list->closed) {
close_file(sp, true);
} else {
return false;
}
}
if (!sp->connected) {
return false;
}
state_change(sp, SS_CWD);
if (sp->cwd) {
deallocate(sp->cwd, V_cstr);
sp->cwd = nil;
}
/* first we change to the root */
if (sp->root) {
sprintf(sp->read_buffer, "CWD %s\r\n", sp->root);
} else {
strcpy(sp->read_buffer, "CWD\r\n");
}
if (control_write(sp, sp->read_buffer, 0) != NO_ERROR) {
show_string("change dir failed 1");
disconnect(sp);
return false;
}
/* this CWD is vital */
if (response(sp, 0, &info, reply) != NO_ERROR) {
show_string("change dir failed 2");
disconnect(sp);
return false;
}
if (info) deallocate(info, V_cstr);
if (reply[0] != '2') {
show_string("change dir failed 3");
disconnect(sp);
return false;
}
/*
* ok, we are at (should be at :) the root (or at least what
* we consider to be the root) of the FS
*/
if (new_dir[0] == 0) {
/* they wanted to change to the root ... so nothing further need be done */
return true;
}
if (sp->unix_paths) {
sprintf(sp->read_buffer, "CWD %s\r\n", new_dir);
if (control_write(sp, sp->read_buffer, 0) != NO_ERROR) {
show_string("change dir failed 4");
disconnect(sp);
return false;
}
if (response(sp, 0, &info, reply) != NO_ERROR) {
show_string("change dir failed 5");
disconnect(sp);
return false;
}
if (info) deallocate(info, V_cstr);
if (reply[0] == '2') {
sp->cwd = (b8 *)allocate(strlen(new_dir) + 1, V_cstr);
if (sp->cwd) {
strcpy(sp->cwd, new_dir);
return true;
}
goto fail_to_root;
}
/* ok, our clumped cwd didn't work, lets try it the slow way */
}
s = z = new_dir;
while (*z) {
while (*s && *s != '/') s++;
if (*s == '/') {
*s = 0;
sprintf(sp->read_buffer, "CWD %s\r\n", z);
*s++ = '/';
} else {
sprintf(sp->read_buffer, "CWD %s\r\n", z);
}
if (control_write(sp, sp->read_buffer, 0) != NO_ERROR) {
show_string("change dir failed 6");
disconnect(sp);
return false;
}
if (response(sp, 0, &info, reply) != NO_ERROR) {
show_string("change dir failed 7");
disconnect(sp);
return false;
}
if (info) deallocate(info, V_cstr);
if (reply[0] != '2') {
goto fail_to_root;
}
z = s;
}
/* we've succeeded where unix_paths failed, so ... */
sp->unix_paths = false;
sp->cwd = (b8 *)allocate(strlen(new_dir) + 1, V_cstr);
if (sp->cwd) {
strcpy(sp->cwd, new_dir);
return true;
}
fail_to_root:
/* something went wrong ... who knows where we are? ... go back to the root */
if (sp->root) {
sprintf(sp->read_buffer, "CWD %s\r\n", sp->root);
} else {
strcpy(sp->read_buffer, "CWD\r\n");
}
if (control_write(sp, sp->read_buffer, 0) != NO_ERROR) {
show_string("change dir failed 8");
disconnect(sp);
return false;
}
if (response(sp, 0, &info, reply) != NO_ERROR) {
show_string("change dir failed 9");
disconnect(sp);
return false;
}
if (info) deallocate(info, V_cstr);
if (reply[0] == '2') {
return false;
}
show_string("change dir failed 10");
disconnect(sp);
return false;
}
b8 *cd_parent(site *sp, b8 *path)
/*
* change to the parent dir of the object described by path
* Inputs:
* sp : site pointer
* path : string describing object
*
* Returns:
* pointer to leaf name (last component of path)
* or nil ... generally indicates gross error or dir not found
*/
{
b8 *leaf;
boolean cd;
verify(sp, V_site);
truth(path != nil);
/* start at end of pathname and work back til we find a / */
leaf = path + strlen(path) - 1;
while (leaf > path && *leaf != '/') leaf--;
if (leaf == path) {
/* no /, so we are talking about an object in the root dir */
cd = change_dir(sp, "");
} else {
/* temporarily knock out / to get parent path */
*leaf = 0;
cd = change_dir(sp, path);
/* then restore the / and move over it */
*leaf++ = '/';
}
if (cd) return leaf;
else return nil;
}
void convert_nt_entry(b8 *is) // Input String from add_info()
/*
* if this is a WinNT entry, convert (in place) to a
* *nix style record. (Unix, Linux, Xenix...)
*
* 03/09/96 Ron Flory (RJF)
*
* This function kindly donated by Ron Flory (), and remain Copyright 1996,
* all rights reserved by him.
*/
{
static b8 *nt_buf = 0; // alloc once, but NEVER free (??)
static char *months[] = { "Jan ", "Feb ", "Mar ", "Apr ", "May ", "Jun ",
"Jul ", "Aug ", "Sep ", "Oct ", "Nov ", "Dec " };
b8 *c1, *c2, tbuf[32], c;
short i;
if (!nt_buf) // if our conversion buf not alloc'd
nt_buf = (b8*) malloc(READ_BUFFER_LENGTH);
// ***** verify inbuffer viable, MsDos-like, conversion buf alloc'd *****
if ( (!is) || (!nt_buf) || (!isdigit(*is)) || (strlen(is) < 41) )
return;
*nt_buf = 0; // term NT->Unix conversion buffer
c1 = strchr(is, 0x0d); // look for MsDos CR
if (c1)
*c1 = 0; // then, remove CR
// ***** avoid altering 'is' unless this really is an MsDos entry *****
strcpy(nt_buf, "-rwxrwxrwx 9 x x "); // assume this is a file
// plug dummy fields (ignored)
// Note: execute flags set
if ( (strstr(is, "<DIR>")) || (strstr(is, "<dir>")) ) // directory ?
{
*nt_buf = 'd'; // force directory flag in return buf
strcat(nt_buf, "size "); // dummy 'size' (ignored on dirs)
}
else
{
// ***** extract filesize *****
c1 = is +30; // start of length field
c2 = nt_buf + strlen(nt_buf);
while (*c1 == ' ') // skip leading spaces
c1++;
while ( ((c = *(c1++)) != 0) && (c <= '9') && (c > ' ') )
if (c != ',')
*(c2++) = c; // copy filesize digits (skip commas)
*(c2++) = ' '; // padd
*c2 = 0; // term
}
// ***** extract creation date *****
memcpy(tbuf, is, 2); // month digits (01..12)
tbuf[2] = 0;
i = atol(tbuf); // convert digits to 1..12 month index
if ( (i) && (i < 13) ) // valid month index ??
strcat(nt_buf, months[i -1]); // insert month string
memcpy(tbuf, is +3, 2); // day digits (01..31)
tbuf[2] = 0;
if (*tbuf == '0') // leading space on 'day' ??
*tbuf = ' '; // need leading space, not zero
strcat(nt_buf, tbuf); // append day of month
strcat(nt_buf, " ");
// ***** get creation time *****
c1 = strchr(is, ':'); // find creation time field
if (!c1)
return; // abort, not an MsDos entry
memcpy(tbuf, c1 -2, 2); // extract hour
tbuf[2] = 0; // term
i = atoi(tbuf); // month digits to index (01..12)
if (toupper(*(c1 +3)) == 'P') // PM ???
i += 12;
stci_d(tbuf, i); // itoa()
strcat(nt_buf, tbuf); // hour
strcat(nt_buf, ":");
memcpy(tbuf, c1 +1, 2); // minutes
tbuf[2] = 0;
strcat(nt_buf, tbuf); // minutes
strcat(nt_buf, " "); // padd
// **** extract directory/file name *****
c1 = is +39; // start of fid (I hate hard offsets)
c2 = nt_buf + strlen(nt_buf);
while ((c = *c1) > ' ') // (might want to skip leading spaces)
*(c2++) = *(c1++); // copy entire dir/filename
*c2 = 0; // term buffer
// ***** copy translated MsDos entry over orig Unix entry *****
strcpy(is, nt_buf); // if we get here, should be OK (?!*)
}
#define LAST_FIELDS 5
void add_info(struct info_header *ih, b8 *s)
/*
* parses s and adds the information to header ih
* Inputs:
* ih : info_header
* s : line returned from LIST
*/
{
b32 perm; /* permission bits */
b32 size;
b8 *fields[LAST_FIELDS], *z; /* want the last 5 fields */
b8 tempd[15], tempt[10];
int i, num_fields;
struct DateTime dtime;
convert_nt_entry(s); // if NT entry, convert to Unix format (RJF)
if (s[0] <= ' ') return;
for (i = 0; i < LAST_FIELDS; i++) fields[i] = s; /* safety */
perm = 0;
if (*s == 'd') perm |= MYFLAG_DIR;
if (*s == 'l') { /* throw away yucky unix soft links */
perm |= MYFLAG_DIR; /* assume its a directory ... it _may_ be a file ... */
z = s + strlen(s) - 1;
while (z > s && !(z[0] == '-' && z[1] == '>')) z--;
if (z > s) {
*z-- = 0;
/* and any space that they've put in for looks */
if (z > s && *z == ' ')
{
*z = 0;
}
}
}
s++;
if (*s < 'A') perm |= FIBF_READ;
s++;
if (*s < 'A') perm |= FIBF_WRITE | FIBF_DELETE;
s++;
if (*s < 'A') perm |= FIBF_EXECUTE;
s++;
while (*s > ' ') s++;
num_fields = 1;
do {
while (*s > 0 && *s <= ' ') s++;
if (!*s) break;
for (i = 0; i < LAST_FIELDS - 1; i++) {
fields[i] = fields[i+1];
}
num_fields++;
fields[LAST_FIELDS - 1] = s;
if (num_fields == 9) break;
if (num_fields == 8)
{
if (s[0] >= '0' && s[0] <= '9' && s[3] >= '0' && s[4] <= '9')
{
// go around another time
}
else
{
break;
}
}
while (*s > ' ') s++;
} while (*s);
/* ok, we now have the last 5 fields, so process them */
size = atoi(fields[0]);
s = fields[4];
while (*s >= ' ') s++;
*s = 0;
if (num_fields > 4) {
/* throw away . & .. */
if (fields[4][0] == '.') {
if (fields[4][1] == '\0') return;
if (fields[4][1] == '.' && fields[4][2] == '\0') return;
}
dtime.dat_Format = FORMAT_INT;
dtime.dat_Flags = 0;
dtime.dat_StrDay = nil;
dtime.dat_StrDate = tempd;
dtime.dat_StrTime = tempt;
s = fields[3];
if (s[1] == ':' || s[2] == ':') {
tempd[0] = (year / 10) % 10 + '0';
tempd[1] = year % 10 + '0';
tempd[2] = '-';
tempd[3] = fields[1][0];
tempd[4] = fields[1][1];
tempd[5] = fields[1][2];
tempd[6] = '-';
tempd[7] = fields[2][0];
tempd[8] = fields[2][1];
if (tempd[8] < '0') tempd[8] = 0;
else tempd[9] = 0;
while (*s > ' ') s++;
*s = 0;
strcpy(tempt, fields[3]);
strcat(tempt, ":00");
} else {
tempd[0] = fields[3][2];
tempd[1] = fields[3][3];
tempd[2] = '-';
tempd[3] = fields[1][0];
tempd[4] = fields[1][1];
tempd[5] = fields[1][2];
tempd[6] = '-';
tempd[7] = fields[2][0];
tempd[8] = fields[2][1];
if (tempd[8] < '0') tempd[8] = 0;
else tempd[9] = 0;
strcpy(tempt, "12:00:00");
}
StrToDate(&dtime);
add_ftpinfo(ih, fields[4], dtime.dat_Stamp, size, (size + 1023) / 1024, perm);
}
}
boolean get_list(site *sp, struct info_header *ih)
/*
* gets LIST in cwd and puts it in ih
* Inputs:
* sp : site pointer
* ih : info_header to hold list information
*
* Returns:
* true if LIST was successful
*/
{
tcpmessage *tm, *listm;
struct MsgPort *sync;
b8 reply[3], *info;
b16 portn;
b32 signals, asigs, rsigs;
verify(sp, V_site);
verify(ih, V_info_header);
truth(sp->connected);
truth(sp->cfile == nil);
state_change(sp, SS_LISTING);
tm = sp->control;
verify(tm, V_tcpmessage);
sync = sp->sync;
asigs = sp->disconnect_signals | sp->abort_signals;
signals = (1 << sync->mp_SigBit) | asigs;
listm = new_message(sp);
if (!listm) return false;
if (control_write(sp, "PASV\r\n", 0) == NO_ERROR) {
if (!sp->quick || control_write(sp, "LIST\r\n", 0) == NO_ERROR) {
if (response(sp, 0, &info, reply) == NO_ERROR) {
if (reply[0] == '2' && info) {
if (passive_response(info, sp->read_buffer, &portn)) {
deallocate(info, V_cstr);
if (make_connection(sp, listm, sp->read_buffer, portn, 0) == NO_ERROR) {
if (sp->quick || control_write(sp, "LIST\r\n", 0) == NO_ERROR) {
/* this next response will be to the LIST */
if (response(sp, 0, &info, reply) == NO_ERROR) {
if (info) deallocate(info, V_cstr);
if (reply[0] == '1') {
/* list should be coming through listm now */
goto read_list;
}
}
}
break_connection(sp, listm);
}
} else {
if (info) deallocate(info, V_cstr);
}
} else {
if (info) deallocate(info, V_cstr);
}
}
}
}
listm->command = TCP_DISPOSE;
PutMsg(tcp, &listm->header);
disconnect(sp);
return false;
read_list:
listm->command = TCP_READ;
listm->data = sp->read_buffer;
listm->flags = FLAG_READLINE;
listm->length = READ_BUFFER_LENGTH;
listm->header.mn_ReplyPort = sync;
do {
PutMsg(tcp, &listm->header);
rsigs = Wait(signals);
if (rsigs & asigs) {
state_change(sp, SS_ABORTING);
interrupt_message(sp, listm);
if (rsigs & sp->disconnect_signals) {
break_connection(sp, listm);
listm->command = TCP_DISPOSE;
PutMsg(tcp, &listm->header);
disconnect(sp);
return false;
}
} else {
GetMsg(sync);
}
if (listm->result > 0) {
sp->read_buffer[listm->result] = 0;
add_info(ih, sp->read_buffer);
}
} while (listm->error == NO_ERROR);
break_connection(sp, listm);
listm->command = TCP_DISPOSE;
PutMsg(tcp, &listm->header);
if (response(sp, 0, &info, reply) != NO_ERROR) {
show_string("get list failed 8");
disconnect(sp);
return true;
}
if (info) deallocate(info, V_cstr);
#ifdef VERIFY
if (reply[0] != '2') {
show_string("received non-2 for end of LIST");
}
#endif
return true;
}
boolean prelim(site *sp, struct Window *w)
/*
* once logged in, does preliminary setup stuff ... for now
* sets TYPE I and figures out where the root of the fs is
* Inputs:
* sp : site pointer
* w : the connection cancel window
*
* Returns:
* true if setup was successful
*/
{
b32 csig;
b8 *info, reply[3];
b8 *s, *z;
csig = (1 << w->UserPort->mp_SigBit);
if (control_write(sp, "TYPE I\r\n", csig) == NO_ERROR) {
/* we either need to change to root, or work out where root is */
if (sp->root) {
sprintf(sp->read_buffer, "CWD %s\r\n", sp->root);
} else {
strcpy(sp->read_buffer, "PWD\r\n");
}
if (!sp->quick || control_write(sp, sp->read_buffer, csig) == NO_ERROR) {
/* first response is to TYPE I */
if (response(sp, csig, &info, reply) == NO_ERROR) {
/* we don't really care what they replied */
if (info) deallocate(info, V_cstr);
if (sp->root) {
sprintf(sp->read_buffer, "CWD %s\r\n", sp->root);
} else {
strcpy(sp->read_buffer, "PWD\r\n");
}
if (sp->quick || control_write(sp, sp->read_buffer, csig) == NO_ERROR) {
/* ... next response is to CWD/PWD */
if (response(sp, csig, &info, reply) == NO_ERROR) {
if (reply[0] == '2') {
if (sp->root) {
/* was the CWD ... was successful */
if (info) deallocate(info, V_cstr);
return true;
} else if (info) {
/* was the PWD ... have to extract the root path */
s = info;
while (*s && *s != '"') s++;
if (*s) {
s++;
z = s;
while (*z && *z != '"') z++;
if (*z) {
sp->root = (b8 *)allocate(z - s + 1, V_cstr);
if (sp->root) {
if (z != s)
memcpy(sp->root, s, z - s);
sp->root[z - s] = 0;
deallocate(info, V_cstr);
return true;
} else if (sp->error_messages)
inform(sp->IBase, strings[MSG_OPERATIONAL_ERROR], strings[MSG_OOM_ROOT], nil, 0);
} else if (sp->error_messages)
inform(sp->IBase, strings[MSG_OPERATIONAL_ERROR], strings[MSG_PWD_GARBAGE], nil, 0);
} else if (sp->error_messages)
inform(sp->IBase, strings[MSG_OPERATIONAL_ERROR], strings[MSG_PWD_GARBAGE], nil, 0);
deallocate(info, V_cstr);
} else {
if (sp->error_messages)
inform(sp->IBase, strings[MSG_OPERATIONAL_ERROR], strings[MSG_FAILED_PWD], nil, 0);
}
} else {
if (sp->error_messages)
ok(sp->IBase, strings[MSG_OPERATIONAL_ERROR], info);
if (info) deallocate(info, V_cstr);
}
} else if (sp->error_messages)
inform(sp->IBase, strings[MSG_OPERATIONAL_ERROR], strings[MSG_ERROR_READING_PWD], nil, 0);
} else if (sp->error_messages)
inform(sp->IBase, strings[MSG_OPERATIONAL_ERROR], strings[MSG_ERROR_REQUESTING_PWD], nil, 0);
} else if (sp->error_messages)
inform(sp->IBase, strings[MSG_OPERATIONAL_ERROR], strings[MSG_ERROR_READING_TYPE], nil, 0);
} else if (sp->error_messages)
inform(sp->IBase, strings[MSG_OPERATIONAL_ERROR], strings[MSG_ERROR_REQUESTING_PWD], nil, 0);
} else if (sp->error_messages)
inform(sp->IBase, strings[MSG_OPERATIONAL_ERROR], strings[MSG_ERROR_SETTING_TYPE], nil, 0);
return false;
}
void login(site *sp, struct Window *w)
/*
* goes through the login sequence once a successful connection has been established
* Inputs:
* sp : site pointer
* w : the connection cancel window
*/
{
tcpmessage *tm;
struct MsgPort *sync;
b8 reply[4], *info;
b32 csig;
boolean early_success = false;
tm = sp->control;
sync = sp->sync;
state_change(sp, SS_LOGIN);
csig = 1 << w->UserPort->mp_SigBit;
retry_login:
if (sp->needs_user || sp->needs_password) {
if (!sp->error_messages || !user_pass_request(sp, w)) {
tm->command = TCP_CLOSE;
PutMsg(tcp, &tm->header);
WaitPort(sync); GetMsg(sync);
close_req(sp, w);
state_change(sp, SS_DISCONNECTED);
return;
}
}
if (sp->user) {
sprintf(sp->read_buffer, "USER %s\r\n", sp->user);
} else {
strcpy(sp->read_buffer, "USER ftp\r\n");
}
if (control_write(sp, sp->read_buffer, csig) == NO_ERROR) {
if (sp->password) {
sprintf(sp->read_buffer, "PASS %s\r\n", sp->password);
} else {
sprintf(sp->read_buffer, "PASS %s\r\n", anon_login);
}
if (control_write(sp, sp->read_buffer, csig) == NO_ERROR) {
/* first response should be to the USER */
switch (response(sp, csig, &info, reply)) {
case NO_ERROR:
switch (reply[0]) {
case '2':
early_success = true;
/* the welcome banner will come here I guess */
if (sp->all_messages && !sp->read_banners) {
ok(sp->IBase, strings[MSG_LOGIN_SUCCEEDED_NO_PASS], info);
sp->read_banners = true;
}
/* fall through */
case '3':
/* ignore the banner here ... usually its just "Anonymous login ok, send ident ..." */
if (info) deallocate(info, V_cstr);
/* now read pass response */
switch (response(sp, csig, &info, reply)) {
case NO_ERROR:
/* if we succeeded early, we don't care what they tell us */
if (!early_success) {
if (reply[0] == '2') {
if (sp->all_messages && !sp->read_banners) {
ok(sp->IBase, strings[MSG_LOGIN_SUCCEEDED], info);
sp->read_banners = true;
}
} else if (reply[0] == '3') {
/* they want an ACCT ... fuck 'em */
if (sp->error_messages)
inform(sp->IBase, strings[MSG_LOGIN_FAILED], strings[MSG_ACCT_REQUESTED], nil, 0);
if (info) deallocate(info, V_cstr);
break;
} else {
if (reply[0] == '5' && reply[1] == '3' && reply[2] == '0') {
/* this is login incorrect */
if (sp->error_messages && retry_cancel(sp->IBase, strings[MSG_LOGIN_INCORRECT], info)) {
if (info) deallocate(info, V_cstr);
sp->needs_password = true;
if (sp->password) deallocate(sp->password, V_cstr);
sp->password = nil;
goto retry_login;
}
if (info) deallocate(info, V_cstr);
break;
}
if (sp->error_messages)
ok(sp->IBase, strings[MSG_LOGIN_FAILED_PASS], info);
if (info) deallocate(info, V_cstr);
break;
}
}
if (info) deallocate(info, V_cstr);
if (prelim(sp, w)) {
close_req(sp, w);
sp->connected = true;
state_change(sp, SS_IDLE);
return;
}
break;
case ERROR_INTERRUPTED:
break;
case ERROR_LOST_CONNECTION:
case ERROR_EOF:
case ERROR_UNREACHABLE:
if (sp->error_messages)
inform(sp->IBase, strings[MSG_LOGIN_ERROR], strings[MSG_LOST_CONN_DURING_LOGIN_PASS], nil, 0);
break;
case ERROR_GARBAGE_RECEIVED:
if (sp->error_messages)
inform(sp->IBase, strings[MSG_LOGIN_ERROR], strings[MSG_GARBAGE_RECEIVED_PASS], nil, 0);
break;
default:
if (sp->error_messages)
inform(sp->IBase, strings[MSG_LOGIN_ERROR], strings[MSG_ERROR_RESPONSE_PASS], nil, 0);
break;
}
break;
case '4':
if (sp->error_messages && retry_cancel(sp->IBase, strings[MSG_TEMP_LOGIN_FAILURE_USER], info)) {
if (info) deallocate(info, V_cstr);
goto retry_login;
}
if (info) deallocate(info, V_cstr);
break;
default:
if (sp->error_messages)
ok(sp->IBase, strings[MSG_LOGIN_FAILED_USER], info);
if (info) deallocate(info, V_cstr);
break;
}
break;
case ERROR_INTERRUPTED:
break;
case ERROR_LOST_CONNECTION:
case ERROR_EOF:
case ERROR_UNREACHABLE:
if (sp->error_messages)
inform(sp->IBase, strings[MSG_LOGIN_ERROR], strings[MSG_LOST_CONN_DURING_LOGIN], nil, 0);
break;
case ERROR_GARBAGE_RECEIVED:
if (sp->error_messages)
inform(sp->IBase, strings[MSG_LOGIN_ERROR], strings[MSG_GARBAGE_RECEIVED_USER], nil, 0);
break;
default:
if (sp->error_messages)
inform(sp->IBase, strings[MSG_LOGIN_ERROR], strings[MSG_ERROR_USER_RESPONSE], nil, 0);
break;
}
} else if (sp->error_messages)
inform(sp->IBase, strings[MSG_LOGIN_ERROR], strings[MSG_ERROR_WRITING_PASS], nil, 0);
} else if (sp->error_messages)
inform(sp->IBase, strings[MSG_LOGIN_ERROR], strings[MSG_ERROR_WRITING_USER], nil, 0);
tm->command = TCP_CLOSE;
PutMsg(tcp, &tm->header);
WaitPort(sync); GetMsg(sync);
close_req(sp, w);
state_change(sp, SS_DISCONNECTED);
return;
}
void init_connect(site *sp)
{
struct Window *w;
b8 *z;
tcpmessage *tm, *intr;
struct MsgPort *sync;
b8 reply[3], *info;
b32 signals, csig;
verify(sp, V_site);
z = sp->host;
while (sp->infos) free_info_header(sp->infos);
w = connect_req(sp, z);
if (!w) {
show_string("connect req failed");
return;
}
state_change(sp, SS_CONNECTING);
tm = sp->control;
sync = sp->sync;
intr = sp->intr;
csig = (1 << w->UserPort->mp_SigBit) | sp->abort_signals | sp->disconnect_signals;
signals = (1 << sync->mp_SigBit) | csig;
if (sp->port_number == 0) {
tm->command = TCP_SERVICE;
tm->data = strings[MSG_SERVICE];
tm->header.mn_ReplyPort = sync;
PutMsg(tcp, &tm->header);
WaitPort(sync); GetMsg(sync);
if (tm->result) {
sp->port_number = ftp_port_number = tm->port.w;
} else if (tm->error == ERROR_NO_CONNECTION) {
close_req(sp, w);
if (sp->error_messages)
inform(sp->IBase, strings[MSG_CONNECT_ERROR], strings[MSG_AMITCP_NOT_RUNNING], nil, 0);
state_change(sp, SS_DISCONNECTED);
return;
} else {
sp->port_number = 21;
}
}
tm->command = TCP_CONNECT;
tm->header.mn_ReplyPort = sync;
tm->data = z;
tm->port.w = sp->port_number;
PutMsg(tcp, &tm->header);
do {
if (Wait(signals) & csig) {
intr->interrupt = tm;
PutMsg(tcp, &intr->header);
WaitPort(sync); GetMsg(sync);
WaitPort(sync); GetMsg(sync);
if (tm->result) { /* it succeeded in connecting */
tm->command = TCP_CLOSE;
PutMsg(tcp, &tm->header);
WaitPort(sync); GetMsg(sync);
}
close_req(sp, w);
state_change(sp, SS_DISCONNECTED);
return;
}
} while (!GetMsg(sync));
if (!tm->result) { /* the connect failed ... tell the user why */
close_req(sp, w);
switch (tm->error) {
case ERROR_NO_CONNECTION:
if (sp->error_messages)
inform(sp->IBase, strings[MSG_CONNECT_ERROR], strings[MSG_AMITCP_NOT_RUNNING], nil, 0);
break;
case ERROR_UNKNOWN_HOST:
if (sp->error_messages)
inform(sp->IBase, strings[MSG_CONNECT_ERROR], strings[MSG_HOST_UNKNOWN], z, 0);
break;
case ERROR_UNREACHABLE:
if (sp->error_messages)
inform(sp->IBase, strings[MSG_CONNECT_ERROR], strings[MSG_HOST_UNREACHABLE], z, 0);
break;
case ERROR_CONNECT_REFUSED:
if (sp->error_messages)
inform(sp->IBase, strings[MSG_CONNECT_ERROR], strings[MSG_FTP_REFUSED], z, 0);
break;
default:
if (sp->error_messages)
inform(sp->IBase, strings[MSG_CONNECT_ERROR], strings[MSG_CANT_CONNECT], z, tm->error);
break;
}
state_change(sp, SS_DISCONNECTED);
return;
}
/* ok, we've connected ... look at the greeting */
retry_intro:
switch (response(sp, csig, &info, reply)) {
case NO_ERROR:
break;
case ERROR_INTERRUPTED:
close_req(sp, w);
goto close_and_exit;
case ERROR_LOST_CONNECTION:
case ERROR_EOF:
case ERROR_UNREACHABLE:
close_req(sp, w);
if (sp->error_messages)
inform(sp->IBase, strings[MSG_CONNECT_ERROR], strings[MSG_LOST_CONN_DURING_INTRO], nil, 0);
goto close_and_exit;
case ERROR_GARBAGE_RECEIVED:
close_req(sp, w);
if (sp->error_messages)
inform(sp->IBase, strings[MSG_CONNECT_ERROR], strings[MSG_GARBAGE_DURING_INTRO], z, 0);
goto close_and_exit;
default:
close_req(sp, w);
if (sp->error_messages)
inform(sp->IBase, strings[MSG_CONNECT_ERROR], strings[MSG_ERROR_DURING_INTRO], nil, 0);
goto close_and_exit;
}
switch (reply[0]) {
case '1':
if (sp->error_messages && retry_cancel(sp->IBase, strings[MSG_CONN_DELAY], info)) {
goto retry_intro;
}
close_req(sp, w);
if (info)
deallocate(info, V_cstr);
goto close_and_exit;
case '2':
case '3':
/* This banner appears to be generally pretty dull, but if
* you really want to see it then remove the comments ...
* if (!sp->read_banners) {
* ok(sp->IBase, "Connected", info);
* }
*/
if (info)
deallocate(info, V_cstr);
login(sp, w);
return;
case '4':
if (retry_cancel(sp->IBase, strings[MSG_TEMP_CONN_FAILURE], info)) {
goto retry_intro;
}
close_req(sp, w);
if (info)
deallocate(info, V_cstr);
goto close_and_exit;
case '5':
default:
close_req(sp, w);
if (sp->error_messages)
ok(sp->IBase, strings[MSG_CONN_FAILED], info);
if (info)
deallocate(info, V_cstr);
break;
}
close_and_exit:
tm->command = TCP_CLOSE;
PutMsg(tcp, &tm->header);
WaitPort(sync); GetMsg(sync);
state_change(sp, SS_DISCONNECTED);
return;
}