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