]>
Commit | Line | Data |
---|---|---|
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 | 113 | static int ftpget_server_read = -1; |
114 | static int ftpget_server_write = -1; | |
115 | static u_short ftpget_port = 0; | |
4db43fab | 116 | static const char *const w_space = " \t\n\r"; |
090089c4 | 117 | |
118 | typedef 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 | 132 | static const char *ftpTransferMode _PARAMS((const char *)); |
133 | static char *ftpGetBasicAuth _PARAMS((const char *)); | |
e5f6c5c2 | 134 | static int ftpReadReply _PARAMS((int, FtpStateData *)); |
135 | static int ftpStateFree _PARAMS((int, FtpStateData *)); | |
136 | static void ftpConnectDone _PARAMS((int fd, int status, void *data)); | |
137 | static void ftpLifetimeExpire _PARAMS((int, FtpStateData *)); | |
0ee4272b | 138 | static void ftpProcessReplyHeader _PARAMS((FtpStateData *, const char *, int)); |
24382924 | 139 | static void ftpSendComplete _PARAMS((int, char *, int, int, void *)); |
e5f6c5c2 | 140 | static void ftpSendRequest _PARAMS((int, FtpStateData *)); |
24382924 | 141 | static void ftpServerClosed _PARAMS((int, void *)); |
0ee4272b | 142 | static void ftp_login_parser _PARAMS((const char *, FtpStateData *)); |
983061ed | 143 | |
e381a13d | 144 | /* External functions */ |
0ee4272b | 145 | extern char *base64_decode _PARAMS((const char *coded)); |
e381a13d | 146 | |
b8d8561b | 147 | static int |
e5f6c5c2 | 148 | ftpStateFree(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 | 162 | static void |
0ee4272b | 163 | ftp_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 | 185 | static void |
e5f6c5c2 | 186 | ftpLifetimeExpire(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 | 198 | static void |
0ee4272b | 199 | ftpProcessReplyHeader(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 | 272 | static int |
e5f6c5c2 | 273 | ftpReadReply(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 | 398 | static void |
b8d8561b | 399 | ftpSendComplete(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 | 427 | static const char * |
428 | ftpTransferMode(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 | 447 | static void |
e5f6c5c2 | 448 | ftpSendRequest(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 | 528 | static char * |
0ee4272b | 529 | ftpGetBasicAuth(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 | 547 | int |
fe4e214f | 548 | ftpStart(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 | 621 | static void |
622 | ftpConnectDone(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 | 651 | static void |
652 | ftpServerClosed(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 | 668 | void |
0673c0ba | 669 | ftpServerClose(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 | 688 | int |
0673c0ba | 689 | ftpInitialize(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 | } |