Annotation of libwww/Library/src/HTFTP.c, revision 1.60
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.60 ! frystyk 156: #define MAX_STATUS_LEN 100 /* Max nb of chars to check StatusLine */
! 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