Annotation of libwww/Library/src/HTFTP.c, revision 1.90.2.1
1.46 frystyk 1: /* HTFTP.c
2: ** FILE TRANSFER PROTOCOL (FTP) CLIENT
3: **
1.52 frystyk 4: ** (c) COPYRIGHT MIT 1995.
1.46 frystyk 5: ** Please first read the full copyright statement in the file COPYRIGH.
1.90.2.1! eric 6: ** @(#) $Id: HTFTP.c,v 1.90 1996/10/07 02:04:33 frystyk Exp $
1.1 timbl 7: **
8: ** A cache of control connections is kept.
9: **
10: ** Note: Port allocation
11: **
12: ** It is essential that the port is allocated by the system, rather
1.79 frystyk 13: ** than chosen in rotation by us (FTP_POLL_PORTS), or the following
1.1 timbl 14: ** problem occurs.
15: **
16: ** It seems that an attempt by the server to connect to a port which has
17: ** been used recently by a listen on the same socket, or by another
18: ** socket this or another process causes a hangup of (almost exactly)
19: ** one minute. Therefore, we have to use a rotating port number.
20: ** The problem remains that if the application is run twice in quick
21: ** succession, it will hang for what remains of a minute.
22: **
23: ** Authors
1.54 frystyk 24: ** TBL Tim Berners-lee <timbl@w3.org>
1.1 timbl 25: ** DD Denis DeLaRoca 310 825-4580 <CSP1DWD@mvs.oac.ucla.edu>
1.22 frystyk 26: ** LM Lou Montulli <montulli@ukanaix.cc.ukans.edu>
27: ** FM Foteos Macrides <macrides@sci.wfeb.edu>
1.54 frystyk 28: ** HF Henrik Frystyk <frystyk@w3.org>
1.30 luotonen 29: ** AL Ari Luotonen <luotonen@www.cern.ch>
1.23 frystyk 30: **
1.1 timbl 31: ** History:
32: ** 2 May 91 Written TBL, as a part of the WorldWideWeb project.
33: ** 15 Jan 92 Bug fix: close() was used for NETCLOSE for control soc
34: ** 10 Feb 92 Retry if cached connection times out or breaks
35: ** 8 Dec 92 Bug fix 921208 TBL after DD
36: ** 17 Dec 92 Anon FTP password now just WWWuser@ suggested by DD
1.2 timbl 37: ** fails on princeton.edu!
1.22 frystyk 38: ** 27 Dec 93 (FM) Fixed up so FTP now works with VMS hosts. Path
39: ** must be Unix-style and cannot include the device
40: ** or top directory.
41: ** ?? ??? ?? (LM) Added code to prompt and send passwords for non
42: ** anonymous FTP
43: ** 25 Mar 94 (LM) Added code to recognize different ftp server types
44: ** and code to parse dates and sizes on most hosts.
45: ** 27 Mar 93 (FM) Added code for getting dates and sizes on VMS hosts.
1.23 frystyk 46: ** 27 Apr 94 (HF) The module is basically rewritten to conform with
47: ** rfc 959, 1123 and 1579 and turned into a state
48: ** machine. New semantics of ftp URLs are supported.
1.30 luotonen 49: ** 2 May 94 (AL) Fixed possible security hole when the URL contains
50: ** a newline, that could cause multiple commands to be
51: ** sent to an FTP server.
1.60 frystyk 52: ** Sep 95 HFN Rewritten to support streams and persistent conenctions
53: ** and multiplexed IO
1.22 frystyk 54: ** Notes:
55: ** Portions Copyright 1994 Trustees of Dartmouth College
56: ** Code for recognizing different FTP servers and
57: ** parsing "ls -l" output taken from Macintosh Fetch
58: ** program with permission from Jim Matthews,
59: ** Dartmouth Software Development Team.
1.23 frystyk 60: */
1.1 timbl 61:
1.22 frystyk 62: /* Library include files */
1.79 frystyk 63: #include "sysdep.h"
1.81 frystyk 64: #include "WWWUtil.h"
65: #include "WWWCore.h"
1.1 timbl 66: #include "HTTCP.h"
1.58 frystyk 67: #include "HTReqMan.h"
1.90.2.1! eric 68: #include "HTNet.h"
1.60 frystyk 69: #include "HTNetMan.h"
1.63 frystyk 70: #include "HTFTPDir.h"
1.22 frystyk 71: #include "HTFTP.h" /* Implemented here */
72:
73: /* Macros and other defines */
1.60 frystyk 74: #if 0
75: /* Only use this if ABSOLUTELY necessary! */
1.79 frystyk 76: #define FTP_POLL_PORTS /* If allocation does not work, poll ourselves.*/
1.1 timbl 77: #endif
78:
1.79 frystyk 79: #ifdef FTP_POLL_PORTS
1.23 frystyk 80: #define FIRST_TCP_PORT 1024 /* Region to try for a listening port */
81: #define LAST_TCP_PORT 5999
1.60 frystyk 82: PRIVATE int DataPort = FIRST_TCP_PORT;
1.1 timbl 83: #endif
84:
1.60 frystyk 85: #ifndef FTP_PORT
86: #define FTP_PORT 21
87: #define FTP_DATA 20
1.1 timbl 88: #endif
1.60 frystyk 89:
90: #define WWW_FTP_CLIENT "libwww@" /* If can't get user-info, use this */
91: #define FTP_DIR(me) ((me)->type=='L' || (me)->type=='N')
1.1 timbl 92:
1.58 frystyk 93: /*
94: ** Local context structure used in the HTNet object.
95: */
1.45 frystyk 96: typedef enum _HTFTPState {
1.60 frystyk 97: FTP_SUCCESS = -2,
98: FTP_ERROR = -1,
1.58 frystyk 99: FTP_BEGIN = 0,
100: FTP_NEED_CCON, /* Control connection */
1.60 frystyk 101: FTP_NEED_LOGIN,
1.58 frystyk 102: FTP_NEED_DCON, /* Data connection */
1.60 frystyk 103: FTP_NEED_DATA,
104: FTP_NEED_SERVER /* For directory listings */
1.39 frystyk 105: } HTFTPState;
106:
1.58 frystyk 107: typedef struct _ftp_ctrl {
1.60 frystyk 108: HTChunk * cmd;
109: int repcode;
110: char * reply;
111: char * uid;
1.58 frystyk 112: char * passwd;
1.60 frystyk 113: char * account;
1.58 frystyk 114: HTFTPState state; /* State of the connection */
1.60 frystyk 115: int substate; /* For hierarchical states */
116: BOOL sent; /* Read or write command */
117: BOOL cwd; /* Done cwd */
118: BOOL reset; /* Expect greeting */
1.63 frystyk 119: FTPServerType server; /* Type of server */
1.60 frystyk 120: HTNet * dnet; /* Data connection */
1.90.2.1! eric 121: HTNet * net;
1.58 frystyk 122: } ftp_ctrl;
1.39 frystyk 123:
1.58 frystyk 124: typedef struct _ftp_data {
1.60 frystyk 125: char host[30]; /* Host to contact for data */
126: char * file; /* File or dir name */
127: char * offset; /* offset into file */
128: BOOL pasv; /* Active or passive */
129: char type; /* 'A', 'I', 'L'(IST), 'N'(LST) */
1.63 frystyk 130: BOOL ready; /* True if either ctrl or data is HT_LOADED */
1.76 frystyk 131: BOOL stream_error;
1.58 frystyk 132: } ftp_data;
1.39 frystyk 133:
1.60 frystyk 134: struct _HTStream {
1.79 frystyk 135: const HTStreamClass * isa;
1.60 frystyk 136: HTStream * target;
137: HTRequest * request;
138: ftp_ctrl * ctrl;
1.80 frystyk 139: HTEOLState state;
1.60 frystyk 140: HTChunk * welcome;
1.62 frystyk 141: BOOL junk; /* For too long lines */
1.60 frystyk 142: BOOL first_line;
1.63 frystyk 143: char buffer[MAX_FTP_LINE+1];
1.60 frystyk 144: int buflen;
145: };
146:
1.81 frystyk 147: struct _HTInputStream {
148: const HTInputStreamClass * isa;
149: };
150:
1.60 frystyk 151: typedef enum _FTPDataCon {
152: FTP_DATA_PASV = 0x1,
153: FTP_DATA_PORT = 0x2
154: } FTPDataCon;
155:
156: PRIVATE FTPDataCon FTPMode = FTP_DATA_PASV;
1.39 frystyk 157:
1.23 frystyk 158: /* ------------------------------------------------------------------------- */
1.60 frystyk 159: /* FTP Status Line Stream */
1.22 frystyk 160: /* ------------------------------------------------------------------------- */
1.60 frystyk 161:
162: /* FTPCleanup
163: ** ----------
164: ** This function closes the connection and frees memory.
165: ** Returns YES on OK, else NO
166: */
1.65 frystyk 167: PRIVATE int FTPCleanup (HTRequest * request, int status)
1.60 frystyk 168: {
1.75 frystyk 169: if (request) {
1.84 frystyk 170: HTNet * cnet = HTRequest_net(request);
171: ftp_ctrl * ctrl = (ftp_ctrl *) HTNet_context(cnet);
172: HTStream * input = HTRequest_inputStream(request);
1.75 frystyk 173:
174: /* Free stream with data TO network */
1.84 frystyk 175: if (!HTRequest_isDestination(request) && input) {
1.75 frystyk 176: if (status == HT_INTERRUPTED)
1.84 frystyk 177: (*input->isa->abort)(input, NULL);
1.75 frystyk 178: else
1.84 frystyk 179: (*input->isa->_free)(input);
1.75 frystyk 180: }
181:
182: /* Remove the request object and our own context structure for http */
1.84 frystyk 183: if (cnet && ctrl) {
1.75 frystyk 184: HTNet * dnet = ctrl->dnet;
1.84 frystyk 185: ftp_data * data = (ftp_data *) HTNet_context(dnet);
1.75 frystyk 186: HTChunk_delete(ctrl->cmd);
1.77 frystyk 187: HT_FREE(ctrl->reply);
188: HT_FREE(ctrl->uid);
189: HT_FREE(ctrl->passwd);
190: HT_FREE(ctrl->account);
191: HT_FREE(ctrl);
1.84 frystyk 192: if (dnet && data) {
1.77 frystyk 193: HT_FREE(data->file);
194: HT_FREE(data);
1.75 frystyk 195: }
1.88 frystyk 196: HTNet_setPersistent(dnet, NO, HT_TP_SINGLE);
1.75 frystyk 197: HTNet_delete(dnet, HT_IGNORE);
198: }
199: HTNet_delete(cnet, status);
200: return YES;
1.60 frystyk 201: }
1.75 frystyk 202: return NO;
1.60 frystyk 203: }
204:
205: /* ScanResponse
206: ** ------------
207: ** Analyzes the response from the FTP server.
208: ** Returns HT_LOADED if OK, HT_OK if more, HT_ERROR if error
209: ** the control connection.
1.33 frystyk 210: */
1.65 frystyk 211: PRIVATE int ScanResponse (HTStream * me)
1.33 frystyk 212: {
1.60 frystyk 213: int reply = 0;
214: char cont = '\0';
215: char *ptr = me->buffer+4;
216: *(me->buffer+me->buflen) = '\0';
217: if (isdigit(*(me->buffer))) sscanf(me->buffer, "%d%c", &reply, &cont);
218: if (me->first_line) {
1.78 eric 219: if (PROT_TRACE) HTTrace("FTP Rx...... `%s\'\n", me->buffer);
1.60 frystyk 220: if (!reply) return HT_ERROR;
221: me->first_line = NO;
222: me->ctrl->repcode = reply;
223: StrAllocCopy(me->ctrl->reply, ptr);
224: } else {
1.73 frystyk 225: HTChunk_puts(me->welcome, ptr);
226: HTChunk_putc(me->welcome, '\n');
1.33 frystyk 227: }
1.60 frystyk 228: me->buflen = 0;
1.68 frystyk 229: me->state = EOL_BEGIN;
1.60 frystyk 230: if (cont != '-') {
231: me->first_line = YES;
232: return HT_LOADED;
1.33 frystyk 233: }
1.60 frystyk 234: return HT_OK;
1.33 frystyk 235: }
236:
1.60 frystyk 237: /*
238: ** Searches for FTP header line until buffer fills up or a CRLF or LF
239: ** is found
1.23 frystyk 240: */
1.79 frystyk 241: PRIVATE int FTPStatus_put_block (HTStream * me, const char * b, int l)
1.60 frystyk 242: {
243: int status;
244: while (l-- > 0) {
245: if (me->state == EOL_FCR) {
246: if (*b == LF) {
1.62 frystyk 247: if (!me->junk) {
248: if ((status = ScanResponse(me)) != HT_OK)
249: return status;
250: } else {
251: me->buflen = 0;
252: me->junk = NO;
253: }
1.60 frystyk 254: }
255: } else if (*b == CR) {
256: me->state = EOL_FCR;
257: } else if (*b == LF) {
1.62 frystyk 258: if (!me->junk) {
259: if ((status = ScanResponse(me)) != HT_OK)
260: return status;
261: } else {
262: me->buflen = 0;
263: me->junk = NO;
264: }
1.60 frystyk 265: } else {
266: *(me->buffer+me->buflen++) = *b;
1.63 frystyk 267: if (me->buflen >= MAX_FTP_LINE) {
1.60 frystyk 268: if (PROT_TRACE)
1.78 eric 269: HTTrace("FTP Status.. Line too long - chopped\n");
1.62 frystyk 270: me->junk = YES;
271: if ((status = ScanResponse(me)) != HT_OK) {
272: me->junk = NO;
273: return status;
274: }
1.23 frystyk 275: }
276: }
1.60 frystyk 277: b++;
1.25 frystyk 278: }
1.60 frystyk 279: return HT_OK;
1.25 frystyk 280: }
281:
1.79 frystyk 282: PRIVATE int FTPStatus_put_string (HTStream * me, const char * s)
1.60 frystyk 283: {
284: return FTPStatus_put_block(me, s, (int) strlen(s));
285: }
1.25 frystyk 286:
1.65 frystyk 287: PRIVATE int FTPStatus_put_character (HTStream * me, char c)
1.25 frystyk 288: {
1.60 frystyk 289: return FTPStatus_put_block(me, &c, 1);
290: }
1.25 frystyk 291:
1.65 frystyk 292: PRIVATE int FTPStatus_flush (HTStream * me)
1.60 frystyk 293: {
294: return (*me->target->isa->flush)(me->target);
1.23 frystyk 295: }
1.22 frystyk 296:
1.65 frystyk 297: PRIVATE int FTPStatus_free (HTStream * me)
1.22 frystyk 298: {
1.60 frystyk 299: int status = HT_OK;
300: if (me->target) {
301: if ((status = (*me->target->isa->_free)(me->target)) == HT_WOULD_BLOCK)
302: return HT_WOULD_BLOCK;
1.22 frystyk 303: }
1.73 frystyk 304: HTChunk_delete(me->welcome);
1.77 frystyk 305: HT_FREE(me);
1.60 frystyk 306: return HT_OK;
1.22 frystyk 307: }
308:
1.68 frystyk 309: PRIVATE int FTPStatus_abort (HTStream * me, HTList * e)
1.22 frystyk 310: {
1.60 frystyk 311: if (me->target)
312: (*me->target->isa->abort)(me->target, e);
1.73 frystyk 313: HTChunk_delete(me->welcome);
1.77 frystyk 314: HT_FREE(me);
1.60 frystyk 315: if (PROT_TRACE)
1.78 eric 316: HTTrace("FTPStatus... ABORTING...\n");
1.60 frystyk 317: return HT_ERROR;
1.22 frystyk 318: }
319:
1.60 frystyk 320: /* FTPStatus Stream
321: ** -----------------
1.22 frystyk 322: */
1.79 frystyk 323: PRIVATE const HTStreamClass FTPStatusClass =
1.60 frystyk 324: {
325: "FTPStatus",
326: FTPStatus_flush,
327: FTPStatus_free,
328: FTPStatus_abort,
329: FTPStatus_put_character,
330: FTPStatus_put_string,
331: FTPStatus_put_block
332: };
333:
1.65 frystyk 334: PRIVATE HTStream * FTPStatus_new (HTRequest * request, ftp_ctrl * ctrl)
1.60 frystyk 335: {
1.77 frystyk 336: HTStream * me;
337: if ((me = (HTStream *) HT_CALLOC(1, sizeof(HTStream))) == NULL)
338: HT_OUTOFMEM("FTPStatus_new");
1.60 frystyk 339: me->isa = &FTPStatusClass;
340: me->request = request;
341: me->first_line = YES;
1.73 frystyk 342: me->welcome = HTChunk_new(256);
1.60 frystyk 343: me->ctrl = ctrl;
344: me->state = EOL_BEGIN;
345: return me;
1.22 frystyk 346: }
347:
1.60 frystyk 348: /* ------------------------------------------------------------------------- */
349: /* FTP Client Functions for managing control and data connections */
350: /* ------------------------------------------------------------------------- */
1.22 frystyk 351:
1.60 frystyk 352: PRIVATE int SendCommand (HTRequest *request, ftp_ctrl *ctrl,
353: char *token, char *pars)
354: {
355: int len = strlen(token) + (pars ? strlen(pars)+1:0) + 2;
1.84 frystyk 356: HTStream * input = HTRequest_inputStream(request);
1.73 frystyk 357: HTChunk_clear(ctrl->cmd);
358: HTChunk_ensure(ctrl->cmd, len);
1.60 frystyk 359: if (pars && *pars)
1.73 frystyk 360: sprintf(HTChunk_data(ctrl->cmd), "%s %s%c%c", token, pars, CR, LF);
1.60 frystyk 361: else
1.73 frystyk 362: sprintf(HTChunk_data(ctrl->cmd), "%s%c%c", token, CR, LF);
1.78 eric 363: if (PROT_TRACE) HTTrace("FTP Tx...... %s", HTChunk_data(ctrl->cmd));
1.84 frystyk 364: return (*input->isa->put_block)(input, HTChunk_data(ctrl->cmd), len);
1.60 frystyk 365: }
366:
367: /* HTFTPParseURL
368: ** -------------
369: ** Scan URL for uid and passwd, and any data type indication. The
370: ** expected format is [user[:password]@]host[:port].
371: ** If no values are found then use defaults.
372: ** Returns YES if OK, else NO
1.22 frystyk 373: */
1.83 frystyk 374: PRIVATE BOOL HTFTPParseURL (HTRequest * request,
375: char *url, ftp_ctrl *ctrl, ftp_data *data)
1.22 frystyk 376: {
1.60 frystyk 377: char *login = HTParse(url, "", PARSE_HOST);
378: char *path = HTParse(url, "", PARSE_PATH+PARSE_PUNCTUATION);
379: char *ptr = strchr(login, '@');
380: if (ptr) { /* Uid and/or passwd specified */
381: char *passwd;
382: *ptr = '\0';
383: if ((passwd = strchr(login, ':'))) { /* Passwd specified */
384: *passwd++ = '\0';
385: HTUnEscape(passwd);
386: StrAllocCopy(ctrl->passwd, passwd);
1.22 frystyk 387: }
1.60 frystyk 388: HTUnEscape(login);
389: StrAllocCopy(ctrl->uid, login);
390: } else { /* Use anonymous */
1.83 frystyk 391: HTUserProfile * up = HTRequest_userProfile(request);
392: const char * mailaddress = HTUserProfile_email(up);
1.60 frystyk 393: StrAllocCopy(ctrl->uid, "anonymous");
394: if (mailaddress)
395: StrAllocCopy(ctrl->passwd, mailaddress);
396: else
397: StrAllocCopy(ctrl->passwd, WWW_FTP_CLIENT);
1.22 frystyk 398: }
1.66 frystyk 399: if (PROT_TRACE)
1.78 eric 400: HTTrace("FTPParse.... uid `%s\' pw `%s\'\n",
1.66 frystyk 401: ctrl->uid ? ctrl->uid : "<null>",
402: ctrl->passwd ? ctrl->passwd : "<null>");
1.60 frystyk 403:
404: ptr = strchr(path, ';');
405: if (ptr) {
406: *ptr = '\0';
407: if (strncasecomp(ptr, ";type=", 6)) /* Look for type */
408: data->type = TOUPPER(*(ptr+6));
409: else if (*(ptr-1) == '/')
410: data->type = 'N';
411: } else if (*(path+strlen(path)-1) == '/') {
412: *(path+strlen(path)-1) = '\0';
413: data->type = 'N';
414: }
415: if (data->type && PROT_TRACE)
1.78 eric 416: HTTrace("FTPParse.... Datatype %c\n", data->type);
1.60 frystyk 417: StrAllocCopy(data->file, path);
418: data->offset = data->file;
1.77 frystyk 419: HT_FREE(login);
420: HT_FREE(path);
1.60 frystyk 421: return YES;
1.22 frystyk 422: }
423:
1.60 frystyk 424: /* Use LIST or NLST
425: ** ----------------
426: ** This function sets the type field for what type of list we can use
427: ** Returns YES if OK, else NO
428: */
1.63 frystyk 429: PRIVATE BOOL FTPListType (ftp_data * data, FTPServerType type)
1.60 frystyk 430: {
431: if (!data) return NO;
432: switch (type) {
433: case FTP_GENERIC: data->type='N'; break;
434: case FTP_MACHTEN: data->type='L'; break;
435: case FTP_UNIX: data->type='L'; break;
436: case FTP_VMS: data->type='L'; break;
437: case FTP_CMS: data->type='N'; break;
438: case FTP_DCTS: data->type='N'; break;
439: case FTP_TCPC: data->type='N'; break;
440: case FTP_PETER_LEWIS: data->type='L'; break;
441: case FTP_NCSA: data->type='N'; break;
442: case FTP_WINNT: data->type='L'; break;
443: default: data->type='N'; break;
444: }
445: return YES;
446: }
447:
448: /* Open a Data socket for listening on
449: ** -----------------------------------
450: ** Set up a port to listen for data
451: ** Returns YES if OK, else NO
1.22 frystyk 452: */
1.60 frystyk 453: PRIVATE BOOL ListenSocket (HTNet *cnet, HTNet *dnet, ftp_data *data)
1.22 frystyk 454: {
1.79 frystyk 455: #ifdef FTP_POLL_PORTS
1.60 frystyk 456: unsigned short old_DataPort = DataPort;
457: for (DataPort=old_DataPort+1;; DataPort++) {
458: if (DataPort > LAST_TCP_PORT)
459: DataPort = FIRST_TCP_PORT;
460: if (DataPort == old_DataPort) {
1.78 eric 461: if(PROT_TRACE) HTTrace("FTP......... No data port found\n");
1.60 frystyk 462: return NO;
463: }
1.71 frystyk 464: if (HTDoListen(dnet, DataPort, 1) == HT_OK)
1.60 frystyk 465: break;
1.85 frystyk 466: #if 0
1.90.2.1! eric 467: if (HTNet_socket(dnet) != INVSOC) {
! 468: NETCLOSE(HTNet_socket(dnet));
! 469: HTNet_socket(dnet) = INVSOC;
1.60 frystyk 470: }
1.85 frystyk 471: #else
472: HTDoClose(dnet);
473: #endif
1.22 frystyk 474: }
1.60 frystyk 475: #else
1.90.2.1! eric 476: if (HTDoListen(dnet, 0, HTNet_socket(cnet), 1) != HT_OK) return NO;
1.79 frystyk 477: #endif /* FTP_POLL_PORTS */
1.22 frystyk 478:
1.60 frystyk 479: /* Now we must find out who we are to tell the other guy */
480: {
481: SockA local_addr;
482: int addr_size = sizeof(local_addr);
483: memset((void *) &local_addr, '\0', sizeof(local_addr));
1.90.2.1! eric 484: if (getsockname(HTNet_socket(dnet), (struct sockaddr *) &local_addr,
1.60 frystyk 485: &addr_size) < 0) {
1.68 frystyk 486: HTRequest_addSystemError(dnet->request, ERR_FATAL, socerrno,
487: NO, "getsockname");
1.60 frystyk 488: return NO;
1.22 frystyk 489: }
1.78 eric 490: if (PROT_TRACE) HTTrace("FTP......... This host is `%s\'\n",
1.60 frystyk 491: HTInetString(&local_addr));
492: {
493: u_long addr = local_addr.sin_addr.s_addr;
494: u_short port = local_addr.sin_port;
495: sprintf(data->host, "%d,%d,%d,%d,%d,%d",
496: (int)*((unsigned char *)(&addr)+0),
497: (int)*((unsigned char *)(&addr)+1),
498: (int)*((unsigned char *)(&addr)+2),
499: (int)*((unsigned char *)(&addr)+3),
500: (int)*((unsigned char *)(&port)+0),
501: (int)*((unsigned char *)(&port)+1));
1.22 frystyk 502: }
503: }
1.60 frystyk 504: return YES;
1.22 frystyk 505: }
506:
1.60 frystyk 507: /* HTFTPLogin
508: ** -----------
509: ** This function makes a login to a ftp-server. It takes the user name
510: ** and passwd specified in ctrl->user and if that fails or an additional
511: ** account is needed, the user is prompted.
512: ** Returns HT_OK, HT_ERROR, or HT_WOULD_BLOCK
1.22 frystyk 513: */
1.60 frystyk 514: PRIVATE int HTFTPLogin (HTRequest *request, HTNet *cnet, ftp_ctrl *ctrl)
1.22 frystyk 515: {
1.60 frystyk 516: int status;
517: typedef enum _state {
518: SUB_ERROR = -2,
519: SUB_SUCCESS = -1,
520: NEED_SELECT = 0,
521: NEED_GREETING,
522: NEED_REIN,
523: NEED_UID,
524: NEED_PASSWD,
525: NEED_ACCOUNT,
526: PROMPT_USER
527: } state;
528:
529: /* Jump into a second level state machine */
530: while (1) {
531: switch ((state) ctrl->substate) {
532: case NEED_SELECT:
533: ctrl->substate = ctrl->reset ? NEED_REIN : NEED_GREETING;
534: break;
535:
536: case NEED_GREETING:
1.90.2.1! eric 537: status = HTHost_read(cnet->host);
1.60 frystyk 538: if (status == HT_WOULD_BLOCK)
539: return HT_WOULD_BLOCK;
540: else if (status == HT_LOADED) {
1.66 frystyk 541: if (ctrl->repcode/100 == 2) {
542: ctrl->substate = (ctrl->uid && *ctrl->uid) ?
543: NEED_UID : PROMPT_USER;
544: } else
1.60 frystyk 545: ctrl->substate = SUB_ERROR;
546: } else
547: ctrl->substate = SUB_ERROR;
548: break;
549:
550: case NEED_REIN:
551: if (!ctrl->sent) {
552: status = SendCommand(request, ctrl, "REIN", NULL);
553: if (status == HT_WOULD_BLOCK)
554: return HT_WOULD_BLOCK;
555: else if (status == HT_ERROR)
556: ctrl->substate = SUB_ERROR;
557: ctrl->sent = YES;
558: } else {
1.90.2.1! eric 559: status = HTHost_read(cnet->host);
1.60 frystyk 560: if (status == HT_WOULD_BLOCK)
561: return HT_WOULD_BLOCK;
562: else if (status == HT_LOADED) {
1.66 frystyk 563: if (ctrl->repcode/100 == 2) {
564: ctrl->substate = (ctrl->uid && *ctrl->uid) ?
565: NEED_UID : PROMPT_USER;
566: } else
1.60 frystyk 567: ctrl->substate = SUB_SUCCESS; /* hope the best */
568: } else
569: ctrl->substate = SUB_ERROR;
570: ctrl->sent = NO;
1.22 frystyk 571: }
1.60 frystyk 572: break;
573:
574: case NEED_UID:
575: if (!ctrl->sent) {
576: status = SendCommand(request, ctrl, "USER", ctrl->uid);
577: if (status == HT_WOULD_BLOCK)
578: return HT_WOULD_BLOCK;
579: else if (status == HT_ERROR)
580: ctrl->substate = SUB_ERROR;
581: ctrl->sent = YES;
1.22 frystyk 582: } else {
1.90.2.1! eric 583: status = HTHost_read(cnet->host);
1.60 frystyk 584: if (status == HT_WOULD_BLOCK)
585: return HT_WOULD_BLOCK;
586: else if (status == HT_LOADED) {
587: int code = ctrl->repcode/100;
588: if (code == 2) /* Logged in w/o passwd! */
589: ctrl->substate = SUB_SUCCESS;
1.66 frystyk 590: else if (code == 3) { /* Password demanded */
591: ctrl->substate = (ctrl->passwd && *ctrl->passwd) ?
592: NEED_PASSWD : PROMPT_USER;
593: } else if (ctrl->repcode == 530)
1.60 frystyk 594: ctrl->substate = PROMPT_USER; /* User unknown */
595: else
596: ctrl->substate = SUB_ERROR;
597: } else
598: ctrl->substate = SUB_ERROR;
599: ctrl->sent = NO;
1.22 frystyk 600: }
1.60 frystyk 601: break;
1.1 timbl 602:
1.60 frystyk 603: case NEED_PASSWD:
604: if (!ctrl->sent) {
605: status = SendCommand(request, ctrl, "PASS", ctrl->passwd);
606: if (status == HT_WOULD_BLOCK)
607: return HT_WOULD_BLOCK;
608: else if (status == HT_ERROR)
609: ctrl->substate = SUB_ERROR;
610: ctrl->sent = YES;
611: } else {
1.90.2.1! eric 612: status = HTHost_read(cnet->host);
1.60 frystyk 613: if (status == HT_WOULD_BLOCK)
614: return HT_WOULD_BLOCK;
615: else if (status == HT_LOADED) {
616: int code = ctrl->repcode/100;
617: if (code == 2) /* Logged in with passwd */
618: ctrl->substate = SUB_SUCCESS;
1.70 frystyk 619: else if (code == 3) { /* Account required */
620: HTAlertCallback *cbf = HTAlert_find(HT_A_PROMPT);
621: HTAlertPar * reply = HTAlert_newReply();
622: if (cbf && (*cbf)(request, HT_A_PROMPT,
623: HT_MSG_ACCOUNT, NULL, NULL, reply)) {
624: ctrl->account = HTAlert_replyMessage(reply);
1.60 frystyk 625: ctrl->substate = NEED_ACCOUNT;
1.70 frystyk 626: } else
1.60 frystyk 627: ctrl->substate = SUB_ERROR;
1.70 frystyk 628: HTAlert_deleteReply(reply);
1.60 frystyk 629: } else if (ctrl->repcode == 530)
630: ctrl->substate = PROMPT_USER;
631: else
632: ctrl->substate = SUB_ERROR;
633: } else
634: ctrl->substate = SUB_ERROR;
635: ctrl->sent = NO;
636: }
637: break;
1.1 timbl 638:
1.60 frystyk 639: case NEED_ACCOUNT:
640: if (!ctrl->sent) {
641: status = SendCommand(request, ctrl, "ACCT", ctrl->account);
642: if (status == HT_WOULD_BLOCK)
643: return HT_WOULD_BLOCK;
644: else if (status == HT_ERROR)
645: ctrl->substate = SUB_ERROR;
646: ctrl->sent = YES;
1.23 frystyk 647: } else {
1.90.2.1! eric 648: status = HTHost_read(cnet->host);
1.60 frystyk 649: if (status == HT_WOULD_BLOCK)
650: return HT_WOULD_BLOCK;
651: else if (status == HT_LOADED) {
652: int code = ctrl->repcode/100;
653: if (code == 2) /* Logged in with account */
654: ctrl->substate = SUB_SUCCESS;
655: else
656: ctrl->substate = SUB_ERROR; /* hopeless */
657: } else
658: ctrl->substate = SUB_ERROR;
659: ctrl->sent = NO;
1.1 timbl 660: }
1.60 frystyk 661: break;
1.1 timbl 662:
1.60 frystyk 663: case PROMPT_USER:
664: {
1.70 frystyk 665: HTAlertCallback *cbf = HTAlert_find(HT_A_USER_PW);
666: HTAlertPar * reply = HTAlert_newReply();
1.77 frystyk 667: HT_FREE(ctrl->uid);
668: HT_FREE(ctrl->passwd);
1.87 frystyk 669: if (cbf && (*cbf)(request, HT_A_USER_PW, HT_MSG_FTP_UID,
670: NULL, NULL, reply)){
1.70 frystyk 671: ctrl->uid = HTAlert_replyMessage(reply);
672: ctrl->passwd = HTAlert_replySecret(reply);
673: }
674: HTAlert_deleteReply(reply);
1.66 frystyk 675: if (ctrl->uid && *ctrl->uid && ctrl->passwd && *ctrl->passwd)
1.60 frystyk 676: ctrl->substate = NEED_UID;
677: else
678: ctrl->substate = SUB_ERROR;
679: }
680: break;
1.1 timbl 681:
1.60 frystyk 682: case SUB_ERROR:
683: if (PROT_TRACE)
1.78 eric 684: HTTrace("FTP......... Login failed\n");
1.60 frystyk 685: ctrl->substate = 0;
686: return HT_ERROR;
687: break;
1.23 frystyk 688:
1.60 frystyk 689: case SUB_SUCCESS:
1.43 frystyk 690: if (PROT_TRACE)
1.78 eric 691: HTTrace("FTP......... Logged in as `%s\'\n", ctrl->uid);
1.60 frystyk 692: ctrl->substate = 0;
693: return HT_OK;
694: break;
1.23 frystyk 695: }
1.22 frystyk 696: }
697: }
698:
1.60 frystyk 699: /* HTFTPDataConnection
700: ** -------------------
701: ** Prepares a data connection to the server and initializes the
702: ** transfer mode.
703: ** Returns HT_OK, HT_ERROR, or HT_WOULD_BLOCK
1.1 timbl 704: */
1.60 frystyk 705: PRIVATE int HTFTPDataConnection (HTRequest * request, HTNet *cnet,
706: ftp_ctrl *ctrl, ftp_data *data)
1.1 timbl 707: {
1.60 frystyk 708: int status;
709: HTNet *dnet = ctrl->dnet;
710: typedef enum _state {
711: SUB_ERROR = -2,
712: SUB_SUCCESS = -1,
713: NEED_TYPE = 0,
714: NEED_SELECT,
715: NEED_PASV,
716: NEED_PORT
717: } state;
718:
719: /* Jump into a second level state machine */
720: while (1) {
721: switch ((state) ctrl->substate) {
722: case NEED_TYPE:
723: if(!data->type|| data->pasv || data->type=='N' || data->type=='L'){
724: ctrl->substate = NEED_SELECT;
725: break;
726: }
727: if (!ctrl->sent) {
728: char type[2];
729: *type = data->type;
730: *(type+1) = '\0';
731: status = SendCommand(request, ctrl, "TYPE", type);
732: if (status == HT_WOULD_BLOCK)
733: return HT_WOULD_BLOCK;
734: else if (status == HT_ERROR)
735: ctrl->substate = SUB_ERROR;
736: ctrl->sent = YES;
737: } else {
1.90.2.1! eric 738: status = HTHost_read(cnet->host);
1.60 frystyk 739: if (status == HT_WOULD_BLOCK)
740: return HT_WOULD_BLOCK;
741: else if (status == HT_LOADED) {
742: if (ctrl->repcode/100 == 2)
743: ctrl->substate = NEED_SELECT;
744: else
745: ctrl->substate = SUB_ERROR;
746: } else
747: ctrl->substate = SUB_ERROR;
748: ctrl->sent = NO;
749: }
750: break;
751:
752: case NEED_SELECT:
753: if (FTPMode & FTP_DATA_PASV && !data->pasv)
754: ctrl->substate = NEED_PASV;
755: else if (ListenSocket(cnet, dnet, data))
756: ctrl->substate = NEED_PORT;
757: else
758: ctrl->substate = SUB_ERROR;
759: break;
760:
761: case NEED_PASV:
762: if (!ctrl->sent) {
763: status = SendCommand(request, ctrl, "PASV", NULL);
764: if (status == HT_WOULD_BLOCK)
765: return HT_WOULD_BLOCK;
766: else if (status == HT_ERROR)
767: ctrl->substate = SUB_ERROR;
768: ctrl->sent = YES;
769: } else {
1.90.2.1! eric 770: status = HTHost_read(cnet->host);
1.60 frystyk 771: if (status == HT_WOULD_BLOCK)
772: return HT_WOULD_BLOCK;
773: else if (status == HT_LOADED) {
774: if (ctrl->repcode == 227) {
775: /*
776: ** If succes, we have to scan for the returned number.
777: ** As the format for the response isn't standard,
778: ** the best thing to do is to scan for the first digit
779: ** after the status code, see RFC1123
780: */
781: char *host = ctrl->reply;
782: int h0, h1, h2, h3, p0=0, p1=0;
783: while (*host && !isdigit(*host++));
784: if (!*host || sscanf(--host, "%d,%d,%d,%d,%d,%d",
785: &h0,&h1,&h2,&h3,&p0,&p1) < 6) {
786: if (PROT_TRACE)
1.78 eric 787: HTTrace("FTP......... PASV No addr\n");
1.60 frystyk 788: ctrl->substate = SUB_ERROR;
789: break;
790: } else {
791: int port = (p0<<8)+p1;
792: sprintf(data->host, "ftp://%d.%d.%d.%d:%d/",
793: h0, h1, h2, h3, port);
794: data->pasv = YES;
795: ctrl->substate = SUB_SUCCESS;
796: }
797: } else {
798: ctrl->substate = ListenSocket(cnet, dnet, data) ?
799: NEED_PORT : SUB_ERROR;
800: }
801: } else
802: ctrl->substate = SUB_ERROR;
803: ctrl->sent = NO;
804: }
805: break;
1.22 frystyk 806:
1.60 frystyk 807: case NEED_PORT:
808: if (!ctrl->sent) {
809: status = SendCommand(request, ctrl, "PORT", data->host);
810: if (status == HT_WOULD_BLOCK)
811: return HT_WOULD_BLOCK;
812: else if (status == HT_ERROR)
813: ctrl->substate = SUB_ERROR;
814: ctrl->sent = YES;
815: } else {
1.90.2.1! eric 816: status = HTHost_read(cnet->host);
1.60 frystyk 817: if (status == HT_WOULD_BLOCK)
818: return HT_WOULD_BLOCK;
819: else if (status == HT_LOADED) {
820: data->pasv = NO;
821: ctrl->substate = (ctrl->repcode/100 == 2) ?
822: SUB_SUCCESS : SUB_ERROR;
823: } else
824: ctrl->substate = SUB_ERROR;
825: ctrl->sent = NO;
826: }
827: break;
1.1 timbl 828:
1.60 frystyk 829: case SUB_ERROR:
830: if (PROT_TRACE)
1.78 eric 831: HTTrace("FTP......... Can't setup data connection\n");
1.60 frystyk 832: ctrl->substate = 0;
833: return HT_ERROR;
834: break;
1.1 timbl 835:
1.60 frystyk 836: case SUB_SUCCESS:
837: if (PROT_TRACE)
1.78 eric 838: HTTrace("FTP......... Data connection negotiated\n");
1.60 frystyk 839: ctrl->substate = 0;
840: return HT_OK;
841: break;
1.23 frystyk 842: }
1.58 frystyk 843: }
1.23 frystyk 844: }
1.1 timbl 845:
846:
1.60 frystyk 847: /* HTFTPServerInfo
848: ** ---------------
849: ** This function finds out what server we are talking to.
850: ** Maybe we can upgrade from NLST to LIST.
851: ** Returns HT_OK, HT_ERROR, or HT_WOULD_BLOCK
852: ** Thanks to James.W.Matthews@Dartmouth.EDU (James W. Matthews) for making
853: ** his code available.
1.1 timbl 854: */
1.60 frystyk 855: PRIVATE int HTFTPServerInfo (HTRequest *request, HTNet *cnet,
856: ftp_ctrl *ctrl, ftp_data *data)
1.23 frystyk 857: {
1.60 frystyk 858: int status;
859: typedef enum _state {
860: SUB_ERROR = -2,
861: SUB_SUCCESS = -1,
862: NEED_SYST = 0,
863: CHECK_SYST,
864: NEED_PWD,
865: CHECK_PWD
866: } state;
867:
868: /* Jump into a second level state machine */
869: while (1) {
870: switch ((state) ctrl->substate) {
871: case NEED_SYST:
872: if (!ctrl->sent) {
1.63 frystyk 873: if (ctrl->server != FTP_UNSURE) {
1.60 frystyk 874: FTPListType(data, ctrl->server);
875: return HT_OK;
876: }
877: status = SendCommand(request, ctrl, "SYST", NULL);
878: if (status == HT_WOULD_BLOCK)
879: return HT_WOULD_BLOCK;
880: else if (status == HT_ERROR)
881: ctrl->substate = SUB_ERROR;
882: ctrl->sent = YES;
883: } else {
1.90.2.1! eric 884: status = HTHost_read(cnet->host);
1.60 frystyk 885: if (status == HT_WOULD_BLOCK)
886: return HT_WOULD_BLOCK;
887: else if (status == HT_LOADED) {
888: ctrl->substate=ctrl->repcode==215 ? CHECK_SYST : NEED_PWD;
889: } else
890: ctrl->substate = SUB_ERROR;
891: ctrl->sent = NO;
1.23 frystyk 892: }
893: break;
894:
1.60 frystyk 895: case CHECK_SYST:
1.23 frystyk 896: {
1.60 frystyk 897: char *reply = ctrl->reply;
898: if (!*reply) {
1.43 frystyk 899: if (PROT_TRACE)
1.78 eric 900: HTTrace("FTP......... No server info?\n");
1.60 frystyk 901: ctrl->substate = NEED_PWD;
1.23 frystyk 902: break;
903: }
1.60 frystyk 904: if (strncmp(reply, "UNIX Type: L8MAC-OSMachTen", 28) == 0) {
905: ctrl->server = FTP_MACHTEN;
906: } else if (strstr(reply, "UNIX") != NULL) {
907: ctrl->server = FTP_UNIX;
908: } else if (strncmp(reply, "VMS", 3) == 0) {
909: ctrl->server = FTP_VMS;
910: } else if ((strncmp(reply, "VM/CMS", 6) == 0) ||
911: (strncmp(reply, "VM", 2) == 0)) {
912: ctrl->server = FTP_CMS;
913: } else if (strncmp(reply, "DCTS", 4) == 0) {
914: ctrl->server = FTP_DCTS;
915: } else if (strstr(reply, "MAC-OS TCP/ConnectII") != NULL) {
1.23 frystyk 916: /* Check old versions of TCP/C using / in pathnames */
1.63 frystyk 917: ctrl->server = FTP_TCPC + FTP_UNSURE;
1.60 frystyk 918: } else if (strncmp(reply, "MACOS Peter's Server", 20) == 0) {
919: ctrl->server = FTP_PETER_LEWIS;
920: } else if (strncmp(reply, "Windows_NT", 10) == 0) {
921: ctrl->server = FTP_WINNT;
1.23 frystyk 922: }
923:
924: /* If we are unsure, try PWD to get more information */
1.60 frystyk 925: if (ctrl->server & FTP_UNSURE)
926: ctrl->substate = NEED_PWD;
1.23 frystyk 927: else
1.60 frystyk 928: ctrl->substate = SUB_SUCCESS;
1.1 timbl 929: }
1.23 frystyk 930: break;
931:
932: case NEED_PWD:
1.60 frystyk 933: if (!ctrl->sent) {
934: status = SendCommand(request, ctrl, "PWD", NULL);
935: if (status == HT_WOULD_BLOCK)
936: return HT_WOULD_BLOCK;
937: else if (status == HT_ERROR)
938: ctrl->substate = SUB_ERROR;
939: ctrl->sent = YES;
1.23 frystyk 940: } else {
1.90.2.1! eric 941: status = HTHost_read(cnet->host);
1.60 frystyk 942: if (status == HT_WOULD_BLOCK)
943: return HT_WOULD_BLOCK;
944: else if (status == HT_LOADED) {
945: ctrl->substate = (ctrl->repcode/100 == 2) ?
946: CHECK_PWD : SUB_ERROR;
947: } else
948: ctrl->substate = SUB_ERROR;
949: ctrl->sent = NO;
1.23 frystyk 950: }
951: break;
952:
1.60 frystyk 953: case CHECK_PWD:
954: {
955: char *start = strchr(ctrl->reply, '"');
956: char *end;
957: if (!start || (end = strchr(++start, '"')) == NULL) {
1.43 frystyk 958: if (PROT_TRACE)
1.78 eric 959: HTTrace("FTP......... No current directory?\n");
1.60 frystyk 960: ctrl->server = FTP_GENERIC;
1.23 frystyk 961: } else {
1.60 frystyk 962: *end = '\0';
1.63 frystyk 963: if (ctrl->server & FTP_TCPC) {
1.60 frystyk 964: ctrl->server = *start == '/' ? FTP_NCSA : FTP_TCPC;
965: } else if (*start == '/') {
966: /* path names starting with / imply Unix, right? */
967: ctrl->server = FTP_UNIX;
968: } else if (*(end-1) == ']') {
969: /* path names ending with ] imply VMS, right? */
970: ctrl->server = FTP_VMS;
971: } else
972: ctrl->server = FTP_GENERIC;
1.23 frystyk 973: }
1.60 frystyk 974: ctrl->substate = SUB_SUCCESS;
1.1 timbl 975: }
1.23 frystyk 976: break;
1.1 timbl 977:
1.60 frystyk 978: case SUB_ERROR:
979: if (PROT_TRACE)
1.78 eric 980: HTTrace("FTP......... Can't get server information\n");
1.60 frystyk 981: ctrl->substate = 0;
1.63 frystyk 982: ctrl->server = FTP_GENERIC;
1.60 frystyk 983: return HT_ERROR;
1.23 frystyk 984: break;
1.22 frystyk 985:
1.60 frystyk 986: case SUB_SUCCESS:
1.81 frystyk 987: {
988: HTHost * host = HTNet_host(cnet);
989: if (PROT_TRACE)
990: HTTrace("FTP Server.. Guessed type %d\n", ctrl->server);
991: HTHost_setVersion(host, ctrl->server);
992: FTPListType(data, ctrl->server);
993: ctrl->substate = 0;
994: return HT_OK;
995: break;
996: }
1.22 frystyk 997: }
1.23 frystyk 998: }
999: }
1000:
1.60 frystyk 1001: /* HTFTPGetData
1002: ** ------------
1003: ** This function asks for the file or a directory. First we try in one go,
1.23 frystyk 1004: ** but if that doesn't work, then we use CWD for each segment and then
1005: ** try to retrieve it. If that also fails, then we try if it is a
1.60 frystyk 1006: ** directory.
1007: ** Returns HT_OK, HT_LOADED, HT_ERROR, or HT_WOULD_BLOCK
1.23 frystyk 1008: */
1.71 frystyk 1009: PRIVATE int HTFTPGetData (HTRequest *request, HTNet *cnet, SOCKET sockfd,
1.60 frystyk 1010: ftp_ctrl *ctrl, ftp_data *data)
1.23 frystyk 1011: {
1012: int status;
1.60 frystyk 1013: char *segment = NULL;
1014: HTNet *dnet = ctrl->dnet;
1015: typedef enum _state {
1016: SUB_ERROR = -2,
1017: SUB_SUCCESS = -1,
1018: NEED_SELECT = 0,
1019: NEED_CONNECT,
1020: NEED_ACCEPT,
1021: NEED_ACTION,
1022: NEED_CWD,
1023: NEED_SEGMENT,
1024: NEED_STREAM,
1.63 frystyk 1025: NEED_BODY
1.60 frystyk 1026: } state;
1027:
1028: /* Jump into a second level state machine */
1029: while (1) {
1030: switch ((state) ctrl->substate) {
1031: case NEED_SELECT:
1032: ctrl->substate = data->pasv ? NEED_CONNECT : NEED_ACTION;
1033: break;
1034:
1035: case NEED_CONNECT:
1036: status = HTDoConnect(dnet, data->host, FTP_DATA);
1037: if (status == HT_WOULD_BLOCK)
1038: return HT_WOULD_BLOCK;
1039: else if (status == HT_OK) {
1040: if (PROT_TRACE)
1.78 eric 1041: HTTrace("FTP Data.... Active data socket %d\n",
1.90.2.1! eric 1042: HTNet_socket(dnet));
1.88 frystyk 1043: HTNet_setPersistent(dnet, YES, HT_TP_INTERLEAVE);
1.60 frystyk 1044: ctrl->substate = NEED_ACTION;
1.81 frystyk 1045: } else { /* Swap to PORT on the fly */
1.90.2.1! eric 1046: NETCLOSE(HTNet_socket(dnet));
! 1047: HTNet_setSocket(dnet, INVSOC);
1.60 frystyk 1048: if (PROT_TRACE)
1.78 eric 1049: HTTrace("FTP......... Swap to PORT on the fly\n");
1.89 frystyk 1050: ctrl->substate = NEED_SELECT;
1.77 frystyk 1051: HT_FREE(segment);
1.60 frystyk 1052: return HT_OK;
1053: }
1.23 frystyk 1054: break;
1055:
1.60 frystyk 1056: case NEED_ACCEPT:
1057: {
1.86 frystyk 1058: status = HTDoAccept(ctrl->dnet, &ctrl->dnet);
1059: dnet = ctrl->dnet;
1.60 frystyk 1060: if (status == HT_WOULD_BLOCK)
1061: return HT_WOULD_BLOCK;
1062: else if (status == HT_OK) {
1063: if (PROT_TRACE)
1.78 eric 1064: HTTrace("FTP Data.... Passive data socket %d\n",
1.90.2.1! eric 1065: HTNet_socket(dnet));
1.60 frystyk 1066: ctrl->substate = NEED_STREAM;
1067: } else
1068: ctrl->substate = SUB_ERROR;
1.23 frystyk 1069: }
1.60 frystyk 1070: break;
1.33 frystyk 1071:
1.60 frystyk 1072: case NEED_ACTION:
1073: if (!ctrl->sent) {
1074: char *cmd = (data->type=='L') ? "LIST" :
1075: (data->type=='N') ? "NLST" : "RETR";
1076: StrAllocCopy(segment, data->offset);
1077: HTUnEscape(segment);
1078: HTCleanTelnetString(segment);
1079: status = SendCommand(request, ctrl, cmd, segment);
1.77 frystyk 1080: HT_FREE(segment);
1.60 frystyk 1081: if (status == HT_WOULD_BLOCK)
1082: return HT_WOULD_BLOCK;
1083: else if (status == HT_ERROR)
1084: ctrl->substate = SUB_ERROR;
1085: ctrl->sent = YES;
1086: } else {
1.90.2.1! eric 1087: status = HTHost_read(cnet->host);
1.60 frystyk 1088: if (status == HT_WOULD_BLOCK)
1089: return HT_WOULD_BLOCK;
1090: else if (status == HT_LOADED) {
1091: int code = ctrl->repcode;
1092: if (code==125 || code==150 || code==225)
1093: ctrl->substate=data->pasv ? NEED_STREAM : NEED_ACCEPT;
1094: else if (code/100==5 && !ctrl->cwd)
1095: ctrl->substate = NEED_SEGMENT;
1096: else
1097: ctrl->substate = SUB_ERROR;
1098: } else
1099: ctrl->substate = SUB_ERROR;
1100: ctrl->sent = NO;
1.23 frystyk 1101: }
1102: break;
1.60 frystyk 1103:
1104: case NEED_SEGMENT:
1.23 frystyk 1105: {
1.60 frystyk 1106: char *ptr;
1107: if (data->offset == data->file) {
1.63 frystyk 1108: if (ctrl->server == FTP_VMS) { /* Change to root */
1.77 frystyk 1109: if ((segment = (char *) HT_MALLOC(strlen(ctrl->uid)+3)) == NULL)
1110: HT_OUTOFMEM("segment ");
1.63 frystyk 1111: sprintf(segment, "[%s]", ctrl->uid);
1112: } else
1113: StrAllocCopy(segment, "/");
1.60 frystyk 1114: data->offset++;
1115: ctrl->substate = NEED_CWD;
1116: } else {
1117: if ((ptr = strchr(data->offset, '/'))) {
1118: *ptr='\0';
1119: StrAllocCopy(segment, data->offset);
1120: *ptr='/';
1121: data->offset = ++ptr;
1122: HTUnEscape(segment);
1123: HTCleanTelnetString(segment);
1124: ctrl->substate = NEED_CWD;
1.33 frystyk 1125: } else
1.60 frystyk 1126: ctrl->substate = NEED_ACTION;
1.23 frystyk 1127: }
1128: }
1129: break;
1130:
1.60 frystyk 1131: case NEED_CWD:
1132: if (!ctrl->sent) {
1133: status = SendCommand(request, ctrl, "CWD", segment);
1.77 frystyk 1134: HT_FREE(segment);
1.60 frystyk 1135: if (status == HT_WOULD_BLOCK)
1136: return HT_WOULD_BLOCK;
1137: else if (status == HT_ERROR)
1138: ctrl->substate = SUB_ERROR;
1139: ctrl->cwd = YES;
1140: ctrl->sent = YES;
1141: } else {
1.90.2.1! eric 1142: status = HTHost_read(cnet->host);
1.60 frystyk 1143: if (status == HT_WOULD_BLOCK)
1144: return HT_WOULD_BLOCK;
1145: else if (status == HT_LOADED) {
1146: if (ctrl->repcode/100 == 2)
1147: ctrl->substate = NEED_SEGMENT;
1148: else
1149: ctrl->substate = SUB_ERROR;
1150: } else
1151: ctrl->substate = SUB_ERROR;
1152: ctrl->sent = NO;
1.33 frystyk 1153: }
1.60 frystyk 1154: break;
1.33 frystyk 1155:
1.81 frystyk 1156: case NEED_STREAM:
1157: /*
1158: ** Create the stream pipe FROM the channel to the application.
1159: ** The target for the input stream pipe is set up using the
1160: ** stream stack.
1161: */
1162: if (FTP_DIR(data)) {
1.90.2.1! eric 1163: dnet->readStream = HTFTPDir_new(request,ctrl->server,data->type);
1.81 frystyk 1164: } else {
1.90.2.1! eric 1165: dnet->readStream = HTStreamStack(HTAnchor_format(request->anchor),
! 1166: request->output_format,
! 1167: request->output_stream,
! 1168: request, YES);
1.81 frystyk 1169: }
1.86 frystyk 1170: HTRequest_setOutputConnected(request, YES);
1.90.2.1! eric 1171: sockfd = HTNet_socket(dnet); /* Ensure that we try data first */
1.81 frystyk 1172: ctrl->substate = NEED_BODY;
1173: #if 0
1.60 frystyk 1174: {
1.63 frystyk 1175: if (FTP_DIR(data)) {
1.90.2.1! eric 1176: dnet->readStream = HTFTPDir_new(request,ctrl->server,data->type);
1.63 frystyk 1177: } else {
1.90.2.1! eric 1178: dnet->readStream =
1.63 frystyk 1179: HTStreamStack(HTAnchor_format(request->anchor),
1180: request->output_format,
1181: request->output_stream,
1182: request,YES);
1183: }
1.60 frystyk 1184: }
1.81 frystyk 1185: #endif
1.90.2.1! eric 1186: break;
1.23 frystyk 1187:
1.60 frystyk 1188: case NEED_BODY:
1.90.2.1! eric 1189: if (sockfd == HTNet_socket(dnet)) {
! 1190: status = HTHost_read(dnet->host);
1.63 frystyk 1191: if (status == HT_WOULD_BLOCK)
1192: return HT_WOULD_BLOCK;
1.75 frystyk 1193: else if (status == HT_LOADED || status == HT_CLOSED) {
1.63 frystyk 1194: if (data->ready)
1195: ctrl->substate = SUB_SUCCESS;
1196: else
1.90.2.1! eric 1197: sockfd = HTNet_socket(cnet);
1.63 frystyk 1198: data->ready = YES;
1.76 frystyk 1199: } else {
1.63 frystyk 1200: ctrl->substate = SUB_ERROR;
1.76 frystyk 1201: data->stream_error = YES;
1202: }
1.63 frystyk 1203: } else {
1.90.2.1! eric 1204: status = HTHost_read(cnet->host);
1.63 frystyk 1205: if (status == HT_WOULD_BLOCK)
1206: return HT_WOULD_BLOCK;
1207: else if (status == HT_LOADED) {
1208: if (ctrl->repcode/100 == 2) {
1209: if (data->ready)
1210: ctrl->substate = SUB_SUCCESS;
1211: else
1.90.2.1! eric 1212: sockfd = HTNet_socket(dnet);
1.63 frystyk 1213: data->ready = YES;
1214: } else
1215: ctrl->substate = SUB_ERROR;
1216: } else
1.60 frystyk 1217: ctrl->substate = SUB_ERROR;
1.63 frystyk 1218: }
1.23 frystyk 1219: break;
1.22 frystyk 1220:
1.60 frystyk 1221: case SUB_ERROR:
1222: if (PROT_TRACE)
1.78 eric 1223: HTTrace("FTP......... Can't retrieve object\n");
1.60 frystyk 1224: ctrl->substate = 0;
1.77 frystyk 1225: HT_FREE(segment);
1.60 frystyk 1226: return HT_ERROR;
1.23 frystyk 1227: break;
1228:
1.60 frystyk 1229: case SUB_SUCCESS:
1230: if (PROT_TRACE)
1.78 eric 1231: HTTrace("FTP......... Object is loaded\n");
1.60 frystyk 1232: ctrl->substate = 0;
1.77 frystyk 1233: HT_FREE(segment);
1.60 frystyk 1234: return HT_LOADED;
1.23 frystyk 1235: break;
1.1 timbl 1236: }
1237: }
1.23 frystyk 1238: }
1.1 timbl 1239:
1.23 frystyk 1240: /* ------------------------------------------------------------------------- */
1241:
1242: /* Retrieve File from Server as an atomic action.
1243: ** -----------------------------------------------
1.58 frystyk 1244: ** Given a hypertext address, this routine loads a document.
1.23 frystyk 1245: **
1246: ** On entry,
1.58 frystyk 1247: ** request This is the request structure
1248: ** returns HT_ERROR Error has occured in call back
1249: ** HT_OK Call back was OK
1250: */
1.90.2.1! eric 1251: PRIVATE int FTPEvent (SOCKET soc, void * pVoid, HTEvent_type type);
! 1252:
! 1253: PUBLIC int HTLoadFTP (SOCKET soc, HTRequest * request)
1.58 frystyk 1254: {
1.84 frystyk 1255: HTNet * cnet = HTRequest_net(request);
1256: ftp_ctrl * ctrl = NULL;
1257: ftp_data * data = NULL;
1258: HTParentAnchor * anchor = HTRequest_anchor(request);
1259: char * url = HTAnchor_physical(anchor);
1.23 frystyk 1260:
1.58 frystyk 1261: /*
1.60 frystyk 1262: ** Initiate a new FTP ctrl and data structure and bind to request structure
1.58 frystyk 1263: ** This is actually state FTP_BEGIN, but it can't be in the state
1264: ** machine as we need the structure first.
1265: */
1.90.2.1! eric 1266: if (PROT_TRACE) HTTrace("FTP......... Looking for `%s\'\n",url);
! 1267: if ((ctrl = (ftp_ctrl *) HT_CALLOC(1, sizeof(ftp_ctrl))) == NULL ||
! 1268: (data = (ftp_data *) HT_CALLOC(1, sizeof(ftp_data))) == NULL)
! 1269: HT_OUTOFMEM("HTLoadFTP");
! 1270: ctrl->cmd = HTChunk_new(128);
! 1271: ctrl->state = FTP_BEGIN;
! 1272: ctrl->server = FTP_UNSURE;
! 1273: ctrl->dnet = HTNet_dup(cnet);
! 1274: cnet->context = ctrl; /* Context for control connection */
! 1275: ctrl->dnet->context = data; /* Context for data connection */
! 1276: ctrl->net = cnet;
! 1277: HTNet_setContext(cnet, ctrl);
! 1278: HTNet_setEventCallback(cnet, FTPEvent);
! 1279: HTNet_setEventParam(cnet, ctrl); /* callbacks get http* */
! 1280: return FTPEvent(soc, ctrl, HTEvent_NONE);
! 1281: }
! 1282:
! 1283: PRIVATE int FTPEvent (SOCKET soc, void * pVoid, HTEvent_type type)
! 1284: {
! 1285: ftp_ctrl * ctrl = (ftp_ctrl *)pVoid;
! 1286: ftp_data * data = ctrl->dnet->context;
! 1287: int status = HT_ERROR;
! 1288: HTNet * cnet = ctrl->net;
! 1289: HTRequest * request = HTNet_request(cnet);
! 1290: HTParentAnchor * anchor = HTRequest_anchor(request);
! 1291: char * url = HTAnchor_physical(anchor);
! 1292:
! 1293: if (type == HTEvent_CLOSE) { /* Interrupted */
1.60 frystyk 1294: if(HTRequest_isPostWeb(request)&&!HTRequest_isMainDestination(request))
1295: FTPCleanup(request, HT_IGNORE);
1296: else
1297: FTPCleanup(request, HT_INTERRUPTED);
1298: return HT_OK;
1299: } else {
1300: ctrl = (ftp_ctrl *) cnet->context; /* Get existing copy */
1301: data = (ftp_data *) ctrl->dnet->context;
1302: }
1.58 frystyk 1303:
1304: /* Now jump into the machine. We know the state from the previous run */
1305: while (1) {
1306: switch (ctrl->state) {
1.60 frystyk 1307: case FTP_BEGIN:
1.83 frystyk 1308: HTFTPParseURL(request, url, ctrl, data);
1.60 frystyk 1309: if (data->type != 'N') {
1.90 frystyk 1310: HTBind_getAnchorBindings(anchor);
1.81 frystyk 1311: #if 0
1.84 frystyk 1312: if (HTAnchor_encoding(anchor) != HTAtom_for("7bit"))
1.81 frystyk 1313: #endif
1.60 frystyk 1314: data->type = 'I';
1315: }
1316: ctrl->state = FTP_NEED_CCON;
1317: break;
1318:
1319: case FTP_NEED_CCON:
1320: status = HTDoConnect(cnet, url, FTP_PORT);
1321: if (status == HT_OK) {
1.81 frystyk 1322: HTHost * host = HTNet_host(cnet);
1323:
1324: /*
1325: ** Check the protocol class to see if we have connected to a
1326: ** the right class of server, in this case HTTP.
1327: */
1328: {
1329: char * s_class = HTHost_class(host);
1330: if (s_class && strcasecomp(s_class, "ftp")) {
1331: HTRequest_addError(request, ERR_FATAL, NO, HTERR_CLASS,
1332: NULL, 0, "HTLoadNews");
1333: ctrl->state = FTP_ERROR;
1334: break;
1335: }
1336: HTHost_setClass(host, "ftp");
1337: }
1338:
1339: /* Check persistent connection */
1340: if (HTNet_persistent(cnet)) {
1341: ctrl->server = HTHost_version(host);
1342: if (PROT_TRACE)
1343: HTTrace("FTP Server.. Cache says type %d server\n",
1344: ctrl->server);
1345: ctrl->reset = 1;
1346: } else
1.88 frystyk 1347: HTNet_setPersistent(cnet, YES, HT_TP_SINGLE);
1.81 frystyk 1348: #if 0
1.60 frystyk 1349: char *s_class = HTDNS_serverClass(cnet->dns);
1350: if (s_class && strcasecomp(s_class, "ftp")) {
1.68 frystyk 1351: HTRequest_addError(request, ERR_FATAL, NO, HTERR_CLASS,
1352: NULL, 0, "HTLoadFTP");
1.60 frystyk 1353: ctrl->state = FTP_ERROR;
1354: break;
1355: }
1356: HTDNS_setServerClass(cnet->dns, "ftp");
1.63 frystyk 1357: if (HTDNS_socket(cnet->dns) != INVSOC) {
1358: ctrl->server = HTDNS_serverVersion(cnet->dns);
1359: if (PROT_TRACE)
1.78 eric 1360: HTTrace("FTP Server.. We know from cache that this is a type %d server\n",
1.63 frystyk 1361: ctrl->server);
1.60 frystyk 1362: ctrl->reset = 1;
1.63 frystyk 1363: } else
1.90.2.1! eric 1364: HTDNS_setSocket(cnet->dns, HTNet_socket(cnet));
1.81 frystyk 1365: #endif
1.23 frystyk 1366:
1.81 frystyk 1367: /*
1368: ** Create the stream pipe FROM the channel to the application.
1369: ** The target for the input stream pipe is set up using the
1370: ** stream stack.
1371: */
1.90.2.1! eric 1372: cnet->readStream = FTPStatus_new(request, ctrl);
1.23 frystyk 1373:
1.60 frystyk 1374: /*
1.81 frystyk 1375: ** Create the stream pipe TO the channel from the application
1376: ** and hook it up to the request object
1377: */
1378: {
1379: HTOutputStream * output = HTNet_getOutput(cnet, NULL, 0);
1380: HTRequest_setInputStream(request, (HTStream *) output);
1381: }
1382:
1383: /*
1.60 frystyk 1384: ** Set up concurrent read/write if this request isn't the
1385: ** source for a PUT or POST. As source we don't start reading
1386: ** before all destinations are ready. If destination then
1387: ** register the input stream and get ready for read
1388: */
1389: if (HTRequest_isPostWeb(request)) {
1.90.2.1! eric 1390: HTEvent_register(HTNet_socket(cnet), HTEvent_READ, &cnet->event);
1.60 frystyk 1391: HTRequest_linkDestination(request);
1392: }
1393:
1394: ctrl->state = FTP_NEED_LOGIN;
1.88 frystyk 1395: } else if (status == HT_WOULD_BLOCK || status == HT_PENDING)
1.60 frystyk 1396: return HT_OK;
1397: else
1398: ctrl->state = FTP_ERROR; /* Error or interrupt */
1399: break;
1400:
1401: case FTP_NEED_LOGIN:
1402: status = HTFTPLogin(request, cnet, ctrl);
1403: if (status == HT_WOULD_BLOCK) return HT_OK;
1404: ctrl->state = (status == HT_OK) ? FTP_NEED_DCON : FTP_ERROR;
1405: break;
1406:
1407: case FTP_NEED_DCON:
1408: status = HTFTPDataConnection(request, cnet, ctrl, data);
1409: if (status == HT_WOULD_BLOCK) return HT_OK;
1410: if (status == HT_OK)
1411: ctrl->state = (data->type=='N') ?
1412: FTP_NEED_SERVER : FTP_NEED_DATA;
1413: else
1414: ctrl->state = FTP_ERROR;
1415: break;
1416:
1417: case FTP_NEED_DATA:
1.63 frystyk 1418: status = HTFTPGetData(request, cnet, soc, ctrl, data);
1.60 frystyk 1419: if (status == HT_WOULD_BLOCK) return HT_OK;
1420: if (status == HT_LOADED)
1421: ctrl->state = FTP_SUCCESS;
1422: else if (status == HT_OK)
1423: ctrl->state = FTP_NEED_DCON;
1.76 frystyk 1424: else if (!FTP_DIR(data) && !data->stream_error) {
1.60 frystyk 1425: FTPListType(data, ctrl->server);
1426: ctrl->state = FTP_NEED_SERVER; /* Try a dir instead? */
1427: } else
1428: ctrl->state = FTP_ERROR;
1429: break;
1.23 frystyk 1430:
1.60 frystyk 1431: case FTP_NEED_SERVER:
1432: status = HTFTPServerInfo(request, cnet, ctrl, data);
1433: if (status == HT_WOULD_BLOCK) return HT_OK;
1434: ctrl->state = FTP_NEED_DATA;
1435: break;
1436:
1437: case FTP_SUCCESS:
1438: if (HTRequest_isPostWeb(request)) {
1439: BOOL main = HTRequest_isMainDestination(request);
1440: if (HTRequest_isDestination(request)) {
1441: HTLink *link =
1.86 frystyk 1442: HTLink_find((HTAnchor *) request->source->anchor,
1.84 frystyk 1443: (HTAnchor *) anchor);
1.72 frystyk 1444: HTLink_setResult(link, HT_LINK_OK);
1.23 frystyk 1445: }
1.60 frystyk 1446: HTRequest_removeDestination(request);
1447: FTPCleanup(request, main ? HT_LOADED : HT_IGNORE);
1448: } else
1449: FTPCleanup(request, HT_LOADED);
1450: return HT_OK;
1451: break;
1452:
1453: case FTP_ERROR:
1454: /* Clean up the other connections or just this one */
1455: if (HTRequest_isPostWeb(request)) {
1456: BOOL main = HTRequest_isMainDestination(request);
1457: HTRequest_killPostWeb(request);
1458: if (HTRequest_isDestination(request)) {
1459: HTLink *link =
1.86 frystyk 1460: HTLink_find((HTAnchor *) request->source->anchor,
1.84 frystyk 1461: (HTAnchor *) anchor);
1.72 frystyk 1462: HTLink_setResult(link, HT_LINK_ERROR);
1.30 luotonen 1463: }
1.60 frystyk 1464: HTRequest_removeDestination(request);
1465: FTPCleanup(request, main ? HT_ERROR : HT_IGNORE);
1466: } else
1467: FTPCleanup(request, HT_ERROR);
1468: return HT_OK;
1469: break;
1.23 frystyk 1470: }
1.60 frystyk 1471: } /* End of while(1) */
1.23 frystyk 1472: }
1.22 frystyk 1473:
Webmaster