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