]>
Commit | Line | Data |
---|---|---|
6fe6313d | 1 | /* $Id: ftp.cc,v 1.14 1996/03/28 20:42:46 wessels Exp $ */ |
44a47c6e | 2 | |
3 | #include "squid.h" | |
090089c4 | 4 | |
5 | #define FTP_DELETE_GAP (64*1024) | |
1a6e21ac | 6 | #define READBUFSIZ 4096 |
7 | #define MAGIC_MARKER "\004\004\004" /* No doubt this should be more configurable */ | |
8 | #define MAGIC_MARKER_SZ 3 | |
090089c4 | 9 | |
10 | static char ftpASCII[] = "A"; | |
11 | static char ftpBinary[] = "I"; | |
12 | ||
13 | typedef struct _Ftpdata { | |
14 | StoreEntry *entry; | |
090089c4 | 15 | char type_id; |
77a30ebb | 16 | char host[SQUIDHOSTNAMELEN + 1]; |
090089c4 | 17 | char request[MAX_URL]; |
77a30ebb | 18 | char user[MAX_URL]; |
19 | char password[MAX_URL]; | |
090089c4 | 20 | char *type; |
21 | char *mime_hdr; | |
090089c4 | 22 | int ftp_fd; |
77a30ebb | 23 | char *icp_page_ptr; /* Used to send proxy-http request: |
24 | * put_free_8k_page(me) if the lifetime | |
25 | * expires */ | |
26 | char *icp_rwd_ptr; /* When a lifetime expires during the | |
27 | * middle of an icpwrite, don't lose the | |
28 | * icpReadWriteData */ | |
1a6e21ac | 29 | int got_marker; /* denotes end of successful request */ |
090089c4 | 30 | } FtpData; |
31 | ||
090089c4 | 32 | /* XXX: this does not support FTP on a different port! */ |
77a30ebb | 33 | int ftp_url_parser(url, data) |
090089c4 | 34 | char *url; |
77a30ebb | 35 | FtpData *data; |
090089c4 | 36 | { |
37 | static char atypebuf[MAX_URL]; | |
38 | static char hostbuf[MAX_URL]; | |
39 | char *tmp = NULL; | |
40 | int t; | |
77a30ebb | 41 | char *host = data->host; |
42 | char *request = data->request; | |
43 | char *user = data->user; | |
44 | char *password = data->password; | |
090089c4 | 45 | |
46 | /* initialize everything */ | |
47 | atypebuf[0] = hostbuf[0] = '\0'; | |
48 | request[0] = host[0] = user[0] = password[0] = '\0'; | |
49 | ||
50 | t = sscanf(url, "%[a-zA-Z]://%[^/]%s", atypebuf, hostbuf, request); | |
51 | if ((t < 2) || | |
52 | !(!strcasecmp(atypebuf, "ftp") || !strcasecmp(atypebuf, "file"))) { | |
53 | return -1; | |
54 | } else if (t == 2) { /* no request */ | |
55 | strcpy(request, "/"); | |
56 | } else { | |
57 | tmp = url_convert_hex(request); /* convert %xx to char */ | |
58 | strncpy(request, tmp, MAX_URL); | |
59 | safe_free(tmp); | |
60 | } | |
61 | ||
62 | /* url address format is something like this: | |
63 | * [ userid [ : password ] @ ] host | |
64 | * or possibly even | |
65 | * [ [ userid ] [ : [ password ] ] @ ] host | |
66 | * | |
67 | * So we must try to make sense of it. */ | |
68 | ||
69 | /* XXX: this only support [user:passwd@]host */ | |
70 | t = sscanf(hostbuf, "%[^:]:%[^@]@%s", user, password, host); | |
71 | if (t < 3) { | |
72 | strcpy(host, user); /* no login/passwd information */ | |
73 | strcpy(user, "anonymous"); | |
74 | strcpy(password, "harvest@"); | |
75 | } | |
76 | /* we need to convert user and password for URL encodings */ | |
77 | tmp = url_convert_hex(user); | |
78 | strcpy(user, tmp); | |
79 | safe_free(tmp); | |
80 | ||
81 | tmp = url_convert_hex(password); | |
82 | strcpy(password, tmp); | |
83 | safe_free(tmp); | |
84 | ||
85 | return 0; | |
86 | } | |
87 | ||
88 | int ftpCachable(url, type, mime_hdr) | |
89 | char *url; | |
90 | char *type; | |
91 | char *mime_hdr; | |
92 | { | |
93 | stoplist *p = NULL; | |
94 | ||
95 | /* scan stop list */ | |
96 | p = ftp_stoplist; | |
97 | while (p) { | |
98 | if (strstr(url, p->key)) | |
99 | return 0; | |
100 | p = p->next; | |
101 | } | |
102 | ||
103 | /* else cachable */ | |
104 | return 1; | |
105 | } | |
106 | ||
107 | /* This will be called when socket lifetime is expired. */ | |
108 | void ftpLifetimeExpire(fd, data) | |
109 | int fd; | |
110 | FtpData *data; | |
111 | { | |
112 | StoreEntry *entry = NULL; | |
113 | entry = data->entry; | |
12b9e9b1 | 114 | debug(0, 4, "ftpLifeTimeExpire: FD %d: <URL:%s>\n", fd, entry->url); |
77a30ebb | 115 | if (data->icp_page_ptr) { |
116 | put_free_8k_page(data->icp_page_ptr); | |
117 | data->icp_page_ptr = NULL; | |
118 | } | |
119 | safe_free(data->icp_rwd_ptr); | |
b367f261 | 120 | cached_error_entry(entry, ERR_LIFETIME_EXP, NULL); |
090089c4 | 121 | comm_close(fd); |
090089c4 | 122 | safe_free(data); |
123 | } | |
124 | ||
125 | ||
126 | ||
127 | /* This will be called when data is ready to be read from fd. Read until | |
128 | * error or connection closed. */ | |
129 | int ftpReadReply(fd, data) | |
130 | int fd; | |
131 | FtpData *data; | |
132 | { | |
1a6e21ac | 133 | static char buf[READBUFSIZ]; |
090089c4 | 134 | int len; |
135 | int clen; | |
136 | int off; | |
137 | StoreEntry *entry = NULL; | |
138 | ||
139 | entry = data->entry; | |
140 | if (entry->flag & DELETE_BEHIND) { | |
141 | if (storeClientWaiting(entry)) { | |
142 | /* check if we want to defer reading */ | |
22e4fa85 | 143 | clen = entry->mem_obj->e_current_len; |
144 | off = entry->mem_obj->e_lowest_offset; | |
090089c4 | 145 | if ((clen - off) > FTP_DELETE_GAP) { |
12b9e9b1 | 146 | debug(0, 3, "ftpReadReply: Read deferred for Object: %s\n", entry->key); |
147 | debug(0, 3, "--> Current Gap: %d bytes\n", clen - off); | |
090089c4 | 148 | /* reschedule, so it will automatically be reactivated when |
149 | * Gap is big enough. */ | |
150 | comm_set_select_handler(fd, | |
151 | COMM_SELECT_READ, | |
152 | (PF) ftpReadReply, | |
153 | (caddr_t) data); | |
45cd3339 | 154 | comm_set_stall(fd, getStallDelay()); /* dont try reading again for a while */ |
090089c4 | 155 | return 0; |
156 | } | |
157 | } else { | |
158 | /* we can terminate connection right now */ | |
b367f261 | 159 | cached_error_entry(entry, ERR_NO_CLIENTS_BIG_OBJ, NULL); |
090089c4 | 160 | comm_close(fd); |
090089c4 | 161 | safe_free(data); |
162 | return 0; | |
163 | } | |
164 | } | |
1a6e21ac | 165 | errno = 0; |
166 | len = read(fd, buf, READBUFSIZ); | |
12b9e9b1 | 167 | debug(0, 5, "ftpReadReply: FD %d, Read %d bytes\n", fd, len); |
090089c4 | 168 | |
22e4fa85 | 169 | if (len < 0 || ((len == 0) && (entry->mem_obj->e_current_len == 0))) { |
090089c4 | 170 | if (len < 0) |
12b9e9b1 | 171 | debug(0, 1, "ftpReadReply: read error: %s\n", xstrerror()); |
6fe6313d | 172 | if (errno == ECONNRESET) { |
173 | /* Connection reset by peer */ | |
174 | /* consider it as a EOF */ | |
175 | if (!(entry->flag & DELETE_BEHIND)) | |
176 | entry->expires = cached_curtime + ttlSet(entry); | |
177 | sprintf(tmp_error_buf, "\nWarning: The Remote Server sent RESET at the end of transmission.\n"); | |
178 | storeAppend(entry, tmp_error_buf, strlen(tmp_error_buf)); | |
179 | storeComplete(entry); | |
180 | comm_close(fd); | |
181 | safe_free(data); | |
182 | } else if (errno == EAGAIN || errno == EWOULDBLOCK) { | |
183 | /* reinstall handlers */ | |
184 | /* XXX This may loop forever */ | |
185 | comm_set_select_handler(fd, COMM_SELECT_READ, | |
186 | (PF) ftpReadReply, (caddr_t) data); | |
187 | /* note there is no ftpReadReplyTimeout. Timeouts are handled | |
188 | * by `ftpget'. */ | |
189 | } else { | |
190 | cached_error_entry(entry, ERR_READ_ERROR, xstrerror()); | |
191 | comm_close(fd); | |
192 | safe_free(data); | |
193 | } | |
090089c4 | 194 | } else if (len == 0) { |
195 | /* Connection closed; retrieval done. */ | |
1a6e21ac | 196 | if (!data->got_marker) { |
197 | /* If we didn't see the magic marker, assume the transfer failed and arrange | |
198 | * so the object gets ejected and never gets to disk. */ | |
12b9e9b1 | 199 | debug(0, 1, "ftpReadReply: Didn't see magic marker, purging <URL:%s>.\n", entry->url); |
090089c4 | 200 | entry->expires = cached_curtime + getNegativeTTL(); |
201 | BIT_RESET(entry->flag, CACHABLE); | |
202 | BIT_SET(entry->flag, RELEASE_REQUEST); | |
1a6e21ac | 203 | } else if (!(entry->flag & DELETE_BEHIND)) { |
090089c4 | 204 | entry->expires = cached_curtime + ttlSet(entry); |
205 | } | |
206 | /* update fdstat and fdtable */ | |
207 | comm_close(fd); | |
208 | storeComplete(entry); | |
209 | safe_free(data); | |
22e4fa85 | 210 | } else if (((entry->mem_obj->e_current_len + len) > getFtpMax()) && |
090089c4 | 211 | !(entry->flag & DELETE_BEHIND)) { |
212 | /* accept data, but start to delete behind it */ | |
213 | storeStartDeleteBehind(entry); | |
090089c4 | 214 | storeAppend(entry, buf, len); |
215 | comm_set_select_handler(fd, | |
216 | COMM_SELECT_READ, | |
217 | (PF) ftpReadReply, | |
218 | (caddr_t) data); | |
090089c4 | 219 | } else if (entry->flag & CLIENT_ABORT_REQUEST) { |
220 | /* append the last bit of info we get */ | |
221 | storeAppend(entry, buf, len); | |
b367f261 | 222 | cached_error_entry(entry, ERR_CLIENT_ABORT, NULL); |
090089c4 | 223 | comm_close(fd); |
090089c4 | 224 | safe_free(data); |
225 | } else { | |
1a6e21ac | 226 | /* check for a magic marker at the end of the read */ |
227 | if (len >= MAGIC_MARKER_SZ) { | |
228 | if (!memcmp(MAGIC_MARKER, buf + len - MAGIC_MARKER_SZ, MAGIC_MARKER_SZ)) { | |
229 | data->got_marker = 1; | |
230 | len -= MAGIC_MARKER_SZ; | |
231 | } | |
232 | } | |
090089c4 | 233 | storeAppend(entry, buf, len); |
234 | comm_set_select_handler(fd, | |
235 | COMM_SELECT_READ, | |
236 | (PF) ftpReadReply, | |
237 | (caddr_t) data); | |
238 | comm_set_select_handler_plus_timeout(fd, | |
239 | COMM_SELECT_TIMEOUT, | |
240 | (PF) ftpLifetimeExpire, | |
241 | (caddr_t) data, | |
242 | getReadTimeout()); | |
243 | } | |
244 | return 0; | |
245 | } | |
246 | ||
77a30ebb | 247 | void ftpSendComplete(fd, buf, size, errflag, data) |
248 | int fd; | |
249 | char *buf; | |
250 | int size; | |
251 | int errflag; | |
252 | FtpData *data; | |
253 | { | |
254 | StoreEntry *entry = NULL; | |
255 | ||
256 | entry = data->entry; | |
12b9e9b1 | 257 | debug(0, 5, "ftpSendComplete: FD %d: size %d: errflag %d.\n", |
77a30ebb | 258 | fd, size, errflag); |
259 | ||
260 | if (buf) { | |
261 | put_free_8k_page(buf); /* Allocated by ftpSendRequest. */ | |
262 | buf = NULL; | |
263 | } | |
264 | data->icp_page_ptr = NULL; /* So lifetime expire doesn't re-free */ | |
265 | data->icp_rwd_ptr = NULL; /* Don't double free in lifetimeexpire */ | |
266 | ||
267 | if (errflag) { | |
b367f261 | 268 | cached_error_entry(entry, ERR_CONNECT_FAIL, xstrerror()); |
77a30ebb | 269 | comm_close(fd); |
270 | safe_free(data); | |
271 | return; | |
272 | } else { | |
273 | comm_set_select_handler(data->ftp_fd, | |
274 | COMM_SELECT_READ, | |
275 | (PF) ftpReadReply, | |
276 | (caddr_t) data); | |
277 | comm_set_select_handler_plus_timeout(data->ftp_fd, | |
278 | COMM_SELECT_TIMEOUT, | |
279 | (PF) ftpLifetimeExpire, | |
280 | (caddr_t) data, getReadTimeout()); | |
281 | } | |
282 | } | |
283 | ||
284 | void ftpSendRequest(fd, data) | |
285 | int fd; | |
286 | FtpData *data; | |
287 | { | |
288 | char *ext = NULL; | |
289 | ext_table_entry *e = NULL; | |
290 | int l; | |
291 | char *path = NULL; | |
292 | char *mode = NULL; | |
293 | char *buf = NULL; | |
294 | static char tbuf[BUFSIZ]; | |
295 | static char opts[BUFSIZ]; | |
296 | static char *space = " "; | |
77a30ebb | 297 | char *s = NULL; |
298 | int got_timeout = 0; | |
299 | int got_negttl = 0; | |
300 | int buflen; | |
301 | ||
12b9e9b1 | 302 | debug(0, 5, "ftpSendRequest: FD %d\n", fd); |
77a30ebb | 303 | |
304 | buflen = strlen(data->request) + 256; | |
305 | buf = (char *) get_free_8k_page(); | |
306 | data->icp_page_ptr = buf; | |
307 | memset(buf, '\0', buflen); | |
308 | ||
309 | path = data->request; | |
310 | l = strlen(path); | |
311 | if (path[l - 1] == '/') | |
312 | mode = ftpASCII; | |
313 | else { | |
314 | if ((ext = strrchr(path, '.')) != NULL) { | |
315 | ext++; | |
316 | mode = ((e = mime_ext_to_type(ext)) && | |
317 | strncmp(e->mime_type, "text", 4) == 0) ? ftpASCII : | |
318 | ftpBinary; | |
319 | } else | |
320 | mode = ftpBinary; | |
321 | } | |
322 | ||
323 | /* Remove leading slash from FTP url-path so that we can | |
324 | * handle ftp://user:pw@host/path objects where path and /path | |
325 | * are quite different. -DW */ | |
326 | if (!strcmp(path, "/")) | |
327 | *path = '.'; | |
328 | if (*path == '/') | |
329 | path++; | |
330 | ||
331 | /* Start building the buffer ... */ | |
332 | ||
333 | strcat(buf, getFtpProgram()); | |
334 | strcat(buf, space); | |
335 | ||
336 | strncpy(opts, getFtpOptions(), BUFSIZ); | |
337 | for (s = strtok(opts, w_space); s; s = strtok(NULL, w_space)) { | |
338 | strcat(buf, s); | |
339 | strcat(buf, space); | |
340 | if (!strncmp(s, "-t", 2)) | |
341 | got_timeout = 1; | |
342 | if (!strncmp(s, "-n", 2)) | |
343 | got_negttl = 1; | |
344 | } | |
345 | if (!got_timeout) { | |
346 | sprintf(tbuf, "-t %d ", getReadTimeout()); | |
347 | strcat(buf, tbuf); | |
348 | } | |
349 | if (!got_negttl) { | |
350 | sprintf(tbuf, "-n %d ", getNegativeTTL()); | |
351 | strcat(buf, tbuf); | |
352 | } | |
353 | strcat(buf, "-h "); /* httpify */ | |
354 | strcat(buf, "- "); /* stdout */ | |
355 | strcat(buf, data->host); | |
356 | strcat(buf, space); | |
357 | strcat(buf, path); | |
358 | strcat(buf, space); | |
359 | strcat(buf, mode); /* A or I */ | |
360 | strcat(buf, space); | |
361 | strcat(buf, data->user); | |
362 | strcat(buf, space); | |
363 | strcat(buf, data->password); | |
364 | strcat(buf, space); | |
12b9e9b1 | 365 | debug(0, 5, "ftpSendRequest: FD %d: buf '%s'\n", fd, buf); |
44a47c6e | 366 | data->icp_rwd_ptr = icpWrite(fd, buf, strlen(buf), 30, ftpSendComplete, (caddr_t) data); |
77a30ebb | 367 | } |
368 | ||
369 | void ftpConnInProgress(fd, data) | |
370 | int fd; | |
371 | FtpData *data; | |
372 | { | |
373 | StoreEntry *entry = data->entry; | |
374 | ||
12b9e9b1 | 375 | debug(0, 5, "ftpConnInProgress: FD %d\n", fd); |
77a30ebb | 376 | |
377 | if (comm_connect(fd, "localhost", 3131) != COMM_OK) | |
378 | switch (errno) { | |
379 | case EINPROGRESS: | |
380 | case EALREADY: | |
381 | /* schedule this handler again */ | |
382 | comm_set_select_handler(fd, | |
383 | COMM_SELECT_WRITE, | |
384 | (PF) ftpConnInProgress, | |
385 | (caddr_t) data); | |
386 | return; | |
387 | case EISCONN: | |
12b9e9b1 | 388 | debug(0, 5, "ftpConnInProgress: FD %d is now connected.", fd); |
77a30ebb | 389 | break; /* cool, we're connected */ |
390 | default: | |
391 | comm_close(fd); | |
b367f261 | 392 | cached_error_entry(entry, ERR_CONNECT_FAIL, xstrerror()); |
77a30ebb | 393 | safe_free(data); |
394 | return; | |
395 | } | |
396 | /* Call the real write handler, now that we're fully connected */ | |
397 | comm_set_select_handler(fd, | |
398 | COMM_SELECT_WRITE, | |
399 | (PF) ftpSendRequest, | |
400 | (caddr_t) data); | |
401 | } | |
402 | ||
403 | ||
090089c4 | 404 | int ftpStart(unusedfd, url, entry) |
405 | int unusedfd; | |
406 | char *url; | |
407 | StoreEntry *entry; | |
408 | { | |
090089c4 | 409 | FtpData *data = NULL; |
77a30ebb | 410 | int status; |
090089c4 | 411 | |
12b9e9b1 | 412 | debug(0, 3, "FtpStart: FD %d <URL:%s>\n", unusedfd, url); |
090089c4 | 413 | |
414 | data = (FtpData *) xcalloc(1, sizeof(FtpData)); | |
415 | data->entry = entry; | |
416 | ||
417 | /* Parse url. */ | |
77a30ebb | 418 | if (ftp_url_parser(url, data)) { |
b367f261 | 419 | cached_error_entry(entry, ERR_INVALID_URL, NULL); |
090089c4 | 420 | safe_free(data); |
421 | return COMM_ERROR; | |
422 | } | |
12b9e9b1 | 423 | debug(0, 5, "FtpStart: FD %d, host=%s, request=%s, user=%s, passwd=%s\n", |
77a30ebb | 424 | unusedfd, data->host, data->request, data->user, data->password); |
425 | ||
426 | data->ftp_fd = comm_open(COMM_NONBLOCKING, 0, 0, url); | |
427 | if (data->ftp_fd == COMM_ERROR) { | |
b367f261 | 428 | cached_error_entry(entry, ERR_CONNECT_FAIL, xstrerror()); |
090089c4 | 429 | safe_free(data); |
430 | return COMM_ERROR; | |
431 | } | |
77a30ebb | 432 | /* Pipe/socket created ok */ |
090089c4 | 433 | |
77a30ebb | 434 | /* Now connect ... */ |
435 | if ((status = comm_connect(data->ftp_fd, "localhost", 3131))) { | |
436 | if (status != EINPROGRESS) { | |
437 | comm_close(data->ftp_fd); | |
b367f261 | 438 | cached_error_entry(entry, ERR_CONNECT_FAIL, xstrerror()); |
77a30ebb | 439 | safe_free(data); |
440 | return COMM_ERROR; | |
441 | } else { | |
12b9e9b1 | 442 | debug(0, 5, "ftpStart: FD %d: EINPROGRESS.\n", data->ftp_fd); |
77a30ebb | 443 | comm_set_select_handler(data->ftp_fd, COMM_SELECT_LIFETIME, |
444 | (PF) ftpLifetimeExpire, (caddr_t) data); | |
445 | comm_set_select_handler(data->ftp_fd, COMM_SELECT_WRITE, | |
446 | (PF) ftpConnInProgress, (caddr_t) data); | |
447 | return COMM_OK; | |
448 | } | |
449 | } | |
450 | fdstat_open(data->ftp_fd, Socket); | |
090089c4 | 451 | commSetNonBlocking(data->ftp_fd); |
452 | (void) fd_note(data->ftp_fd, entry->url); | |
453 | ||
454 | /* Install connection complete handler. */ | |
455 | fd_note(data->ftp_fd, entry->url); | |
090089c4 | 456 | comm_set_select_handler(data->ftp_fd, |
77a30ebb | 457 | COMM_SELECT_WRITE, |
458 | (PF) ftpSendRequest, | |
090089c4 | 459 | (caddr_t) data); |
77a30ebb | 460 | comm_set_fd_lifetime(data->ftp_fd, |
461 | getClientLifetime()); | |
090089c4 | 462 | comm_set_select_handler(data->ftp_fd, |
77a30ebb | 463 | COMM_SELECT_LIFETIME, |
090089c4 | 464 | (PF) ftpLifetimeExpire, |
77a30ebb | 465 | (caddr_t) data); |
466 | ||
090089c4 | 467 | return COMM_OK; |
468 | } | |
469 | ||
77a30ebb | 470 | int ftpInitialize() |
471 | { | |
472 | int pid; | |
473 | int fd; | |
474 | int p[2]; | |
475 | static char pbuf[128]; | |
476 | char *ftpget = getFtpProgram(); | |
477 | ||
478 | if (pipe(p) < 0) { | |
12b9e9b1 | 479 | debug(0, 0, "ftpInitialize: pipe: %s\n", xstrerror()); |
77a30ebb | 480 | return -1; |
481 | } | |
482 | if ((pid = fork()) < 0) { | |
12b9e9b1 | 483 | debug(0, 0, "ftpInitialize: fork: %s\n", xstrerror()); |
77a30ebb | 484 | return -1; |
485 | } | |
486 | if (pid != 0) { /* parent */ | |
487 | close(p[0]); | |
488 | fdstat_open(p[1], Pipe); | |
489 | fd_note(p[1], "ftpget -S"); | |
490 | fcntl(p[1], F_SETFD, 1); /* set close-on-exec */ | |
491 | return 0; | |
492 | } | |
493 | /* child */ | |
494 | close(0); | |
495 | dup(p[0]); | |
496 | close(p[0]); | |
497 | close(p[1]); | |
498 | /* inherit stdin,stdout,stderr */ | |
499 | for (fd = 3; fd < fdstat_biggest_fd(); fd++) | |
500 | (void) close(fd); | |
501 | sprintf(pbuf, "%d", 3131); | |
502 | execlp(ftpget, ftpget, "-D26,1", "-S", pbuf, NULL); | |
12b9e9b1 | 503 | debug(0, 0, "ftpInitialize: %s: %s\n", ftpget, xstrerror()); |
77a30ebb | 504 | _exit(1); |
505 | return (1); /* eliminate compiler warning */ | |
506 | } |