Annotation of libwww/Library/src/HTFTP.c, revision 1.23
1.1 timbl 1: /* File Transfer Protocol (FTP) Client
2: ** for a WorldWideWeb browser
3: ** ===================================
4: **
5: ** A cache of control connections is kept.
6: **
7: ** Note: Port allocation
8: **
9: ** It is essential that the port is allocated by the system, rather
10: ** than chosen in rotation by us (POLL_PORTS), or the following
11: ** problem occurs.
12: **
13: ** It seems that an attempt by the server to connect to a port which has
14: ** been used recently by a listen on the same socket, or by another
15: ** socket this or another process causes a hangup of (almost exactly)
16: ** one minute. Therefore, we have to use a rotating port number.
17: ** The problem remains that if the application is run twice in quick
18: ** succession, it will hang for what remains of a minute.
19: **
20: ** Authors
21: ** TBL Tim Berners-lee <timbl@info.cern.ch>
22: ** DD Denis DeLaRoca 310 825-4580 <CSP1DWD@mvs.oac.ucla.edu>
1.22 frystyk 23: ** LM Lou Montulli <montulli@ukanaix.cc.ukans.edu>
24: ** FM Foteos Macrides <macrides@sci.wfeb.edu>
1.23 ! frystyk 25: ** HF Henrik Frystyk <frystyk@dxcern.cern.ch>
! 26: **
1.1 timbl 27: ** History:
28: ** 2 May 91 Written TBL, as a part of the WorldWideWeb project.
29: ** 15 Jan 92 Bug fix: close() was used for NETCLOSE for control soc
30: ** 10 Feb 92 Retry if cached connection times out or breaks
31: ** 8 Dec 92 Bug fix 921208 TBL after DD
32: ** 17 Dec 92 Anon FTP password now just WWWuser@ suggested by DD
1.2 timbl 33: ** fails on princeton.edu!
1.22 frystyk 34: ** 27 Dec 93 (FM) Fixed up so FTP now works with VMS hosts. Path
35: ** must be Unix-style and cannot include the device
36: ** or top directory.
37: ** ?? ??? ?? (LM) Added code to prompt and send passwords for non
38: ** anonymous FTP
39: ** 25 Mar 94 (LM) Added code to recognize different ftp server types
40: ** and code to parse dates and sizes on most hosts.
41: ** 27 Mar 93 (FM) Added code for getting dates and sizes on VMS hosts.
1.23 ! frystyk 42: ** 27 Apr 94 (HF) The module is basically rewritten to conform with
! 43: ** rfc 959, 1123 and 1579 and turned into a state
! 44: ** machine. New semantics of ftp URLs are supported.
1.1 timbl 45: **
46: ** Options:
1.23 ! frystyk 47: ** LISTEN The default way to open a dats connection is by using
! 48: ** PASV, but if that fails, we try PORT. If the PORT part
! 49: ** is unwanted, it can be disabled by undefine LISTEN.
1.1 timbl 50: **
1.22 frystyk 51: ** Notes:
52: ** Portions Copyright 1994 Trustees of Dartmouth College
53: ** Code for recognizing different FTP servers and
54: ** parsing "ls -l" output taken from Macintosh Fetch
55: ** program with permission from Jim Matthews,
56: ** Dartmouth Software Development Team.
57: **
1.23 ! frystyk 58: ** BUGS: @@@ Use configuration file for user names
! 59: **
! 60: */
1.1 timbl 61:
1.22 frystyk 62: /* Implementation dependent include files */
63: #include "tcp.h"
1.1 timbl 64:
1.22 frystyk 65: /* Library include files */
1.1 timbl 66: #include "HTParse.h"
67: #include "HTUtils.h"
68: #include "HTTCP.h"
69: #include "HTAnchor.h"
1.22 frystyk 70: #include "HTFile.h"
1.6 secret 71: #include "HTBTree.h"
72: #include "HTChunk.h"
1.22 frystyk 73: #include "HTAlert.h"
1.21 frystyk 74: #include "HTDirBrw.h"
1.22 frystyk 75: #include "HTFTP.h" /* Implemented here */
76:
77: /* Macros and other defines */
1.23 ! frystyk 78: /* If LISTEN is defined, then first 'PASV' then 'PORT' (if error) is tried,
! 79: else ONLY 'PASV' is used in order to establish a data connection. */
! 80: #define LISTEN
! 81: #ifdef LISTEN
! 82: /* #define REPEAT_LISTEN */ /* Reuse the portnumber once found */
! 83: /* #define POLL_PORTS */ /* If allocation does not work, poll ourselves.*/
! 84: #endif
1.22 frystyk 85:
1.1 timbl 86: #ifndef IPPORT_FTP
1.22 frystyk 87: #define IPPORT_FTP 21
1.1 timbl 88: #endif
89:
1.22 frystyk 90: /* Globals */
1.23 ! frystyk 91: PUBLIC BOOL HTFTPUserInfo = YES;
1.22 frystyk 92:
1.23 ! frystyk 93: /* Type definitions and global variables etc. local to this module */
! 94: PRIVATE user_info *old_user; /* Only used if HT_REUSE_USER_INFO is on */
! 95: PRIVATE HTList *session; /* List of control connections in a session */
1.1 timbl 96:
97: #ifdef POLL_PORTS
1.23 ! frystyk 98: #define FIRST_TCP_PORT 1024 /* Region to try for a listening port */
! 99: #define LAST_TCP_PORT 5999
! 100: PRIVATE unsigned short port_number = FIRST_TCP_PORT;
1.1 timbl 101: #endif
102:
103: #ifdef LISTEN
1.23 ! frystyk 104: #ifdef REPEAT_LISTEN
! 105: PRIVATE int master_socket = -1; /* Listening socket = invalid */
! 106: #endif
! 107: PRIVATE char * this_addr; /* Local address */
1.1 timbl 108: #endif
109:
1.23 ! frystyk 110: /* ------------------------------------------------------------------------- */
! 111: /* ************** TEMPORARY STUFF ************* */
! 112: /* ------------------------------------------------------------------------- */
! 113: #define MAX_ACCEPT_POLL 30
! 114: #define HT_INTERRUPTED -29998
! 115: #define FCNTL(r, s, t) fcntl(r, s, t)
! 116:
! 117: PRIVATE int HTDoConnect ARGS5(char *, url, char *, protocol,
! 118: u_short, default_port, int *, s,
! 119: u_long *, addr)
! 120: {
! 121: int status;
! 122: struct sockaddr_in sock_addr;
! 123: char *p1 = HTParse(url, "", PARSE_HOST);
! 124: char *at_sign;
! 125: char *host;
! 126:
! 127: /* if theres an @ then use the stuff after it as a hostname */
! 128: if((at_sign = strchr(p1,'@')) != NULL)
! 129: host = at_sign+1;
! 130: else
! 131: host = p1;
! 132: if (TRACE) fprintf(stderr, "HTDoConnect. Looking up `%s\'\n", host);
! 133:
! 134: /* Set up defaults: */
! 135: bzero((char *) &sock_addr, sizeof(sock_addr));
! 136: sock_addr.sin_family = AF_INET;
! 137: sock_addr.sin_port = htons(default_port);
! 138:
! 139: /* Get node name */
! 140: if (HTParseInet(&sock_addr, host)) {
! 141: if (TRACE) fprintf(stderr, "HTDoConnect. Can't locate remote host `%s\'\n", host);
! 142: free (p1);
! 143: *s = -1;
! 144: return -1;
! 145: }
! 146:
! 147: if ((*s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
! 148: free (p1);
! 149: return HTInetStatus("socket");
! 150: }
! 151: if (addr)
! 152: *addr = ntohl(sock_addr.sin_addr.s_addr);
! 153:
! 154: if (TRACE)
! 155: fprintf(stderr, "HTDoConnect. Created socket number %d\n", *s);
! 156:
! 157: if ((status = connect(*s, (struct sockaddr *) &sock_addr,
! 158: sizeof(sock_addr))) < 0) {
! 159: HTInetStatus("connect");
! 160: if (NETCLOSE(*s) < 0)
! 161: HTInetStatus("close");
! 162: free(p1);
! 163: *s = -1;
! 164: return -1;
! 165: }
! 166: free(p1);
! 167: return status;
! 168: }
! 169:
! 170:
! 171: /* HTDoAccept()
! 172: **
! 173: ** This function makes a non-blocking accept on a port and polls every
! 174: ** second until MAX_ACCEPT_POLL or interrupted by user.
! 175: **
! 176: ** BUGS Interrupted is not yet implemented!!!
! 177: **
! 178: ** Returns 0 if OK, -1 on error
! 179: */
! 180: PRIVATE int HTDoAccept ARGS1(int, sockfd)
! 181: {
! 182: struct sockaddr_in soc_address;
! 183: int status;
! 184: int cnt;
! 185: int soc_addrlen = sizeof(soc_address);
! 186: if (sockfd < 0) {
! 187: if (TRACE) fprintf(stderr, "HTDoAccept.. Bad socket number\n");
! 188: return -1;
! 189: }
! 190:
! 191: /* First make the socket non-blocking */
! 192: if((status = FCNTL(sockfd, F_GETFL, 0)) != -1) {
! 193: status |= FNDELAY; /* O_NDELAY; */
! 194: status = FCNTL(sockfd, F_SETFL, status);
! 195: }
! 196: if (status == -1) {
! 197: HTInetStatus("fcntl");
! 198: return -1;
! 199: }
1.22 frystyk 200:
1.23 ! frystyk 201: /* Now poll every sekund */
! 202: for(cnt=0; cnt<MAX_ACCEPT_POLL; cnt++) {
! 203: if ((status = accept(sockfd, (struct sockaddr*) &soc_address,
! 204: &soc_addrlen)) >= 0) {
! 205: if (TRACE) fprintf(stderr,
! 206: "HTDoAccept.. Accepted new socket %d\n",
! 207: status);
! 208: return status;
! 209: } else
! 210: HTInetStatus("Accept");
! 211: sleep(1);
! 212: }
! 213:
! 214: /* If nothing has happened */
! 215: if (TRACE)
! 216: fprintf(stderr, "HTDoAccept.. Timed out, no connection!\n");
! 217: return -1;
! 218: }
1.22 frystyk 219:
220: /* ------------------------------------------------------------------------- */
221: /* Directory Specific Functions */
222: /* ------------------------------------------------------------------------- */
1.23 ! frystyk 223: /* HTParseWelcome
! 224: **
! 225: ** This function parses the welcome message stored in ctrl->welcome.
! 226: ** Only multi-line messages are considered interesting, and the starting
! 227: ** return code is removed.
! 228: **
! 229: */
! 230: PRIVATE void HTParseWelcome ARGS1(ftp_ctrl_info *, ctrl)
! 231: {
! 232: HTChunk *oldtext = ctrl->welcome;
! 233: if (!oldtext) {
! 234: if (TRACE) fprintf(stderr, "FTP......... No welcome message?\n");
! 235: return;
! 236: }
! 237: {
! 238: BOOL lastline;
! 239: char *oldp = oldtext->data+3; /* Start after first return code */
! 240: HTChunk *newtext = HTChunkCreate(128);
! 241: if (oldtext->size > 4) {
! 242: lastline = *oldp++ == '-' ? NO : YES;
! 243: while (!lastline) {
! 244: HTChunkPutc(newtext, *oldp);
! 245: if (*oldp == '\n') {
! 246: oldp += 4;
! 247: lastline = *oldp == '-' ? NO : YES;
! 248: }
! 249: ++oldp;
! 250: }
! 251: }
! 252: HTChunkTerminate(newtext);
! 253: HTChunkFree(oldtext);
! 254: ctrl->welcome = newtext;
! 255: }
! 256: }
1.22 frystyk 257:
1.23 ! frystyk 258:
! 259: #if 0 /* NOT NEEDED FOR THE MOMENT */
1.22 frystyk 260: /*
261: * is_ls_date() --
262: * Return TRUE if s points to a string of the form:
263: * "Sep 1 1990 " or
264: * "Sep 11 11:59 " or
265: * "Dec 12 1989 " or
266: * "FCv 23 1990 " ...
267: *
268: * Thanks to James.W.Matthews@Dartmouth.EDU (James W. Matthews)
269: */
270: PRIVATE BOOL is_ls_date ARGS1(char *, s)
271: {
272: /* must start with three alpha characget_striters */
273: if (!isalpha(*s++) || !isalpha(*s++) || !isalpha(*s++))
274: return FALSE;
275:
276: /* space */
277: if (*s++ != ' ')
278: return FALSE;
279:
280: /* space or digit */
281: if ((*s != ' ') && !isdigit(*s))
282: return FALSE;
283: s++;
284:
285: /* digit */
286: if (!isdigit(*s++))
287: return FALSE;
288:
289: /* space */
290: if (*s++ != ' ')
291: return FALSE;
292:
293: /* space or digit */
294: if ((*s != ' ') && !isdigit(*s))
295: return FALSE;
296: s++;
297:
298: /* digit */
299: if (!isdigit(*s++))
300: return FALSE;
301:
302: /* colon or digit */
303: if ((*s != ':') && !isdigit(*s))
304: return FALSE;
305: s++;
306:
307: /* digit */
308: if (!isdigit(*s++))
309: return FALSE;
310:
311: /* space or digit */
312: if ((*s != ' ') && !isdigit(*s))
313: return FALSE;
314: s++;
315:
316: /* space */
317: if (*s++ != ' ')
318: return FALSE;
319:
320: return TRUE;
321: } /* is_ls_date() */
1.23 ! frystyk 322: #endif
1.22 frystyk 323:
324: /* HTStrpMonth()
325: **
326: ** Returns the number of the month given or -1 on error.
327: **
328: ** BUG: Handles US dates only!!!
329: */
330: PRIVATE int HTStrpMonth ARGS1(char *, month)
331: {
332: int ret;
333: if (!strncmp(month, "JAN", 3))
334: ret = 0;
335: else if (!strncmp(month, "FEB", 3))
336: ret = 1;
337: else if (!strncmp(month, "MAR", 3))
338: ret = 2;
339: else if (!strncmp(month, "APR", 3))
340: ret = 3;
341: else if (!strncmp(month, "MAY", 3))
342: ret = 4;
343: else if (!strncmp(month, "JUN", 3))
344: ret = 5;
345: else if (!strncmp(month, "JUL", 3))
346: ret = 6;
347: else if (!strncmp(month, "AUG", 3))
348: ret = 7;
349: else if (!strncmp(month, "SEP", 3))
350: ret = 8;
351: else if (!strncmp(month, "OCT", 3))
352: ret = 9;
353: else if (!strncmp(month, "NOV", 3))
354: ret = 10;
355: else if (!strncmp(month, "DEC", 3))
356: ret = 11;
357: else {
358: ret = -1;
1.23 ! frystyk 359: if (TRACE) fprintf(stderr, "HTStrpMonth. Couldn't resolve date.\n");
1.22 frystyk 360: }
361: return ret;
362: }
363:
364:
365: /* HTStrpTime()
366: **
1.23 ! frystyk 367: ** Converts a date string from 'ls -l' to a time_t number
1.22 frystyk 368: ** This is needed in order to put out the date using the same format
369: ** for all directory listings.
370: **
1.23 ! frystyk 371: ** Returns 0 on error.
1.22 frystyk 372: */
1.23 ! frystyk 373: PRIVATE time_t HTStrpTime ARGS1(char *, datestr)
1.22 frystyk 374: {
375: struct tm *time_info; /* Points to static tm structure */
376: char *bcol = datestr; /* Column begin */
377: char *ecol; /* Column end */
378: long tval;
379: int cnt;
1.23 ! frystyk 380: time_t curtime = time(NULL);
1.22 frystyk 381: if ((time_info = gmtime(&curtime)) == NULL) {
382: if (TRACE)
1.23 ! frystyk 383: fprintf(stderr, "HTStrpTime.. Can't get current time.\n");
! 384: return (time_t) 0;
1.22 frystyk 385: }
386: time_info->tm_isdst = -1; /* Disable summer time */
387: for (cnt=0; cnt<3; cnt++) /* Month */
388: *bcol++ = toupper(*bcol);
389: if ((time_info->tm_mon = HTStrpMonth(datestr)) < 0)
1.23 ! frystyk 390: return (time_t) 0;
1.22 frystyk 391: ecol = bcol; /* Day */
392: while (*ecol++ == ' '); /* Spool to other side of day */
393: while (*ecol++ != ' ');
394: *--ecol = '\0';
395: time_info->tm_mday = atoi(bcol);
396: time_info->tm_wday = 0;
397: time_info->tm_yday = 0;
398: bcol = ++ecol; /* Year */
399: if ((ecol = strchr(bcol, ':')) == NULL) {
400: time_info->tm_year = atoi(bcol)-1900;
401: time_info->tm_sec = 0;
402: time_info->tm_min = 0;
403: time_info->tm_hour = 0;
404: } else { /* Time */
1.23 ! frystyk 405: /* If the time is given as hh:mm, then the file is less than 1 year
! 406: old, but we might shift calandar year. This is avoided by checking
! 407: if the date parsed is future or not. */
1.22 frystyk 408: *ecol = '\0';
409: time_info->tm_sec = 0;
1.23 ! frystyk 410: time_info->tm_min = atoi(++ecol); /* Right side of ':' */
1.22 frystyk 411: time_info->tm_hour = atoi(bcol); /* Left side of ':' */
1.23 ! frystyk 412: if (mktime(time_info) > curtime)
! 413: --time_info->tm_year;
1.22 frystyk 414: }
1.23 ! frystyk 415: return ((tval = mktime(time_info)) == -1 ? (time_t) 0 : tval);
1.22 frystyk 416: }
417:
418:
419: /* HTVMSStrpTime()
420: **
1.23 ! frystyk 421: ** Converts a date string from vms to a time_t number
1.22 frystyk 422: ** This is needed in order to put out the date using the same format
423: ** for all directory listings.
424: **
1.23 ! frystyk 425: ** Returns 0 on error
1.22 frystyk 426: */
1.23 ! frystyk 427: PRIVATE time_t HTVMSStrpTime ARGS1(char *, datestr)
1.22 frystyk 428: {
429: struct tm *time_info; /* Points to static tm structure */
430: char *col;
431: long tval;
1.23 ! frystyk 432: time_t curtime = time(NULL);
1.22 frystyk 433: if ((time_info = gmtime(&curtime)) == NULL)
1.23 ! frystyk 434: return (time_t) 0;
1.22 frystyk 435: time_info->tm_isdst = -1; /* Disable summer time */
436: if ((col = strtok(datestr, "-")) == NULL)
1.23 ! frystyk 437: return (time_t) 0;
1.22 frystyk 438: time_info->tm_mday = atoi(col); /* Day */
439: time_info->tm_wday = 0;
440: time_info->tm_yday = 0;
441: if ((col = strtok(NULL, "-")) == NULL ||
442: (time_info->tm_mon = HTStrpMonth(col)) < 0)
1.23 ! frystyk 443: return (time_t) 0;
1.22 frystyk 444: if ((col = strtok(NULL, " ")) == NULL) /* Year */
1.23 ! frystyk 445: return (time_t) 0;
1.22 frystyk 446: time_info->tm_year = atoi(col)-1900;
447: if ((col = strtok(NULL, ":")) == NULL) /* Hour */
1.23 ! frystyk 448: return (time_t) 0;
1.22 frystyk 449: time_info->tm_hour = atoi(col);
450: if ((col = strtok(NULL, " ")) == NULL) /* Mins */
1.23 ! frystyk 451: return (time_t) 0;
1.22 frystyk 452: time_info->tm_min = atoi(col);
453: time_info->tm_sec = 0;
1.23 ! frystyk 454: return ((tval = mktime(time_info)) < 0 ? (time_t) 0 : tval);
1.22 frystyk 455: }
456:
457:
458: /* HTFTPFilePerm()
459: **
460: ** Converts the file type from 'ls -l' into a long. The reason for
461: ** doing this is to be able to handle the file type and icon selection
462: ** similar to the Unix way used in HTBrowseDirectory().
463: **
464: */
465: PRIVATE long HTFTPFilePerm ARGS1(char *, permission)
466: {
467: char *strptr = permission;
468: long mode = 0L;
469:
470: /* Special files etc. are all handled like regular files */
471: switch (*strptr++) { /* File type */
472: case 'd': mode = S_IFMT & S_IFDIR; break;
473: case 'l': mode = S_IFMT & S_IFLNK; break;
474: default: mode = S_IFMT & S_IFREG; break;
475: }
476: if (*strptr++ == 'r') mode |= S_IRUSR; /* User */
477: if (*strptr++ == 'w') mode |= S_IWUSR;
478: if (*strptr == 'x')
479: mode |= S_IXUSR;
480: else if (*strptr == 's')
481: mode |= (S_IXUSR | S_ISUID);
482: else if (*strptr == 'S')
483: mode |= S_ISUID;
484: strptr++;
485: if (*strptr++ == 'r') mode |= S_IRGRP; /* Group */
486: if (*strptr++ == 'w') mode |= S_IWGRP;
487: if (*strptr == 'x')
488: mode |= S_IXGRP;
489: else if (*strptr == 's')
490: mode |= (S_IXGRP | S_ISGID);
491: else if (*strptr == 'S')
492: mode |= S_ISGID;
493: strptr++;
494: if (*strptr++ == 'r') mode |= S_IROTH; /* Other */
495: if (*strptr++ == 'w') mode |= S_IWOTH;
496: if (*strptr == 'x')
497: mode |= S_IXOTH;
498: else if (*strptr == 't')
499: mode |= (S_IXOTH | S_ISVTX);
500: else if (*strptr == 'T')
501: mode |= S_ISVTX;
502: strptr++;
503: return mode;
504: }
505:
506:
507: /* parse_unix_line()
508: **
509: ** Extract the name, size, and date from an 'ls'. The function expects
510: ** the following format of the ls-line:
511: **
512: ** <permission> <nlink> <owner> [<group>] <size> <date> <filename>
513: **
514: ** The group is not always present and is therefore optional. Both owner
515: ** and group can be numbers.
516: **
517: ** Returns YES if OK, NO on error
518: */
519: PRIVATE BOOL parse_unix_line ARGS2(char *,line, dir_file_info *,f_info)
520: {
521: char *column;
522: char *strptr;
523: int ival;
524: unsigned long lval;
525: if (!line || !*line || !f_info)
526: return NO;
527:
528: if ((column = strtok(line, " ")) == NULL) /* Permissions */
529: return NO;
530: f_info->f_mode = HTFTPFilePerm(column);
531: if ((column = strtok(NULL, " ")) == NULL) /* Links */
532: return NO;
533: if (sscanf(column, "%d", &ival) == 1)
534: f_info->f_nlink = ival;
535: if ((column = strtok(NULL, " ")) == NULL) /* Owner */
536: return NO;
537: StrAllocCopy(f_info->f_uid, column);
538: if ((column = strtok(NULL, " ")) == NULL) /* Group and/or Size */
539: return NO;
540: if (sscanf(column, "%lu", &lval) != 1) {
541: StrAllocCopy(f_info->f_gid, column);
542: if ((column = strtok(NULL, " ")) == NULL)
543: return NO;
544: if (sscanf(column, "%lu", &lval) == 1)
545: f_info->f_size = lval;
546: } else { /* Group can be a number! */
547: strptr = column+strlen(column)+1;
548: while (*strptr++ == ' ');
549: if (isdigit(*--strptr)) { /* Month can't start with a digit */
550: StrAllocCopy(f_info->f_gid, column);
551: if ((column = strtok(NULL, " ")) == NULL)
552: return NO;
553: if (sscanf(column, "%lu", &lval) == 1)
554: f_info->f_size = lval;
555: } else {
1.23 ! frystyk 556: StrAllocCopy(f_info->f_gid, "");
1.22 frystyk 557: f_info->f_size = lval;
558: }
559: }
560: column = column+strlen(column)+1;
561: while (*column++ == ' ');
562: strptr = --column+12; /* Find the date column */
563: *strptr++ = '\0';
1.23 ! frystyk 564: if ((f_info->f_mtime = HTStrpTime(column)) == (time_t) 0)
1.22 frystyk 565: return NO;
566: while (*strptr++ == ' '); /* Spool to filename */
567: if ((f_info->f_mode & S_IFMT) == S_IFLNK) { /* Strip any '->' */
568: char *link = strstr(strptr-1, " -> ");
569: if (link)
570: *link = '\0';
571: }
572: StrAllocCopy(f_info->f_name, --strptr);
573: return YES; /* We have a full structure! */
574: }
575:
576:
577: /* parse_vms_line()
578: **
579: ** Format the name, date, and size from a VMS LIST line
580: ** into the dir_file_info structure
581: **
1.23 ! frystyk 582: ** BUGS: Group, user and permissions are not parsed!
! 583: **
1.22 frystyk 584: ** Returns YES if OK, NO on error
585: */
586: PRIVATE BOOL parse_vms_line ARGS2(char *, line, dir_file_info *, f_info)
587: {
588: int i, j, ialloc;
589: char *cp, *cpd, *cps, *cdir, *sp = " ";
590:
591: /** Get rid of information lines by making them blank too **/
592: /** Valid lines have the semi-colon version number token **/
593: if (!line || !*line || !f_info || (cp = strchr(line, ';')) == NULL) {
594: return NO;
595: }
596:
597: /** Cut out file or directory name at VMS version number **/
598: *cp++ ='\0';
599: StrAllocCopy(f_info->f_name,line);
600:
601: /** Cast VMS file and directory names to lowercase **/
602: for (i=0; f_info->f_name[i]; i++)
603: f_info->f_name[i] = tolower(f_info->f_name[i]);
604:
605: /** Uppercase terminal .z's or _z's **/
606: if ((--i > 2) && f_info->f_name[i] == 'z' &&
607: (f_info->f_name[i-1] == '.' || f_info->f_name[i-1] == '_'))
608: f_info->f_name[i] = 'Z';
609:
610: /* Trim off VMS directory extensions */
611: if ((cdir = strstr(f_info->f_name, ".dir")) != NULL) { /* Strip any .dir */
612: f_info->f_mode = S_IFMT & S_IFDIR;
613: *cdir = '\0';
614: } else
615: f_info->f_mode = S_IFMT & S_IFREG;
616:
617: /** Convert any tabs in rest of line to spaces **/
618: cps = cp-1;
619: while ((cps=strchr(cps+1, '\t')) != NULL)
620: *cps = ' ';
621:
622: /** Collapse serial spaces **/
623: i = 0; j = 1;
624: cps = cp;
625: while (cps[j] != '\0') {
626: if (cps[i] == ' ' && cps[j] == ' ')
627: j++;
628: else
629: cps[++i] = cps[j++];
630: }
631: cps[++i] = '\0';
632:
633: /** Track down the date **/
634: if ((cpd=strchr(cp, '-')) != NULL) {
635: if (strlen(cpd) > 9 && isdigit(*(cpd-1)) &&
636: isalpha(*(cpd+1)) && *(cpd+4) == '-') {
1.23 ! frystyk 637: if ((f_info->f_mtime = HTVMSStrpTime(cpd-2)) == (time_t) 0)
1.22 frystyk 638: return NO;
639: }
640: }
641:
642: /** Track down the size **/
643: if ((cpd = strchr(cp, '/')) != NULL) {
644: /* Appears be in used/allocated format */
645: cps = cpd;
646: while (isdigit(*(cps-1)))
647: cps--;
648: if (cps < cpd)
649: *cpd = '\0';
650: f_info->f_size = atoi(cps);
651: cps = cpd+1;
652: while (isdigit(*cps))
653: cps++;
654: *cps = '\0';
655: ialloc = atoi(cpd+1);
656: /* Check if used is in blocks or bytes */
657: if (f_info->f_size <= ialloc)
658: f_info->f_size *= 512;
659: }
660: else if ((cps=strtok(cp, sp)) != NULL) {
661: /* We just initialized on the version number */
662: /* Now let's hunt for a lone, size number */
663: while ((cps=strtok(NULL, sp)) != NULL) {
664: cpd = cps;
665: while (isdigit(*cpd))
666: cpd++;
667: if (*cpd == '\0') {
668: /* Assume it's blocks */
669: f_info->f_size = atoi(cps) * 512;
670: break;
671: }
672: }
673: }
674: return YES; /* We have a full structure! */
675: }
676:
677:
678: /* parse_dir_entry()
679: **
680: ** Given a line of LIST/NLST output in entry, return results
681: ** and a file/dir name in f_info struct
682: **
1.23 ! frystyk 683: ** If first_entry is true, this is the first name in a directory.
1.22 frystyk 684: ** Returns YES if OK, NO on error
685: */
1.23 ! frystyk 686: PRIVATE BOOL parse_dir_entry ARGS4(ftp_data_info *, data, char *, entry,
! 687: BOOL, first_entry, dir_file_info *, f_info)
1.22 frystyk 688: {
689: BOOL status = YES;
1.23 ! frystyk 690: switch (data->ctrl->server) {
1.22 frystyk 691: case UNIX_SERVER:
692: case PETER_LEWIS_SERVER:
693: case MACHTEN_SERVER:
694: /* Interpret and edit LIST output from Unix server */
1.23 ! frystyk 695: if (first_entry) {
! 696: if (data->ctrl->unsure_type == YES &&
! 697: strncmp(entry, "total ", 6) &&
! 698: (strstr(entry, "not available") != NULL)) {
1.22 frystyk 699: /* this isn't really a unix server! */
1.23 ! frystyk 700: if (TRACE)
! 701: fprintf(stderr, "FTP......... No, this isn't a UNIX server anyway :-(\n");
! 702: data->ctrl->server = GENERIC_SERVER;
1.22 frystyk 703: }
1.23 ! frystyk 704: /* We might as well say that it is not unsure any more, as we
! 705: can't (or don't) do anything about it, */
! 706: data->ctrl->unsure_type = NO;
! 707: status = NO;
! 708: } else
! 709: status = parse_unix_line(entry, f_info);
1.22 frystyk 710: break;
711:
712: case VMS_SERVER:
713: /* Interpret and edit LIST output from VMS server */
714: /* and convert information lines to zero length. */
715: status = parse_vms_line(entry, f_info);
716: break;
717:
718: case CMS_SERVER:
719: /* Can't be directory... "entry" already equals the correct f_name */
720: StrAllocCopy(f_info->f_name, entry);
721: f_info->f_mode = S_IFMT & S_IFREG;
722: break;
723:
724: case NCSA_SERVER:
725: case TCPC_SERVER:
726: /* Directories identified by trailing "/" characters */
727: StrAllocCopy(f_info->f_name, entry);
728: {
729: int len = strlen(entry);
730: if (*(entry+len-1) == '/') {
731: *(entry+len-1) = '\0';
732: f_info->f_mode = S_IFMT & S_IFDIR;
733: } else {
734: f_info->f_mode = S_IFMT & S_IFREG;
735: }
736: }
737: break;
738:
739: default:
740: /* We cant tell if it is a directory since we only did an NLST :-( */
741: StrAllocCopy(f_info->f_name, entry);
742: f_info->f_mode = S_IFMT & S_IFREG;
743: break;
744: }
745: return status;
746: }
1.1 timbl 747:
748:
1.23 ! frystyk 749: PRIVATE int HTFTP_get_dir_string ARGS2(ftp_data_info *, data,
! 750: dir_file_info *, f_info)
1.21 frystyk 751: {
1.23 ! frystyk 752: int status = 1;
1.22 frystyk 753: int ch; /* Must be int in order to contain EOF */
1.23 ! frystyk 754: BOOL got_line = NO;
1.22 frystyk 755: static BOOL first = YES; /* Is it the first time through? */
1.23 ! frystyk 756: static HTInputSocket *isoc;
1.22 frystyk 757: HTChunk *chunk = HTChunkCreate(128);
758:
1.23 ! frystyk 759: if (first == YES)
! 760: isoc = HTInputSocket_new(data->socket); /* Set up buffering */
! 761: do { /* Until we have a nice line */
! 762: while ((ch = HTInputSocket_getCharacter(isoc)) >= 0) {
! 763: if (ch == CR || ch == LF) { /* Terminator? */
! 764: if (chunk->size != 0) { /* got some text */
! 765: if (data->ctrl->server == VMS_SERVER) {
! 766: /* Deal with MultiNet's wrapping of long lines - F.M.*/
! 767: if (isdigit(*(chunk->data+chunk->size-1)))
! 768: continue;
! 769: }
! 770: HTChunkTerminate(chunk);
! 771: if (parse_dir_entry(data, chunk->data, first, f_info)) {
! 772: got_line = YES;
! 773: break;
! 774: } else {
! 775: HTChunkClear(chunk);
1.22 frystyk 776: }
777: first = NO;
778: }
1.23 ! frystyk 779: } else
! 780: HTChunkPutc(chunk, ch);
1.21 frystyk 781: }
1.23 ! frystyk 782: } while (got_line == NO && ch >= 0);
! 783: if (ch < 0) {
! 784: first = YES;
! 785: status = (ch == EOF) ? 0 : ch;
! 786: }
! 787: if (first) {
! 788: HTInputSocket_free(isoc);
! 789: isoc = NULL;
! 790: }
1.22 frystyk 791: HTChunkFree(chunk);
1.21 frystyk 792: return status;
793: }
794:
1.22 frystyk 795: /* ------------------------------------------------------------------------- */
1.23 ! frystyk 796: /* FTP Client Functions for managing control and data connections */
1.22 frystyk 797: /* ------------------------------------------------------------------------- */
798:
1.23 ! frystyk 799: /* HTFTP_send_cmd
1.22 frystyk 800: **
1.23 ! frystyk 801: ** This function sends a command through the control connection
! 802: ** specified. The Telnet terminating end of line code <CRLF> is
! 803: ** appended automaticly. The parameter argument is ignored if NULL.
1.1 timbl 804: **
1.23 ! frystyk 805: ** Returns 0 on OK, else -1 but does NOT close the connection
1.1 timbl 806: */
1.23 ! frystyk 807: PRIVATE int HTFTP_send_cmd ARGS3(ftp_ctrl_info *, ctrl_info, char *, cmd,
! 808: char *, pars)
1.1 timbl 809: {
1.23 ! frystyk 810: char *command;
! 811: if (!ctrl_info && ctrl_info->socket < 0) {
! 812: if (TRACE)
! 813: fprintf(stderr, "HTFTP_send_cmd: Invalid socket\n");
! 814: return -1;
! 815: }
! 816: if ((command = (char *) malloc(strlen(cmd) + (pars ? strlen(pars)+1 : 0) +
! 817: 2 + 1)) == NULL)
! 818: outofmem(__FILE__, "HTFTP_send_cmd");
! 819: if (pars && *pars)
! 820: sprintf(command, "%s %s%c%c", cmd, pars, CR, LF);
! 821: else
! 822: sprintf(command, "%s%c%c", cmd, CR, LF);
! 823: if (TRACE) {
! 824: if (!strcasecomp(cmd, "pass"))
! 825: fprintf(stderr, "FTP Tx...... PASS ********\n");
1.22 frystyk 826: else
1.23 ! frystyk 827: fprintf(stderr, "FTP Tx...... %s", command);
! 828: }
! 829: #ifdef NOT_ASCII
! 830: {
! 831: char *sp;
! 832: for (sp = command; *sp; sp++) {
! 833: *sp = TOASCII(*sp);
! 834: }
! 835: }
! 836: #endif
! 837: {
! 838: int status = NETWRITE(ctrl_info->socket, command,
! 839: (int) strlen(command));
! 840: if (status < 0) {
! 841: if (TRACE)
! 842: fprintf(stderr, "FTP......... Error %d sending command: %s\n",
! 843: status, command);
! 844: free(command);
! 845: return -1;
! 846: }
! 847: free(command);
! 848: return 0;
! 849: }
! 850: }
1.1 timbl 851:
852:
1.23 ! frystyk 853: /* HTFTP_get_response
1.8 timbl 854: **
1.23 ! frystyk 855: ** This function gets the response from the net to the control connection
! 856: ** specified. If text is not NULL, the response text is put into this
! 857: ** chunk. In case of OK, the freeing is then left to the callee. The
! 858: ** response is read until a <LF> is encountered, since not all servers
! 859: ** use a full telnet <CRLF> code.
1.1 timbl 860: **
1.23 ! frystyk 861: ** Returns the 3 digit return code on OK, else -1 but does NOT close
! 862: ** the control connection.
1.1 timbl 863: */
1.23 ! frystyk 864: PRIVATE int HTFTP_get_response ARGS2(ftp_ctrl_info *, ctrl_info,
! 865: HTChunk **, text)
1.1 timbl 866: {
1.23 ! frystyk 867: int result; /* Three-digit decimal code */
! 868: int offset = 0; /* Offset for each newline in response */
! 869: BOOL first_line = YES;
! 870: int ch;
! 871: HTChunk *chunk = HTChunkCreate(128);
! 872:
! 873: if (!ctrl_info && ctrl_info->socket < 0) {
! 874: if (TRACE)
! 875: fprintf(stderr, "HTFTP_get_response: Invalid socket\n");
! 876: return -1;
1.1 timbl 877: }
878:
1.23 ! frystyk 879: /* Read response */
! 880: while ((ch = HTInputSocket_getCharacter(ctrl_info->isoc)) >= 0) {
! 881: if (ch == LF) {
! 882: int tmpres;
! 883: char cont;
! 884: if (first_line == YES) {
! 885: if (sscanf(chunk->data, "%d%c", &result, &cont) < 2) {
! 886: HTChunkFree(chunk);
! 887: if (TRACE)
! 888: fprintf(stderr,
! 889: "FTP Rx...... `%s\' - no code found?\n",
! 890: chunk->data);
! 891: return -1;
! 892: }
! 893: if (cont == '-') {
! 894: HTChunkPutc(chunk, '\n');
! 895: offset = chunk->size; /* Remember offset */
! 896: first_line = NO;
! 897: } else {
! 898: HTChunkTerminate(chunk);
! 899: break;
! 900: }
! 901: } else {
! 902: if (sscanf(chunk->data+offset, "%d%c", &tmpres, &cont) == 2 &&
! 903: tmpres == result && cont == ' ') {
! 904: HTChunkTerminate(chunk);
! 905: break;
! 906: } else {
! 907: HTChunkPutc(chunk, '\n');
! 908: offset = chunk->size; /* Update offset */
! 909: }
1.1 timbl 910: }
1.23 ! frystyk 911: } else
! 912: HTChunkPutc(chunk, (char) ch);
1.1 timbl 913: }
1.23 ! frystyk 914: if (!chunk->size || ch < 0) { /* No response read? */
! 915: HTChunkFree(chunk);
! 916: if (TRACE) fprintf(stderr, "FTP Rx...... No response?\n");
1.1 timbl 917: return -1;
918: }
1.23 ! frystyk 919: if (TRACE) fprintf(stderr, "FTP Rx...... %s\n", chunk->data);
! 920: if (!text) /* Response text not wanted so we free the chunk */
! 921: HTChunkFree(chunk);
! 922: else {
! 923: if (*text) /* Free old value, if any */
! 924: HTChunkFree(*text);
! 925: *text = chunk;
! 926: }
! 927: return result;
1.1 timbl 928: }
929:
930:
1.23 ! frystyk 931:
! 932: /* HTFTP_close_data_con
! 933: ** Closes the data connection and frees memory
! 934: ** Returns 0 if OK, -1 on error
1.22 frystyk 935: */
1.23 ! frystyk 936: PRIVATE int HTFTP_close_data_con ARGS1(ftp_data_info *, data)
1.22 frystyk 937: {
1.23 ! frystyk 938: int status = 0;
! 939: if (data) {
! 940: if (data->socket >= 0) {
! 941: if (TRACE)
! 942: fprintf(stderr, "FTP......... Closing data socket %d\n",
! 943: data->socket);
! 944: if ((status = NETCLOSE(data->socket)) < 0)
! 945: HTInetStatus("close data socket");
! 946: #ifdef REPEAT_LISTEN
! 947: if (master_socket == data->socket)
! 948: master_socket = -1;
! 949: #endif
! 950: }
! 951: FREE(data->datatype);
! 952: free(data);
! 953: } else {
! 954: if (TRACE) fprintf(stderr, "HTFTP_close_data_con: bad argument!");
! 955: status = -1;
1.22 frystyk 956: }
957: return status;
958: }
959:
960:
1.23 ! frystyk 961: /* HTFTP_close_ctrl_con
! 962: ** Only if the control connection has no data connection(s) pending
! 963: ** then it can be closed and the memory freed.
! 964: ** Returns 0 if OK, -1 on error
1.1 timbl 965: */
1.23 ! frystyk 966: PRIVATE int HTFTP_close_ctrl_con ARGS1(ftp_ctrl_info *, ctrl)
1.1 timbl 967: {
1.23 ! frystyk 968: int status = 0;
! 969: if (ctrl && (!ctrl->data_cons ||
! 970: (ctrl->data_cons && !HTList_count(ctrl->data_cons)))) {
! 971: if (TRACE)
! 972: fprintf(stderr,
! 973: "FTP......... Closing control socket %d\n", ctrl->socket);
! 974: if (ctrl->socket >= 0) {
! 975: if ((status = NETCLOSE(ctrl->socket)) < 0)
! 976: HTInetStatus("close control socket");
! 977: }
! 978: if (ctrl->isoc)
! 979: HTInputSocket_free(ctrl->isoc);
! 980: FREE(ctrl->location);
! 981: if (ctrl->user) {
! 982: FREE(ctrl->user->domain);
! 983: FREE(ctrl->user->id);
! 984: FREE(ctrl->user->passwd);
! 985: free(ctrl->user);
! 986: }
! 987: if (ctrl->welcome)
! 988: HTChunkFree(ctrl->welcome);
! 989: HTList_delete(ctrl->data_cons);
! 990: free(ctrl);
! 991: }
! 992: return status;
! 993: }
1.22 frystyk 994:
1.1 timbl 995:
1.23 ! frystyk 996: /* HTFTP_abort_ctrl_con
! 997: ** Closes the control connection without looking if any data connections
! 998: ** are pending => they are all removed, so be careful!
! 999: ** Returns 0 if OK, -1 on error
! 1000: */
! 1001: PRIVATE int HTFTP_abort_ctrl_con ARGS1(ftp_ctrl_info *, ctrl)
! 1002: {
! 1003: int status = 0;
! 1004: if (!ctrl) {
! 1005: if (TRACE)
! 1006: fprintf(stderr, "HTFTP_abort_ctrl_con called with bad argument\n");
! 1007: return -1;
! 1008: }
! 1009: if (TRACE) fprintf(stderr, "FTP......... Aborting control socket %d\n",
! 1010: ctrl->socket);
1.1 timbl 1011:
1.23 ! frystyk 1012: /* Close any pending data connections */
! 1013: if (ctrl->data_cons && HTList_count(ctrl->data_cons)) {
! 1014: HTList *cur = ctrl->data_cons;
! 1015: ftp_data_info *pres;
! 1016: while ((pres = (ftp_data_info *) HTList_nextObject(cur))) {
! 1017: HTFTP_close_data_con(pres);
! 1018: }
! 1019: HTList_delete(ctrl->data_cons);
! 1020: ctrl->data_cons = NULL;
! 1021: }
1.1 timbl 1022:
1.23 ! frystyk 1023: /* If a session is going on, the control connections are closed later */
! 1024: if (!session) {
! 1025: if (ctrl->socket >= 0) {
! 1026: if ((status = NETCLOSE(ctrl->socket)) < 0)
! 1027: HTInetStatus("abot control socket");
! 1028: }
! 1029: if (ctrl->isoc)
! 1030: HTInputSocket_free(ctrl->isoc);
! 1031: FREE(ctrl->location);
! 1032: if (ctrl->user) {
! 1033: FREE(ctrl->user->domain);
! 1034: FREE(ctrl->user->id);
! 1035: FREE(ctrl->user->passwd);
! 1036: free(ctrl->user);
! 1037: }
! 1038: if (ctrl->welcome)
! 1039: HTChunkFree(ctrl->welcome);
! 1040: free(ctrl);
1.1 timbl 1041: }
1.23 ! frystyk 1042: return status;
! 1043: }
1.1 timbl 1044:
1045:
1.23 ! frystyk 1046: /* HTFTP_parse_login
! 1047: **
! 1048: ** Scan 'login' part of URL for portnumber, uid and passwd. The
! 1049: ** expected format is [user[:password]@]host[:port]. The 'domain' field
! 1050: ** in the user structure is always filled out together with the
! 1051: ** serv_port. The rest is optional.
! 1052: **
! 1053: ** Returns YES if anything BUT the domain and serv_port is specified,
! 1054: ** else NO
1.1 timbl 1055: */
1.23 ! frystyk 1056: PRIVATE BOOL HTFTP_parse_login ARGS3(char *, url, user_info *, user,
! 1057: u_short *, serv_port)
! 1058: {
! 1059: BOOL status = NO;
! 1060: char *login = HTParse(url, "", PARSE_HOST);
! 1061: char *host = strrchr(login, '@');
! 1062:
! 1063: if (host) { /* Uid and/or passwd specified */
! 1064: char *uid = login;
! 1065: char *passwd;
! 1066: FREE(user->id); /* Skip old values */
! 1067: FREE(user->passwd);
! 1068: *host++ = '\0';
! 1069: if ((passwd = strrchr(uid, ':')) != NULL) { /* Passwd specified */
! 1070: *passwd++ = '\0';
! 1071: if (passwd-1 > uid) { /* Passwd AND uid specified */
! 1072: StrAllocCopy(user->passwd, passwd);
! 1073: }
! 1074: }
! 1075: StrAllocCopy(user->id, uid);
! 1076: status = YES;
1.22 frystyk 1077: } else {
1.23 ! frystyk 1078: host = login;
1.22 frystyk 1079: }
1.23 ! frystyk 1080: {
! 1081: char *portstr;
! 1082: if ((portstr = strrchr(host, ':')) != NULL) { /* Port specified */
! 1083: char *endp = NULL;
! 1084: *portstr++ = '\0';
! 1085: *serv_port = (u_short) strtol(portstr, &endp, 10);
! 1086: if (endp && *endp) /* If portstr is not good, use default port */
! 1087: *serv_port = (u_short) IPPORT_FTP;
! 1088: }
1.22 frystyk 1089: }
1.23 ! frystyk 1090: StrAllocCopy(user->domain, host); /* This is what's left */
! 1091: free(login);
! 1092: return status;
1.22 frystyk 1093: }
1.1 timbl 1094:
1095:
1.23 ! frystyk 1096: /* HTFTP_parse_datatype
1.1 timbl 1097: **
1.23 ! frystyk 1098: ** Scan 'ftptype' part of URL for the data type with parameters and
! 1099: ** returns the result in accordance with the FTP standard. If nothing is
! 1100: ** found then datatype = NULL.
1.1 timbl 1101: **
1.23 ! frystyk 1102: ** Returns YES if type is found, else NO
1.1 timbl 1103: */
1.23 ! frystyk 1104: PRIVATE BOOL HTFTP_parse_datatype ARGS2(char *, url, char **, datatype)
1.1 timbl 1105: {
1.23 ! frystyk 1106: BOOL retour = NO;
! 1107: char *path = HTParse(url, "", PARSE_PATH);
! 1108: char *type = strrchr(path, ';');
! 1109: char dtype[6];
! 1110: char *tptr = dtype;
! 1111:
! 1112: if (type && !strncasecomp(++type, "type=", 5)) { /* type specified */
! 1113: *tptr++ = toupper(*(type+5)); /* Look at the type-code */
! 1114: if (*dtype == 'L') { /* We must look for a byte_size */
! 1115: int cnt;
! 1116: *tptr++ = ' ';
! 1117: for (cnt=0; cnt<3 && *(type+6+cnt); cnt++) /* Max 3 digits */
! 1118: *tptr++ = *(type+6+cnt);
! 1119: } else if (*dtype == 'A' || *dtype == 'E') {
! 1120: *tptr++ = ' ';
! 1121: *tptr++ = toupper(*(type+6)); /* Get form-code */
! 1122: }
! 1123: *tptr = '\0';
! 1124: StrAllocCopy(*datatype, dtype);
! 1125: if (TRACE)
! 1126: fprintf(stderr, "FTP......... Datatype found: `%s\'\n", *datatype);
! 1127: retour = YES;
! 1128: }
! 1129: free(path);
! 1130: return retour;
1.1 timbl 1131: }
1132:
1133:
1.23 ! frystyk 1134: /* HTFTP_init_con
1.1 timbl 1135: **
1.23 ! frystyk 1136: ** This function returns a control connection structure linked with a
! 1137: ** data connection structure. The control connection might already be
! 1138: ** open if HTFTPReuseCtrlCon == YES, but that is indicated in the state
! 1139: ** variable. ctrl->user->domain is always filled out but id and passwd
! 1140: ** are optional.
! 1141: ** If error, NULL is returned.
1.1 timbl 1142: */
1.23 ! frystyk 1143: PRIVATE ftp_ctrl_info *HTFTP_init_con ARGS1(char *, url)
1.1 timbl 1144: {
1.23 ! frystyk 1145: int status;
! 1146: BOOL use_url = NO; /* YES if uid and passwd are specified in the URL */
! 1147: u_short serv_port = IPPORT_FTP; /* Might be changed from URL */
1.1 timbl 1148:
1.23 ! frystyk 1149: ftp_ctrl_info *ctrl;
! 1150: ftp_data_info *data;
! 1151: user_info user; /* Contains userid, passwd etc. from URL */
! 1152:
! 1153: if (!url || !*url) {
! 1154: if (TRACE)
! 1155: fprintf(stderr, "HTFTP_get_connection: Bad server address!\n");
! 1156: return NULL;
! 1157: }
! 1158:
! 1159: /* Initiate new data connection structure */
! 1160: if ((data = (ftp_data_info *) calloc(1, sizeof(ftp_data_info))) == NULL)
! 1161: outofmem(__FILE__, "HTFTP_get_ctrl_con");
! 1162: data->socket = -1; /* Illigal socket number */
! 1163: data->active = YES; /* We do the active open */
! 1164:
! 1165: /* Scan URL for uid, pw and portnumber */
! 1166: memset(&user, '\0', sizeof(user_info));
! 1167: use_url = HTFTP_parse_login(url, &user, &serv_port);
! 1168:
! 1169: {
! 1170: char *filename = HTParse(url, "", PARSE_PATH);
! 1171: char *strptr;
! 1172:
! 1173: /* Check if file name is a directory */
! 1174: if (!*(strptr = filename) ||
! 1175: *(strptr = filename+strlen(filename)-1) == '/') {
! 1176: data->directory = YES;
! 1177: } else {
! 1178: /* If data type is not specified in URL let's find it ourselves. */
! 1179: HTAtom *encoding;
! 1180: HTAtom *language;
! 1181: HTUnEscape(filename);
! 1182: data->fileformat = HTFileFormat(filename, &encoding, &language);
! 1183: if (HTFTP_parse_datatype(filename, &data->datatype) != YES) {
! 1184: if ((encoding != HTAtom_for("8bit") &&
! 1185: encoding != HTAtom_for("7bit"))) {
! 1186: if (TRACE)
! 1187: fprintf(stderr, "FTP......... File is binary\n");
! 1188: StrAllocCopy(data->datatype, "I");
! 1189: }
! 1190: } else {
! 1191: /* Chop off data type */
! 1192: strptr = strrchr(filename, ';');
! 1193: *strptr = '\0';
! 1194: }
! 1195: }
! 1196: FREE(filename);
! 1197: }
! 1198:
! 1199: /* Look if control connection already exists else generate new one */
! 1200: if (session) {
! 1201: BOOL found = NO;
! 1202: HTList *cur = session;
! 1203: struct sockaddr_in sock_addr;
! 1204: char *host;
! 1205:
! 1206: /* if theres an @ then use the stuff after it as a hostname */
! 1207: {
! 1208: char *fullhost = HTParse(url, "", PARSE_HOST);
! 1209: char *at_sign;
! 1210: if((at_sign = strchr(fullhost, '@')) != NULL)
! 1211: host = at_sign+1;
! 1212: else
! 1213: host = fullhost;
! 1214: }
! 1215:
! 1216: /* Set up defaults: */
! 1217: bzero((char *) &sock_addr, sizeof(sock_addr));
! 1218: sock_addr.sin_family = AF_INET;
! 1219: sock_addr.sin_port = htons(serv_port);
! 1220:
! 1221: /* Get node name */
! 1222: if (HTParseInet(&sock_addr, host)) {
! 1223: if (TRACE) fprintf(stderr,
! 1224: "FTP......... Can't locate remote host `%s\'\n", host);
! 1225: FREE(user.domain);
! 1226: FREE(user.id);
! 1227: FREE(user.passwd);
! 1228: free(host);
! 1229: return NULL;
! 1230: }
! 1231: {
! 1232: /* Check if host, port and user info is the same */
! 1233: u_long new_node = ntohl(sock_addr.sin_addr.s_addr);
! 1234: while ((ctrl = (ftp_ctrl_info *) HTList_nextObject(cur))) {
! 1235: if (new_node==ctrl->serv_node && serv_port==ctrl->serv_port) {
! 1236: if ((user.id && strcmp(user.id, ctrl->user->id)) ||
! 1237: (user.passwd && strcmp(user.id, ctrl->user->passwd))) {
! 1238: found = NO;
! 1239: } else {
! 1240: found = YES;
! 1241: break;
! 1242: }
! 1243: }
! 1244: }
! 1245: }
! 1246: FREE(host);
! 1247: if (found) {
! 1248: if (TRACE) fprintf(stderr,
! 1249: "FTP......... Already have connection for %d.%d.%d.%d. on port %d, socket %d at location `%s\'\n",
! 1250: (int)*((unsigned char *)(&ctrl->serv_node)+0),
! 1251: (int)*((unsigned char *)(&ctrl->serv_node)+1),
! 1252: (int)*((unsigned char *)(&ctrl->serv_node)+2),
! 1253: (int)*((unsigned char *)(&ctrl->serv_node)+3),
! 1254: ctrl->serv_port,
! 1255: ctrl->socket,
! 1256: ctrl->location);
! 1257: data->ctrl = ctrl; /* Link them together */
! 1258: HTList_addObject(ctrl->data_cons, (void *) data); /* Add to list */
! 1259: FREE(user.domain);
! 1260: FREE(user.id);
! 1261: FREE(user.passwd);
! 1262: return ctrl; /* This should return the found structure */
! 1263: } else {
! 1264: if (TRACE)
! 1265: fprintf(stderr, "FTP......... No existing connection found, so I build a new\n");
! 1266: }
! 1267: }
1.1 timbl 1268:
1.23 ! frystyk 1269: /* Set up data structure for control connection */
! 1270: if ((ctrl = (ftp_ctrl_info *) calloc(1, sizeof(ftp_ctrl_info))) == NULL ||
! 1271: (ctrl->user = (user_info *) calloc(1, sizeof(user_info))) == NULL)
! 1272: outofmem(__FILE__, "HTFTP_init_con");
! 1273: ctrl->serv_port = serv_port;
! 1274: ctrl->socket = -1; /* Illigal socket number */
! 1275: StrAllocCopy(ctrl->location, ""); /* Default is root */
! 1276: ctrl->server = UNKNOWN;
! 1277: ctrl->unsure_type = YES;
! 1278: ctrl->use_list = NO;
! 1279: ctrl->state = IDLE;
! 1280: if (session)
! 1281: HTList_addObject(session, (void *) ctrl);
! 1282: data->ctrl = ctrl; /* Link them together */
! 1283: ctrl->data_cons = HTList_new();
! 1284: HTList_addObject(ctrl->data_cons, (void *) data); /* First element */
! 1285:
! 1286: /* Initialize user info */
! 1287: if (HTFTPUserInfo && !old_user) { /* If first time */
! 1288: if ((old_user = (user_info *) calloc(1, sizeof(user_info))) == NULL)
! 1289: outofmem(__FILE__, "HTFTP_init_con");
! 1290: StrAllocCopy(old_user->domain, "");/* Can't strcmp with NULL, can I? */
! 1291: }
! 1292: if (use_url) {
! 1293: StrAllocCopy(ctrl->user->domain, user.domain);
! 1294: StrAllocCopy(ctrl->user->id, user.id);
! 1295: if (user.passwd) {
! 1296: StrAllocCopy(ctrl->user->passwd, user.passwd);
! 1297: }
! 1298: } else if (HTFTPUserInfo && !strcmp(old_user->domain, user.domain)) {
! 1299: StrAllocCopy(ctrl->user->domain, user.domain);
! 1300: if (old_user->id) {
! 1301: StrAllocCopy(ctrl->user->id, old_user->id);
! 1302: if (old_user->passwd)
! 1303: StrAllocCopy(ctrl->user->passwd, old_user->passwd);
! 1304: }
! 1305: } else {
! 1306: char *uid = getenv("USER");
! 1307: StrAllocCopy(ctrl->user->domain, user.domain);
! 1308: StrAllocCopy(ctrl->user->id, "anonymous");
! 1309: if (uid)
! 1310: StrAllocCopy(ctrl->user->passwd, uid);
! 1311: else
! 1312: StrAllocCopy(ctrl->user->passwd, WWW_FTP_CLIENT);
! 1313: StrAllocCat(ctrl->user->passwd, "@");
! 1314: }
! 1315: FREE(user.domain);
! 1316: FREE(user.id);
! 1317: FREE(user.passwd);
! 1318:
! 1319: /* Now get ready for a connect */
! 1320: if (TRACE) fprintf(stderr, "FTP......... Looking for `%s\'\n", url);
! 1321: if ((status = HTDoConnect (url, "FTP", serv_port, &ctrl->socket,
! 1322: &ctrl->serv_node)) < 0)
! 1323: {
! 1324: if (TRACE)
! 1325: fprintf(stderr, "HTFTP_init_con: Connection not established!\n");
! 1326: HTFTP_abort_ctrl_con(ctrl);
! 1327: return NULL;
! 1328: }
! 1329: ctrl->isoc = HTInputSocket_new(ctrl->socket); /* buffering */
1.1 timbl 1330:
1.23 ! frystyk 1331: if (TRACE)
! 1332: fprintf(stderr, "FTP......... Control connected, socket %d\n",
! 1333: ctrl->socket);
! 1334: return ctrl;
! 1335: }
! 1336:
! 1337:
! 1338: #ifdef LISTEN
! 1339: /* Open a master socket for listening on
! 1340: ** -------------------------------------
! 1341: **
! 1342: ** When data is transferred, we open a port, and wait for the server to
! 1343: ** connect with the data.
! 1344: **
! 1345: ** On entry,
! 1346: ** master_socket Must be negative if not set up already.
! 1347: ** On exit,
! 1348: ** Returns The listening data socket if OK
! 1349: ** Less than zero if error.
! 1350: ** master_socket is socket number if good, else negative.
! 1351: ** port_number is valid if good.
! 1352: */
! 1353: PRIVATE BOOL get_listen_socket ARGS1(ftp_data_info *, data)
! 1354: {
! 1355: struct sockaddr_in local_addr; /* Binary network address */
! 1356: int status = -1;
! 1357:
! 1358: #ifdef REPEAT_LISTEN
! 1359: if (master_socket >= 0) { /* Done already */
! 1360: data->socket = master_socket;
! 1361: if (TRACE)
! 1362: fprintf(stderr, "FTP......... Reusing passive data socket: %d\n",
! 1363: data->socket);
! 1364: return data->socket;
! 1365: }
1.1 timbl 1366: #endif
1367:
1.23 ! frystyk 1368: /* Create internet socket */
! 1369: if ((data->socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
! 1370: return HTInetStatus("socket for ftp data");
! 1371: if (TRACE)
! 1372: fprintf(stderr, "HTDoConnect. Created socket number %d\n",
! 1373: data->socket);
1.1 timbl 1374:
1.23 ! frystyk 1375: /* Search for a free port. */
! 1376: bzero((char *) &local_addr, sizeof(local_addr));
! 1377: local_addr.sin_family = AF_INET; /* Family = internet, host order */
! 1378: local_addr.sin_addr.s_addr = htonl(INADDR_ANY); /* For multi homed hosts */
! 1379:
! 1380: /* Inherit the local address information from the control socket */
! 1381: {
! 1382: int addr_size = sizeof(local_addr);
! 1383: if (getsockname(data->ctrl->socket, (struct sockaddr *)
! 1384: &local_addr, &addr_size) < 0) {
! 1385: status = HTInetStatus("getsockname");
! 1386: goto errorfin;
! 1387: }
! 1388: }
! 1389:
1.1 timbl 1390: #ifdef POLL_PORTS
1391: {
1392: unsigned short old_port_number = port_number;
1.23 ! frystyk 1393: for (port_number=old_port_number+1;;port_number++) {
1.1 timbl 1394: if (port_number > LAST_TCP_PORT)
1395: port_number = FIRST_TCP_PORT;
1396: if (port_number == old_port_number) {
1.23 ! frystyk 1397: if (TRACE)
! 1398: fprintf(stderr, "FTP......... No data port available.\n");
! 1399: goto errorfin;
1.1 timbl 1400: }
1.23 ! frystyk 1401: local_addr.sin_port = htons(port_number);
! 1402:
! 1403: /* The socket address is casted to generic sockaddr */
! 1404: if (bind(data->socket, (struct sockaddr *) &local_addr,
! 1405: sizeof(local_addr)) == 0)
! 1406: break; /* We have found a port */
! 1407: status = HTInetStatus("bind"); /* else, what error did we get */
! 1408: }
1.1 timbl 1409: }
1410: #else
1411: {
1.23 ! frystyk 1412: local_addr.sin_port = 0; /* Unspecified: please allocate */
! 1413:
! 1414: /* The socket address is casted to a generic address */
! 1415: if (bind(data->socket, (struct sockaddr *) &local_addr,
! 1416: sizeof(local_addr)) < 0) {
! 1417: status = HTInetStatus("bind");
! 1418: goto errorfin;
! 1419: }
! 1420: }
! 1421: #endif
! 1422: /* Now we must find out who we are to tell the other guy. */
! 1423: {
! 1424: int addr_size = sizeof(local_addr);
! 1425: if (getsockname(data->socket, (struct sockaddr *) &local_addr,
! 1426: &addr_size) < 0) {
! 1427: status = HTInetStatus("getsockname");
! 1428: goto errorfin;
! 1429: }
! 1430: if (TRACE) fprintf(stderr, "FTP......... This host is `%s\'\n",
! 1431: HTInetString(&local_addr));
! 1432: }
! 1433: if (TRACE) fprintf(stderr, "FTP......... Bound to port %d on %s\n",
! 1434: (int) ntohs(local_addr.sin_port),
! 1435: HTInetString(&local_addr));
! 1436:
! 1437: /* this_addr is a static global, we can refer to later */
! 1438: if (!this_addr && (this_addr = (char *) malloc(24)) == NULL)
! 1439: outofmem(__FILE__, "get_listen_socket");
! 1440: {
! 1441: u_long addr = ntohl(local_addr.sin_addr.s_addr);
! 1442: u_short port = ntohs(local_addr.sin_port);
! 1443: sprintf(this_addr, "%d,%d,%d,%d,%d,%d",
! 1444: (int)*((unsigned char *)(&addr)+0),
! 1445: (int)*((unsigned char *)(&addr)+1),
! 1446: (int)*((unsigned char *)(&addr)+2),
! 1447: (int)*((unsigned char *)(&addr)+3),
! 1448: (int)*((unsigned char *)(&port)+0),
! 1449: (int)*((unsigned char *)(&port)+1));
! 1450: }
! 1451:
! 1452: /* Inform TCP that we will accept connections. Backlog is 1 as we only
! 1453: want (and expect) one connection. If a 3rd host makes a connect
! 1454: to this port, we have problems! */
! 1455: if (listen(data->socket, 1) < 0) {
! 1456: status = HTInetStatus("listen");
! 1457: goto errorfin;
! 1458: }
! 1459: if (TRACE) fprintf(stderr,
! 1460: "FTP......... Data socket number %d listening\n",
! 1461: data->socket);
1.1 timbl 1462:
1463: #ifdef REPEAT_LISTEN
1.23 ! frystyk 1464: master_socket = data->socket; /* Update master_socket */
! 1465: #endif
! 1466: return data->socket; /* Good */
! 1467:
! 1468: errorfin:
! 1469: NETCLOSE(data->socket);
! 1470: data->socket = -1;
! 1471: return -1;
! 1472: }
! 1473: #endif
! 1474:
! 1475:
! 1476: /* HTFTP_login
! 1477: **
! 1478: ** This function makes a login to a ftp-server. It takes the user name
! 1479: ** and passwd specified in ctrl->user and if that fails or an additional
! 1480: ** account is needed, the user is prompted. As it is difficult, when
! 1481: ** the server sends it's welcome message, we receive them all and choose
! 1482: ** the longest.
! 1483: **
! 1484: ** Returns -2 on ERROR, -1 on FAILURE, 0 on SUCCESS.
1.1 timbl 1485: */
1.23 ! frystyk 1486: PRIVATE int HTFTP_login ARGS1(ftp_ctrl_info *, ctrl)
! 1487: {
! 1488: enum _state {
! 1489: ERROR = -2,
! 1490: FAILURE = -1,
! 1491: SUCCESS = 0,
! 1492: BEGIN,
! 1493: SENT_UID,
! 1494: SENT_PASSWD,
! 1495: NEED_USER_INFO,
! 1496: NEED_PASSWD,
! 1497: NEED_ACCOUNT,
! 1498: SENT_ACCOUNT
! 1499: } state = BEGIN;
! 1500: HTChunk *newtext = HTChunkCreate(128);
! 1501: BOOL asked = YES; /* Have we already asked for uid/passwd? */
! 1502: int status = HTFTP_get_response(ctrl, &ctrl->welcome); /* Get greeting */
! 1503: if (status < 0) {
! 1504: if (TRACE) fprintf (stderr, "FTP......... Interrupted at beginning of login.\n");
! 1505: return ERROR;
! 1506: }
! 1507:
! 1508: /* This loop only stops if state is ERROR, FAILURE or SUCCESS */
! 1509: while (state > 0) {
! 1510: switch (state) {
! 1511: case BEGIN:
! 1512: if (!HTFTP_send_cmd(ctrl, "USER", ctrl->user->id))
! 1513: state = SENT_UID;
! 1514: else
! 1515: state = ERROR;
! 1516: break;
! 1517:
! 1518: case SENT_UID:
! 1519: status = HTFTP_get_response(ctrl, &newtext);
! 1520: if (newtext->size > ctrl->welcome->size) {
! 1521: HTChunkFree(ctrl->welcome);
! 1522: ctrl->welcome = newtext;
! 1523: }
! 1524: if (status/100 == 2) /* Logged in w/o passwd! */
! 1525: state = SUCCESS;
! 1526: else if (status/100 == 3) /* Password demanded */
! 1527: state = NEED_PASSWD;
! 1528: else if (status == 530 && asked == YES)
! 1529: state = NEED_USER_INFO; /* User unknown */
! 1530: else if (status/100 == 4)
! 1531: state = FAILURE;
! 1532: else
! 1533: state = ERROR;
! 1534: break;
1.1 timbl 1535:
1.23 ! frystyk 1536: case NEED_PASSWD:
! 1537: if (!ctrl->user->passwd) { /* Got to ask for it */
! 1538: char *prompt = NULL;
! 1539: StrAllocCopy(prompt, "Enter password for user: ");
! 1540: StrAllocCat(prompt, ctrl->user->id);
! 1541: StrAllocCat(prompt, "@");
! 1542: StrAllocCat(prompt, ctrl->user->domain);
! 1543: StrAllocCat(prompt, ": ");
! 1544: if ((ctrl->user->passwd = HTPromptPassword(prompt)) == NULL) {
! 1545: state = ERROR;
! 1546: free(prompt);
! 1547: break;
! 1548: }
! 1549: free(prompt);
! 1550: }
! 1551: /* If userid = "anonymous" then make sure that there is a '@' at
! 1552: the end of the passwd */
! 1553: if (!strcasecomp(ctrl->user->id, "anonymous")) {
! 1554: if (*(ctrl->user->passwd+strlen(ctrl->user->passwd)-1) != '@')
! 1555: StrAllocCat(ctrl->user->passwd, "@");
! 1556: }
! 1557: if (!HTFTP_send_cmd(ctrl, "PASS", ctrl->user->passwd))
! 1558: state = SENT_PASSWD;
! 1559: else
! 1560: state = ERROR;
! 1561: break;
1.1 timbl 1562:
1.23 ! frystyk 1563: case SENT_PASSWD:
! 1564: status = HTFTP_get_response(ctrl, &newtext);
! 1565: if (newtext->size > ctrl->welcome->size) {
! 1566: HTChunkFree(ctrl->welcome);
! 1567: ctrl->welcome = newtext;
! 1568: }
! 1569: if (status/100 == 2) /* Logged in with passwd */
! 1570: state = SUCCESS;
! 1571: else if (status/100 == 3) /* Account demanded */
! 1572: state = NEED_ACCOUNT;
! 1573: else if (status == 530 && asked == YES)
! 1574: state = NEED_USER_INFO; /* User unknown */
! 1575: else if (status/100 == 4)
! 1576: state = FAILURE;
! 1577: else
! 1578: state = ERROR;
! 1579: break;
! 1580:
! 1581: case NEED_ACCOUNT:
! 1582: {
! 1583: char *prompt = NULL;
! 1584: char *account = NULL;
! 1585: StrAllocCopy(prompt, "Enter account for user: ");
! 1586: StrAllocCat(prompt, ctrl->user->domain);
! 1587: StrAllocCat(prompt, "@");
! 1588: StrAllocCat(prompt, ctrl->user->id);
! 1589: if ((account = HTPrompt(prompt, NULL)) != NULL &&
! 1590: !HTFTP_send_cmd(ctrl, "ACCT", account)) {
! 1591: state = SENT_ACCOUNT;
! 1592: } else {
! 1593: state = ERROR;
! 1594: }
! 1595: free(prompt);
! 1596: free(account);
! 1597: }
! 1598: break;
1.1 timbl 1599:
1.23 ! frystyk 1600: case SENT_ACCOUNT:
! 1601: status = HTFTP_get_response(ctrl, &newtext);
! 1602: if (newtext->size > ctrl->welcome->size) {
! 1603: HTChunkFree(ctrl->welcome);
! 1604: ctrl->welcome = newtext;
! 1605: }
! 1606: if (status/100 == 2)
! 1607: state = SUCCESS;
! 1608: else if (status/100 == 4)
! 1609: state = FAILURE;
! 1610: else
! 1611: state = ERROR;
! 1612: break;
! 1613:
! 1614: case NEED_USER_INFO:
! 1615: {
! 1616: char *prompt = NULL;
! 1617: StrAllocCopy(prompt, "Enter username and password for: ");
! 1618: StrAllocCat(prompt, ctrl->user->domain);
! 1619: FREE(ctrl->user->id);
! 1620: FREE(ctrl->user->passwd);
! 1621: HTPromptUsernameAndPassword(prompt, &ctrl->user->id,
! 1622: &ctrl->user->passwd);
! 1623: if (ctrl->user->id && ctrl->user->passwd &&
! 1624: !HTFTP_send_cmd(ctrl, "USER", ctrl->user->id))
! 1625: state = SENT_UID;
! 1626: else
! 1627: state = ERROR;
! 1628: free(prompt);
! 1629: }
! 1630: asked = NO;
! 1631: break;
1.1 timbl 1632:
1.23 ! frystyk 1633: case FAILURE: /* Otherwise gcc complains :-( */
! 1634: case ERROR:
! 1635: case SUCCESS:
! 1636: break;
! 1637: } /* end of switch */
! 1638: }
! 1639: if (state == SUCCESS) {
! 1640: if (TRACE)
! 1641: fprintf(stderr, "FTP......... Logged in at `%s\' as `%s\'\n",
! 1642: ctrl->user->domain, ctrl->user->id);
! 1643: }
1.1 timbl 1644:
1.23 ! frystyk 1645: /* This is a real pain this reuse user stuff :-( */
! 1646: if (HTFTPUserInfo) {
! 1647: StrAllocCopy(old_user->domain, ctrl->user->domain);
! 1648: StrAllocCopy(old_user->id, ctrl->user->id);
! 1649: StrAllocCopy(old_user->passwd, ctrl->user->passwd);
! 1650: }
! 1651: if (newtext != ctrl->welcome)
! 1652: HTChunkFree(newtext);
! 1653: return state;
! 1654: }
1.1 timbl 1655:
1.23 ! frystyk 1656: /* HTFTP_logout
1.1 timbl 1657: **
1.23 ! frystyk 1658: ** This function logs out from a ftp-server.
! 1659: **
! 1660: ** Returns -2 on ERROR, -1 on FAILURE, 0 on SUCCESS.
1.1 timbl 1661: */
1.23 ! frystyk 1662: PRIVATE int HTFTP_logout ARGS1(ftp_ctrl_info *, ctrl)
! 1663: {
! 1664: enum _state {
! 1665: ERROR = -2,
! 1666: FAILURE = -1,
! 1667: SUCCESS = 0,
! 1668: BEGIN,
! 1669: SENT_QUIT
! 1670: } state = BEGIN;
! 1671: int status;
! 1672:
! 1673: /* This loop only stops if state is ERROR, FAILURE or SUCCESS */
! 1674: while (state > 0) {
! 1675: switch (state) {
! 1676: case BEGIN:
! 1677: if (!HTFTP_send_cmd(ctrl, "QUIT", NULL))
! 1678: state = SENT_QUIT;
! 1679: else
! 1680: state = ERROR;
! 1681: break;
! 1682:
! 1683: case SENT_QUIT:
! 1684: status = HTFTP_get_response(ctrl, NULL);
! 1685: if (status/100 == 2)
! 1686: state = SUCCESS;
! 1687: else if (status/100 == 4)
! 1688: state = FAILURE;
! 1689: else
! 1690: state = ERROR;
! 1691: break;
! 1692:
! 1693: case FAILURE: /* Otherwise gcc complains :-( */
! 1694: case ERROR:
! 1695: case SUCCESS:
! 1696: break;
! 1697: }
! 1698: }
! 1699: return state;
! 1700: }
! 1701:
1.22 frystyk 1702:
1.23 ! frystyk 1703: /* HTFTP_get_data_con
! 1704: **
! 1705: ** Gets a valid data connection to the server and initializes the
! 1706: ** transfer mode.
! 1707: **
! 1708: ** Returns -2 on ERROR, -1 on FAILURE, 0 on SUCCESS.
! 1709: */
! 1710: PRIVATE int HTFTP_get_data_con ARGS2(ftp_data_info *, data, char *, url)
1.1 timbl 1711: {
1.23 ! frystyk 1712: enum _state {
! 1713: ERROR = -2,
! 1714: FAILURE = -1,
! 1715: SUCCESS = 0,
! 1716: BEGIN,
! 1717: SENT_TYPE,
! 1718: SENT_PASV,
! 1719: SENT_PORT,
! 1720: NEED_ACTIVE,
! 1721: NEED_PASSIVE,
! 1722: } state = BEGIN;
! 1723: int serv_port;
1.1 timbl 1724: int status;
1725:
1.23 ! frystyk 1726: /* This loop only stops if state is ERROR, FAILURE or SUCCESS */
! 1727: while (state > 0) {
! 1728: switch (state) {
! 1729: case BEGIN:
! 1730: /* First check if it is necessary to send TYPE, else send PASV */
! 1731: if (data->datatype) {
! 1732: if (!HTFTP_send_cmd(data->ctrl, "TYPE", data->datatype))
! 1733: state = SENT_TYPE;
! 1734: else
! 1735: state = ERROR;
! 1736: } else {
! 1737: if (!HTFTP_send_cmd(data->ctrl, "PASV", NULL))
! 1738: state = SENT_PASV;
! 1739: else
! 1740: state = ERROR;
! 1741: }
! 1742: break;
! 1743:
! 1744: case SENT_PASV:
! 1745: {
! 1746: HTChunk *text = NULL;
! 1747: status = HTFTP_get_response(data->ctrl, &text);
! 1748: if (status == 227) {
! 1749: /* If succes, we have to scan for the returned port number.
! 1750: However, the format for the response is not standard, so
! 1751: the best thing to do is to scan for the first digit
! 1752: after the status code, see RFC1123 */
! 1753: char *portstr;
! 1754: int h0, h1, h2, h3, p0, p1;
! 1755: portstr = text->data+3;
! 1756: while (*portstr && !isdigit(*portstr++));
! 1757: if (!*portstr || sscanf(--portstr, "%d,%d,%d,%d,%d,%d",
! 1758: &h0, &h1, &h2, &h3, &p0, &p1)<4) {
! 1759: if (TRACE) fprintf(stderr,
! 1760: "FTP......... PASV reply has no inet address!\n");
! 1761: state = ERROR;
! 1762: } else {
! 1763: serv_port = (p0<<8)+p1;
! 1764: state = NEED_ACTIVE;
! 1765: }
! 1766: } else if (status/100 == 4)
! 1767: state = FAILURE;
! 1768: else if (state == 530) /* Not logged in??? */
! 1769: state = ERROR;
! 1770: else
! 1771: state = NEED_PASSIVE; /* If error, try PORT instead */
! 1772: if (text)
! 1773: HTChunkFree(text);
! 1774: }
! 1775: break;
1.22 frystyk 1776:
1.23 ! frystyk 1777: case NEED_ACTIVE:
! 1778: /* Now get ready for a connect */
! 1779: if (TRACE) fprintf(stderr,
! 1780: "FTP......... Server is listening on port %d\n",
! 1781: serv_port);
! 1782: status = HTDoConnect(url, "FTP", serv_port,
! 1783: &data->socket, (u_long *) NULL);
! 1784: if (status == -1) {
! 1785: if (TRACE) fprintf(stderr,
! 1786: "FTP......... Data connection failed using PASV, let's try PORT instead\n");
! 1787: state = NEED_PASSIVE;
! 1788: } else if (status >= 0) {
! 1789: if (TRACE) fprintf(stderr, "FTP......... Data connected using PASV, socket %d\n", data->socket);
! 1790: state = SUCCESS;
! 1791: } else {
! 1792: state = ERROR; /* Interrupted */
! 1793: }
! 1794: break;
1.22 frystyk 1795:
1.23 ! frystyk 1796: case NEED_PASSIVE:
! 1797: #ifdef LISTEN
! 1798: /* The server didn't accept our PASV so now we try ourselves to be
! 1799: passive using PORT */
! 1800: if (get_listen_socket(data) < 0 ||
! 1801: HTFTP_send_cmd(data->ctrl, "PORT", this_addr))
! 1802: state = ERROR;
! 1803: else
! 1804: state = SENT_PORT;
! 1805: #else
! 1806: /* If PORT is not compiled, then there is nothing we can do! */
! 1807: if (TRACE) fprintf(stderr, "FTP......... PORT is not possible!\n");
! 1808: state = ERROR;
! 1809: #endif
! 1810: break;
! 1811:
! 1812: case SENT_PORT:
! 1813: status = HTFTP_get_response(data->ctrl, NULL);
! 1814: if (status/100 == 2) {
! 1815: data->active = NO;
! 1816: state = SUCCESS;
! 1817: } else if (status/100 == 4)
! 1818: state = FAILURE;
! 1819: else
! 1820: state = ERROR;
! 1821: break;
1.1 timbl 1822:
1.23 ! frystyk 1823: case SENT_TYPE:
! 1824: status = HTFTP_get_response(data->ctrl, NULL);
! 1825: /* If OK, then tell the server to be passive */
! 1826: if (status/100 == 2) {
! 1827: if (!HTFTP_send_cmd(data->ctrl, "PASV", NULL))
! 1828: state = SENT_PASV;
! 1829: else
! 1830: state = ERROR;
! 1831: } else if (status/100 == 4)
! 1832: state = FAILURE;
! 1833: else
! 1834: state = ERROR;
! 1835: break;
! 1836:
! 1837: case ERROR: /* Otherwise gcc complains :-( */
! 1838: case FAILURE:
! 1839: case SUCCESS:
! 1840: break;
1.22 frystyk 1841: }
1.23 ! frystyk 1842: }
! 1843: return state;
! 1844: }
! 1845:
! 1846:
! 1847: /* HTFTPServerInfo()
! 1848: **
! 1849: ** This function finds out what server we are talking to.
! 1850: ** Returns -2 on ERROR, -1 on FAILURE, 0 on SUCCESS.
! 1851: **
! 1852: ** Thanks to James.W.Matthews@Dartmouth.EDU (James W. Matthews) for making
! 1853: ** his code available.
1.1 timbl 1854: */
1.23 ! frystyk 1855: PRIVATE int HTFTPServerInfo ARGS1(ftp_ctrl_info *, ctrl)
! 1856: {
! 1857: enum _state {
! 1858: ERROR = -2,
! 1859: FAILURE = -1,
! 1860: SUCCESS = 0,
! 1861: BEGIN,
! 1862: SENT_SYST,
! 1863: NEED_PWD,
! 1864: SENT_PWD
! 1865: } state = BEGIN;
! 1866: int status;
! 1867:
! 1868: /* This loop only stops if state is ERROR, FAILURE or SUCCESS */
! 1869: while (state > 0) {
! 1870: switch (state) {
! 1871: case BEGIN:
! 1872: if (!HTFTP_send_cmd(ctrl, "SYST", NULL))
! 1873: state = SENT_SYST;
! 1874: else
! 1875: state = ERROR;
! 1876: break;
! 1877:
! 1878: case SENT_SYST:
! 1879: {
! 1880: HTChunk *text = NULL;
! 1881: char *info;
! 1882: status = HTFTP_get_response(ctrl, &text);
! 1883: if (status/100 != 2) {
! 1884: HTChunkFree(text);
! 1885: state = NEED_PWD;
! 1886: break;
! 1887: }
! 1888:
! 1889: /* Got a line - what kind of server are we talking to? */
! 1890: info = text->data+3; /* Skip response code */
! 1891: while (*info && *info++ == ' ');
! 1892: if (!*info) {
! 1893: if (TRACE)
! 1894: fprintf(stderr, "FTP......... No server info?\n");
! 1895: HTChunkFree(text);
! 1896: state = NEED_PWD;
! 1897: break;
! 1898: }
! 1899: --info;
! 1900: if (strncmp(info, "UNIX Type: L8MAC-OSMachTen", 28) == 0) {
! 1901: ctrl->server = MACHTEN_SERVER;
! 1902: ctrl->use_list = YES;
! 1903: ctrl->unsure_type = NO;
! 1904: } else if (strstr(info, "UNIX") != NULL) {
! 1905: ctrl->server = UNIX_SERVER;
! 1906: ctrl->use_list = YES;
! 1907: ctrl->unsure_type = NO;
! 1908: } else if (strncmp(info, "VMS", 3) == 0) {
! 1909: ctrl->server = VMS_SERVER;
! 1910: ctrl->use_list = YES;
! 1911: ctrl->unsure_type = NO;
! 1912: } else if ((strncmp(info, "VM/CMS", 6) == 0) ||
! 1913: (strncmp(info, "VM", 2) == 0)) {
! 1914: ctrl->server = CMS_SERVER;
! 1915: ctrl->unsure_type = NO;
! 1916: } else if (strncmp(info, "DCTS", 4) == 0) {
! 1917: ctrl->server = DCTS_SERVER;
! 1918: ctrl->unsure_type = NO;
! 1919: } else if (strstr(info, "MAC-OS TCP/ConnectII") != NULL) {
! 1920: ctrl->server = TCPC_SERVER;
! 1921: /* Check old versions of TCP/C using / in pathnames */
! 1922: ctrl->unsure_type = YES;
! 1923: } else if (strncmp(info, "MACOS Peter's Server", 20) == 0) {
! 1924: ctrl->server = PETER_LEWIS_SERVER;
! 1925: ctrl->use_list = YES;
! 1926: ctrl->unsure_type = NO;
! 1927: }
! 1928:
! 1929: /* If we are unsure, try PWD to get more information */
! 1930: if (ctrl->server == UNKNOWN || ctrl->unsure_type == YES)
! 1931: state = NEED_PWD;
! 1932: else
! 1933: state = SUCCESS;
! 1934: HTChunkFree(text);
1.1 timbl 1935: }
1.23 ! frystyk 1936: break;
! 1937:
! 1938: case NEED_PWD:
! 1939: /* get the working directory (to see what it looks like) */
! 1940: if (!HTFTP_send_cmd(ctrl, "PWD", NULL))
! 1941: state = SENT_PWD;
! 1942: else
! 1943: state = ERROR;
! 1944: break;
! 1945:
! 1946: case SENT_PWD:
! 1947: {
! 1948: HTChunk *path = NULL;
! 1949: status = HTFTP_get_response(ctrl, &path);
! 1950: if (status/100 != 2)
! 1951: state = ERROR;
! 1952: else {
! 1953: char *start, *end;
! 1954:
! 1955: /* Now analyze response information between "'s */
! 1956: if ((start = strchr(path->data, '"')) == NULL ||
! 1957: (end = strchr(++start, '"')) == NULL) {
! 1958: if (TRACE)
! 1959: fprintf(stderr,
! 1960: "FTP......... No current directory?\n");
! 1961: ctrl->server = GENERIC_SERVER;
! 1962: } else {
! 1963: *end = '\0';
! 1964: if (ctrl->server == TCPC_SERVER) {
! 1965: ctrl->server = *start == '/' ?
! 1966: NCSA_SERVER : TCPC_SERVER;
! 1967: ctrl->unsure_type = NO;
! 1968: } else if (*start == '/') {
! 1969: /* path names starting with / imply Unix, right? */
! 1970: ctrl->server = UNIX_SERVER;
! 1971: ctrl->use_list = YES;
! 1972: } else if (*(end-1) == ']') {
! 1973: /* path names ending with ] imply VMS, right? */
! 1974: ctrl->server = VMS_SERVER;
! 1975: } else
! 1976: ctrl->server = GENERIC_SERVER;
! 1977: }
! 1978: state = SUCCESS;
! 1979: }
! 1980: HTChunkFree(path);
! 1981: }
! 1982: break;
! 1983:
! 1984: case FAILURE: /* Otherwise gcc complains :-( */
! 1985: case ERROR:
! 1986: case SUCCESS:
! 1987: break;
1.1 timbl 1988: }
1.23 ! frystyk 1989: }
! 1990: if (TRACE) {
! 1991: static char *servers[] = {
! 1992: "UNKNOWN",
! 1993: "GENERIC",
! 1994: "MACHTEN",
! 1995: "UNIX",
! 1996: "VMS",
! 1997: "CMS",
! 1998: "DCTS",
! 1999: "TCPC",
! 2000: "PETER_LEWIS",
! 2001: "NCSA"
! 2002: };
! 2003: if (ctrl->unsure_type == YES)
! 2004: fprintf(stderr, "FTP......... This might be a %s server\n",
! 2005: *(servers+ctrl->server+1));
! 2006: else
! 2007: fprintf(stderr, "FTP......... We are talking to a %s server\n",
! 2008: *(servers+ctrl->server+1));
! 2009: }
! 2010: return state;
! 2011: }
! 2012:
! 2013:
! 2014: /* HTFTPLocation()
! 2015: **
! 2016: ** This function compares the current directory location in the
! 2017: ** ftp-session to the new URL and returns the relative position. If
! 2018: ** the new URL is at a higher location, the function performs CDUP's
! 2019: ** until this location is reached so that the relative position is '.'
! 2020: **
! 2021: ** Returns relative path name if OK, else current location
! 2022: **
1.1 timbl 2023: */
1.23 ! frystyk 2024: PRIVATE char *HTFTPLocation ARGS2(ftp_ctrl_info *, ctrl, char *, url)
! 2025: {
! 2026: unsigned char getup = 0;
! 2027: char *current;
! 2028: char *new;
! 2029: char *relative;
! 2030: char *result = NULL;
! 2031: char *strptr;
! 2032:
! 2033: /* Make a full URL out of current location */
! 2034: current = HTParse(url, "", PARSE_ACCESS+PARSE_HOST+PARSE_PUNCTUATION);
! 2035: StrAllocCat(current, "/");
! 2036: StrAllocCat(current, ctrl->location);
! 2037: if (*(current+strlen(current)-1) != '/')
! 2038: StrAllocCat(current, "/");
! 2039:
! 2040: /* Make a temporary URL without any type indication */
! 2041: new = HTParse(url, "", PARSE_ALL);
! 2042: if ((strptr = strrchr(new, ';')) != NULL)
! 2043: *strptr = '\0';
! 2044:
! 2045: /* Compare those two URLs */
! 2046: relative = HTRelative(new, current);
! 2047: {
! 2048: /* Now send any CDUP if necessary */
! 2049: char *tail = relative;
! 2050: int status;
! 2051: while (tail && (strptr = strstr(tail, "../")) != NULL) {
! 2052: if (HTFTP_send_cmd(ctrl, "CDUP", NULL)) {
! 2053: break;
! 2054: }
! 2055: status = HTFTP_get_response(ctrl, NULL);
! 2056: if (status/100 != 2) {
! 2057: break;
1.1 timbl 2058: }
1.23 ! frystyk 2059: ++getup;
! 2060: tail += 3;
! 2061: }
! 2062: }
! 2063:
! 2064: /* Now update current location if we have used CDUP and make relative
! 2065: return value. */
! 2066: if (getup) {
! 2067: char *location = HTParse(new, "", PARSE_PATH);
! 2068: free(ctrl->location);
! 2069: if (*location == '/')
! 2070: ctrl->location = ++location;
! 2071: else
! 2072: ctrl->location = location;
! 2073: if (*ctrl->location &&
! 2074: *(ctrl->location+strlen(ctrl->location)-1) == '/')
! 2075: *(ctrl->location+strlen(ctrl->location)-1) = '\0';
! 2076: StrAllocCopy(result, "");
! 2077: free(relative);
! 2078: } else {
! 2079: if (*relative == '/') {
! 2080: StrAllocCopy(result, relative+1);
! 2081: free(relative);
! 2082: } else
! 2083: result = relative;
! 2084: if (*(relative+strlen(relative)-1) == '/')
! 2085: *(relative+strlen(relative)-1) = '\0';
! 2086: }
! 2087: if (TRACE)
! 2088: fprintf(stderr, "FTP......... current location on server: `%s\'\n",
! 2089: ctrl->location);
! 2090: free(current);
! 2091: free(new);
! 2092: return result;
! 2093: }
1.22 frystyk 2094:
2095:
1.23 ! frystyk 2096: /* ------------------------------------------------------------------------- */
! 2097: /* FTP Client Functions for receiving data */
! 2098: /* ------------------------------------------------------------------------- */
1.1 timbl 2099:
1.23 ! frystyk 2100: /* HTFTP_get_dir
! 2101: **
! 2102: ** This function asks for the directory specified and calls
! 2103: ** HTFTPBrowseDirectory. First we try in one go, but if that doesn't
! 2104: ** work, then we use CWD for each segment. The directory is searched
! 2105: ** relative to the login directory, that is without a starting '/'.
! 2106: **
! 2107: ** Returns -2 on ERROR, -1 on FAILURE, 0 on SUCCESS.
1.1 timbl 2108: */
1.23 ! frystyk 2109: PRIVATE int HTFTP_get_dir ARGS4(ftp_ctrl_info *, ctrl, HTRequest *, req,
! 2110: char *, relative, char, retry)
! 2111: {
! 2112: enum _state {
! 2113: ERROR = -2,
! 2114: FAILURE = -1,
! 2115: SUCCESS = 0,
! 2116: BEGIN,
! 2117: NEED_LIST,
! 2118: SENT_LIST,
! 2119: SENT_CWD,
! 2120: MULTIPLE_CWD,
! 2121: READY_FOR_DATA,
! 2122: SENT_ABOR,
! 2123: GOT_DATA
! 2124: } state = BEGIN;
! 2125: ftp_data_info *data = (ftp_data_info *) ctrl->data_cons->next->object;
! 2126: int status;
! 2127: char *unescaped = NULL;
! 2128: StrAllocCopy(unescaped, relative);
! 2129: HTUnEscape(unescaped);
! 2130:
! 2131: /* This loop only stops if state is ERROR, FAILURE or SUCCESS */
! 2132: while (state > 0) {
! 2133: switch (state) {
! 2134: case BEGIN:
! 2135: /* Only if the root directory is requested, we can use LIST right
! 2136: away, else we must first use CWD */
! 2137: if (!*unescaped || !strcmp(unescaped, "/"))
! 2138: state = NEED_LIST;
! 2139: else {
! 2140: /* We first try to CWD to the location in one go. */
! 2141: if (!HTFTP_send_cmd(ctrl, "CWD", unescaped))
! 2142: state = SENT_CWD;
! 2143: else
! 2144: state = ERROR;
! 2145: }
! 2146: break;
! 2147:
! 2148: case NEED_LIST:
! 2149: if (ctrl->use_list == YES) {
! 2150: if (!HTFTP_send_cmd(ctrl, "LIST", NULL))
! 2151: state = SENT_LIST;
! 2152: else
! 2153: state = ERROR;
! 2154: } else {
! 2155: if (!HTFTP_send_cmd(ctrl, "NLST", NULL))
! 2156: state = SENT_LIST;
! 2157: else
! 2158: state = ERROR;
! 2159: }
! 2160: break;
! 2161:
! 2162: case SENT_LIST:
! 2163: /* If we are listening, then do a non-blocking accept now, as the
! 2164: accept on some systems (DYNIX) completes the connection. On
! 2165: BSD systems, the completion is done independently of the
! 2166: accept. (thanks to Bill Rushka, wcr@aps.org) */
! 2167: if (data->active == NO && !retry) {
! 2168: int newfd;
! 2169: if ((newfd = HTDoAccept(data->socket)) >= 0) {
! 2170: #ifdef REPEAT_LISTEN
! 2171: if (TRACE) fprintf(stderr, "FTP......... Passive data channel number %d stays open.\n", data->socket);
! 2172: #else
! 2173: if (TRACE) fprintf(stderr, "FTP......... Passive data channel number %d closed.\n", data->socket);
! 2174: if (NETCLOSE(data->socket) < 0) {
! 2175: HTInetStatus("close");
! 2176: state = ERROR;
! 2177: break;
! 2178: }
1.22 frystyk 2179: #endif
1.23 ! frystyk 2180: data->socket = newfd; /* Switch to new socket */
! 2181: if (TRACE)
! 2182: fprintf(stderr, "FTP......... New data socket: %d\n",
! 2183: data->socket);
! 2184: } else {
! 2185: state = ERROR;
! 2186: break;
! 2187: }
1.1 timbl 2188: }
1.23 ! frystyk 2189: status = HTFTP_get_response(ctrl, NULL);
! 2190: if (status/100 == 1)
! 2191: state = READY_FOR_DATA;
! 2192: else if (status/100 == 4)
! 2193: state = FAILURE;
! 2194: else
! 2195: state = ERROR;
! 2196: break;
1.1 timbl 2197:
1.23 ! frystyk 2198: case SENT_CWD:
! 2199: status = HTFTP_get_response(ctrl, &ctrl->welcome);
! 2200: if (status/100 == 2) {
! 2201: /* Update current location */
! 2202: if (*ctrl->location)
! 2203: StrAllocCat(ctrl->location, "/");
! 2204: StrAllocCat(ctrl->location, relative);
! 2205: state = NEED_LIST;
! 2206: } else if (status/100 == 4)
! 2207: state = FAILURE;
! 2208: else
! 2209: state = MULTIPLE_CWD;
! 2210: break;
1.1 timbl 2211:
1.23 ! frystyk 2212: case MULTIPLE_CWD:
! 2213: /* We must use the escaped version when looking for '/' as
! 2214: delimiter between segments, and then unescape each segment */
! 2215: if (TRACE) fprintf(stderr, "FTP......... Can't jump directly to location, try multiple CD's instead\n");
! 2216: state = NEED_LIST; /* This is overwritten if error */
! 2217: {
! 2218: char *path = NULL;
! 2219: char *segment;
! 2220: StrAllocCopy(path, relative);
! 2221: segment = strtok(path, "/");
! 2222: while (segment && *segment) {
! 2223: HTUnEscape(segment);
! 2224: if (HTFTP_send_cmd(ctrl, "CWD", segment)) {
! 2225: state = ERROR;
! 2226: break;
! 2227: }
! 2228: status = HTFTP_get_response(ctrl, &ctrl->welcome);
! 2229: if (status/100 != 2) {
! 2230: if (status/100 == 4)
! 2231: state = FAILURE;
! 2232: else
! 2233: state = ERROR;
! 2234: break;
! 2235: } else { /* Update current location */
! 2236: char *new_seg = HTEscape(segment, URL_XPALPHAS);
! 2237: if (*ctrl->location)
! 2238: StrAllocCat(ctrl->location, "/");
! 2239: StrAllocCat(ctrl->location, new_seg);
! 2240: free(new_seg);
! 2241: }
! 2242: segment = strtok(NULL, "/"); /* Get next token */
1.22 frystyk 2243: }
1.23 ! frystyk 2244: free(path);
1.22 frystyk 2245: }
1.23 ! frystyk 2246: break;
1.22 frystyk 2247:
1.23 ! frystyk 2248: case READY_FOR_DATA:
! 2249: /* Now, the browsing module can be called */
! 2250: {
! 2251: char *url = HTAnchor_physical(req->anchor);
! 2252: char *path = HTParse(url, "", PARSE_PATH+PARSE_PUNCTUATION);
! 2253: HTUnEscape(path);
! 2254: if (TRACE)
! 2255: fprintf(stderr, "FTP......... Receiving directory `%s\'\n",
! 2256: path);
! 2257: HTParseWelcome(ctrl);
! 2258: status = HTFTPBrowseDirectory(req, path, data,
! 2259: HTFTP_get_dir_string);
! 2260: if (status == -1)
! 2261: state = ERROR;
! 2262: else if (status == HT_INTERRUPTED) {
! 2263: if (!HTFTP_send_cmd(ctrl, "ABOR", NULL))
! 2264: state = SENT_ABOR;
! 2265: else
! 2266: state = ERROR;
! 2267: } else
! 2268: state = GOT_DATA;
! 2269: free(path);
1.22 frystyk 2270: }
1.23 ! frystyk 2271: break;
! 2272:
! 2273: case GOT_DATA:
! 2274: status = HTFTP_get_response(ctrl, NULL);
! 2275: if (status/100 == 2)
! 2276: state = SUCCESS; /* Directory read */
! 2277: else if (status/100 == 4)
! 2278: state = FAILURE;
! 2279: else
! 2280: state = ERROR;
! 2281: break;
! 2282:
! 2283: case SENT_ABOR:
! 2284: status = HTFTP_get_response(ctrl, NULL);
! 2285: if (status/100 == 2)
! 2286: state = SUCCESS;
! 2287: else if (status/100 == 4)
! 2288: state = FAILURE;
1.22 frystyk 2289: else
1.23 ! frystyk 2290: state = ERROR;
! 2291: break;
! 2292:
! 2293: case FAILURE: /* Otherwise gcc complains :-( */
! 2294: case ERROR:
! 2295: case SUCCESS:
! 2296: break;
1.22 frystyk 2297: }
1.23 ! frystyk 2298: }
! 2299: FREE(unescaped);
! 2300: return state;
! 2301: }
! 2302:
! 2303:
! 2304: /* HTFTP_get_file
! 2305: **
! 2306: ** This function asks for the file specified. First we try in one go,
! 2307: ** but if that doesn't work, then we use CWD for each segment and then
! 2308: ** try to retrieve it. If that also fails, then we try if it is a
! 2309: ** directory. This procedure causes that directory links generated in
! 2310: ** HTFTPBrowseDirectory should have a '/' at the end in order to go
! 2311: ** directly to HTFTP_get_dir. The relative is searched relative to
! 2312: ** the login directory, that is without a starting '/'.
! 2313: **
! 2314: ** Returns -2 on ERROR, -1 on FAILURE, 0 on SUCCESS.
! 2315: */
! 2316: PRIVATE int HTFTP_get_file ARGS4(ftp_ctrl_info *, ctrl, HTRequest *, req,
! 2317: char *, relative, char, retry)
! 2318: {
! 2319: enum _state {
! 2320: ERROR = -2,
! 2321: FAILURE = -1,
! 2322: SUCCESS = 0,
! 2323: BEGIN,
! 2324: SENT_RETR,
! 2325: MULTIPLE_CWD,
! 2326: READY_FOR_DATA,
! 2327: SENT_ABOR,
! 2328: GOT_DATA
! 2329: } state = BEGIN;
! 2330: BOOL multiple = NO; /* Have we already tried multiple CWD? */
! 2331: ftp_data_info *data = (ftp_data_info *) ctrl->data_cons->next->object;
! 2332: int status;
! 2333: char *unescaped = NULL;
! 2334: StrAllocCopy(unescaped, relative);
! 2335: HTUnEscape(unescaped);
! 2336:
! 2337: /* Thsi loop only stops if state is ERROR, FAILURE or SUCCESS */
! 2338: while (state > 0) {
! 2339: switch (state) {
! 2340: case BEGIN:
! 2341: /* First we try to retrieve the file in one go. */
! 2342: if (!HTFTP_send_cmd(ctrl, "RETR", unescaped))
! 2343: state = SENT_RETR;
! 2344: else
! 2345: state = ERROR;
! 2346: break;
! 2347:
! 2348: case SENT_RETR:
! 2349: /* If we are listening, then do a non-blocking accept now, as the
! 2350: accept on some systems (DYNIX) completes the connection. On
! 2351: BSD systems, the completion is done independently of the
! 2352: accept. (thanks to Bill Rushka, wcr@aps.org) */
! 2353: if (data->active == NO && !retry) {
! 2354: int newfd;
! 2355: if ((newfd = HTDoAccept(data->socket)) >= 0) {
! 2356: #ifdef REPEAT_LISTEN
! 2357: if (TRACE) fprintf(stderr, "FTP......... Passive data channel number %d stays open.\n", data->socket);
! 2358: #else
! 2359: if (TRACE) fprintf(stderr, "FTP......... Passive data channel number %d closed.\n", data->socket);
! 2360: if (NETCLOSE(data->socket) < 0) {
! 2361: HTInetStatus("close");
! 2362: state = ERROR;
! 2363: break;
! 2364: }
1.22 frystyk 2365: #endif
1.23 ! frystyk 2366: data->socket = newfd; /* Switch to new socket */
! 2367: if (TRACE)
! 2368: fprintf(stderr, "FTP......... New data socket: %d\n",
! 2369: data->socket);
! 2370: } else {
! 2371: state = ERROR;
! 2372: break;
! 2373: }
! 2374: }
! 2375: status = HTFTP_get_response(ctrl, NULL);
! 2376: if (status/100 == 1)
! 2377: state = READY_FOR_DATA;
! 2378: else if (status/100 == 4)
! 2379: state = FAILURE;
! 2380: /* If there is no '/' in unescaped, it won't help to try
! 2381: multiple CWD's, as it either doesn't exist or is a directory */
! 2382: else if (multiple == NO && strchr(unescaped, '/') != NULL)
! 2383: state = MULTIPLE_CWD;
! 2384: else {
! 2385: data->directory = YES;
! 2386: state = FAILURE;
! 2387: }
! 2388: break;
! 2389:
! 2390: case MULTIPLE_CWD:
! 2391: /* We must use the escaped version when looking for '/' as
! 2392: delimiter between segments, and then unescape each segment */
! 2393: if (TRACE) fprintf(stderr, "FTP......... Can't jump directly to location, try multiple CD's instead\n");
! 2394: multiple = YES;
! 2395: {
! 2396: char *path = NULL;
! 2397: char *segment;
! 2398: char *last_slash; /* Used to identify the last segment */
! 2399: StrAllocCopy(path, relative);
! 2400: if ((last_slash = strrchr(path, '/')) == NULL)
! 2401: last_slash = path;
! 2402: else
! 2403: last_slash++;
! 2404: segment = strtok(path, "/");
! 2405: while (segment && *segment && segment != last_slash) {
! 2406: HTUnEscape(segment);
! 2407: if (HTFTP_send_cmd(ctrl, "CWD", segment)) {
! 2408: state = ERROR;
! 2409: break;
! 2410: }
! 2411: status = HTFTP_get_response(ctrl, NULL);
! 2412: if (status/100 != 2) {
! 2413: if (status/100 == 4)
! 2414: state = FAILURE;
! 2415: else
! 2416: state = ERROR;
! 2417: break;
! 2418: } else { /* Update current location */
! 2419: char *new_seg = HTEscape(segment, URL_XPALPHAS);
! 2420: if (*ctrl->location)
! 2421: StrAllocCat(ctrl->location, "/");
! 2422: StrAllocCat(ctrl->location, new_seg);
! 2423: free(new_seg);
! 2424: }
! 2425: segment = strtok(NULL, "/"); /* Get next token */
! 2426: }
! 2427: /* Now try to retrieve the last segment */
! 2428: if (segment == last_slash) {
! 2429: HTUnEscape(segment);
! 2430: if (!HTFTP_send_cmd(ctrl, "RETR", segment))
! 2431: state = SENT_RETR;
! 2432: else
! 2433: state = ERROR;
! 2434: } else {
! 2435: if (TRACE) fprintf(stderr, "FTP......... Strange error, filename not found?\n");
! 2436: state = ERROR;
! 2437: }
! 2438: free(path);
! 2439: }
! 2440: break;
! 2441:
! 2442: case READY_FOR_DATA:
! 2443: /* Now, the browsing module can be called */
! 2444: if (TRACE) fprintf(stderr, "FTP......... Receiving file `%s\'\n",
! 2445: unescaped);
! 2446: status = HTParseSocket(data->fileformat, data->socket, req);
! 2447: if (status != HT_LOADED) {
! 2448: if (status == HT_INTERRUPTED) {
! 2449: if (!HTFTP_send_cmd(ctrl, "ABOR", NULL))
! 2450: state = SENT_ABOR;
! 2451: else
! 2452: state = ERROR;
! 2453: } else
! 2454: state = ERROR;
! 2455: } else
! 2456: state = GOT_DATA;
! 2457: break;
! 2458:
! 2459: case GOT_DATA:
! 2460: status = HTFTP_get_response(ctrl, NULL);
! 2461: if (status/100 == 2)
! 2462: state = SUCCESS; /* Directory read */
! 2463: else if (status/100 == 4)
! 2464: state = FAILURE;
! 2465: else
! 2466: state = ERROR;
! 2467: break;
1.22 frystyk 2468:
1.23 ! frystyk 2469: case SENT_ABOR:
! 2470: status = HTFTP_get_response(ctrl, NULL);
! 2471: if (status/100 == 2)
! 2472: state = SUCCESS;
! 2473: else if (status/100 == 4)
! 2474: state = FAILURE;
1.22 frystyk 2475: else
1.23 ! frystyk 2476: state = ERROR;
! 2477: break;
! 2478:
! 2479: case FAILURE: /* Otherwise gcc complains :-( */
! 2480: case ERROR:
! 2481: case SUCCESS:
! 2482: break;
1.1 timbl 2483: }
2484: }
1.23 ! frystyk 2485: FREE(unescaped);
! 2486: return state;
! 2487: }
1.1 timbl 2488:
1.23 ! frystyk 2489:
! 2490: /* ------------------------------------------------------------------------- */
! 2491: /* PUBLIC FTP functions */
! 2492: /* ------------------------------------------------------------------------- */
! 2493:
! 2494: /* HTFTP_enable_session
! 2495: **
! 2496: ** This function makes it possible to reuse the same control connections
! 2497: ** until they are either timed out by the server, or that the session
! 2498: ** is closed by HTFTP_end_session. Note that HTFTPLoad can run
! 2499: ** independently of start and end session, and then each load runs like
! 2500: ** an atomic action.
! 2501: */
! 2502: PUBLIC void HTFTP_enable_session NOARGS
! 2503: {
! 2504: if (session) {
! 2505: if (TRACE)
! 2506: fprintf(stderr, "FTP......... Session is already enabled?\n");
! 2507: return;
! 2508: }
! 2509: session = HTList_new();
! 2510: }
! 2511:
! 2512:
! 2513: /* HTFTP_disable_session
! 2514: **
! 2515: ** This function is the closing function for HTFTP_enable_session.
! 2516: **
! 2517: ** Returns YES if OK, else NO
1.1 timbl 2518: */
1.23 ! frystyk 2519: PUBLIC BOOL HTFTP_disable_session NOARGS
! 2520: {
! 2521: BOOL status = YES;
! 2522: if (!session) {
! 2523: if (TRACE)
! 2524: fprintf(stderr, "FTP......... No session to disable?\n");
! 2525: return NO;
! 2526: }
1.1 timbl 2527: {
1.23 ! frystyk 2528: HTList *cur = session;
! 2529: ftp_ctrl_info *pres;
! 2530: while ((pres = (ftp_ctrl_info *) HTList_nextObject(cur))) {
! 2531: if (HTFTP_close_ctrl_con(pres))
! 2532: status = NO;
! 2533: }
! 2534: HTList_delete(session);
1.1 timbl 2535: }
1.23 ! frystyk 2536: session = NULL;
! 2537: return status;
! 2538: }
! 2539:
! 2540:
! 2541: /* Retrieve File from Server as an atomic action.
! 2542: ** -----------------------------------------------
! 2543: **
! 2544: ** On entry,
! 2545: ** request This is the request structure
! 2546: ** On exit,
! 2547: ** returns <0 Error has occured
! 2548: ** HT_LOADED OK
! 2549: */
! 2550: PUBLIC int HTFTPLoad ARGS1(HTRequest *, request)
! 2551: {
! 2552: char *url = HTAnchor_physical(request->anchor);
! 2553: int status = -1;
! 2554: int retry; /* How many times tried? */
! 2555: ftp_ctrl_info *ctrl;
! 2556:
! 2557: /* Initiate a (possibly already exsisting) control connection and a
! 2558: corresponding data connection */
! 2559: HTSimplify(url);
! 2560: if((ctrl = HTFTP_init_con(url)) == NULL)
! 2561: return -1;
! 2562:
! 2563: /* Only if the control connection is in IDLE state, a new
! 2564: transfer can be started. The control connection can be in another
! 2565: mode if (session), and then the request is getting queued in
! 2566: ctrl->data_cons. */
! 2567: if (ctrl->state == IDLE || (session && ctrl->state == LOGGED_IN)) {
! 2568: ftp_data_info *data = ctrl->data_cons->next->object;
! 2569: if (ctrl->state == IDLE)
! 2570: ctrl->state = BEGIN;
! 2571: while (ctrl->state != IDLE) { /* Do until finished */
! 2572: switch (ctrl->state) {
! 2573: case BEGIN:
! 2574: if (!HTFTP_login(ctrl))
! 2575: ctrl->state = LOGGED_IN;
! 2576: else
! 2577: ctrl->state = ERROR;
! 2578: break;
! 2579:
! 2580: case LOGGED_IN:
! 2581: if (!HTFTP_get_data_con(data, url))
! 2582: ctrl->state = GOT_DATA_CON;
! 2583: else
! 2584: ctrl->state = ERROR;
! 2585: break;
! 2586:
! 2587: case GOT_DATA_CON:
! 2588: {
! 2589: /* Now we must ask for the URL requested. If FAILURE, then
! 2590: we try twice to see, if it helps */
! 2591: char *rel;
! 2592: for (retry=0; retry<2; retry++) {
! 2593: if ((rel = HTFTPLocation(ctrl, url)) == NULL) {
! 2594: ctrl->state = ERROR;
! 2595: break;
! 2596: }
! 2597: if (retry == 1 && TRACE) fprintf(stderr,
! 2598: "FTP......... First attempt to get URL failed, let's try again\n");
! 2599:
! 2600: if (data->directory == YES) {
! 2601: /* If we haven't already got server-info */
! 2602: if (ctrl->server == UNKNOWN) {
! 2603: if (HTFTPServerInfo(ctrl)) {
! 2604: ctrl->state = ERROR;
! 2605: break;
! 2606: }
! 2607: }
! 2608: status = HTFTP_get_dir(ctrl, request, rel, retry);
! 2609: }
! 2610: else
! 2611: status = HTFTP_get_file(ctrl, request, rel, retry);
! 2612: if (!status) {
! 2613: ctrl->state = GOT_DATA;
! 2614: break;
! 2615: } else if (status == -2) { /* Error */
! 2616: ctrl->state = ERROR;
! 2617: break;
! 2618: } else {
! 2619: free(rel);
! 2620: ctrl->state = FAILURE; /* Try twice */
! 2621: }
! 2622: }
! 2623: free(rel);
! 2624: }
! 2625: if (retry == 2 && ctrl->state == FAILURE)
! 2626: ctrl->state = ERROR;
! 2627: break;
! 2628:
! 2629: case GOT_DATA:
! 2630: if (HTFTP_close_data_con(data))
! 2631: ctrl->state = ERROR;
! 2632: else {
! 2633: HTList_removeLastObject(ctrl->data_cons);
! 2634: if (!session) {
! 2635: if (!HTFTP_logout(ctrl)) {
! 2636: ctrl->state = IDLE;
! 2637: status = HT_LOADED;
! 2638: } else
! 2639: ctrl->state = ERROR;
! 2640: } else {
! 2641: ctrl->state = LOGGED_IN; /* Ready for next request */
! 2642: return HT_LOADED;
! 2643: }
! 2644: break;
! 2645: }
! 2646: break;
! 2647:
! 2648: case ERROR:
! 2649: HTFTP_abort_ctrl_con(ctrl);
! 2650: return -1; /* Exit immediately */
! 2651: break;
! 2652:
! 2653: default:
! 2654: if (TRACE) fprintf(stderr, "FTP......... Unknown state, what happened?\n");
! 2655: break;
! 2656: }
! 2657: }
1.22 frystyk 2658:
1.23 ! frystyk 2659: /* The control connection is only closed if the load is atomic */
! 2660: if (!session && HTFTP_close_ctrl_con(ctrl))
! 2661: status = -1;
! 2662: }
! 2663: if (status < 0 && status != HT_INTERRUPTED)
! 2664: return HTLoadError(request, 500,
! 2665: "Document not loaded due to strange behavior from FTP-server\n");
! 2666: return status;
! 2667: }
1.22 frystyk 2668:
1.23 ! frystyk 2669: /* END OF MODULE */
1.22 frystyk 2670:
2671:
Webmaster