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