/* * This source file is Copyright 1995 by Evan Scott. * All rights reserved. * Permission is granted to distribute this file provided no * fees beyond distribution costs are levied. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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; } #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; 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; } 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; }