Annotation of libwww/Library/src/HTFTP.c, revision 1.29
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.29 ! frystyk 1176: PRIVATE ftp_ctrl_info *HTFTP_init_con ARGS2(HTRequest *, req, 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");
1.29 ! frystyk 1222: req->content_encoding = HTAtom_for("binary");
1.23 frystyk 1223: }
1224: } else {
1225: /* Chop off data type */
1226: strptr = strrchr(filename, ';');
1227: *strptr = '\0';
1228: }
1229: }
1230: FREE(filename);
1231: }
1232:
1233: /* Look if control connection already exists else generate new one */
1234: if (session) {
1235: BOOL found = NO;
1236: HTList *cur = session;
1237: struct sockaddr_in sock_addr;
1238: char *host;
1239:
1240: /* if theres an @ then use the stuff after it as a hostname */
1241: {
1242: char *fullhost = HTParse(url, "", PARSE_HOST);
1243: char *at_sign;
1244: if((at_sign = strchr(fullhost, '@')) != NULL)
1245: host = at_sign+1;
1246: else
1247: host = fullhost;
1248: }
1249:
1250: /* Set up defaults: */
1.25 frystyk 1251: memset((void *) &sock_addr, '\0', sizeof(sock_addr));
1.23 frystyk 1252: sock_addr.sin_family = AF_INET;
1253: sock_addr.sin_port = htons(serv_port);
1254:
1255: /* Get node name */
1256: if (HTParseInet(&sock_addr, host)) {
1257: if (TRACE) fprintf(stderr,
1258: "FTP......... Can't locate remote host `%s\'\n", host);
1259: FREE(user.domain);
1260: FREE(user.id);
1261: FREE(user.passwd);
1262: free(host);
1263: return NULL;
1264: }
1265: {
1266: /* Check if host, port and user info is the same */
1267: u_long new_node = ntohl(sock_addr.sin_addr.s_addr);
1268: while ((ctrl = (ftp_ctrl_info *) HTList_nextObject(cur))) {
1269: if (new_node==ctrl->serv_node && serv_port==ctrl->serv_port) {
1270: if ((user.id && strcmp(user.id, ctrl->user->id)) ||
1271: (user.passwd && strcmp(user.id, ctrl->user->passwd))) {
1272: found = NO;
1273: } else {
1274: found = YES;
1275: break;
1276: }
1277: }
1278: }
1279: }
1280: FREE(host);
1281: if (found) {
1282: if (TRACE) fprintf(stderr,
1283: "FTP......... Already have connection for %d.%d.%d.%d. on port %d, socket %d at location `%s\'\n",
1284: (int)*((unsigned char *)(&ctrl->serv_node)+0),
1285: (int)*((unsigned char *)(&ctrl->serv_node)+1),
1286: (int)*((unsigned char *)(&ctrl->serv_node)+2),
1287: (int)*((unsigned char *)(&ctrl->serv_node)+3),
1288: ctrl->serv_port,
1289: ctrl->socket,
1290: ctrl->location);
1291: data->ctrl = ctrl; /* Link them together */
1292: HTList_addObject(ctrl->data_cons, (void *) data); /* Add to list */
1293: FREE(user.domain);
1294: FREE(user.id);
1295: FREE(user.passwd);
1296: return ctrl; /* This should return the found structure */
1297: } else {
1298: if (TRACE)
1299: fprintf(stderr, "FTP......... No existing connection found, so I build a new\n");
1300: }
1301: }
1.1 timbl 1302:
1.23 frystyk 1303: /* Set up data structure for control connection */
1304: if ((ctrl = (ftp_ctrl_info *) calloc(1, sizeof(ftp_ctrl_info))) == NULL ||
1305: (ctrl->user = (user_info *) calloc(1, sizeof(user_info))) == NULL)
1306: outofmem(__FILE__, "HTFTP_init_con");
1307: ctrl->serv_port = serv_port;
1308: ctrl->socket = -1; /* Illigal socket number */
1309: StrAllocCopy(ctrl->location, ""); /* Default is root */
1310: ctrl->server = UNKNOWN;
1311: ctrl->unsure_type = YES;
1312: ctrl->use_list = NO;
1313: ctrl->state = IDLE;
1314: if (session)
1315: HTList_addObject(session, (void *) ctrl);
1316: data->ctrl = ctrl; /* Link them together */
1317: ctrl->data_cons = HTList_new();
1318: HTList_addObject(ctrl->data_cons, (void *) data); /* First element */
1319:
1320: /* Initialize user info */
1321: if (HTFTPUserInfo && !old_user) { /* If first time */
1322: if ((old_user = (user_info *) calloc(1, sizeof(user_info))) == NULL)
1323: outofmem(__FILE__, "HTFTP_init_con");
1324: StrAllocCopy(old_user->domain, "");/* Can't strcmp with NULL, can I? */
1325: }
1326: if (use_url) {
1327: StrAllocCopy(ctrl->user->domain, user.domain);
1328: StrAllocCopy(ctrl->user->id, user.id);
1329: if (user.passwd) {
1330: StrAllocCopy(ctrl->user->passwd, user.passwd);
1331: }
1332: } else if (HTFTPUserInfo && !strcmp(old_user->domain, user.domain)) {
1333: StrAllocCopy(ctrl->user->domain, user.domain);
1334: if (old_user->id) {
1335: StrAllocCopy(ctrl->user->id, old_user->id);
1336: if (old_user->passwd)
1337: StrAllocCopy(ctrl->user->passwd, old_user->passwd);
1338: }
1339: } else {
1340: char *uid = getenv("USER");
1341: StrAllocCopy(ctrl->user->domain, user.domain);
1342: StrAllocCopy(ctrl->user->id, "anonymous");
1343: if (uid)
1344: StrAllocCopy(ctrl->user->passwd, uid);
1345: else
1346: StrAllocCopy(ctrl->user->passwd, WWW_FTP_CLIENT);
1347: StrAllocCat(ctrl->user->passwd, "@");
1348: }
1349: FREE(user.domain);
1350: FREE(user.id);
1351: FREE(user.passwd);
1352:
1353: /* Now get ready for a connect */
1354: if (TRACE) fprintf(stderr, "FTP......... Looking for `%s\'\n", url);
1355: if ((status = HTDoConnect (url, "FTP", serv_port, &ctrl->socket,
1356: &ctrl->serv_node)) < 0)
1357: {
1358: if (TRACE)
1359: fprintf(stderr, "HTFTP_init_con: Connection not established!\n");
1360: HTFTP_abort_ctrl_con(ctrl);
1361: return NULL;
1362: }
1363: ctrl->isoc = HTInputSocket_new(ctrl->socket); /* buffering */
1.1 timbl 1364:
1.23 frystyk 1365: if (TRACE)
1366: fprintf(stderr, "FTP......... Control connected, socket %d\n",
1367: ctrl->socket);
1368: return ctrl;
1369: }
1370:
1371:
1372: #ifdef LISTEN
1373: /* Open a master socket for listening on
1374: ** -------------------------------------
1375: **
1376: ** When data is transferred, we open a port, and wait for the server to
1377: ** connect with the data.
1378: **
1379: ** On entry,
1380: ** master_socket Must be negative if not set up already.
1381: ** On exit,
1382: ** Returns The listening data socket if OK
1383: ** Less than zero if error.
1384: ** master_socket is socket number if good, else negative.
1385: ** port_number is valid if good.
1386: */
1387: PRIVATE BOOL get_listen_socket ARGS1(ftp_data_info *, data)
1388: {
1389: struct sockaddr_in local_addr; /* Binary network address */
1390: int status = -1;
1391:
1392: #ifdef REPEAT_LISTEN
1393: if (master_socket >= 0) { /* Done already */
1394: data->socket = master_socket;
1395: if (TRACE)
1396: fprintf(stderr, "FTP......... Reusing passive data socket: %d\n",
1397: data->socket);
1398: return data->socket;
1399: }
1.1 timbl 1400: #endif
1401:
1.23 frystyk 1402: /* Create internet socket */
1403: if ((data->socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
1404: return HTInetStatus("socket for ftp data");
1405: if (TRACE)
1406: fprintf(stderr, "HTDoConnect. Created socket number %d\n",
1407: data->socket);
1.1 timbl 1408:
1.23 frystyk 1409: /* Search for a free port. */
1.25 frystyk 1410: memset((void *) &local_addr, '\0', sizeof(local_addr));
1.23 frystyk 1411: local_addr.sin_family = AF_INET; /* Family = internet, host order */
1412: local_addr.sin_addr.s_addr = htonl(INADDR_ANY); /* For multi homed hosts */
1413:
1414: /* Inherit the local address information from the control socket */
1415: {
1416: int addr_size = sizeof(local_addr);
1417: if (getsockname(data->ctrl->socket, (struct sockaddr *)
1418: &local_addr, &addr_size) < 0) {
1419: status = HTInetStatus("getsockname");
1420: goto errorfin;
1421: }
1422: }
1423:
1.1 timbl 1424: #ifdef POLL_PORTS
1425: {
1426: unsigned short old_port_number = port_number;
1.23 frystyk 1427: for (port_number=old_port_number+1;;port_number++) {
1.1 timbl 1428: if (port_number > LAST_TCP_PORT)
1429: port_number = FIRST_TCP_PORT;
1430: if (port_number == old_port_number) {
1.23 frystyk 1431: if (TRACE)
1432: fprintf(stderr, "FTP......... No data port available.\n");
1433: goto errorfin;
1.1 timbl 1434: }
1.23 frystyk 1435: local_addr.sin_port = htons(port_number);
1436:
1437: /* The socket address is casted to generic sockaddr */
1438: if (bind(data->socket, (struct sockaddr *) &local_addr,
1439: sizeof(local_addr)) == 0)
1440: break; /* We have found a port */
1441: status = HTInetStatus("bind"); /* else, what error did we get */
1442: }
1.1 timbl 1443: }
1444: #else
1445: {
1.23 frystyk 1446: local_addr.sin_port = 0; /* Unspecified: please allocate */
1447:
1448: /* The socket address is casted to a generic address */
1449: if (bind(data->socket, (struct sockaddr *) &local_addr,
1450: sizeof(local_addr)) < 0) {
1451: status = HTInetStatus("bind");
1452: goto errorfin;
1453: }
1454: }
1455: #endif
1456: /* Now we must find out who we are to tell the other guy. */
1457: {
1458: int addr_size = sizeof(local_addr);
1459: if (getsockname(data->socket, (struct sockaddr *) &local_addr,
1460: &addr_size) < 0) {
1461: status = HTInetStatus("getsockname");
1462: goto errorfin;
1463: }
1464: if (TRACE) fprintf(stderr, "FTP......... This host is `%s\'\n",
1465: HTInetString(&local_addr));
1466: }
1467: if (TRACE) fprintf(stderr, "FTP......... Bound to port %d on %s\n",
1468: (int) ntohs(local_addr.sin_port),
1469: HTInetString(&local_addr));
1470:
1471: /* this_addr is a static global, we can refer to later */
1472: if (!this_addr && (this_addr = (char *) malloc(24)) == NULL)
1473: outofmem(__FILE__, "get_listen_socket");
1474: {
1475: u_long addr = ntohl(local_addr.sin_addr.s_addr);
1476: u_short port = ntohs(local_addr.sin_port);
1477: sprintf(this_addr, "%d,%d,%d,%d,%d,%d",
1478: (int)*((unsigned char *)(&addr)+0),
1479: (int)*((unsigned char *)(&addr)+1),
1480: (int)*((unsigned char *)(&addr)+2),
1481: (int)*((unsigned char *)(&addr)+3),
1482: (int)*((unsigned char *)(&port)+0),
1483: (int)*((unsigned char *)(&port)+1));
1484: }
1485:
1486: /* Inform TCP that we will accept connections. Backlog is 1 as we only
1487: want (and expect) one connection. If a 3rd host makes a connect
1488: to this port, we have problems! */
1489: if (listen(data->socket, 1) < 0) {
1490: status = HTInetStatus("listen");
1491: goto errorfin;
1492: }
1493: if (TRACE) fprintf(stderr,
1494: "FTP......... Data socket number %d listening\n",
1495: data->socket);
1.1 timbl 1496:
1497: #ifdef REPEAT_LISTEN
1.23 frystyk 1498: master_socket = data->socket; /* Update master_socket */
1499: #endif
1500: return data->socket; /* Good */
1501:
1502: errorfin:
1503: NETCLOSE(data->socket);
1504: data->socket = -1;
1505: return -1;
1506: }
1507: #endif
1508:
1509:
1510: /* HTFTP_login
1511: **
1512: ** This function makes a login to a ftp-server. It takes the user name
1513: ** and passwd specified in ctrl->user and if that fails or an additional
1514: ** account is needed, the user is prompted. As it is difficult, when
1515: ** the server sends it's welcome message, we receive them all and choose
1516: ** the longest.
1517: **
1518: ** Returns -2 on ERROR, -1 on FAILURE, 0 on SUCCESS.
1.1 timbl 1519: */
1.23 frystyk 1520: PRIVATE int HTFTP_login ARGS1(ftp_ctrl_info *, ctrl)
1521: {
1522: enum _state {
1523: ERROR = -2,
1524: FAILURE = -1,
1525: SUCCESS = 0,
1526: BEGIN,
1527: SENT_UID,
1528: SENT_PASSWD,
1529: NEED_USER_INFO,
1530: NEED_PASSWD,
1531: NEED_ACCOUNT,
1532: SENT_ACCOUNT
1533: } state = BEGIN;
1534: BOOL asked = YES; /* Have we already asked for uid/passwd? */
1.25 frystyk 1535: int status = HTFTP_get_response(ctrl, &ctrl->reply); /* Get greeting */
1.23 frystyk 1536: if (status < 0) {
1537: if (TRACE) fprintf (stderr, "FTP......... Interrupted at beginning of login.\n");
1538: return ERROR;
1.25 frystyk 1539: } else
1540: HTFTPAddWelcome(ctrl);
1.23 frystyk 1541:
1542: /* This loop only stops if state is ERROR, FAILURE or SUCCESS */
1543: while (state > 0) {
1544: switch (state) {
1545: case BEGIN:
1546: if (!HTFTP_send_cmd(ctrl, "USER", ctrl->user->id))
1547: state = SENT_UID;
1548: else
1549: state = ERROR;
1550: break;
1551:
1552: case SENT_UID:
1.25 frystyk 1553: status = HTFTP_get_response(ctrl, &ctrl->reply);
1554: if (status/100 == 2) { /* Logged in w/o passwd! */
1.28 frystyk 1555: HTFTPAddWelcome(ctrl);
1.23 frystyk 1556: state = SUCCESS;
1.28 frystyk 1557: } else if (status/100 == 3) { /* Password demanded */
1558: HTFTPAddWelcome(ctrl);
1.23 frystyk 1559: state = NEED_PASSWD;
1.28 frystyk 1560: } else if (status == 530 && asked == YES)
1.23 frystyk 1561: state = NEED_USER_INFO; /* User unknown */
1562: else if (status/100 == 4)
1563: state = FAILURE;
1564: else
1565: state = ERROR;
1566: break;
1.1 timbl 1567:
1.23 frystyk 1568: case NEED_PASSWD:
1569: if (!ctrl->user->passwd) { /* Got to ask for it */
1570: char *prompt = NULL;
1571: StrAllocCopy(prompt, "Enter password for user: ");
1572: StrAllocCat(prompt, ctrl->user->id);
1573: StrAllocCat(prompt, "@");
1574: StrAllocCat(prompt, ctrl->user->domain);
1575: StrAllocCat(prompt, ": ");
1576: if ((ctrl->user->passwd = HTPromptPassword(prompt)) == NULL) {
1577: state = ERROR;
1578: free(prompt);
1579: break;
1580: }
1581: free(prompt);
1582: }
1583: /* If userid = "anonymous" then make sure that there is a '@' at
1584: the end of the passwd */
1585: if (!strcasecomp(ctrl->user->id, "anonymous")) {
1586: if (*(ctrl->user->passwd+strlen(ctrl->user->passwd)-1) != '@')
1587: StrAllocCat(ctrl->user->passwd, "@");
1588: }
1589: if (!HTFTP_send_cmd(ctrl, "PASS", ctrl->user->passwd))
1590: state = SENT_PASSWD;
1591: else
1592: state = ERROR;
1593: break;
1.1 timbl 1594:
1.23 frystyk 1595: case SENT_PASSWD:
1.25 frystyk 1596: status = HTFTP_get_response(ctrl, &ctrl->reply);
1597: if (status/100 == 2) { /* Logged in with passwd */
1598: HTFTPAddWelcome(ctrl);
1.23 frystyk 1599: state = SUCCESS;
1.28 frystyk 1600: } else if (status/100 == 3) { /* Account demanded */
1601: HTFTPAddWelcome(ctrl);
1.23 frystyk 1602: state = NEED_ACCOUNT;
1.28 frystyk 1603: } else if (status == 530 && asked == YES)
1.23 frystyk 1604: state = NEED_USER_INFO; /* User unknown */
1605: else if (status/100 == 4)
1606: state = FAILURE;
1607: else
1608: state = ERROR;
1609: break;
1610:
1611: case NEED_ACCOUNT:
1612: {
1613: char *prompt = NULL;
1614: char *account = NULL;
1615: StrAllocCopy(prompt, "Enter account for user: ");
1616: StrAllocCat(prompt, ctrl->user->domain);
1617: StrAllocCat(prompt, "@");
1618: StrAllocCat(prompt, ctrl->user->id);
1619: if ((account = HTPrompt(prompt, NULL)) != NULL &&
1620: !HTFTP_send_cmd(ctrl, "ACCT", account)) {
1621: state = SENT_ACCOUNT;
1622: } else {
1623: state = ERROR;
1624: }
1625: free(prompt);
1626: free(account);
1627: }
1628: break;
1.1 timbl 1629:
1.23 frystyk 1630: case SENT_ACCOUNT:
1.25 frystyk 1631: status = HTFTP_get_response(ctrl, &ctrl->reply);
1632: if (status/100 == 2) {
1633: HTFTPAddWelcome(ctrl);
1.23 frystyk 1634: state = SUCCESS;
1.25 frystyk 1635: } else if (status/100 == 4)
1.23 frystyk 1636: state = FAILURE;
1637: else
1638: state = ERROR;
1639: break;
1640:
1641: case NEED_USER_INFO:
1642: {
1643: char *prompt = NULL;
1644: StrAllocCopy(prompt, "Enter username and password for: ");
1645: StrAllocCat(prompt, ctrl->user->domain);
1646: FREE(ctrl->user->id);
1647: FREE(ctrl->user->passwd);
1648: HTPromptUsernameAndPassword(prompt, &ctrl->user->id,
1649: &ctrl->user->passwd);
1650: if (ctrl->user->id && ctrl->user->passwd &&
1651: !HTFTP_send_cmd(ctrl, "USER", ctrl->user->id))
1652: state = SENT_UID;
1653: else
1654: state = ERROR;
1655: free(prompt);
1656: }
1657: asked = NO;
1658: break;
1.1 timbl 1659:
1.23 frystyk 1660: case FAILURE: /* Otherwise gcc complains :-( */
1661: case ERROR:
1662: case SUCCESS:
1663: break;
1664: } /* end of switch */
1665: }
1666: if (state == SUCCESS) {
1667: if (TRACE)
1668: fprintf(stderr, "FTP......... Logged in at `%s\' as `%s\'\n",
1669: ctrl->user->domain, ctrl->user->id);
1670: }
1.1 timbl 1671:
1.23 frystyk 1672: /* This is a real pain this reuse user stuff :-( */
1673: if (HTFTPUserInfo) {
1674: StrAllocCopy(old_user->domain, ctrl->user->domain);
1675: StrAllocCopy(old_user->id, ctrl->user->id);
1676: StrAllocCopy(old_user->passwd, ctrl->user->passwd);
1677: }
1678: return state;
1679: }
1.1 timbl 1680:
1.23 frystyk 1681: /* HTFTP_logout
1.1 timbl 1682: **
1.23 frystyk 1683: ** This function logs out from a ftp-server.
1684: **
1685: ** Returns -2 on ERROR, -1 on FAILURE, 0 on SUCCESS.
1.1 timbl 1686: */
1.23 frystyk 1687: PRIVATE int HTFTP_logout ARGS1(ftp_ctrl_info *, ctrl)
1688: {
1689: enum _state {
1690: ERROR = -2,
1691: FAILURE = -1,
1692: SUCCESS = 0,
1693: BEGIN,
1694: SENT_QUIT
1695: } state = BEGIN;
1696: int status;
1697:
1698: /* This loop only stops if state is ERROR, FAILURE or SUCCESS */
1699: while (state > 0) {
1700: switch (state) {
1701: case BEGIN:
1702: if (!HTFTP_send_cmd(ctrl, "QUIT", NULL))
1703: state = SENT_QUIT;
1704: else
1705: state = ERROR;
1706: break;
1707:
1708: case SENT_QUIT:
1.28 frystyk 1709: status = HTFTP_get_response(ctrl, &ctrl->reply);
1.23 frystyk 1710: if (status/100 == 2)
1711: state = SUCCESS;
1712: else if (status/100 == 4)
1713: state = FAILURE;
1714: else
1715: state = ERROR;
1716: break;
1717:
1718: case FAILURE: /* Otherwise gcc complains :-( */
1719: case ERROR:
1720: case SUCCESS:
1721: break;
1722: }
1723: }
1724: return state;
1725: }
1726:
1.22 frystyk 1727:
1.23 frystyk 1728: /* HTFTP_get_data_con
1729: **
1730: ** Gets a valid data connection to the server and initializes the
1731: ** transfer mode.
1732: **
1733: ** Returns -2 on ERROR, -1 on FAILURE, 0 on SUCCESS.
1734: */
1735: PRIVATE int HTFTP_get_data_con ARGS2(ftp_data_info *, data, char *, url)
1.1 timbl 1736: {
1.23 frystyk 1737: enum _state {
1738: ERROR = -2,
1739: FAILURE = -1,
1740: SUCCESS = 0,
1741: BEGIN,
1742: SENT_TYPE,
1743: SENT_PASV,
1744: SENT_PORT,
1745: NEED_ACTIVE,
1746: NEED_PASSIVE,
1747: } state = BEGIN;
1748: int serv_port;
1.1 timbl 1749: int status;
1.28 frystyk 1750: ftp_ctrl_info *ctrl = data->ctrl;
1.1 timbl 1751:
1.23 frystyk 1752: /* This loop only stops if state is ERROR, FAILURE or SUCCESS */
1753: while (state > 0) {
1754: switch (state) {
1755: case BEGIN:
1756: /* First check if it is necessary to send TYPE, else send PASV */
1757: if (data->datatype) {
1.28 frystyk 1758: if (!HTFTP_send_cmd(ctrl, "TYPE", data->datatype))
1.23 frystyk 1759: state = SENT_TYPE;
1760: else
1761: state = ERROR;
1762: } else {
1.28 frystyk 1763: if (!HTFTP_send_cmd(ctrl, "PASV", NULL))
1.23 frystyk 1764: state = SENT_PASV;
1765: else
1766: state = ERROR;
1767: }
1768: break;
1769:
1770: case SENT_PASV:
1771: {
1.28 frystyk 1772: status = HTFTP_get_response(ctrl, &ctrl->reply);
1.23 frystyk 1773: if (status == 227) {
1774: /* If succes, we have to scan for the returned port number.
1775: However, the format for the response is not standard, so
1776: the best thing to do is to scan for the first digit
1777: after the status code, see RFC1123 */
1778: char *portstr;
1779: int h0, h1, h2, h3, p0, p1;
1.28 frystyk 1780: portstr = ctrl->reply->data+3;
1.23 frystyk 1781: while (*portstr && !isdigit(*portstr++));
1782: if (!*portstr || sscanf(--portstr, "%d,%d,%d,%d,%d,%d",
1783: &h0, &h1, &h2, &h3, &p0, &p1)<4) {
1784: if (TRACE) fprintf(stderr,
1785: "FTP......... PASV reply has no inet address!\n");
1786: state = ERROR;
1787: } else {
1788: serv_port = (p0<<8)+p1;
1789: state = NEED_ACTIVE;
1790: }
1791: } else if (status/100 == 4)
1792: state = FAILURE;
1793: else if (state == 530) /* Not logged in??? */
1794: state = ERROR;
1795: else
1796: state = NEED_PASSIVE; /* If error, try PORT instead */
1797: }
1798: break;
1.22 frystyk 1799:
1.23 frystyk 1800: case NEED_ACTIVE:
1801: /* Now get ready for a connect */
1802: if (TRACE) fprintf(stderr,
1803: "FTP......... Server is listening on port %d\n",
1804: serv_port);
1805: status = HTDoConnect(url, "FTP", serv_port,
1806: &data->socket, (u_long *) NULL);
1807: if (status == -1) {
1808: if (TRACE) fprintf(stderr,
1809: "FTP......... Data connection failed using PASV, let's try PORT instead\n");
1810: state = NEED_PASSIVE;
1811: } else if (status >= 0) {
1812: if (TRACE) fprintf(stderr, "FTP......... Data connected using PASV, socket %d\n", data->socket);
1813: state = SUCCESS;
1814: } else {
1815: state = ERROR; /* Interrupted */
1816: }
1817: break;
1.22 frystyk 1818:
1.23 frystyk 1819: case NEED_PASSIVE:
1820: #ifdef LISTEN
1821: /* The server didn't accept our PASV so now we try ourselves to be
1822: passive using PORT */
1823: if (get_listen_socket(data) < 0 ||
1.28 frystyk 1824: HTFTP_send_cmd(ctrl, "PORT", this_addr))
1.23 frystyk 1825: state = ERROR;
1826: else
1827: state = SENT_PORT;
1828: #else
1829: /* If PORT is not compiled, then there is nothing we can do! */
1830: if (TRACE) fprintf(stderr, "FTP......... PORT is not possible!\n");
1831: state = ERROR;
1832: #endif
1833: break;
1834:
1835: case SENT_PORT:
1.28 frystyk 1836: status = HTFTP_get_response(ctrl, &ctrl->reply);
1.23 frystyk 1837: if (status/100 == 2) {
1838: data->active = NO;
1839: state = SUCCESS;
1840: } else if (status/100 == 4)
1841: state = FAILURE;
1842: else
1843: state = ERROR;
1844: break;
1.1 timbl 1845:
1.23 frystyk 1846: case SENT_TYPE:
1.28 frystyk 1847: status = HTFTP_get_response(ctrl, &ctrl->reply);
1.23 frystyk 1848: /* If OK, then tell the server to be passive */
1849: if (status/100 == 2) {
1.28 frystyk 1850: if (!HTFTP_send_cmd(ctrl, "PASV", NULL))
1.23 frystyk 1851: state = SENT_PASV;
1852: else
1853: state = ERROR;
1854: } else if (status/100 == 4)
1855: state = FAILURE;
1856: else
1857: state = ERROR;
1858: break;
1859:
1860: case ERROR: /* Otherwise gcc complains :-( */
1861: case FAILURE:
1862: case SUCCESS:
1863: break;
1.22 frystyk 1864: }
1.23 frystyk 1865: }
1866: return state;
1867: }
1868:
1869:
1870: /* HTFTPServerInfo()
1871: **
1872: ** This function finds out what server we are talking to.
1873: ** Returns -2 on ERROR, -1 on FAILURE, 0 on SUCCESS.
1874: **
1875: ** Thanks to James.W.Matthews@Dartmouth.EDU (James W. Matthews) for making
1876: ** his code available.
1.1 timbl 1877: */
1.23 frystyk 1878: PRIVATE int HTFTPServerInfo ARGS1(ftp_ctrl_info *, ctrl)
1879: {
1880: enum _state {
1881: ERROR = -2,
1882: FAILURE = -1,
1883: SUCCESS = 0,
1884: BEGIN,
1885: SENT_SYST,
1886: NEED_PWD,
1887: SENT_PWD
1888: } state = BEGIN;
1889: int status;
1890:
1891: /* This loop only stops if state is ERROR, FAILURE or SUCCESS */
1892: while (state > 0) {
1893: switch (state) {
1894: case BEGIN:
1895: if (!HTFTP_send_cmd(ctrl, "SYST", NULL))
1896: state = SENT_SYST;
1897: else
1898: state = ERROR;
1899: break;
1900:
1901: case SENT_SYST:
1902: {
1903: char *info;
1.28 frystyk 1904: status = HTFTP_get_response(ctrl, &ctrl->reply);
1.23 frystyk 1905: if (status/100 != 2) {
1906: state = NEED_PWD;
1907: break;
1908: }
1909:
1910: /* Got a line - what kind of server are we talking to? */
1.28 frystyk 1911: info = ctrl->reply->data+3; /* Skip response code */
1.23 frystyk 1912: while (*info && *info++ == ' ');
1913: if (!*info) {
1914: if (TRACE)
1915: fprintf(stderr, "FTP......... No server info?\n");
1916: state = NEED_PWD;
1917: break;
1918: }
1919: --info;
1920: if (strncmp(info, "UNIX Type: L8MAC-OSMachTen", 28) == 0) {
1921: ctrl->server = MACHTEN_SERVER;
1922: ctrl->use_list = YES;
1923: ctrl->unsure_type = NO;
1924: } else if (strstr(info, "UNIX") != NULL) {
1925: ctrl->server = UNIX_SERVER;
1926: ctrl->use_list = YES;
1927: ctrl->unsure_type = NO;
1928: } else if (strncmp(info, "VMS", 3) == 0) {
1929: ctrl->server = VMS_SERVER;
1930: ctrl->use_list = YES;
1931: ctrl->unsure_type = NO;
1932: } else if ((strncmp(info, "VM/CMS", 6) == 0) ||
1933: (strncmp(info, "VM", 2) == 0)) {
1934: ctrl->server = CMS_SERVER;
1935: ctrl->unsure_type = NO;
1936: } else if (strncmp(info, "DCTS", 4) == 0) {
1937: ctrl->server = DCTS_SERVER;
1938: ctrl->unsure_type = NO;
1939: } else if (strstr(info, "MAC-OS TCP/ConnectII") != NULL) {
1940: ctrl->server = TCPC_SERVER;
1941: /* Check old versions of TCP/C using / in pathnames */
1942: ctrl->unsure_type = YES;
1943: } else if (strncmp(info, "MACOS Peter's Server", 20) == 0) {
1944: ctrl->server = PETER_LEWIS_SERVER;
1945: ctrl->use_list = YES;
1946: ctrl->unsure_type = NO;
1947: }
1948:
1949: /* If we are unsure, try PWD to get more information */
1950: if (ctrl->server == UNKNOWN || ctrl->unsure_type == YES)
1951: state = NEED_PWD;
1952: else
1953: state = SUCCESS;
1.1 timbl 1954: }
1.23 frystyk 1955: break;
1956:
1957: case NEED_PWD:
1958: /* get the working directory (to see what it looks like) */
1959: if (!HTFTP_send_cmd(ctrl, "PWD", NULL))
1960: state = SENT_PWD;
1961: else
1962: state = ERROR;
1963: break;
1964:
1965: case SENT_PWD:
1966: {
1.28 frystyk 1967: status = HTFTP_get_response(ctrl, &ctrl->reply);
1.23 frystyk 1968: if (status/100 != 2)
1969: state = ERROR;
1970: else {
1971: char *start, *end;
1972:
1973: /* Now analyze response information between "'s */
1.28 frystyk 1974: if ((start = strchr(ctrl->reply->data, '"')) == NULL ||
1.23 frystyk 1975: (end = strchr(++start, '"')) == NULL) {
1976: if (TRACE)
1977: fprintf(stderr,
1978: "FTP......... No current directory?\n");
1979: ctrl->server = GENERIC_SERVER;
1980: } else {
1981: *end = '\0';
1982: if (ctrl->server == TCPC_SERVER) {
1983: ctrl->server = *start == '/' ?
1984: NCSA_SERVER : TCPC_SERVER;
1985: ctrl->unsure_type = NO;
1986: } else if (*start == '/') {
1987: /* path names starting with / imply Unix, right? */
1988: ctrl->server = UNIX_SERVER;
1989: ctrl->use_list = YES;
1990: } else if (*(end-1) == ']') {
1991: /* path names ending with ] imply VMS, right? */
1992: ctrl->server = VMS_SERVER;
1993: } else
1994: ctrl->server = GENERIC_SERVER;
1995: }
1996: state = SUCCESS;
1997: }
1998: }
1999: break;
2000:
2001: case FAILURE: /* Otherwise gcc complains :-( */
2002: case ERROR:
2003: case SUCCESS:
2004: break;
1.1 timbl 2005: }
1.23 frystyk 2006: }
2007: if (TRACE) {
2008: static char *servers[] = {
2009: "UNKNOWN",
2010: "GENERIC",
2011: "MACHTEN",
2012: "UNIX",
2013: "VMS",
2014: "CMS",
2015: "DCTS",
2016: "TCPC",
2017: "PETER_LEWIS",
2018: "NCSA"
2019: };
2020: if (ctrl->unsure_type == YES)
2021: fprintf(stderr, "FTP......... This might be a %s server\n",
2022: *(servers+ctrl->server+1));
2023: else
2024: fprintf(stderr, "FTP......... We are talking to a %s server\n",
2025: *(servers+ctrl->server+1));
2026: }
2027: return state;
2028: }
2029:
2030:
2031: /* HTFTPLocation()
2032: **
2033: ** This function compares the current directory location in the
2034: ** ftp-session to the new URL and returns the relative position. If
2035: ** the new URL is at a higher location, the function performs CDUP's
2036: ** until this location is reached so that the relative position is '.'
2037: **
2038: ** Returns relative path name if OK, else current location
2039: **
1.1 timbl 2040: */
1.23 frystyk 2041: PRIVATE char *HTFTPLocation ARGS2(ftp_ctrl_info *, ctrl, char *, url)
2042: {
2043: unsigned char getup = 0;
2044: char *current;
2045: char *new;
2046: char *relative;
2047: char *result = NULL;
2048: char *strptr;
2049:
2050: /* Make a full URL out of current location */
2051: current = HTParse(url, "", PARSE_ACCESS+PARSE_HOST+PARSE_PUNCTUATION);
2052: StrAllocCat(current, "/");
2053: StrAllocCat(current, ctrl->location);
2054: if (*(current+strlen(current)-1) != '/')
2055: StrAllocCat(current, "/");
2056:
2057: /* Make a temporary URL without any type indication */
2058: new = HTParse(url, "", PARSE_ALL);
2059: if ((strptr = strrchr(new, ';')) != NULL)
2060: *strptr = '\0';
2061:
2062: /* Compare those two URLs */
2063: relative = HTRelative(new, current);
2064: {
2065: /* Now send any CDUP if necessary */
2066: char *tail = relative;
2067: int status;
2068: while (tail && (strptr = strstr(tail, "../")) != NULL) {
2069: if (HTFTP_send_cmd(ctrl, "CDUP", NULL)) {
2070: break;
2071: }
1.28 frystyk 2072: status = HTFTP_get_response(ctrl, &ctrl->reply);
1.23 frystyk 2073: if (status/100 != 2) {
2074: break;
1.1 timbl 2075: }
1.23 frystyk 2076: ++getup;
2077: tail += 3;
2078: }
2079: }
2080:
2081: /* Now update current location if we have used CDUP and make relative
2082: return value. */
2083: if (getup) {
2084: char *location = HTParse(new, "", PARSE_PATH);
2085: free(ctrl->location);
2086: if (*location == '/')
2087: ctrl->location = ++location;
2088: else
2089: ctrl->location = location;
2090: if (*ctrl->location &&
2091: *(ctrl->location+strlen(ctrl->location)-1) == '/')
2092: *(ctrl->location+strlen(ctrl->location)-1) = '\0';
2093: StrAllocCopy(result, "");
2094: free(relative);
2095: } else {
2096: if (*relative == '/') {
2097: StrAllocCopy(result, relative+1);
2098: free(relative);
2099: } else
2100: result = relative;
1.25 frystyk 2101: if (*relative && *(relative+strlen(relative)-1) == '/')
1.23 frystyk 2102: *(relative+strlen(relative)-1) = '\0';
2103: }
2104: if (TRACE)
2105: fprintf(stderr, "FTP......... current location on server: `%s\'\n",
2106: ctrl->location);
2107: free(current);
2108: free(new);
2109: return result;
2110: }
1.22 frystyk 2111:
2112:
1.23 frystyk 2113: /* ------------------------------------------------------------------------- */
2114: /* FTP Client Functions for receiving data */
2115: /* ------------------------------------------------------------------------- */
1.1 timbl 2116:
1.23 frystyk 2117: /* HTFTP_get_dir
2118: **
2119: ** This function asks for the directory specified and calls
2120: ** HTFTPBrowseDirectory. First we try in one go, but if that doesn't
2121: ** work, then we use CWD for each segment. The directory is searched
2122: ** relative to the login directory, that is without a starting '/'.
2123: **
2124: ** Returns -2 on ERROR, -1 on FAILURE, 0 on SUCCESS.
1.1 timbl 2125: */
1.23 frystyk 2126: PRIVATE int HTFTP_get_dir ARGS4(ftp_ctrl_info *, ctrl, HTRequest *, req,
2127: char *, relative, char, retry)
2128: {
2129: enum _state {
2130: ERROR = -2,
2131: FAILURE = -1,
2132: SUCCESS = 0,
2133: BEGIN,
2134: NEED_LIST,
2135: SENT_LIST,
2136: SENT_CWD,
2137: MULTIPLE_CWD,
2138: READY_FOR_DATA,
2139: SENT_ABOR,
2140: GOT_DATA
2141: } state = BEGIN;
2142: ftp_data_info *data = (ftp_data_info *) ctrl->data_cons->next->object;
2143: int status;
2144: char *unescaped = NULL;
2145: StrAllocCopy(unescaped, relative);
2146: HTUnEscape(unescaped);
2147:
2148: /* This loop only stops if state is ERROR, FAILURE or SUCCESS */
2149: while (state > 0) {
2150: switch (state) {
2151: case BEGIN:
2152: /* Only if the root directory is requested, we can use LIST right
2153: away, else we must first use CWD */
2154: if (!*unescaped || !strcmp(unescaped, "/"))
2155: state = NEED_LIST;
2156: else {
2157: /* We first try to CWD to the location in one go. */
2158: if (!HTFTP_send_cmd(ctrl, "CWD", unescaped))
2159: state = SENT_CWD;
2160: else
2161: state = ERROR;
2162: }
2163: break;
2164:
2165: case NEED_LIST:
2166: if (ctrl->use_list == YES) {
2167: if (!HTFTP_send_cmd(ctrl, "LIST", NULL))
2168: state = SENT_LIST;
2169: else
2170: state = ERROR;
2171: } else {
2172: if (!HTFTP_send_cmd(ctrl, "NLST", NULL))
2173: state = SENT_LIST;
2174: else
2175: state = ERROR;
2176: }
2177: break;
2178:
2179: case SENT_LIST:
2180: /* If we are listening, then do a non-blocking accept now, as the
2181: accept on some systems (DYNIX) completes the connection. On
2182: BSD systems, the completion is done independently of the
2183: accept. (thanks to Bill Rushka, wcr@aps.org) */
2184: if (data->active == NO && !retry) {
2185: int newfd;
2186: if ((newfd = HTDoAccept(data->socket)) >= 0) {
2187: #ifdef REPEAT_LISTEN
2188: if (TRACE) fprintf(stderr, "FTP......... Passive data channel number %d stays open.\n", data->socket);
2189: #else
2190: if (TRACE) fprintf(stderr, "FTP......... Passive data channel number %d closed.\n", data->socket);
2191: if (NETCLOSE(data->socket) < 0) {
2192: HTInetStatus("close");
2193: state = ERROR;
2194: break;
2195: }
1.22 frystyk 2196: #endif
1.23 frystyk 2197: data->socket = newfd; /* Switch to new socket */
2198: if (TRACE)
2199: fprintf(stderr, "FTP......... New data socket: %d\n",
2200: data->socket);
2201: } else {
2202: state = ERROR;
2203: break;
2204: }
1.1 timbl 2205: }
1.28 frystyk 2206: status = HTFTP_get_response(ctrl, &ctrl->reply);
1.23 frystyk 2207: if (status/100 == 1)
2208: state = READY_FOR_DATA;
2209: else if (status/100 == 4)
2210: state = FAILURE;
2211: else
2212: state = ERROR;
2213: break;
1.1 timbl 2214:
1.23 frystyk 2215: case SENT_CWD:
1.28 frystyk 2216: status = HTFTP_get_response(ctrl, &ctrl->reply);
1.23 frystyk 2217: if (status/100 == 2) {
2218: /* Update current location */
2219: if (*ctrl->location)
2220: StrAllocCat(ctrl->location, "/");
2221: StrAllocCat(ctrl->location, relative);
1.28 frystyk 2222: HTChunkClear(ctrl->welcome);
2223: HTFTPAddWelcome(ctrl);
1.23 frystyk 2224: state = NEED_LIST;
2225: } else if (status/100 == 4)
2226: state = FAILURE;
2227: else
2228: state = MULTIPLE_CWD;
2229: break;
1.1 timbl 2230:
1.23 frystyk 2231: case MULTIPLE_CWD:
2232: /* We must use the escaped version when looking for '/' as
2233: delimiter between segments, and then unescape each segment */
2234: if (TRACE) fprintf(stderr, "FTP......... Can't jump directly to location, try multiple CD's instead\n");
2235: state = NEED_LIST; /* This is overwritten if error */
2236: {
2237: char *path = NULL;
2238: char *segment;
2239: StrAllocCopy(path, relative);
2240: segment = strtok(path, "/");
2241: while (segment && *segment) {
2242: HTUnEscape(segment);
2243: if (HTFTP_send_cmd(ctrl, "CWD", segment)) {
2244: state = ERROR;
2245: break;
2246: }
1.28 frystyk 2247: status = HTFTP_get_response(ctrl, &ctrl->reply);
1.23 frystyk 2248: if (status/100 != 2) {
2249: if (status/100 == 4)
2250: state = FAILURE;
2251: else
2252: state = ERROR;
2253: break;
2254: } else { /* Update current location */
2255: char *new_seg = HTEscape(segment, URL_XPALPHAS);
2256: if (*ctrl->location)
2257: StrAllocCat(ctrl->location, "/");
2258: StrAllocCat(ctrl->location, new_seg);
2259: free(new_seg);
1.28 frystyk 2260: HTChunkClear(ctrl->welcome);
2261: HTFTPAddWelcome(ctrl);
1.23 frystyk 2262: }
2263: segment = strtok(NULL, "/"); /* Get next token */
1.22 frystyk 2264: }
1.23 frystyk 2265: free(path);
1.22 frystyk 2266: }
1.23 frystyk 2267: break;
1.22 frystyk 2268:
1.23 frystyk 2269: case READY_FOR_DATA:
2270: /* Now, the browsing module can be called */
2271: {
2272: char *url = HTAnchor_physical(req->anchor);
2273: char *path = HTParse(url, "", PARSE_PATH+PARSE_PUNCTUATION);
2274: HTUnEscape(path);
2275: if (TRACE)
2276: fprintf(stderr, "FTP......... Receiving directory `%s\'\n",
2277: path);
2278: status = HTFTPBrowseDirectory(req, path, data,
2279: HTFTP_get_dir_string);
2280: if (status == -1)
2281: state = ERROR;
2282: else if (status == HT_INTERRUPTED) {
2283: if (!HTFTP_send_cmd(ctrl, "ABOR", NULL))
2284: state = SENT_ABOR;
2285: else
2286: state = ERROR;
2287: } else
2288: state = GOT_DATA;
2289: free(path);
1.22 frystyk 2290: }
1.23 frystyk 2291: break;
2292:
2293: case GOT_DATA:
1.28 frystyk 2294: status = HTFTP_get_response(ctrl, &ctrl->reply);
1.23 frystyk 2295: if (status/100 == 2)
2296: state = SUCCESS; /* Directory read */
2297: else if (status/100 == 4)
2298: state = FAILURE;
2299: else
2300: state = ERROR;
2301: break;
2302:
2303: case SENT_ABOR:
1.28 frystyk 2304: status = HTFTP_get_response(ctrl, &ctrl->reply);
1.23 frystyk 2305: if (status/100 == 2)
2306: state = SUCCESS;
2307: else if (status/100 == 4)
2308: state = FAILURE;
1.22 frystyk 2309: else
1.23 frystyk 2310: state = ERROR;
2311: break;
2312:
2313: case FAILURE: /* Otherwise gcc complains :-( */
2314: case ERROR:
2315: case SUCCESS:
2316: break;
1.22 frystyk 2317: }
1.23 frystyk 2318: }
2319: FREE(unescaped);
2320: return state;
2321: }
2322:
2323:
2324: /* HTFTP_get_file
2325: **
2326: ** This function asks for the file specified. First we try in one go,
2327: ** but if that doesn't work, then we use CWD for each segment and then
2328: ** try to retrieve it. If that also fails, then we try if it is a
2329: ** directory. This procedure causes that directory links generated in
2330: ** HTFTPBrowseDirectory should have a '/' at the end in order to go
2331: ** directly to HTFTP_get_dir. The relative is searched relative to
2332: ** the login directory, that is without a starting '/'.
2333: **
2334: ** Returns -2 on ERROR, -1 on FAILURE, 0 on SUCCESS.
2335: */
2336: PRIVATE int HTFTP_get_file ARGS4(ftp_ctrl_info *, ctrl, HTRequest *, req,
2337: char *, relative, char, retry)
2338: {
2339: enum _state {
2340: ERROR = -2,
2341: FAILURE = -1,
2342: SUCCESS = 0,
2343: BEGIN,
2344: SENT_RETR,
2345: MULTIPLE_CWD,
2346: READY_FOR_DATA,
2347: SENT_ABOR,
2348: GOT_DATA
2349: } state = BEGIN;
2350: BOOL multiple = NO; /* Have we already tried multiple CWD? */
2351: ftp_data_info *data = (ftp_data_info *) ctrl->data_cons->next->object;
2352: int status;
2353: char *unescaped = NULL;
2354: StrAllocCopy(unescaped, relative);
2355: HTUnEscape(unescaped);
2356:
2357: /* Thsi loop only stops if state is ERROR, FAILURE or SUCCESS */
2358: while (state > 0) {
2359: switch (state) {
2360: case BEGIN:
2361: /* First we try to retrieve the file in one go. */
2362: if (!HTFTP_send_cmd(ctrl, "RETR", unescaped))
2363: state = SENT_RETR;
2364: else
2365: state = ERROR;
2366: break;
2367:
2368: case SENT_RETR:
2369: /* If we are listening, then do a non-blocking accept now, as the
2370: accept on some systems (DYNIX) completes the connection. On
2371: BSD systems, the completion is done independently of the
2372: accept. (thanks to Bill Rushka, wcr@aps.org) */
2373: if (data->active == NO && !retry) {
2374: int newfd;
2375: if ((newfd = HTDoAccept(data->socket)) >= 0) {
2376: #ifdef REPEAT_LISTEN
2377: if (TRACE) fprintf(stderr, "FTP......... Passive data channel number %d stays open.\n", data->socket);
2378: #else
2379: if (TRACE) fprintf(stderr, "FTP......... Passive data channel number %d closed.\n", data->socket);
2380: if (NETCLOSE(data->socket) < 0) {
2381: HTInetStatus("close");
2382: state = ERROR;
2383: break;
2384: }
1.22 frystyk 2385: #endif
1.23 frystyk 2386: data->socket = newfd; /* Switch to new socket */
2387: if (TRACE)
2388: fprintf(stderr, "FTP......... New data socket: %d\n",
2389: data->socket);
2390: } else {
2391: state = ERROR;
2392: break;
2393: }
2394: }
1.28 frystyk 2395: status = HTFTP_get_response(ctrl, &ctrl->reply);
1.23 frystyk 2396: if (status/100 == 1)
2397: state = READY_FOR_DATA;
2398: else if (status/100 == 4)
2399: state = FAILURE;
2400: /* If there is no '/' in unescaped, it won't help to try
2401: multiple CWD's, as it either doesn't exist or is a directory */
2402: else if (multiple == NO && strchr(unescaped, '/') != NULL)
2403: state = MULTIPLE_CWD;
2404: else {
2405: data->directory = YES;
2406: state = FAILURE;
2407: }
2408: break;
2409:
2410: case MULTIPLE_CWD:
2411: /* We must use the escaped version when looking for '/' as
2412: delimiter between segments, and then unescape each segment */
2413: if (TRACE) fprintf(stderr, "FTP......... Can't jump directly to location, try multiple CD's instead\n");
2414: multiple = YES;
2415: {
2416: char *path = NULL;
2417: char *segment;
2418: char *last_slash; /* Used to identify the last segment */
2419: StrAllocCopy(path, relative);
2420: if ((last_slash = strrchr(path, '/')) == NULL)
2421: last_slash = path;
2422: else
2423: last_slash++;
2424: segment = strtok(path, "/");
2425: while (segment && *segment && segment != last_slash) {
2426: HTUnEscape(segment);
2427: if (HTFTP_send_cmd(ctrl, "CWD", segment)) {
2428: state = ERROR;
2429: break;
2430: }
1.28 frystyk 2431: status = HTFTP_get_response(ctrl, &ctrl->reply);
1.23 frystyk 2432: if (status/100 != 2) {
2433: if (status/100 == 4)
2434: state = FAILURE;
2435: else
2436: state = ERROR;
2437: break;
2438: } else { /* Update current location */
2439: char *new_seg = HTEscape(segment, URL_XPALPHAS);
2440: if (*ctrl->location)
2441: StrAllocCat(ctrl->location, "/");
2442: StrAllocCat(ctrl->location, new_seg);
2443: free(new_seg);
2444: }
2445: segment = strtok(NULL, "/"); /* Get next token */
2446: }
2447: /* Now try to retrieve the last segment */
2448: if (segment == last_slash) {
2449: HTUnEscape(segment);
2450: if (!HTFTP_send_cmd(ctrl, "RETR", segment))
2451: state = SENT_RETR;
2452: else
2453: state = ERROR;
2454: } else {
2455: if (TRACE) fprintf(stderr, "FTP......... Strange error, filename not found?\n");
2456: state = ERROR;
2457: }
2458: free(path);
2459: }
2460: break;
2461:
2462: case READY_FOR_DATA:
2463: /* Now, the browsing module can be called */
2464: if (TRACE) fprintf(stderr, "FTP......... Receiving file `%s\'\n",
2465: unescaped);
2466: status = HTParseSocket(data->fileformat, data->socket, req);
2467: if (status != HT_LOADED) {
2468: if (status == HT_INTERRUPTED) {
2469: if (!HTFTP_send_cmd(ctrl, "ABOR", NULL))
2470: state = SENT_ABOR;
2471: else
2472: state = ERROR;
2473: } else
2474: state = ERROR;
2475: } else
2476: state = GOT_DATA;
2477: break;
2478:
2479: case GOT_DATA:
1.28 frystyk 2480: status = HTFTP_get_response(ctrl, &ctrl->reply);
1.23 frystyk 2481: if (status/100 == 2)
2482: state = SUCCESS; /* Directory read */
2483: else if (status/100 == 4)
2484: state = FAILURE;
2485: else
2486: state = ERROR;
2487: break;
1.22 frystyk 2488:
1.23 frystyk 2489: case SENT_ABOR:
1.28 frystyk 2490: status = HTFTP_get_response(ctrl, &ctrl->reply);
1.23 frystyk 2491: if (status/100 == 2)
2492: state = SUCCESS;
2493: else if (status/100 == 4)
2494: state = FAILURE;
1.22 frystyk 2495: else
1.23 frystyk 2496: state = ERROR;
2497: break;
2498:
2499: case FAILURE: /* Otherwise gcc complains :-( */
2500: case ERROR:
2501: case SUCCESS:
2502: break;
1.1 timbl 2503: }
2504: }
1.23 frystyk 2505: FREE(unescaped);
2506: return state;
2507: }
1.1 timbl 2508:
1.23 frystyk 2509:
2510: /* ------------------------------------------------------------------------- */
2511: /* PUBLIC FTP functions */
2512: /* ------------------------------------------------------------------------- */
2513:
2514: /* HTFTP_enable_session
2515: **
2516: ** This function makes it possible to reuse the same control connections
2517: ** until they are either timed out by the server, or that the session
2518: ** is closed by HTFTP_end_session. Note that HTFTPLoad can run
2519: ** independently of start and end session, and then each load runs like
2520: ** an atomic action.
2521: */
2522: PUBLIC void HTFTP_enable_session NOARGS
2523: {
2524: if (session) {
2525: if (TRACE)
2526: fprintf(stderr, "FTP......... Session is already enabled?\n");
2527: return;
2528: }
2529: session = HTList_new();
2530: }
2531:
2532:
2533: /* HTFTP_disable_session
2534: **
2535: ** This function is the closing function for HTFTP_enable_session.
2536: **
2537: ** Returns YES if OK, else NO
1.1 timbl 2538: */
1.23 frystyk 2539: PUBLIC BOOL HTFTP_disable_session NOARGS
2540: {
2541: BOOL status = YES;
2542: if (!session) {
2543: if (TRACE)
2544: fprintf(stderr, "FTP......... No session to disable?\n");
2545: return NO;
2546: }
1.1 timbl 2547: {
1.23 frystyk 2548: HTList *cur = session;
2549: ftp_ctrl_info *pres;
2550: while ((pres = (ftp_ctrl_info *) HTList_nextObject(cur))) {
2551: if (HTFTP_close_ctrl_con(pres))
2552: status = NO;
2553: }
2554: HTList_delete(session);
1.1 timbl 2555: }
1.23 frystyk 2556: session = NULL;
2557: return status;
2558: }
2559:
2560:
2561: /* Retrieve File from Server as an atomic action.
2562: ** -----------------------------------------------
2563: **
2564: ** On entry,
2565: ** request This is the request structure
2566: ** On exit,
2567: ** returns <0 Error has occured
2568: ** HT_LOADED OK
2569: */
2570: PUBLIC int HTFTPLoad ARGS1(HTRequest *, request)
2571: {
2572: char *url = HTAnchor_physical(request->anchor);
2573: int status = -1;
2574: int retry; /* How many times tried? */
2575: ftp_ctrl_info *ctrl;
2576:
2577: /* Initiate a (possibly already exsisting) control connection and a
2578: corresponding data connection */
2579: HTSimplify(url);
1.29 ! frystyk 2580: if((ctrl = HTFTP_init_con(request, url)) == NULL) {
1.25 frystyk 2581: HTLoadError(request, 500,
2582: "Could not establish connection to FTP-server\n");
1.23 frystyk 2583: return -1;
1.25 frystyk 2584: }
1.23 frystyk 2585:
2586: /* Only if the control connection is in IDLE state, a new
2587: transfer can be started. The control connection can be in another
2588: mode if (session), and then the request is getting queued in
2589: ctrl->data_cons. */
2590: if (ctrl->state == IDLE || (session && ctrl->state == LOGGED_IN)) {
2591: ftp_data_info *data = ctrl->data_cons->next->object;
2592: if (ctrl->state == IDLE)
2593: ctrl->state = BEGIN;
2594: while (ctrl->state != IDLE) { /* Do until finished */
2595: switch (ctrl->state) {
2596: case BEGIN:
2597: if (!HTFTP_login(ctrl))
2598: ctrl->state = LOGGED_IN;
2599: else
2600: ctrl->state = ERROR;
2601: break;
2602:
2603: case LOGGED_IN:
2604: if (!HTFTP_get_data_con(data, url))
2605: ctrl->state = GOT_DATA_CON;
2606: else
2607: ctrl->state = ERROR;
2608: break;
2609:
2610: case GOT_DATA_CON:
2611: {
2612: /* Now we must ask for the URL requested. If FAILURE, then
2613: we try twice to see, if it helps */
2614: char *rel;
2615: for (retry=0; retry<2; retry++) {
2616: if ((rel = HTFTPLocation(ctrl, url)) == NULL) {
2617: ctrl->state = ERROR;
2618: break;
2619: }
2620: if (retry == 1 && TRACE) fprintf(stderr,
2621: "FTP......... First attempt to get URL failed, let's try again\n");
2622:
2623: if (data->directory == YES) {
2624: /* If we haven't already got server-info */
2625: if (ctrl->server == UNKNOWN) {
2626: if (HTFTPServerInfo(ctrl)) {
2627: ctrl->state = ERROR;
2628: break;
2629: }
2630: }
2631: status = HTFTP_get_dir(ctrl, request, rel, retry);
2632: }
2633: else
2634: status = HTFTP_get_file(ctrl, request, rel, retry);
2635: if (!status) {
2636: ctrl->state = GOT_DATA;
2637: break;
2638: } else if (status == -2) { /* Error */
2639: ctrl->state = ERROR;
2640: break;
2641: } else {
2642: free(rel);
2643: ctrl->state = FAILURE; /* Try twice */
2644: }
2645: }
2646: free(rel);
2647: }
2648: if (retry == 2 && ctrl->state == FAILURE)
2649: ctrl->state = ERROR;
2650: break;
2651:
2652: case GOT_DATA:
2653: if (HTFTP_close_data_con(data))
2654: ctrl->state = ERROR;
2655: else {
2656: HTList_removeLastObject(ctrl->data_cons);
2657: if (!session) {
2658: if (!HTFTP_logout(ctrl)) {
2659: ctrl->state = IDLE;
2660: status = HT_LOADED;
2661: } else
2662: ctrl->state = ERROR;
2663: } else {
2664: ctrl->state = LOGGED_IN; /* Ready for next request */
2665: return HT_LOADED;
2666: }
2667: break;
2668: }
2669: break;
2670:
2671: case ERROR:
1.25 frystyk 2672: HTLoadError(request, 500, ctrl->reply->data);
1.23 frystyk 2673: HTFTP_abort_ctrl_con(ctrl);
2674: return -1; /* Exit immediately */
2675: break;
2676:
2677: default:
2678: if (TRACE) fprintf(stderr, "FTP......... Unknown state, what happened?\n");
2679: break;
2680: }
2681: }
1.22 frystyk 2682:
1.23 frystyk 2683: /* The control connection is only closed if the load is atomic */
2684: if (!session && HTFTP_close_ctrl_con(ctrl))
2685: status = -1;
2686: }
2687: if (status < 0 && status != HT_INTERRUPTED)
2688: return HTLoadError(request, 500,
2689: "Document not loaded due to strange behavior from FTP-server\n");
2690: return status;
2691: }
1.22 frystyk 2692:
1.23 frystyk 2693: /* END OF MODULE */
1.22 frystyk 2694:
2695:
Webmaster