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