]> git.ipfire.org Git - thirdparty/squid.git/blame - src/ftp.cc
adding
[thirdparty/squid.git] / src / ftp.cc
CommitLineData
95d659f0 1
30a4f2a8 2/*
65c0dfdb 3 * $Id: ftp.cc,v 1.94 1997/01/13 19:29:12 wessels Exp $
30a4f2a8 4 *
5 * DEBUG: section 9 File Transfer Protocol (FTP)
6 * AUTHOR: Harvest Derived
7 *
42c04c16 8 * SQUID Internet Object Cache http://squid.nlanr.net/Squid/
30a4f2a8 9 * --------------------------------------------------------
10 *
11 * Squid is the result of efforts by numerous individuals from the
12 * Internet community. Development is led by Duane Wessels of the
13 * National Laboratory for Applied Network Research and funded by
14 * the National Science Foundation.
15 *
16 * This program is free software; you can redistribute it and/or modify
17 * it under the terms of the GNU General Public License as published by
18 * the Free Software Foundation; either version 2 of the License, or
19 * (at your option) any later version.
20 *
21 * This program is distributed in the hope that it will be useful,
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 * GNU General Public License for more details.
25 *
26 * You should have received a copy of the GNU General Public License
27 * along with this program; if not, write to the Free Software
28 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
29 *
30 */
019dd986 31
32/*
30a4f2a8 33 * Copyright (c) 1994, 1995. All rights reserved.
34 *
35 * The Harvest software was developed by the Internet Research Task
36 * Force Research Group on Resource Discovery (IRTF-RD):
37 *
38 * Mic Bowman of Transarc Corporation.
39 * Peter Danzig of the University of Southern California.
40 * Darren R. Hardy of the University of Colorado at Boulder.
41 * Udi Manber of the University of Arizona.
42 * Michael F. Schwartz of the University of Colorado at Boulder.
43 * Duane Wessels of the University of Colorado at Boulder.
44 *
45 * This copyright notice applies to software in the Harvest
46 * ``src/'' directory only. Users should consult the individual
47 * copyright notices in the ``components/'' subdirectories for
48 * copyright information about other software bundled with the
49 * Harvest source code distribution.
50 *
51 * TERMS OF USE
52 *
53 * The Harvest software may be used and re-distributed without
54 * charge, provided that the software origin and research team are
55 * cited in any use of the system. Most commonly this is
56 * accomplished by including a link to the Harvest Home Page
57 * (http://harvest.cs.colorado.edu/) from the query page of any
58 * Broker you deploy, as well as in the query result pages. These
59 * links are generated automatically by the standard Broker
60 * software distribution.
61 *
62 * The Harvest software is provided ``as is'', without express or
63 * implied warranty, and with no support nor obligation to assist
64 * in its use, correction, modification or enhancement. We assume
65 * no liability with respect to the infringement of copyrights,
66 * trade secrets, or any patents, and are not responsible for
67 * consequential damages. Proper use of the Harvest software is
68 * entirely the responsibility of the user.
69 *
70 * DERIVATIVE WORKS
71 *
72 * Users may make derivative works from the Harvest software, subject
73 * to the following constraints:
74 *
75 * - You must include the above copyright notice and these
76 * accompanying paragraphs in all forms of derivative works,
77 * and any documentation and other materials related to such
78 * distribution and use acknowledge that the software was
79 * developed at the above institutions.
80 *
81 * - You must notify IRTF-RD regarding your distribution of
82 * the derivative work.
83 *
84 * - You must clearly notify users that your are distributing
85 * a modified version and not the original Harvest software.
86 *
87 * - Any derivative product is also subject to these copyright
88 * and use restrictions.
89 *
90 * Note that the Harvest software is NOT in the public domain. We
91 * retain copyright, as specified above.
92 *
93 * HISTORY OF FREE SOFTWARE STATUS
94 *
95 * Originally we required sites to license the software in cases
96 * where they were going to build commercial products/services
97 * around Harvest. In June 1995 we changed this policy. We now
98 * allow people to use the core Harvest software (the code found in
99 * the Harvest ``src/'' directory) for free. We made this change
100 * in the interest of encouraging the widest possible deployment of
101 * the technology. The Harvest software is really a reference
102 * implementation of a set of protocols and formats, some of which
103 * we intend to standardize. We encourage commercial
104 * re-implementations of code complying to this set of standards.
019dd986 105 */
44a47c6e 106
107#include "squid.h"
090089c4 108
30a4f2a8 109#define FTP_DELETE_GAP (1<<18)
1a6e21ac 110#define MAGIC_MARKER "\004\004\004" /* No doubt this should be more configurable */
111#define MAGIC_MARKER_SZ 3
090089c4 112
30a4f2a8 113static int ftpget_server_read = -1;
114static int ftpget_server_write = -1;
115static u_short ftpget_port = 0;
4db43fab 116static const char *const w_space = " \t\n\r";
090089c4 117
118typedef struct _Ftpdata {
119 StoreEntry *entry;
983061ed 120 request_t *request;
77a30ebb 121 char user[MAX_URL];
122 char password[MAX_URL];
f0afe435 123 char *reply_hdr;
090089c4 124 int ftp_fd;
1a6e21ac 125 int got_marker; /* denotes end of successful request */
f0afe435 126 int reply_hdr_state;
e381a13d 127 int authenticated; /* This ftp request is authenticated */
e5f6c5c2 128 ConnectStateData connectState;
129} FtpStateData;
090089c4 130
983061ed 131/* Local functions */
0ee4272b 132static const char *ftpTransferMode _PARAMS((const char *));
133static char *ftpGetBasicAuth _PARAMS((const char *));
e5f6c5c2 134static int ftpReadReply _PARAMS((int, FtpStateData *));
135static int ftpStateFree _PARAMS((int, FtpStateData *));
136static void ftpConnectDone _PARAMS((int fd, int status, void *data));
137static void ftpLifetimeExpire _PARAMS((int, FtpStateData *));
0ee4272b 138static void ftpProcessReplyHeader _PARAMS((FtpStateData *, const char *, int));
24382924 139static void ftpSendComplete _PARAMS((int, char *, int, int, void *));
e5f6c5c2 140static void ftpSendRequest _PARAMS((int, FtpStateData *));
24382924 141static void ftpServerClosed _PARAMS((int, void *));
0ee4272b 142static void ftp_login_parser _PARAMS((const char *, FtpStateData *));
983061ed 143
e381a13d 144/* External functions */
0ee4272b 145extern char *base64_decode _PARAMS((const char *coded));
e381a13d 146
b8d8561b 147static int
e5f6c5c2 148ftpStateFree(int fd, FtpStateData * ftpState)
ba718c8f 149{
51fa90db 150 if (ftpState == NULL)
151 return 1;
30a4f2a8 152 storeUnlockObject(ftpState->entry);
51fa90db 153 if (ftpState->reply_hdr) {
154 put_free_8k_page(ftpState->reply_hdr);
155 ftpState->reply_hdr = NULL;
f0afe435 156 }
30a4f2a8 157 requestUnlink(ftpState->request);
51fa90db 158 xfree(ftpState);
159 return 0;
ba718c8f 160}
161
b8d8561b 162static void
0ee4272b 163ftp_login_parser(const char *login, FtpStateData * data)
090089c4 164{
77a30ebb 165 char *user = data->user;
166 char *password = data->password;
983061ed 167 char *s = NULL;
090089c4 168
983061ed 169 strcpy(user, login);
170 s = strchr(user, ':');
171 if (s) {
172 *s = 0;
d3c1a245 173 strcpy(password, s + 1);
983061ed 174 } else {
3c62dc1b 175 strcpy(password, null_string);
090089c4 176 }
983061ed 177
178 if (!*user && !*password) {
090089c4 179 strcpy(user, "anonymous");
b6f794d6 180 strcpy(password, Config.ftpUser);
983061ed 181 }
090089c4 182}
183
090089c4 184/* This will be called when socket lifetime is expired. */
24382924 185static void
e5f6c5c2 186ftpLifetimeExpire(int fd, FtpStateData * data)
090089c4 187{
188 StoreEntry *entry = NULL;
189 entry = data->entry;
593c9a75 190 debug(9, 4, "ftpLifeTimeExpire: FD %d: '%s'\n", fd, entry->url);
ce49f524 191 squid_error_entry(entry, ERR_LIFETIME_EXP, NULL);
51fa90db 192 comm_close(fd);
090089c4 193}
194
195
f0afe435 196/* This is too much duplicated code from httpProcessReplyHeader. Only
e5f6c5c2 197 * difference is FtpStateData vs HttpData. */
b8d8561b 198static void
0ee4272b 199ftpProcessReplyHeader(FtpStateData * data, const char *buf, int size)
f0afe435 200{
f0afe435 201 char *t = NULL;
f0afe435 202 StoreEntry *entry = data->entry;
f0afe435 203 int room;
204 int hdr_len;
33b589ff 205 struct _http_reply *reply = entry->mem_obj->reply;
f0afe435 206
207 debug(11, 3, "ftpProcessReplyHeader: key '%s'\n", entry->key);
208
209 if (data->reply_hdr == NULL) {
2daae136 210 data->reply_hdr = get_free_8k_page();
f0afe435 211 memset(data->reply_hdr, '\0', 8192);
212 }
213 if (data->reply_hdr_state == 0) {
214 hdr_len = strlen(data->reply_hdr);
215 room = 8191 - hdr_len;
216 strncat(data->reply_hdr, buf, room < size ? room : size);
217 hdr_len += room < size ? room : size;
218 if (hdr_len > 4 && strncmp(data->reply_hdr, "HTTP/", 5)) {
219 debug(11, 3, "ftpProcessReplyHeader: Non-HTTP-compliant header: '%s'\n", entry->key);
220 data->reply_hdr_state += 2;
221 return;
222 }
30a4f2a8 223 /* Find the end of the headers */
33b589ff 224 if ((t = mime_headers_end(data->reply_hdr)) == NULL)
f0afe435 225 return; /* headers not complete */
30a4f2a8 226 /* Cut after end of headers */
f0afe435 227 *t = '\0';
f0afe435 228 data->reply_hdr_state++;
229 }
230 if (data->reply_hdr_state == 1) {
f0afe435 231 data->reply_hdr_state++;
232 debug(11, 9, "GOT HTTP REPLY HDR:\n---------\n%s\n----------\n",
233 data->reply_hdr);
30a4f2a8 234 /* Parse headers into reply structure */
48f44632 235 httpParseReplyHeaders(data->reply_hdr, reply);
ca98227c 236 storeTimestampsSet(entry);
30a4f2a8 237 /* Check if object is cacheable or not based on reply code */
f0afe435 238 if (reply->code)
239 debug(11, 3, "ftpProcessReplyHeader: HTTP CODE: %d\n", reply->code);
240 switch (reply->code) {
241 case 200: /* OK */
242 case 203: /* Non-Authoritative Information */
243 case 300: /* Multiple Choices */
244 case 301: /* Moved Permanently */
245 case 410: /* Gone */
246 /* These can be cached for a long time, make the key public */
1c481e00 247 if (BIT_TEST(entry->flag, ENTRY_CACHABLE))
f0afe435 248 storeSetPublicKey(entry);
249 break;
30a4f2a8 250 case 302: /* Moved Temporarily */
234967c9 251 case 304: /* Not Modified */
f0afe435 252 case 401: /* Unauthorized */
253 case 407: /* Proxy Authentication Required */
254 /* These should never be cached at all */
f0afe435 255 storeExpireNow(entry);
1c481e00 256 BIT_RESET(entry->flag, ENTRY_CACHABLE);
2daae136 257 storeReleaseRequest(entry);
f0afe435 258 break;
259 default:
260 /* These can be negative cached, make key public */
79b5cc5f 261 storeNegativeCache(entry);
1c481e00 262 if (BIT_TEST(entry->flag, ENTRY_CACHABLE))
f0afe435 263 storeSetPublicKey(entry);
264 break;
265 }
266 }
267}
268
090089c4 269
270/* This will be called when data is ready to be read from fd. Read until
271 * error or connection closed. */
24382924 272static int
e5f6c5c2 273ftpReadReply(int fd, FtpStateData * data)
090089c4 274{
95d659f0 275 LOCAL_ARRAY(char, buf, SQUID_TCP_SO_RCVBUF);
090089c4 276 int len;
277 int clen;
278 int off;
30a4f2a8 279 int bin;
090089c4 280 StoreEntry *entry = NULL;
281
282 entry = data->entry;
30a4f2a8 283 if (entry->flag & DELETE_BEHIND && !storeClientWaiting(entry)) {
284 /* we can terminate connection right now */
285 squid_error_entry(entry, ERR_NO_CLIENTS_BIG_OBJ, NULL);
286 comm_close(fd);
287 return 0;
288 }
289 /* check if we want to defer reading */
290 clen = entry->mem_obj->e_current_len;
291 off = storeGetLowestReaderOffset(entry);
292 if ((clen - off) > FTP_DELETE_GAP) {
293 if (entry->flag & CLIENT_ABORT_REQUEST) {
294 squid_error_entry(entry, ERR_CLIENT_ABORT, NULL);
51fa90db 295 comm_close(fd);
090089c4 296 }
30a4f2a8 297 IOStats.Ftp.reads_deferred++;
298 debug(11, 3, "ftpReadReply: Read deferred for Object: %s\n",
299 entry->url);
300 debug(11, 3, " Current Gap: %d bytes\n", clen - off);
301 /* reschedule, so it will be automatically reactivated
302 * when Gap is big enough. */
b177367b 303 commSetSelect(fd,
30a4f2a8 304 COMM_SELECT_READ,
305 (PF) ftpReadReply,
b177367b 306 (void *) data, 0);
56fa4cad 307 if (!BIT_TEST(entry->flag, READ_DEFERRED)) {
308 /* NOTE there is no read timeout handler to disable */
309 BIT_SET(entry->flag, READ_DEFERRED);
310 }
30a4f2a8 311 /* dont try reading again for a while */
b6f794d6 312 comm_set_stall(fd, Config.stallDelay);
30a4f2a8 313 return 0;
56fa4cad 314 } else {
315 BIT_RESET(entry->flag, READ_DEFERRED);
090089c4 316 }
1a6e21ac 317 errno = 0;
30a4f2a8 318 len = read(fd, buf, SQUID_TCP_SO_RCVBUF);
019dd986 319 debug(9, 5, "ftpReadReply: FD %d, Read %d bytes\n", fd, len);
30a4f2a8 320 if (len > 0) {
4a63c85f 321 IOStats.Ftp.reads++;
30a4f2a8 322 for (clen = len - 1, bin = 0; clen; bin++)
323 clen >>= 1;
324 IOStats.Ftp.read_hist[bin]++;
325 }
ba718c8f 326 if (len < 0) {
881f7a6c 327 debug(50, 1, "ftpReadReply: read error: %s\n", xstrerror());
ba718c8f 328 if (errno == EAGAIN || errno == EWOULDBLOCK) {
6fe6313d 329 /* reinstall handlers */
330 /* XXX This may loop forever */
b177367b 331 commSetSelect(fd, COMM_SELECT_READ,
332 (PF) ftpReadReply, (void *) data, 0);
6fe6313d 333 /* note there is no ftpReadReplyTimeout. Timeouts are handled
334 * by `ftpget'. */
335 } else {
1c481e00 336 BIT_RESET(entry->flag, ENTRY_CACHABLE);
2daae136 337 storeReleaseRequest(entry);
b8de7ebe 338 squid_error_entry(entry, ERR_READ_ERROR, xstrerror());
51fa90db 339 comm_close(fd);
6fe6313d 340 }
ba718c8f 341 } else if (len == 0 && entry->mem_obj->e_current_len == 0) {
b8de7ebe 342 squid_error_entry(entry,
ba718c8f 343 ERR_ZERO_SIZE_OBJECT,
344 errno ? xstrerror() : NULL);
51fa90db 345 comm_close(fd);
090089c4 346 } else if (len == 0) {
347 /* Connection closed; retrieval done. */
1a6e21ac 348 if (!data->got_marker) {
ba718c8f 349 /* If we didn't see the magic marker, assume the transfer
350 * failed and arrange so the object gets ejected and
351 * never gets to disk. */
30a4f2a8 352 debug(9, 1, "ftpReadReply: Purging '%s'\n", entry->url);
79b5cc5f 353 storeNegativeCache(entry);
1c481e00 354 BIT_RESET(entry->flag, ENTRY_CACHABLE);
2daae136 355 storeReleaseRequest(entry);
1a6e21ac 356 } else if (!(entry->flag & DELETE_BEHIND)) {
ca98227c 357 storeTimestampsSet(entry);
090089c4 358 }
359 /* update fdstat and fdtable */
090089c4 360 storeComplete(entry);
51fa90db 361 comm_close(fd);
090089c4 362 } else if (entry->flag & CLIENT_ABORT_REQUEST) {
363 /* append the last bit of info we get */
364 storeAppend(entry, buf, len);
b8de7ebe 365 squid_error_entry(entry, ERR_CLIENT_ABORT, NULL);
51fa90db 366 comm_close(fd);
090089c4 367 } else {
65c0dfdb 368 if (data->got_marker) {
369 /* oh, this is so gross -- we found the marker at the
370 * end of the previous read, but theres more data!
371 * So put the marker back in. */
372 storeAppend(entry, MAGIC_MARKER, MAGIC_MARKER_SZ);
373 }
1a6e21ac 374 /* check for a magic marker at the end of the read */
ba718c8f 375 data->got_marker = 0;
1a6e21ac 376 if (len >= MAGIC_MARKER_SZ) {
377 if (!memcmp(MAGIC_MARKER, buf + len - MAGIC_MARKER_SZ, MAGIC_MARKER_SZ)) {
378 data->got_marker = 1;
379 len -= MAGIC_MARKER_SZ;
380 }
381 }
090089c4 382 storeAppend(entry, buf, len);
2291ddab 383 if (data->reply_hdr_state < 2 && len > 0)
384 ftpProcessReplyHeader(data, buf, len);
b177367b 385 commSetSelect(fd,
090089c4 386 COMM_SELECT_READ,
387 (PF) ftpReadReply,
b177367b 388 (void *) data, 0);
389 commSetSelect(fd,
090089c4 390 COMM_SELECT_TIMEOUT,
391 (PF) ftpLifetimeExpire,
51496678 392 (void *) data,
b6f794d6 393 Config.readTimeout);
090089c4 394 }
395 return 0;
396}
397
24382924 398static void
b8d8561b 399ftpSendComplete(int fd, char *buf, int size, int errflag, void *data)
77a30ebb 400{
e5f6c5c2 401 FtpStateData *ftpState = (FtpStateData *) data;
77a30ebb 402 StoreEntry *entry = NULL;
403
30a4f2a8 404 entry = ftpState->entry;
019dd986 405 debug(9, 5, "ftpSendComplete: FD %d: size %d: errflag %d.\n",
77a30ebb 406 fd, size, errflag);
407
408 if (buf) {
2daae136 409 put_free_8k_page(buf); /* Allocated by ftpSendRequest. */
77a30ebb 410 buf = NULL;
411 }
77a30ebb 412 if (errflag) {
ce49f524 413 squid_error_entry(entry, ERR_CONNECT_FAIL, xstrerror());
414 comm_close(fd);
77a30ebb 415 return;
77a30ebb 416 }
df64e67e 417 commSetSelect(ftpState->ftp_fd,
418 COMM_SELECT_READ,
419 (PF) ftpReadReply,
420 (void *) ftpState, 0);
421 commSetSelect(ftpState->ftp_fd,
422 COMM_SELECT_TIMEOUT,
423 (PF) ftpLifetimeExpire,
424 (void *) ftpState, Config.readTimeout);
77a30ebb 425}
426
0ee4272b 427static const char *
428ftpTransferMode(const char *urlpath)
058faf85 429{
0ee4272b 430 const char *const ftpASCII = "A";
431 const char *const ftpBinary = "I";
058faf85 432 char *ext = NULL;
0ee4272b 433 const ext_table_entry *mime = NULL;
058faf85 434 int len;
435 len = strlen(urlpath);
436 if (*(urlpath + len - 1) == '/')
437 return ftpASCII;
438 if ((ext = strrchr(urlpath, '.')) == NULL)
439 return ftpBinary;
440 if ((mime = mime_ext_to_type(++ext)) == NULL)
441 return ftpBinary;
442 if (!strcmp(mime->mime_encoding, "7bit"))
443 return ftpASCII;
444 return ftpBinary;
445}
446
24382924 447static void
e5f6c5c2 448ftpSendRequest(int fd, FtpStateData * data)
77a30ebb 449{
77a30ebb 450 char *path = NULL;
0ee4272b 451 const char *mode = NULL;
77a30ebb 452 char *buf = NULL;
95d659f0 453 LOCAL_ARRAY(char, tbuf, BUFSIZ);
454 LOCAL_ARRAY(char, opts, BUFSIZ);
0ee4272b 455 const char *const space = " ";
77a30ebb 456 char *s = NULL;
457 int got_timeout = 0;
458 int got_negttl = 0;
459 int buflen;
460
019dd986 461 debug(9, 5, "ftpSendRequest: FD %d\n", fd);
77a30ebb 462
983061ed 463 buflen = strlen(data->request->urlpath) + 256;
2daae136 464 buf = (char *) get_free_8k_page();
77a30ebb 465 memset(buf, '\0', buflen);
466
983061ed 467 path = data->request->urlpath;
058faf85 468 mode = ftpTransferMode(path);
77a30ebb 469
77a30ebb 470 /* Start building the buffer ... */
b6f794d6 471 strcat(buf, Config.Program.ftpget);
77a30ebb 472 strcat(buf, space);
473
d5aa0e3b 474 xstrncpy(opts, Config.Program.ftpget_opts, BUFSIZ);
77a30ebb 475 for (s = strtok(opts, w_space); s; s = strtok(NULL, w_space)) {
476 strcat(buf, s);
477 strcat(buf, space);
478 if (!strncmp(s, "-t", 2))
479 got_timeout = 1;
480 if (!strncmp(s, "-n", 2))
481 got_negttl = 1;
482 }
483 if (!got_timeout) {
b6f794d6 484 sprintf(tbuf, "-t %d ", Config.readTimeout);
77a30ebb 485 strcat(buf, tbuf);
486 }
487 if (!got_negttl) {
b6f794d6 488 sprintf(tbuf, "-n %d ", Config.negativeTtl);
77a30ebb 489 strcat(buf, tbuf);
490 }
983061ed 491 if (data->request->port) {
492 sprintf(tbuf, "-P %d ", data->request->port);
00132f18 493 strcat(buf, tbuf);
494 }
b6f794d6 495 if ((s = Config.visibleHostname)) {
30a4f2a8 496 sprintf(tbuf, "-H %s ", s);
497 strcat(buf, tbuf);
498 }
e381a13d 499 if (data->authenticated) {
500 strcat(buf, "-a ");
501 }
451bf90b 502 if (Config.Addrs.tcp_outgoing.s_addr != inaddr_none) {
caebbe00 503 sprintf(tbuf, "-o %s ", inet_ntoa(Config.Addrs.tcp_outgoing));
504 strcat(buf, tbuf);
505 }
77a30ebb 506 strcat(buf, "-h "); /* httpify */
507 strcat(buf, "- "); /* stdout */
983061ed 508 strcat(buf, data->request->host);
77a30ebb 509 strcat(buf, space);
234967c9 510 strcat(buf, *path ? path : "\"\"");
77a30ebb 511 strcat(buf, space);
512 strcat(buf, mode); /* A or I */
513 strcat(buf, space);
234967c9 514 strcat(buf, *data->user ? data->user : "\"\"");
77a30ebb 515 strcat(buf, space);
234967c9 516 strcat(buf, *data->password ? data->password : "\"\"");
517 strcat(buf, "\n");
019dd986 518 debug(9, 5, "ftpSendRequest: FD %d: buf '%s'\n", fd, buf);
30a4f2a8 519 comm_write(fd,
2285407f 520 buf,
521 strlen(buf),
522 30,
523 ftpSendComplete,
9864ee44 524 (void *) data,
525 put_free_8k_page);
77a30ebb 526}
527
0f9306d9 528static char *
0ee4272b 529ftpGetBasicAuth(const char *req_hdr)
0f9306d9 530{
531 char *auth_hdr;
532 char *t;
533 if (req_hdr == NULL)
534 return NULL;
535 if ((auth_hdr = mime_get_header(req_hdr, "Authorization")) == NULL)
536 return NULL;
537 if ((t = strtok(auth_hdr, " \t")) == NULL)
538 return NULL;
539 if (strcasecmp(t, "Basic") != 0)
540 return NULL;
541 if ((t = strtok(NULL, " \t")) == NULL)
542 return NULL;
543 return base64_decode(t);
544}
545
77a30ebb 546
b8d8561b 547int
fe4e214f 548ftpStart(int unusedfd, const char *url, request_t * request, StoreEntry * entry)
090089c4 549{
95d659f0 550 LOCAL_ARRAY(char, realm, 8192);
e5f6c5c2 551 FtpStateData *ftpData = NULL;
e381a13d 552 char *req_hdr = entry->mem_obj->mime_hdr;
e381a13d 553 char *response;
554 char *auth;
555
593c9a75 556 debug(9, 3, "FtpStart: FD %d '%s'\n", unusedfd, url);
090089c4 557
c021888f 558 if (ftpget_server_write < 0) {
559 squid_error_entry(entry, ERR_FTP_DISABLED, NULL);
560 return COMM_ERROR;
561 }
e5f6c5c2 562 ftpData = xcalloc(1, sizeof(FtpStateData));
563 storeLockObject(ftpData->entry = entry, NULL, NULL);
564 ftpData->request = requestLink(request);
090089c4 565
983061ed 566 /* Parse login info. */
0f9306d9 567 if ((auth = ftpGetBasicAuth(req_hdr))) {
e5f6c5c2 568 ftp_login_parser(auth, ftpData);
569 ftpData->authenticated = 1;
e381a13d 570 } else {
e5f6c5c2 571 ftp_login_parser(request->login, ftpData);
572 if (*ftpData->user && !*ftpData->password) {
e381a13d 573 /* This request is not fully authenticated */
574 if (request->port == 21) {
e5f6c5c2 575 sprintf(realm, "ftp %s", ftpData->user);
e381a13d 576 } else {
577 sprintf(realm, "ftp %s port %d",
e5f6c5c2 578 ftpData->user, request->port);
e381a13d 579 }
580 response = authorization_needed_msg(request, realm);
581 storeAppend(entry, response, strlen(response));
48f44632 582 httpParseReplyHeaders(response, entry->mem_obj->reply);
e381a13d 583 storeComplete(entry);
e5f6c5c2 584 ftpStateFree(-1, ftpData);
e381a13d 585 return COMM_OK;
586 }
587 }
983061ed 588
589 debug(9, 5, "FtpStart: FD %d, host=%s, path=%s, user=%s, passwd=%s\n",
e5f6c5c2 590 unusedfd, ftpData->request->host, ftpData->request->urlpath,
591 ftpData->user, ftpData->password);
77a30ebb 592
e5f6c5c2 593 ftpData->ftp_fd = comm_open(SOCK_STREAM,
16b204c4 594 0,
30a4f2a8 595 local_addr,
596 0,
16b204c4 597 COMM_NONBLOCKING,
30a4f2a8 598 url);
e5f6c5c2 599 if (ftpData->ftp_fd == COMM_ERROR) {
b8de7ebe 600 squid_error_entry(entry, ERR_CONNECT_FAIL, xstrerror());
e5f6c5c2 601 ftpStateFree(-1, ftpData);
090089c4 602 return COMM_ERROR;
603 }
77a30ebb 604 /* Pipe/socket created ok */
090089c4 605
51fa90db 606 /* register close handler */
e5f6c5c2 607 comm_add_close_handler(ftpData->ftp_fd,
983061ed 608 (PF) ftpStateFree,
e5f6c5c2 609 (void *) ftpData);
51fa90db 610
77a30ebb 611 /* Now connect ... */
e5f6c5c2 612 ftpData->connectState.fd = ftpData->ftp_fd;
613 ftpData->connectState.host = localhost;
614 ftpData->connectState.port = ftpget_port;
615 ftpData->connectState.handler = ftpConnectDone;
616 ftpData->connectState.data = ftpData;
617 comm_nbconnect(ftpData->ftp_fd, &ftpData->connectState);
618 return COMM_OK;
619}
090089c4 620
e5f6c5c2 621static void
622ftpConnectDone(int fd, int status, void *data)
623{
624 FtpStateData *ftpData = data;
625 if (status == COMM_ERROR) {
626 squid_error_entry(ftpData->entry, ERR_CONNECT_FAIL, xstrerror());
627 comm_close(fd);
628 return;
629 }
630 fdstat_open(fd, FD_SOCKET);
631 commSetNonBlocking(fd);
632 (void) fd_note(fd, ftpData->entry->url);
090089c4 633 /* Install connection complete handler. */
e5f6c5c2 634 fd_note(fd, ftpData->entry->url);
b177367b 635 commSetSelect(fd,
77a30ebb 636 COMM_SELECT_WRITE,
637 (PF) ftpSendRequest,
b177367b 638 (void *) data, 0);
e5f6c5c2 639 comm_set_fd_lifetime(fd,
b6f794d6 640 Config.lifetimeDefault);
b177367b 641 commSetSelect(fd,
77a30ebb 642 COMM_SELECT_LIFETIME,
090089c4 643 (PF) ftpLifetimeExpire,
b177367b 644 (void *) ftpData, 0);
f900607e 645 if (opt_no_ipcache)
e5f6c5c2 646 ipcacheInvalidate(ftpData->request->host);
9d4b2981 647 if (vizSock > -1)
df64e67e 648 vizHackSendPkt(&ftpData->connectState.S, 2);
090089c4 649}
650
b8d8561b 651static void
652ftpServerClosed(int fd, void *nodata)
234967c9 653{
654 static time_t last_restart = 0;
655 comm_close(fd);
656 if (squid_curtime - last_restart < 2) {
657 debug(9, 0, "ftpget server failing too rapidly\n");
658 debug(9, 0, "WARNING: FTP access is disabled!\n");
c021888f 659 ftpget_server_write = -1;
660 ftpget_server_read = -1;
234967c9 661 return;
662 }
663 last_restart = squid_curtime;
664 debug(9, 1, "Restarting ftpget server...\n");
665 (void) ftpInitialize();
666}
667
b8d8561b 668void
0673c0ba 669ftpServerClose(void)
234967c9 670{
30a4f2a8 671 /* NOTE: this function will be called repeatedly while shutdown is
672 * pending */
673 if (ftpget_server_read < 0)
234967c9 674 return;
b177367b 675 commSetSelect(ftpget_server_read,
30a4f2a8 676 COMM_SELECT_READ,
234967c9 677 (PF) NULL,
b177367b 678 (void *) NULL, 0);
30a4f2a8 679 fdstat_close(ftpget_server_read);
680 close(ftpget_server_read);
681 fdstat_close(ftpget_server_write);
682 close(ftpget_server_write);
683 ftpget_server_read = -1;
684 ftpget_server_write = -1;
234967c9 685}
686
687
b8d8561b 688int
0673c0ba 689ftpInitialize(void)
77a30ebb 690{
ff8d0ea6 691 pid_t pid;
30a4f2a8 692 int cfd;
693 int squid_to_ftpget[2];
694 int ftpget_to_squid[2];
95d659f0 695 LOCAL_ARRAY(char, pbuf, 128);
b6f794d6 696 char *ftpget = Config.Program.ftpget;
30a4f2a8 697 struct sockaddr_in S;
698 int len;
c420f330 699 struct timeval slp;
77a30ebb 700
c021888f 701 if (!strcmp(ftpget, "none")) {
f6c78bd2 702 debug(9, 1, "ftpInitialize: ftpget is disabled.\n");
c021888f 703 return -1;
704 }
92a8c005 705 debug(9, 5, "ftpInitialize: Initializing...\n");
30a4f2a8 706 if (pipe(squid_to_ftpget) < 0) {
881f7a6c 707 debug(50, 0, "ftpInitialize: pipe: %s\n", xstrerror());
77a30ebb 708 return -1;
709 }
30a4f2a8 710 if (pipe(ftpget_to_squid) < 0) {
881f7a6c 711 debug(50, 0, "ftpInitialize: pipe: %s\n", xstrerror());
30a4f2a8 712 return -1;
713 }
16b204c4 714 cfd = comm_open(SOCK_STREAM,
715 0,
30a4f2a8 716 local_addr,
717 0,
16b204c4 718 COMM_NOCLOEXEC,
30a4f2a8 719 "ftpget -S socket");
92a8c005 720 debug(9, 5, "ftpget -S socket on FD %d\n", cfd);
30a4f2a8 721 if (cfd == COMM_ERROR) {
722 debug(9, 0, "ftpInitialize: Failed to create socket\n");
723 return -1;
724 }
725 len = sizeof(S);
726 memset(&S, '\0', len);
727 if (getsockname(cfd, (struct sockaddr *) &S, &len) < 0) {
881f7a6c 728 debug(50, 0, "ftpInitialize: getsockname: %s\n", xstrerror());
30a4f2a8 729 comm_close(cfd);
730 return -1;
731 }
732 ftpget_port = ntohs(S.sin_port);
e83892e9 733 listen(cfd, Squid_MaxFD >> 2);
77a30ebb 734 if ((pid = fork()) < 0) {
881f7a6c 735 debug(50, 0, "ftpInitialize: fork: %s\n", xstrerror());
30a4f2a8 736 comm_close(cfd);
13a0a452 737 fatal("Failed to fork() for ftpget.");
77a30ebb 738 }
739 if (pid != 0) { /* parent */
30a4f2a8 740 comm_close(cfd);
741 close(squid_to_ftpget[0]);
742 close(ftpget_to_squid[1]);
743 fdstat_open(squid_to_ftpget[1], FD_PIPE);
744 fdstat_open(ftpget_to_squid[0], FD_PIPE);
745 fd_note(squid_to_ftpget[1], "ftpget -S");
746 fd_note(ftpget_to_squid[0], "ftpget -S");
c420f330 747 commSetCloseOnExec(squid_to_ftpget[1]);
748 commSetCloseOnExec(ftpget_to_squid[0]);
234967c9 749 /* if ftpget -S goes away, this handler should get called */
b177367b 750 commSetSelect(ftpget_to_squid[0],
30a4f2a8 751 COMM_SELECT_READ,
234967c9 752 (PF) ftpServerClosed,
b177367b 753 (void *) NULL, 0);
30a4f2a8 754 ftpget_server_write = squid_to_ftpget[1];
755 ftpget_server_read = ftpget_to_squid[0];
c420f330 756 slp.tv_sec = 0;
757 slp.tv_usec = 250000;
758 select(0, NULL, NULL, NULL, &slp);
77a30ebb 759 return 0;
760 }
761 /* child */
234967c9 762 /* give up all extra priviligies */
763 no_suid();
764 /* set up stdin,stdout */
30a4f2a8 765 dup2(squid_to_ftpget[0], 0);
766 dup2(ftpget_to_squid[1], 1);
2c47cf74 767 dup2(fileno(debug_log), 2);
30a4f2a8 768 close(squid_to_ftpget[0]);
769 close(squid_to_ftpget[1]);
770 close(ftpget_to_squid[0]);
771 close(ftpget_to_squid[1]);
772 dup2(cfd, 3); /* pass listening socket to ftpget */
77a30ebb 773 /* inherit stdin,stdout,stderr */
30a4f2a8 774 for (cfd = 4; cfd <= fdstat_biggest_fd(); cfd++)
775 (void) close(cfd);
776 sprintf(pbuf, "%d", ftpget_port);
a26bdc75 777 execlp(ftpget, ftpget, "-S", pbuf, NULL);
881f7a6c 778 debug(50, 0, "ftpInitialize: %s: %s\n", ftpget, xstrerror());
77a30ebb 779 _exit(1);
780 return (1); /* eliminate compiler warning */
781}