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