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