]>
Commit | Line | Data |
---|---|---|
be335c22 | 1 | |
30a4f2a8 | 2 | /* |
067bea91 | 3 | * $Id: ftp.cc,v 1.187 1998/01/12 04:30:38 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 | |
44a47c6e | 32 | #include "squid.h" |
090089c4 | 33 | |
3fdadc70 | 34 | enum { |
35 | FTP_ISDIR, | |
36 | FTP_PASV_SUPPORTED, | |
37 | FTP_SKIP_WHITESPACE, | |
38 | FTP_REST_SUPPORTED, | |
39 | FTP_PASV_ONLY, | |
40 | FTP_AUTHENTICATED, | |
3fdadc70 | 41 | FTP_HTTP_HEADER_SENT, |
42 | FTP_TRIED_NLST, | |
43 | FTP_USE_BASE, | |
44 | FTP_ROOT_DIR, | |
13e80e5b | 45 | FTP_NO_DOTDOT, |
2761b2be | 46 | FTP_HTML_HEADER_SENT, |
969c39b9 | 47 | FTP_BINARY, |
48 | FTP_TRY_SLASH_HACK | |
3fdadc70 | 49 | }; |
50 | ||
51 | static const char *const crlf = "\r\n"; | |
52 | static char cbuf[1024]; | |
53 | ||
54 | typedef enum { | |
55 | BEGIN, | |
56 | SENT_USER, | |
57 | SENT_PASS, | |
58 | SENT_TYPE, | |
59 | SENT_MDTM, | |
60 | SENT_SIZE, | |
61 | SENT_PORT, | |
62 | SENT_PASV, | |
63 | SENT_CWD, | |
64 | SENT_LIST, | |
65 | SENT_NLST, | |
66 | SENT_REST, | |
67 | SENT_RETR, | |
68 | SENT_QUIT, | |
69 | READING_DATA | |
70 | } ftp_state_t; | |
090089c4 | 71 | |
72 | typedef struct _Ftpdata { | |
73 | StoreEntry *entry; | |
983061ed | 74 | request_t *request; |
77a30ebb | 75 | char user[MAX_URL]; |
76 | char password[MAX_URL]; | |
f0afe435 | 77 | char *reply_hdr; |
f0afe435 | 78 | int reply_hdr_state; |
3fdadc70 | 79 | char *title_url; |
80 | int conn_att; | |
81 | int login_att; | |
82 | ftp_state_t state; | |
3fdadc70 | 83 | time_t mdtm; |
84 | int size; | |
85 | int flags; | |
86 | wordlist *pathcomps; | |
87 | char *filepath; | |
88 | int restart_offset; | |
89 | int rest_att; | |
90 | char *proxy_host; | |
91 | size_t list_width; | |
92 | wordlist *cwd_message; | |
969c39b9 | 93 | char *old_request; |
94 | char *old_reply; | |
9e242e02 | 95 | char typecode; |
3fdadc70 | 96 | struct { |
97 | int fd; | |
98 | char *buf; | |
99 | size_t size; | |
100 | off_t offset; | |
101 | FREE *freefunc; | |
102 | wordlist *message; | |
b9916917 | 103 | char *last_command; |
104 | char *last_reply; | |
3fdadc70 | 105 | int replycode; |
106 | } ctrl; | |
107 | struct { | |
108 | int fd; | |
109 | char *buf; | |
110 | size_t size; | |
111 | off_t offset; | |
112 | FREE *freefunc; | |
9b312a19 | 113 | char *host; |
114 | u_short port; | |
3fdadc70 | 115 | } data; |
e5f6c5c2 | 116 | } FtpStateData; |
090089c4 | 117 | |
3fdadc70 | 118 | typedef struct { |
119 | char type; | |
120 | int size; | |
121 | char *date; | |
122 | char *name; | |
123 | char *showname; | |
124 | char *link; | |
125 | } ftpListParts; | |
126 | ||
ea3a2a69 | 127 | typedef void (FTPSM) (FtpStateData *); |
0a0bf5db | 128 | |
983061ed | 129 | /* Local functions */ |
9e4ad609 | 130 | static CNCB ftpConnectDone; |
3fdadc70 | 131 | static CNCB ftpPasvCallback; |
3fdadc70 | 132 | static PF ftpReadData; |
9e4ad609 | 133 | static PF ftpStateFree; |
5c5783a2 | 134 | static PF ftpTimeout; |
3fdadc70 | 135 | static PF ftpReadControlReply; |
136 | static CWCB ftpWriteCommandCallback; | |
f5b8bbc4 | 137 | static char *ftpGetBasicAuth(const char *); |
138 | static void ftpLoginParser(const char *, FtpStateData *); | |
f5b8bbc4 | 139 | static wordlist *ftpParseControlReply(char *buf, size_t len, int *code); |
f5b8bbc4 | 140 | static void ftpRestOrList(FtpStateData * ftpState); |
f5b8bbc4 | 141 | static void ftpDataTransferDone(FtpStateData * ftpState); |
142 | static void ftpAppendSuccessHeader(FtpStateData * ftpState); | |
143 | static char *ftpAuthRequired(const request_t *, const char *); | |
bfcaf585 | 144 | static STABH ftpAbort; |
969c39b9 | 145 | static void ftpHackShortcut(FtpStateData * ftpState, FTPSM * nextState); |
983061ed | 146 | |
969c39b9 | 147 | /* State machine functions |
148 | * send == state transition | |
149 | * read == wait for response, and select next state transition | |
150 | */ | |
3fdadc70 | 151 | static FTPSM ftpReadWelcome; |
969c39b9 | 152 | static FTPSM ftpSendUser; |
3fdadc70 | 153 | static FTPSM ftpReadUser; |
969c39b9 | 154 | static FTPSM ftpSendPass; |
3fdadc70 | 155 | static FTPSM ftpReadPass; |
969c39b9 | 156 | static FTPSM ftpSendType; |
3fdadc70 | 157 | static FTPSM ftpReadType; |
969c39b9 | 158 | static FTPSM ftpSendMdtm; |
3fdadc70 | 159 | static FTPSM ftpReadMdtm; |
969c39b9 | 160 | static FTPSM ftpSendSize; |
3fdadc70 | 161 | static FTPSM ftpReadSize; |
969c39b9 | 162 | static FTPSM ftpSendPort; |
3fdadc70 | 163 | static FTPSM ftpReadPort; |
969c39b9 | 164 | static FTPSM ftpSendPasv; |
3fdadc70 | 165 | static FTPSM ftpReadPasv; |
969c39b9 | 166 | static FTPSM ftpTraverseDirectory; /* Selects CWD or RETR */ |
167 | static FTPSM ftpSendCwd; | |
3fdadc70 | 168 | static FTPSM ftpReadCwd; |
969c39b9 | 169 | static FTPSM ftpSendList; |
170 | static FTPSM ftpSendNlst; | |
3fdadc70 | 171 | static FTPSM ftpReadList; |
969c39b9 | 172 | static FTPSM ftpSendRest; |
3fdadc70 | 173 | static FTPSM ftpReadRest; |
969c39b9 | 174 | static FTPSM ftpSendRetr; |
3fdadc70 | 175 | static FTPSM ftpReadRetr; |
176 | static FTPSM ftpReadTransferDone; | |
969c39b9 | 177 | static FTPSM ftpSendQuit; |
178 | static FTPSM ftpReadQuit; | |
179 | static FTPSM ftpFail; | |
3fdadc70 | 180 | |
181 | FTPSM *FTP_SM_FUNCS[] = | |
182 | { | |
183 | ftpReadWelcome, | |
184 | ftpReadUser, | |
185 | ftpReadPass, | |
186 | ftpReadType, | |
187 | ftpReadMdtm, | |
188 | ftpReadSize, | |
189 | ftpReadPort, | |
190 | ftpReadPasv, | |
191 | ftpReadCwd, | |
192 | ftpReadList, /* SENT_LIST */ | |
193 | ftpReadList, /* SENT_NLST */ | |
194 | ftpReadRest, | |
195 | ftpReadRetr, | |
196 | ftpReadQuit, | |
197 | ftpReadTransferDone | |
198 | }; | |
e381a13d | 199 | |
582b6456 | 200 | static void |
79d39a72 | 201 | ftpStateFree(int fdnotused, void *data) |
ba718c8f | 202 | { |
582b6456 | 203 | FtpStateData *ftpState = data; |
51fa90db | 204 | if (ftpState == NULL) |
582b6456 | 205 | return; |
9fb13bb6 | 206 | debug(9, 3) ("ftpStateFree: %s\n", storeUrl(ftpState->entry)); |
bfcaf585 | 207 | storeUnregisterAbort(ftpState->entry); |
f88211e8 | 208 | storeUnlockObject(ftpState->entry); |
51fa90db | 209 | if (ftpState->reply_hdr) { |
3f6c0fb2 | 210 | memFree(MEM_8K_BUF, ftpState->reply_hdr); |
51fa90db | 211 | ftpState->reply_hdr = NULL; |
f0afe435 | 212 | } |
30a4f2a8 | 213 | requestUnlink(ftpState->request); |
3fdadc70 | 214 | if (ftpState->ctrl.buf) |
215 | ftpState->ctrl.freefunc(ftpState->ctrl.buf); | |
216 | if (ftpState->data.buf) | |
217 | ftpState->data.freefunc(ftpState->data.buf); | |
218 | if (ftpState->pathcomps) | |
219 | wordlistDestroy(&ftpState->pathcomps); | |
220 | if (ftpState->ctrl.message) | |
221 | wordlistDestroy(&ftpState->ctrl.message); | |
222 | if (ftpState->cwd_message) | |
223 | wordlistDestroy(&ftpState->cwd_message); | |
b9916917 | 224 | safe_free(ftpState->ctrl.last_reply); |
225 | safe_free(ftpState->ctrl.last_command); | |
969c39b9 | 226 | safe_free(ftpState->old_request); |
227 | safe_free(ftpState->old_reply); | |
b5639035 | 228 | safe_free(ftpState->title_url); |
229 | safe_free(ftpState->filepath); | |
9b312a19 | 230 | safe_free(ftpState->data.host); |
a0eff6be | 231 | if (ftpState->data.fd > -1) { |
15888cf7 | 232 | comm_close(ftpState->data.fd); |
a0eff6be | 233 | ftpState->data.fd = -1; |
234 | } | |
7dd44885 | 235 | cbdataFree(ftpState); |
ba718c8f | 236 | } |
237 | ||
b8d8561b | 238 | static void |
9e4ad609 | 239 | ftpLoginParser(const char *login, FtpStateData * ftpState) |
090089c4 | 240 | { |
983061ed | 241 | char *s = NULL; |
582b6456 | 242 | xstrncpy(ftpState->user, login, MAX_URL); |
243 | if ((s = strchr(ftpState->user, ':'))) { | |
983061ed | 244 | *s = 0; |
582b6456 | 245 | xstrncpy(ftpState->password, s + 1, MAX_URL); |
983061ed | 246 | } else { |
582b6456 | 247 | xstrncpy(ftpState->password, null_string, MAX_URL); |
983061ed | 248 | } |
582b6456 | 249 | if (ftpState->user[0] || ftpState->password[0]) |
429fdbec | 250 | return; |
582b6456 | 251 | xstrncpy(ftpState->user, "anonymous", MAX_URL); |
e34e0322 | 252 | xstrncpy(ftpState->password, Config.Ftp.anon_user, MAX_URL); |
090089c4 | 253 | } |
254 | ||
24382924 | 255 | static void |
5c5783a2 | 256 | ftpTimeout(int fd, void *data) |
090089c4 | 257 | { |
582b6456 | 258 | FtpStateData *ftpState = data; |
259 | StoreEntry *entry = ftpState->entry; | |
9b312a19 | 260 | ErrorState *err; |
9fb13bb6 | 261 | debug(9, 4) ("ftpTimeout: FD %d: '%s'\n", fd, storeUrl(entry)); |
185b9571 | 262 | if (entry->store_status == STORE_PENDING) { |
263 | if (entry->mem_obj->inmem_hi == 0) { | |
fe40a877 | 264 | err = errorCon(ERR_READ_TIMEOUT, HTTP_GATEWAY_TIMEOUT); |
185b9571 | 265 | err->request = requestLink(ftpState->request); |
266 | errorAppendEntry(entry, err); | |
b50179a6 | 267 | } else { |
268 | storeAbort(entry, 0); | |
185b9571 | 269 | } |
9b312a19 | 270 | } |
15888cf7 | 271 | if (ftpState->data.fd >= 0) { |
3fdadc70 | 272 | comm_close(ftpState->data.fd); |
15888cf7 | 273 | ftpState->data.fd = -1; |
274 | } | |
3fdadc70 | 275 | comm_close(ftpState->ctrl.fd); |
3c30232f | 276 | /* don't modify ftpState here, it has been freed */ |
090089c4 | 277 | } |
278 | ||
3fdadc70 | 279 | static void |
280 | ftpListingStart(FtpStateData * ftpState) | |
281 | { | |
282 | StoreEntry *e = ftpState->entry; | |
283 | wordlist *w; | |
438fc1e3 | 284 | storeBuffer(e); |
3fdadc70 | 285 | storeAppendPrintf(e, "<!-- HTML listing generated by Squid %s -->\n", |
286 | version_string); | |
287 | storeAppendPrintf(e, "<!-- %s -->\n", mkrfc1123(squid_curtime)); | |
288 | storeAppendPrintf(e, "<HTML><HEAD><TITLE>\n"); | |
289 | storeAppendPrintf(e, "FTP Directory: %s\n", | |
290 | ftpState->title_url); | |
291 | storeAppendPrintf(e, "</TITLE>\n"); | |
292 | if (EBIT_TEST(ftpState->flags, FTP_USE_BASE)) | |
293 | storeAppendPrintf(e, "<BASE HREF=\"%s\">\n", | |
13e80e5b | 294 | ftpState->title_url); |
3fdadc70 | 295 | storeAppendPrintf(e, "</HEAD><BODY>\n"); |
296 | if (ftpState->cwd_message) { | |
297 | storeAppendPrintf(e, "<PRE>\n"); | |
298 | for (w = ftpState->cwd_message; w; w = w->next) | |
299 | storeAppendPrintf(e, "%s\n", w->key); | |
300 | storeAppendPrintf(e, "</PRE>\n"); | |
301 | storeAppendPrintf(e, "<HR>\n"); | |
302 | wordlistDestroy(&ftpState->cwd_message); | |
303 | } | |
304 | storeAppendPrintf(e, "<H2>\n"); | |
305 | storeAppendPrintf(e, "FTP Directory: %s\n", ftpState->title_url); | |
306 | storeAppendPrintf(e, "</H2>\n"); | |
307 | storeAppendPrintf(e, "<PRE>\n"); | |
438fc1e3 | 308 | storeBufferFlush(e); |
3fdadc70 | 309 | EBIT_SET(ftpState->flags, FTP_HTML_HEADER_SENT); |
310 | } | |
311 | ||
312 | static void | |
313 | ftpListingFinish(FtpStateData * ftpState) | |
314 | { | |
315 | StoreEntry *e = ftpState->entry; | |
438fc1e3 | 316 | storeBuffer(e); |
3fdadc70 | 317 | storeAppendPrintf(e, "</PRE>\n"); |
318 | storeAppendPrintf(e, "<HR>\n"); | |
319 | storeAppendPrintf(e, "<ADDRESS>\n"); | |
320 | storeAppendPrintf(e, "Generated %s, by %s/%s@%s\n", | |
321 | mkrfc1123(squid_curtime), | |
322 | appname, | |
323 | version_string, | |
324 | getMyHostname()); | |
325 | storeAppendPrintf(e, "</ADDRESS></BODY></HTML>\n"); | |
438fc1e3 | 326 | storeBufferFlush(e); |
3fdadc70 | 327 | } |
328 | ||
329 | static const char *Month[] = | |
330 | { | |
331 | "Jan", "Feb", "Mar", "Apr", "May", "Jun", | |
332 | "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" | |
333 | }; | |
334 | ||
335 | static int | |
336 | is_month(const char *buf) | |
337 | { | |
338 | int i; | |
339 | for (i = 0; i < 12; i++) | |
340 | if (!strcasecmp(buf, Month[i])) | |
341 | return 1; | |
342 | return 0; | |
343 | } | |
344 | ||
345 | ||
346 | static void | |
347 | ftpListPartsFree(ftpListParts ** parts) | |
348 | { | |
349 | safe_free((*parts)->date); | |
350 | safe_free((*parts)->name); | |
351 | safe_free((*parts)->showname); | |
352 | safe_free((*parts)->link); | |
353 | safe_free(*parts); | |
354 | } | |
355 | ||
356 | #define MAX_TOKENS 64 | |
357 | ||
75e5a32e | 358 | #define SCAN_FTP1 "%[0123456789]" |
359 | #define SCAN_FTP2 "%[0123456789:]" | |
360 | #define SCAN_FTP3 "%[0123456789]-%[0123456789]-%[0123456789]" | |
361 | #define SCAN_FTP4 "%[0123456789]:%[0123456789]%[AaPp]%[Mm]" | |
362 | ||
3fdadc70 | 363 | static ftpListParts * |
364 | ftpListParseParts(const char *buf, int flags) | |
365 | { | |
366 | ftpListParts *p = NULL; | |
367 | char *t = NULL; | |
368 | const char *ct = NULL; | |
369 | char *tokens[MAX_TOKENS]; | |
370 | int i; | |
371 | int n_tokens; | |
372 | static char sbuf[128]; | |
373 | char *xbuf = NULL; | |
374 | if (buf == NULL) | |
375 | return NULL; | |
376 | if (*buf == '\0') | |
377 | return NULL; | |
378 | p = xcalloc(1, sizeof(ftpListParts)); | |
379 | n_tokens = 0; | |
380 | for (i = 0; i < MAX_TOKENS; i++) | |
381 | tokens[i] = (char *) NULL; | |
382 | xbuf = xstrdup(buf); | |
383 | for (t = strtok(xbuf, w_space); t && n_tokens < MAX_TOKENS; t = strtok(NULL, w_space)) | |
384 | tokens[n_tokens++] = xstrdup(t); | |
385 | xfree(xbuf); | |
386 | /* locate the Month field */ | |
387 | for (i = 3; i < n_tokens - 3; i++) { | |
388 | if (!is_month(tokens[i])) /* Month */ | |
389 | continue; | |
75e5a32e | 390 | if (!sscanf(tokens[i - 1], SCAN_FTP1, sbuf)) /* Size */ |
3fdadc70 | 391 | continue; |
75e5a32e | 392 | if (!sscanf(tokens[i + 1], SCAN_FTP1, sbuf)) /* Day */ |
3fdadc70 | 393 | continue; |
75e5a32e | 394 | if (!sscanf(tokens[i + 2], SCAN_FTP2, sbuf)) /* Yr | hh:mm */ |
3fdadc70 | 395 | continue; |
396 | p->type = *tokens[0]; | |
397 | p->size = atoi(tokens[i - 1]); | |
042461c3 | 398 | snprintf(sbuf, 128, "%s %2s %5s", |
3fdadc70 | 399 | tokens[i], tokens[i + 1], tokens[i + 2]); |
400 | if (!strstr(buf, sbuf)) | |
042461c3 | 401 | snprintf(sbuf, 128, "%s %2s %-5s", |
3fdadc70 | 402 | tokens[i], tokens[i + 1], tokens[i + 2]); |
403 | if ((t = strstr(buf, sbuf))) { | |
404 | p->date = xstrdup(sbuf); | |
b9916917 | 405 | if (EBIT_TEST(flags, FTP_SKIP_WHITESPACE)) { |
3fdadc70 | 406 | t += strlen(sbuf); |
407 | while (strchr(w_space, *t)) | |
408 | t++; | |
409 | } else { | |
410 | /* XXX assumes a single space between date and filename | |
411 | * suggested by: Nathan.Bailey@cc.monash.edu.au and | |
412 | * Mike Battersby <mike@starbug.bofh.asn.au> */ | |
413 | t += strlen(sbuf) + 1; | |
414 | } | |
415 | p->name = xstrdup(t); | |
416 | if ((t = strstr(p->name, " -> "))) { | |
417 | *t = '\0'; | |
418 | p->link = xstrdup(t + 4); | |
419 | } | |
420 | } | |
421 | break; | |
422 | } | |
423 | /* try it as a DOS listing */ | |
424 | if (n_tokens > 3 && p->name == NULL && | |
75e5a32e | 425 | sscanf(tokens[0], SCAN_FTP3, sbuf, sbuf, sbuf) == 3 && |
3fdadc70 | 426 | /* 04-05-70 */ |
75e5a32e | 427 | sscanf(tokens[1], SCAN_FTP4, sbuf, sbuf, sbuf, sbuf) == 4) { |
3fdadc70 | 428 | /* 09:33PM */ |
429 | if (!strcasecmp(tokens[2], "<dir>")) { | |
430 | p->type = 'd'; | |
431 | } else { | |
432 | p->type = '-'; | |
433 | p->size = atoi(tokens[2]); | |
434 | } | |
042461c3 | 435 | snprintf(sbuf, 128, "%s %s", tokens[0], tokens[1]); |
3fdadc70 | 436 | p->date = xstrdup(sbuf); |
437 | p->name = xstrdup(tokens[3]); | |
438 | } | |
439 | /* Try EPLF format; carson@lehman.com */ | |
440 | if (p->name == NULL && buf[0] == '+') { | |
441 | ct = buf + 1; | |
442 | p->type = 0; | |
443 | while (ct && *ct) { | |
444 | switch (*ct) { | |
445 | case '\t': | |
446 | sscanf(ct + 1, "%[^,]", sbuf); | |
447 | p->name = xstrdup(sbuf); | |
448 | break; | |
449 | case 's': | |
450 | sscanf(ct + 1, "%d", &(p->size)); | |
451 | break; | |
452 | case 'm': | |
453 | sscanf(ct + 1, "%d", &i); | |
454 | p->date = xstrdup(ctime((time_t *) & i)); | |
455 | *(strstr(p->date, "\n")) = '\0'; | |
456 | break; | |
457 | case '/': | |
458 | p->type = 'd'; | |
459 | break; | |
460 | case 'r': | |
461 | p->type = '-'; | |
462 | break; | |
463 | case 'i': | |
464 | break; | |
465 | default: | |
466 | break; | |
467 | } | |
468 | ct = strstr(ct, ","); | |
469 | if (ct) { | |
470 | ct++; | |
471 | } | |
472 | } | |
473 | if (p->type == 0) { | |
474 | p->type = '-'; | |
475 | } | |
476 | } | |
477 | for (i = 0; i < n_tokens; i++) | |
478 | xfree(tokens[i]); | |
b5639035 | 479 | if (p->name == NULL) |
480 | ftpListPartsFree(&p); | |
3fdadc70 | 481 | return p; |
482 | } | |
483 | ||
484 | static const char * | |
485 | dots_fill(size_t len) | |
486 | { | |
487 | static char buf[256]; | |
488 | int i = 0; | |
489 | if (len > Config.Ftp.list_width) { | |
490 | memset(buf, ' ', 256); | |
491 | buf[0] = '\n'; | |
492 | buf[Config.Ftp.list_width + 4] = '\0'; | |
493 | return buf; | |
494 | } | |
495 | for (i = (int) len; i < Config.Ftp.list_width; i++) | |
496 | buf[i - len] = (i % 2) ? '.' : ' '; | |
497 | buf[i - len] = '\0'; | |
498 | return buf; | |
499 | } | |
500 | ||
501 | static char * | |
502 | ftpHtmlifyListEntry(char *line, int flags) | |
503 | { | |
13e80e5b | 504 | LOCAL_ARRAY(char, link, 2048 + 40); |
9fc0b4b8 | 505 | LOCAL_ARRAY(char, link2, 2048 + 40); |
3fdadc70 | 506 | LOCAL_ARRAY(char, icon, 2048); |
507 | LOCAL_ARRAY(char, html, 8192); | |
3fdadc70 | 508 | size_t width = Config.Ftp.list_width; |
509 | ftpListParts *parts; | |
3fdadc70 | 510 | if (strlen(line) > 1024) { |
042461c3 | 511 | snprintf(html, 8192, "%s\n", line); |
3fdadc70 | 512 | return html; |
513 | } | |
514 | if ((parts = ftpListParseParts(line, flags)) == NULL) { | |
042461c3 | 515 | snprintf(html, 8192, "%s\n", line); |
3fdadc70 | 516 | return html; |
517 | } | |
13e80e5b | 518 | /* check .. as special case */ |
519 | if (!strcmp(parts->name, "..")) { | |
520 | snprintf(icon, 2048, "<IMG BORDER=0 SRC=\"%s%s\" ALT=\"%-6s\">", | |
521 | "http://internal.squid/icons/", | |
522 | ICON_DIRUP, | |
523 | "[DIR]"); | |
524 | if (!EBIT_TEST(flags, FTP_NO_DOTDOT) && !EBIT_TEST(flags, FTP_ROOT_DIR)) { | |
525 | /* Normal directory */ | |
526 | snprintf(link, 2048, "<A HREF=\"%s\">%s</A>", | |
527 | "../", | |
528 | "Parent Directory"); | |
529 | } else if (!EBIT_TEST(flags, FTP_NO_DOTDOT) && EBIT_TEST(flags, FTP_ROOT_DIR)) { | |
530 | /* "Top level" directory */ | |
531 | snprintf(link, 2048, "<A HREF=\"%s\">%s</A> (<A HREF=\"%s\">%s</A>)", | |
532 | "%2e%2e/", | |
533 | "Parent Directory", | |
534 | "%2f/", | |
535 | "Root Directory"); | |
536 | } else if (EBIT_TEST(flags, FTP_NO_DOTDOT) && !EBIT_TEST(flags, FTP_ROOT_DIR)) { | |
537 | /* Normal directory where last component is / or .. */ | |
538 | snprintf(link, 2048, "<A HREF=\"%s\">%s</A> (<A HREF=\"%s\">%s</A>)", | |
539 | "%2e%2e/", | |
540 | "Parent Directory", | |
541 | "../", | |
542 | "Up"); | |
543 | } else { /* NO_DOTDOT && ROOT_DIR */ | |
544 | /* "UNIX Root" directory */ | |
545 | snprintf(link, 2048, "<A HREF=\"%s\">%s</A>", | |
546 | "../", | |
547 | "Home Directory"); | |
548 | } | |
549 | snprintf(html, 8192, "%s %s\n", icon, link); | |
550 | ftpListPartsFree(&parts); | |
551 | return html; | |
552 | } | |
3fdadc70 | 553 | if (!strcmp(parts->name, ".") || !strcmp(parts->name, "..")) { |
554 | *html = '\0'; | |
b5639035 | 555 | ftpListPartsFree(&parts); |
3fdadc70 | 556 | return html; |
557 | } | |
558 | parts->size += 1023; | |
559 | parts->size >>= 10; | |
560 | parts->showname = xstrdup(parts->name); | |
561 | if (!Config.Ftp.list_wrap) { | |
562 | if (strlen(parts->showname) > width - 1) { | |
563 | *(parts->showname + width - 1) = '>'; | |
564 | *(parts->showname + width - 0) = '\0'; | |
565 | } | |
566 | } | |
3fdadc70 | 567 | switch (parts->type) { |
568 | case 'd': | |
042461c3 | 569 | snprintf(icon, 2048, "<IMG SRC=\"%s%s\" ALT=\"%-6s\">", |
365cb147 | 570 | "http://internal.squid/icons/", |
4f117cb4 | 571 | ICON_MENU, |
3fdadc70 | 572 | "[DIR]"); |
56878878 | 573 | snprintf(link, 2048, "<A HREF=\"%s/\">%s</A>%s", |
9fc0b4b8 | 574 | rfc1738_escape(parts->name), |
3fdadc70 | 575 | parts->showname, |
576 | dots_fill(strlen(parts->showname))); | |
042461c3 | 577 | snprintf(html, 8192, "%s %s [%s]\n", |
3fdadc70 | 578 | icon, |
579 | link, | |
580 | parts->date); | |
581 | break; | |
582 | case 'l': | |
56878878 | 583 | snprintf(icon, 2048, "<IMG SRC=\"%s%s\" ALT=\"%-6s\">", |
365cb147 | 584 | "http://internal.squid/icons/", |
4f117cb4 | 585 | ICON_LINK, |
3fdadc70 | 586 | "[LINK]"); |
042461c3 | 587 | snprintf(link, 2048, "<A HREF=\"%s\">%s</A>%s", |
9fc0b4b8 | 588 | rfc1738_escape(parts->name), |
3fdadc70 | 589 | parts->showname, |
590 | dots_fill(strlen(parts->showname))); | |
9fc0b4b8 | 591 | snprintf(link2, 2048, "<A HREF=\"%s\">%s</A>", |
592 | rfc1738_escape(parts->link), | |
593 | parts->link); | |
594 | snprintf(html, 8192, "%s %s [%s] -> %s\n", | |
3fdadc70 | 595 | icon, |
596 | link, | |
9fc0b4b8 | 597 | parts->date, |
598 | link2); | |
3fdadc70 | 599 | break; |
600 | case '-': | |
601 | default: | |
56878878 | 602 | snprintf(icon, 2048, "<IMG SRC=\"%s%s\" ALT=\"%-6s\">", |
365cb147 | 603 | "http://internal.squid/icons/", |
3fdadc70 | 604 | mimeGetIcon(parts->name), |
3fdadc70 | 605 | "[FILE]"); |
042461c3 | 606 | snprintf(link, 2048, "<A HREF=\"%s\">%s</A>%s", |
9fc0b4b8 | 607 | rfc1738_escape(parts->name), |
3fdadc70 | 608 | parts->showname, |
609 | dots_fill(strlen(parts->showname))); | |
042461c3 | 610 | snprintf(html, 8192, "%s %s [%s] %6dk\n", |
3fdadc70 | 611 | icon, |
612 | link, | |
613 | parts->date, | |
614 | parts->size); | |
615 | break; | |
616 | } | |
617 | ftpListPartsFree(&parts); | |
3fdadc70 | 618 | return html; |
619 | } | |
620 | ||
621 | static void | |
622 | ftpParseListing(FtpStateData * ftpState, int len) | |
623 | { | |
624 | char *buf = ftpState->data.buf; | |
b5639035 | 625 | char *end; |
626 | char *line; | |
3fdadc70 | 627 | char *s; |
628 | char *t; | |
629 | size_t linelen; | |
b5639035 | 630 | size_t usable; |
3fdadc70 | 631 | StoreEntry *e = ftpState->entry; |
b5639035 | 632 | len += ftpState->data.offset; |
633 | end = buf + len - 1; | |
3fdadc70 | 634 | while (*end != '\r' && *end != '\n' && end > buf) |
635 | end--; | |
b5639035 | 636 | usable = end - buf; |
637 | if (usable == 0) { | |
9fb13bb6 | 638 | debug(9, 3) ("ftpParseListing: didn't find end for %s\n", storeUrl(e)); |
3fdadc70 | 639 | return; |
640 | } | |
3f6c0fb2 | 641 | line = memAllocate(MEM_4K_BUF, 1); |
3fdadc70 | 642 | end++; |
1931735b | 643 | /* XXX there is an ABR bug here. We need to make sure buf is |
644 | * NULL terminated */ | |
438fc1e3 | 645 | storeBuffer(e); |
3fdadc70 | 646 | for (s = buf; s < end; s += strcspn(s, crlf), s += strspn(s, crlf)) { |
647 | linelen = strcspn(s, crlf) + 1; | |
648 | if (linelen > 4096) | |
649 | linelen = 4096; | |
650 | xstrncpy(line, s, linelen); | |
a3d5953d | 651 | debug(9, 7) ("%s\n", line); |
3fdadc70 | 652 | if (!strncmp(line, "total", 5)) |
653 | continue; | |
654 | t = ftpHtmlifyListEntry(line, ftpState->flags); | |
655 | assert(t != NULL); | |
656 | storeAppend(e, t, strlen(t)); | |
657 | } | |
438fc1e3 | 658 | storeBufferFlush(e); |
b5639035 | 659 | assert(usable <= len); |
660 | if (usable < len) { | |
661 | /* must copy partial line to beginning of buf */ | |
e29f7586 | 662 | linelen = len - usable; |
b5639035 | 663 | if (linelen > 4096) |
664 | linelen = 4096; | |
665 | xstrncpy(line, end, linelen); | |
666 | xstrncpy(ftpState->data.buf, line, ftpState->data.size); | |
667 | ftpState->data.offset = strlen(ftpState->data.buf); | |
668 | } | |
3f6c0fb2 | 669 | memFree(MEM_4K_BUF, line); |
3fdadc70 | 670 | } |
090089c4 | 671 | |
79a15e0a | 672 | static void |
673 | ftpReadComplete(FtpStateData * ftpState) | |
674 | { | |
4cca8b93 | 675 | debug(9, 3) ("ftpReadComplete\n"); |
79a15e0a | 676 | /* Connection closed; retrieval done. */ |
677 | if (EBIT_TEST(ftpState->flags, FTP_HTML_HEADER_SENT)) | |
678 | ftpListingFinish(ftpState); | |
679 | storeTimestampsSet(ftpState->entry); | |
680 | storeComplete(ftpState->entry); | |
681 | /* expect the "transfer complete" message on the control socket */ | |
682 | commSetSelect(ftpState->ctrl.fd, | |
683 | COMM_SELECT_READ, | |
684 | ftpReadControlReply, | |
685 | ftpState, | |
686 | Config.Timeout.read); | |
687 | } | |
688 | ||
582b6456 | 689 | static void |
3fdadc70 | 690 | ftpReadData(int fd, void *data) |
090089c4 | 691 | { |
582b6456 | 692 | FtpStateData *ftpState = data; |
090089c4 | 693 | int len; |
a57512fa | 694 | int j; |
30a4f2a8 | 695 | int bin; |
bfcaf585 | 696 | StoreEntry *entry = ftpState->entry; |
79a15e0a | 697 | MemObject *mem = entry->mem_obj; |
3fdadc70 | 698 | assert(fd == ftpState->data.fd); |
3fdadc70 | 699 | if (protoAbortFetch(entry)) { |
9b312a19 | 700 | storeAbort(entry, 0); |
a3d5953d | 701 | ftpDataTransferDone(ftpState); |
702 | return; | |
30a4f2a8 | 703 | } |
1a6e21ac | 704 | errno = 0; |
3fdadc70 | 705 | memset(ftpState->data.buf + ftpState->data.offset, '\0', |
706 | ftpState->data.size - ftpState->data.offset); | |
707 | len = read(fd, | |
708 | ftpState->data.buf + ftpState->data.offset, | |
709 | ftpState->data.size - ftpState->data.offset); | |
4f92c80c | 710 | fd_bytes(fd, len, FD_READ); |
a3d5953d | 711 | debug(9, 5) ("ftpReadData: FD %d, Read %d bytes\n", fd, len); |
30a4f2a8 | 712 | if (len > 0) { |
4a63c85f | 713 | IOStats.Ftp.reads++; |
a57512fa | 714 | for (j = len - 1, bin = 0; j; bin++) |
715 | j >>= 1; | |
30a4f2a8 | 716 | IOStats.Ftp.read_hist[bin]++; |
717 | } | |
ba718c8f | 718 | if (len < 0) { |
a3d5953d | 719 | debug(50, 1) ("ftpReadData: read error: %s\n", xstrerror()); |
b224ea98 | 720 | if (ignoreErrno(errno)) { |
a57512fa | 721 | commSetSelect(fd, |
722 | COMM_SELECT_READ, | |
723 | ftpReadData, | |
724 | data, | |
725 | Config.Timeout.read); | |
6fe6313d | 726 | } else { |
a57512fa | 727 | assert(mem->inmem_hi > 0); |
728 | storeAbort(entry, 0); | |
3fdadc70 | 729 | ftpDataTransferDone(ftpState); |
6fe6313d | 730 | } |
090089c4 | 731 | } else if (len == 0) { |
79a15e0a | 732 | ftpReadComplete(ftpState); |
090089c4 | 733 | } else { |
b5639035 | 734 | if (EBIT_TEST(ftpState->flags, FTP_ISDIR)) { |
a57512fa | 735 | if (!EBIT_TEST(ftpState->flags, FTP_HTML_HEADER_SENT)) |
736 | ftpListingStart(ftpState); | |
3fdadc70 | 737 | ftpParseListing(ftpState, len); |
b5639035 | 738 | } else { |
739 | assert(ftpState->data.offset == 0); | |
740 | storeAppend(entry, ftpState->data.buf, len); | |
741 | } | |
3ccfdd9e | 742 | if (ftpState->size && mem->inmem_hi >= ftpState->size + mem->reply->hdr_sz) |
79a15e0a | 743 | ftpReadComplete(ftpState); |
744 | else | |
745 | commSetSelect(fd, | |
746 | COMM_SELECT_READ, | |
747 | ftpReadData, | |
748 | data, | |
749 | Config.Timeout.read); | |
090089c4 | 750 | } |
090089c4 | 751 | } |
752 | ||
0f9306d9 | 753 | static char * |
0ee4272b | 754 | ftpGetBasicAuth(const char *req_hdr) |
0f9306d9 | 755 | { |
756 | char *auth_hdr; | |
757 | char *t; | |
758 | if (req_hdr == NULL) | |
759 | return NULL; | |
760 | if ((auth_hdr = mime_get_header(req_hdr, "Authorization")) == NULL) | |
761 | return NULL; | |
762 | if ((t = strtok(auth_hdr, " \t")) == NULL) | |
763 | return NULL; | |
764 | if (strcasecmp(t, "Basic") != 0) | |
765 | return NULL; | |
766 | if ((t = strtok(NULL, " \t")) == NULL) | |
767 | return NULL; | |
768 | return base64_decode(t); | |
769 | } | |
770 | ||
429fdbec | 771 | /* |
772 | * ftpCheckAuth | |
773 | * | |
774 | * Return 1 if we have everything needed to complete this request. | |
775 | * Return 0 if something is missing. | |
776 | */ | |
777 | static int | |
778 | ftpCheckAuth(FtpStateData * ftpState, char *req_hdr) | |
779 | { | |
780 | char *orig_user; | |
781 | char *auth; | |
9e4ad609 | 782 | ftpLoginParser(ftpState->request->login, ftpState); |
429fdbec | 783 | if (ftpState->user[0] && ftpState->password[0]) |
784 | return 1; /* name and passwd both in URL */ | |
785 | if (!ftpState->user[0] && !ftpState->password[0]) | |
786 | return 1; /* no name or passwd */ | |
787 | if (ftpState->password[0]) | |
788 | return 1; /* passwd with no name? */ | |
789 | /* URL has name, but no passwd */ | |
790 | if ((auth = ftpGetBasicAuth(req_hdr)) == NULL) | |
791 | return 0; /* need auth header */ | |
792 | orig_user = xstrdup(ftpState->user); | |
9e4ad609 | 793 | ftpLoginParser(auth, ftpState); |
429fdbec | 794 | if (!strcmp(orig_user, ftpState->user)) { |
795 | xfree(orig_user); | |
796 | return 1; /* same username */ | |
797 | } | |
798 | strcpy(ftpState->user, orig_user); | |
799 | xfree(orig_user); | |
800 | return 0; /* different username */ | |
801 | } | |
802 | ||
13e80e5b | 803 | static void |
804 | ftpCheckUrlpath(FtpStateData * ftpState) | |
805 | { | |
806 | request_t *request = ftpState->request; | |
807 | int l; | |
9e242e02 | 808 | char *t; |
13e80e5b | 809 | l = strlen(request->urlpath); |
810 | EBIT_SET(ftpState->flags, FTP_USE_BASE); | |
811 | /* check for null path */ | |
812 | if (*request->urlpath == '\0') { | |
813 | xstrncpy(request->urlpath, "/", MAX_URL); | |
814 | EBIT_SET(ftpState->flags, FTP_ISDIR); | |
815 | EBIT_SET(ftpState->flags, FTP_ROOT_DIR); | |
816 | } else if (!strcmp(request->urlpath, "/%2f/")) { | |
817 | EBIT_SET(ftpState->flags, FTP_ISDIR); | |
818 | EBIT_SET(ftpState->flags, FTP_ROOT_DIR); | |
819 | } else if ((l >= 1) && (*(request->urlpath + l - 1) == '/')) { | |
820 | EBIT_SET(ftpState->flags, FTP_ISDIR); | |
821 | EBIT_CLR(ftpState->flags, FTP_USE_BASE); | |
822 | if (l == 1) | |
823 | EBIT_SET(ftpState->flags, FTP_ROOT_DIR); | |
824 | } | |
9e242e02 | 825 | if ((t = strrchr(request->urlpath, ';')) != NULL) { |
826 | if (strncasecmp(t + 1, "type=", 5) == 0) { | |
827 | ftpState->typecode = (char) toupper((int) *(t + 6)); | |
70dcc6ec | 828 | *t = '\0'; |
9e242e02 | 829 | } |
830 | } | |
13e80e5b | 831 | } |
832 | ||
3fdadc70 | 833 | static void |
834 | ftpBuildTitleUrl(FtpStateData * ftpState) | |
835 | { | |
836 | request_t *request = ftpState->request; | |
837 | size_t len; | |
838 | char *t; | |
839 | len = 64 | |
840 | + strlen(ftpState->user) | |
841 | + strlen(ftpState->password) | |
842 | + strlen(request->host) | |
843 | + strlen(request->urlpath); | |
844 | t = ftpState->title_url = xcalloc(len, 1); | |
845 | strcat(t, "ftp://"); | |
846 | if (strcmp(ftpState->user, "anonymous")) { | |
847 | strcat(t, ftpState->user); | |
848 | strcat(t, "@"); | |
849 | } | |
850 | strcat(t, request->host); | |
851 | if (request->port != urlDefaultPort(PROTO_FTP)) | |
56878878 | 852 | snprintf(&t[strlen(t)], len - strlen(t), ":%d", request->port); |
13e80e5b | 853 | strcat(t, request->urlpath); |
3fdadc70 | 854 | } |
855 | ||
770f051d | 856 | void |
75e88d56 | 857 | ftpStart(request_t * request, StoreEntry * entry) |
0a0bf5db | 858 | { |
0a0bf5db | 859 | LOCAL_ARRAY(char, realm, 8192); |
9fb13bb6 | 860 | const char *url = storeUrl(entry); |
5c5783a2 | 861 | FtpStateData *ftpState = xcalloc(1, sizeof(FtpStateData)); |
0a0bf5db | 862 | char *response; |
3fdadc70 | 863 | int fd; |
9b312a19 | 864 | ErrorState *err; |
3f6c0fb2 | 865 | cbdataAdd(ftpState, MEM_NONE); |
9fb13bb6 | 866 | debug(9, 3) ("FtpStart: '%s'\n", url); |
770f051d | 867 | storeLockObject(entry); |
5c5783a2 | 868 | ftpState->entry = entry; |
5c5783a2 | 869 | ftpState->request = requestLink(request); |
3fdadc70 | 870 | ftpState->ctrl.fd = -1; |
871 | ftpState->data.fd = -1; | |
872 | EBIT_SET(ftpState->flags, FTP_PASV_SUPPORTED); | |
873 | EBIT_SET(ftpState->flags, FTP_REST_SUPPORTED); | |
62dec5a7 | 874 | if (!ftpCheckAuth(ftpState, request->headers)) { |
429fdbec | 875 | /* This request is not fully authenticated */ |
876 | if (request->port == 21) { | |
56878878 | 877 | snprintf(realm, 8192, "ftp %s", ftpState->user); |
429fdbec | 878 | } else { |
56878878 | 879 | snprintf(realm, 8192, "ftp %s port %d", |
5c5783a2 | 880 | ftpState->user, request->port); |
e381a13d | 881 | } |
9b312a19 | 882 | response = ftpAuthRequired(request, realm); |
429fdbec | 883 | storeAppend(entry, response, strlen(response)); |
884 | httpParseReplyHeaders(response, entry->mem_obj->reply); | |
885 | storeComplete(entry); | |
5c5783a2 | 886 | ftpStateFree(-1, ftpState); |
429fdbec | 887 | return; |
e381a13d | 888 | } |
13e80e5b | 889 | ftpCheckUrlpath(ftpState); |
3fdadc70 | 890 | ftpBuildTitleUrl(ftpState); |
a3d5953d | 891 | debug(9, 5) ("FtpStart: host=%s, path=%s, user=%s, passwd=%s\n", |
5c5783a2 | 892 | ftpState->request->host, ftpState->request->urlpath, |
893 | ftpState->user, ftpState->password); | |
3fdadc70 | 894 | fd = comm_open(SOCK_STREAM, |
16b204c4 | 895 | 0, |
3fdadc70 | 896 | Config.Addrs.tcp_outgoing, |
30a4f2a8 | 897 | 0, |
16b204c4 | 898 | COMM_NONBLOCKING, |
30a4f2a8 | 899 | url); |
3fdadc70 | 900 | if (fd == COMM_ERROR) { |
185b9571 | 901 | debug(9, 4) ("ftpStart: Failed to open a socket.\n"); |
fe40a877 | 902 | err = errorCon(ERR_SOCKET_FAILURE, HTTP_INTERNAL_SERVER_ERROR); |
c45ed9ad | 903 | err->xerrno = errno; |
9b312a19 | 904 | err->request = requestLink(ftpState->request); |
905 | errorAppendEntry(entry, err); | |
0a0bf5db | 906 | return; |
090089c4 | 907 | } |
3fdadc70 | 908 | ftpState->ctrl.fd = fd; |
909 | comm_add_close_handler(fd, ftpStateFree, ftpState); | |
5fbef0c9 | 910 | storeRegisterAbort(entry, ftpAbort, ftpState); |
3fdadc70 | 911 | commSetTimeout(fd, Config.Timeout.connect, ftpTimeout, ftpState); |
3fdadc70 | 912 | commConnectStart(ftpState->ctrl.fd, |
913 | request->host, | |
914 | request->port, | |
e924600d | 915 | ftpConnectDone, |
5c5783a2 | 916 | ftpState); |
e5f6c5c2 | 917 | } |
090089c4 | 918 | |
e5f6c5c2 | 919 | static void |
920 | ftpConnectDone(int fd, int status, void *data) | |
921 | { | |
5c5783a2 | 922 | FtpStateData *ftpState = data; |
9e975e4e | 923 | request_t *request = ftpState->request; |
9b312a19 | 924 | ErrorState *err; |
9e975e4e | 925 | debug(9, 3) ("ftpConnectDone, status = %d\n", status); |
926 | if (status == COMM_ERR_DNS) { | |
927 | debug(9, 4) ("ftpConnectDone: Unknown host: %s\n", request->host); | |
fe40a877 | 928 | err = errorCon(ERR_DNS_FAIL, HTTP_SERVICE_UNAVAILABLE); |
9b312a19 | 929 | err->dnsserver_msg = xstrdup(dns_error_message); |
930 | err->request = requestLink(request); | |
931 | errorAppendEntry(ftpState->entry, err); | |
ff008d84 | 932 | comm_close(ftpState->ctrl.fd); |
9e975e4e | 933 | } else if (status != COMM_OK) { |
fe40a877 | 934 | err = errorCon(ERR_CONNECT_FAIL, HTTP_SERVICE_UNAVAILABLE); |
c45ed9ad | 935 | err->xerrno = errno; |
9b312a19 | 936 | err->host = xstrdup(request->host); |
937 | err->port = request->port; | |
938 | err->request = requestLink(request); | |
939 | errorAppendEntry(ftpState->entry, err); | |
ff008d84 | 940 | comm_close(ftpState->ctrl.fd); |
9e975e4e | 941 | } else { |
942 | ftpState->state = BEGIN; | |
3f6c0fb2 | 943 | ftpState->ctrl.buf = memAllocate(MEM_4K_BUF, 1); |
944 | ftpState->ctrl.freefunc = memFree4K; | |
9e975e4e | 945 | ftpState->ctrl.size = 4096; |
946 | ftpState->ctrl.offset = 0; | |
947 | ftpState->data.buf = xmalloc(SQUID_TCP_SO_RCVBUF); | |
948 | ftpState->data.size = SQUID_TCP_SO_RCVBUF; | |
949 | ftpState->data.freefunc = xfree; | |
2acf4be6 | 950 | commSetSelect(fd, COMM_SELECT_READ, ftpReadControlReply, ftpState, Config.Timeout.read); |
e5f6c5c2 | 951 | } |
090089c4 | 952 | } |
953 | ||
3fdadc70 | 954 | /* ====================================================================== */ |
955 | ||
b8d8561b | 956 | static void |
3fdadc70 | 957 | ftpWriteCommand(const char *buf, FtpStateData * ftpState) |
234967c9 | 958 | { |
a3d5953d | 959 | debug(9, 5) ("ftpWriteCommand: %s\n", buf); |
b9916917 | 960 | safe_free(ftpState->ctrl.last_command); |
961 | ftpState->ctrl.last_command = xstrdup(buf); | |
3fdadc70 | 962 | comm_write(ftpState->ctrl.fd, |
963 | xstrdup(buf), | |
964 | strlen(buf), | |
965 | ftpWriteCommandCallback, | |
966 | ftpState, | |
967 | xfree); | |
968 | commSetSelect(ftpState->ctrl.fd, | |
969 | COMM_SELECT_READ, | |
970 | ftpReadControlReply, | |
971 | ftpState, | |
2acf4be6 | 972 | Config.Timeout.read); |
3fdadc70 | 973 | } |
974 | ||
975 | static void | |
79a15e0a | 976 | ftpWriteCommandCallback(int fd, char *bufnotused, size_t size, int errflag, void *data) |
3fdadc70 | 977 | { |
978 | FtpStateData *ftpState = data; | |
979 | StoreEntry *entry = ftpState->entry; | |
9b312a19 | 980 | ErrorState *err; |
a3d5953d | 981 | debug(9, 7) ("ftpWriteCommandCallback: wrote %d bytes\n", size); |
96f1be5d | 982 | if (errflag == COMM_ERR_CLOSING) |
983 | return; | |
3fdadc70 | 984 | if (errflag) { |
270b86af | 985 | debug(50, 1) ("ftpWriteCommandCallback: FD %d: %s\n", fd, xstrerror()); |
73a3014d | 986 | if (entry->mem_obj->inmem_hi == 0) { |
fe40a877 | 987 | err = errorCon(ERR_WRITE_ERROR, HTTP_SERVICE_UNAVAILABLE); |
c45ed9ad | 988 | err->xerrno = errno; |
9b312a19 | 989 | err->request = requestLink(ftpState->request); |
990 | errorAppendEntry(entry, err); | |
991 | } | |
43d9de30 | 992 | if (entry->store_status == STORE_PENDING) |
993 | storeAbort(entry, 0); | |
ff008d84 | 994 | comm_close(ftpState->ctrl.fd); |
3fdadc70 | 995 | } |
996 | } | |
997 | ||
998 | static wordlist * | |
999 | ftpParseControlReply(char *buf, size_t len, int *codep) | |
1000 | { | |
1001 | char *s; | |
1002 | int complete = 0; | |
1003 | wordlist *head; | |
1004 | wordlist *list; | |
1005 | wordlist **tail = &head; | |
1006 | off_t offset; | |
1007 | size_t linelen; | |
b5639035 | 1008 | int code = -1; |
a3d5953d | 1009 | debug(9, 5) ("ftpParseControlReply\n"); |
3fdadc70 | 1010 | if (*(buf + len - 1) != '\n') |
1011 | return NULL; | |
1012 | for (s = buf; s - buf < len; s += strcspn(s, crlf), s += strspn(s, crlf)) { | |
1013 | linelen = strcspn(s, crlf) + 1; | |
1014 | if (linelen > 3) | |
1015 | complete = (*s >= '0' && *s <= '9' && *(s + 3) == ' '); | |
1016 | if (complete) | |
1017 | code = atoi(s); | |
1018 | offset = 0; | |
1019 | if (linelen > 3) | |
1020 | if (*s >= '0' && *s <= '9' && (*(s + 3) == '-' || *(s + 3) == ' ')) | |
1021 | offset = 4; | |
1022 | list = xcalloc(1, sizeof(wordlist)); | |
1023 | list->key = xmalloc(linelen - offset); | |
1024 | xstrncpy(list->key, s + offset, linelen - offset); | |
4939c5da | 1025 | debug(9, 7) ("%d %s\n", code, list->key); |
3fdadc70 | 1026 | *tail = list; |
1027 | tail = &list->next; | |
1028 | } | |
1029 | if (!complete) | |
1030 | wordlistDestroy(&head); | |
1031 | if (codep) | |
1032 | *codep = code; | |
1033 | return head; | |
1034 | } | |
1035 | ||
1036 | static void | |
1037 | ftpReadControlReply(int fd, void *data) | |
1038 | { | |
1039 | FtpStateData *ftpState = data; | |
1040 | StoreEntry *entry = ftpState->entry; | |
1041 | char *oldbuf; | |
1042 | wordlist **W; | |
1043 | int len; | |
9b312a19 | 1044 | ErrorState *err; |
a3d5953d | 1045 | debug(9, 5) ("ftpReadControlReply\n"); |
3fdadc70 | 1046 | assert(ftpState->ctrl.offset < ftpState->ctrl.size); |
1047 | len = read(fd, | |
1048 | ftpState->ctrl.buf + ftpState->ctrl.offset, | |
1049 | ftpState->ctrl.size - ftpState->ctrl.offset); | |
1050 | fd_bytes(fd, len, FD_READ); | |
a3d5953d | 1051 | debug(9, 5) ("ftpReadControlReply: FD %d, Read %d bytes\n", fd, len); |
3fdadc70 | 1052 | if (len < 0) { |
a3d5953d | 1053 | debug(50, 1) ("ftpReadControlReply: read error: %s\n", xstrerror()); |
b224ea98 | 1054 | if (ignoreErrno(errno)) { |
3fdadc70 | 1055 | commSetSelect(fd, |
1056 | COMM_SELECT_READ, | |
1057 | ftpReadControlReply, | |
1058 | ftpState, | |
2acf4be6 | 1059 | Config.Timeout.read); |
3fdadc70 | 1060 | } else { |
73a3014d | 1061 | if (entry->mem_obj->inmem_hi == 0) { |
fe40a877 | 1062 | err = errorCon(ERR_READ_ERROR, HTTP_INTERNAL_SERVER_ERROR); |
c45ed9ad | 1063 | err->xerrno = errno; |
9b312a19 | 1064 | err->request = requestLink(ftpState->request); |
1065 | errorAppendEntry(entry, err); | |
1066 | } | |
43d9de30 | 1067 | if (entry->store_status == STORE_PENDING) |
1068 | storeAbort(entry, 0); | |
ff008d84 | 1069 | comm_close(ftpState->ctrl.fd); |
3fdadc70 | 1070 | } |
1071 | return; | |
1072 | } | |
1073 | if (len == 0) { | |
cc11e161 | 1074 | debug(9, 1) ("ftpReadControlReply: FD %d Read 0 bytes\n", fd); |
1075 | if (entry->store_status == STORE_PENDING) { | |
1076 | storeReleaseRequest(entry); | |
1077 | if (entry->mem_obj->inmem_hi == 0) { | |
fe40a877 | 1078 | err = errorCon(ERR_READ_ERROR, HTTP_INTERNAL_SERVER_ERROR); |
c45ed9ad | 1079 | err->xerrno = errno; |
cc11e161 | 1080 | err->request = requestLink(ftpState->request); |
1081 | errorAppendEntry(entry, err); | |
1082 | } | |
9b312a19 | 1083 | } |
ff008d84 | 1084 | comm_close(ftpState->ctrl.fd); |
3fdadc70 | 1085 | return; |
1086 | } | |
b5639035 | 1087 | len += ftpState->ctrl.offset; |
1088 | ftpState->ctrl.offset = len; | |
3fdadc70 | 1089 | assert(len <= ftpState->ctrl.size); |
1090 | wordlistDestroy(&ftpState->ctrl.message); | |
1091 | ftpState->ctrl.message = ftpParseControlReply(ftpState->ctrl.buf, len, | |
1092 | &ftpState->ctrl.replycode); | |
1093 | if (ftpState->ctrl.message == NULL) { | |
a3d5953d | 1094 | debug(9, 5) ("ftpReadControlReply: partial server reply\n"); |
3fdadc70 | 1095 | if (len == ftpState->ctrl.size) { |
1096 | oldbuf = ftpState->ctrl.buf; | |
1097 | ftpState->ctrl.buf = xcalloc(ftpState->ctrl.size << 1, 1); | |
1098 | xmemcpy(ftpState->ctrl.buf, oldbuf, ftpState->ctrl.size); | |
1099 | ftpState->ctrl.size <<= 1; | |
1100 | ftpState->ctrl.freefunc(oldbuf); | |
1101 | ftpState->ctrl.freefunc = xfree; | |
1102 | } | |
2acf4be6 | 1103 | commSetSelect(fd, COMM_SELECT_READ, ftpReadControlReply, ftpState, Config.Timeout.read); |
3fdadc70 | 1104 | return; |
1105 | } | |
1106 | for (W = &ftpState->ctrl.message; *W && (*W)->next; W = &(*W)->next); | |
b9916917 | 1107 | safe_free(ftpState->ctrl.last_reply); |
1108 | ftpState->ctrl.last_reply = (*W)->key; | |
3fdadc70 | 1109 | safe_free(*W); |
1110 | ftpState->ctrl.offset = 0; | |
1111 | FTP_SM_FUNCS[ftpState->state] (ftpState); | |
234967c9 | 1112 | } |
1113 | ||
3fdadc70 | 1114 | /* ====================================================================== */ |
1115 | ||
1116 | static void | |
1117 | ftpReadWelcome(FtpStateData * ftpState) | |
1118 | { | |
1119 | int code = ftpState->ctrl.replycode; | |
a3d5953d | 1120 | debug(9, 3) ("ftpReadWelcome\n"); |
3fdadc70 | 1121 | if (EBIT_TEST(ftpState->flags, FTP_PASV_ONLY)) |
1122 | ftpState->login_att++; | |
1123 | if (code == 220) { | |
1124 | if (ftpState->ctrl.message) | |
1125 | if (strstr(ftpState->ctrl.message->key, "NetWare")) | |
1126 | EBIT_SET(ftpState->flags, FTP_SKIP_WHITESPACE); | |
969c39b9 | 1127 | ftpSendUser(ftpState); |
3fdadc70 | 1128 | } else { |
1129 | ftpFail(ftpState); | |
1130 | } | |
1131 | } | |
1132 | ||
969c39b9 | 1133 | static void |
1134 | ftpSendUser(FtpStateData * ftpState) | |
1135 | { | |
1136 | if (ftpState->proxy_host != NULL) | |
1137 | snprintf(cbuf, 1024, "USER %s@%s\r\n", | |
1138 | ftpState->user, | |
1139 | ftpState->request->host); | |
1140 | else | |
1141 | snprintf(cbuf, 1024, "USER %s\r\n", ftpState->user); | |
1142 | ftpWriteCommand(cbuf, ftpState); | |
1143 | ftpState->state = SENT_USER; | |
1144 | } | |
1145 | ||
3fdadc70 | 1146 | static void |
1147 | ftpReadUser(FtpStateData * ftpState) | |
234967c9 | 1148 | { |
3fdadc70 | 1149 | int code = ftpState->ctrl.replycode; |
a3d5953d | 1150 | debug(9, 3) ("ftpReadUser\n"); |
3fdadc70 | 1151 | if (code == 230) { |
1152 | ftpReadPass(ftpState); | |
1153 | } else if (code == 331) { | |
969c39b9 | 1154 | ftpSendPass(ftpState); |
3fdadc70 | 1155 | } else { |
1156 | ftpFail(ftpState); | |
1157 | } | |
1158 | } | |
1159 | ||
969c39b9 | 1160 | static void |
1161 | ftpSendPass(FtpStateData * ftpState) | |
1162 | { | |
1163 | snprintf(cbuf, 1024, "PASS %s\r\n", ftpState->password); | |
1164 | ftpWriteCommand(cbuf, ftpState); | |
1165 | ftpState->state = SENT_PASS; | |
1166 | } | |
1167 | ||
3fdadc70 | 1168 | static void |
1169 | ftpReadPass(FtpStateData * ftpState) | |
1170 | { | |
1171 | int code = ftpState->ctrl.replycode; | |
a3d5953d | 1172 | debug(9, 3) ("ftpReadPass\n"); |
3fdadc70 | 1173 | if (code == 230) { |
969c39b9 | 1174 | ftpSendType(ftpState); |
3fdadc70 | 1175 | } else { |
1176 | ftpFail(ftpState); | |
1177 | } | |
1178 | } | |
1179 | ||
969c39b9 | 1180 | static void |
1181 | ftpSendType(FtpStateData * ftpState) | |
1182 | { | |
1183 | char *t; | |
1184 | char *filename; | |
1185 | char mode; | |
9e242e02 | 1186 | /* |
1187 | * Ref section 3.2.2 of RFC 1738 | |
1188 | */ | |
1189 | switch (mode = ftpState->typecode) { | |
1190 | case 'D': | |
1191 | mode = 'A'; | |
1192 | break; | |
1193 | case 'A': | |
1194 | case 'I': | |
1195 | break; | |
1196 | default: | |
1197 | t = strrchr(ftpState->request->urlpath, '/'); | |
1198 | filename = t ? t + 1 : ftpState->request->urlpath; | |
1199 | mode = mimeGetTransferMode(filename); | |
1200 | break; | |
1201 | } | |
969c39b9 | 1202 | if (mode == 'I') |
1203 | EBIT_SET(ftpState->flags, FTP_BINARY); | |
1204 | snprintf(cbuf, 1024, "TYPE %c\r\n", mode); | |
1205 | ftpWriteCommand(cbuf, ftpState); | |
1206 | ftpState->state = SENT_TYPE; | |
1207 | } | |
1208 | ||
3fdadc70 | 1209 | static void |
1210 | ftpReadType(FtpStateData * ftpState) | |
1211 | { | |
1212 | int code = ftpState->ctrl.replycode; | |
1213 | wordlist *w; | |
1214 | wordlist **T; | |
1215 | char *path; | |
1216 | char *d; | |
a3d5953d | 1217 | debug(9, 3) ("This is ftpReadType\n"); |
3fdadc70 | 1218 | if (code == 200) { |
13e80e5b | 1219 | path = xstrdup(ftpState->request->urlpath); |
1220 | T = &ftpState->pathcomps; | |
1221 | for (d = strtok(path, "/"); d; d = strtok(NULL, "/")) { | |
1222 | rfc1738_unescape(d); | |
1223 | w = xcalloc(1, sizeof(wordlist)); | |
1224 | w->key = xstrdup(d); | |
1225 | *T = w; | |
1226 | T = &w->next; | |
3fdadc70 | 1227 | } |
13e80e5b | 1228 | xfree(path); |
969c39b9 | 1229 | if (ftpState->pathcomps) |
1230 | ftpTraverseDirectory(ftpState); | |
1231 | else | |
13e80e5b | 1232 | ftpSendPasv(ftpState); |
3fdadc70 | 1233 | } else { |
1234 | ftpFail(ftpState); | |
1235 | } | |
1236 | } | |
1237 | ||
1238 | static void | |
969c39b9 | 1239 | ftpTraverseDirectory(FtpStateData * ftpState) |
3fdadc70 | 1240 | { |
1241 | wordlist *w; | |
969c39b9 | 1242 | debug(9, 4) ("ftpTraverseDirectory\n"); |
1243 | ||
1244 | safe_free(ftpState->filepath); | |
1245 | /* Done? */ | |
1246 | if (ftpState->pathcomps == NULL) { | |
a3d5953d | 1247 | debug(9, 3) ("the final component was a directory\n"); |
13e80e5b | 1248 | if (!EBIT_TEST(ftpState->flags, FTP_ISDIR)) { |
1249 | debug(9, 3) ("and path did not end in /\n"); | |
3fdadc70 | 1250 | strcat(ftpState->title_url, "/"); |
13e80e5b | 1251 | EBIT_SET(ftpState->flags, FTP_ISDIR); |
1252 | EBIT_SET(ftpState->flags, FTP_USE_BASE); | |
1253 | } | |
3fdadc70 | 1254 | ftpSendPasv(ftpState); |
234967c9 | 1255 | return; |
3fdadc70 | 1256 | } |
969c39b9 | 1257 | /* Go to next path component */ |
1258 | w = ftpState->pathcomps; | |
1259 | ftpState->filepath = w->key; | |
1260 | ftpState->pathcomps = w->next; | |
1261 | xfree(w); | |
1262 | /* Check if we are to CWD or RETR */ | |
1263 | if (ftpState->pathcomps != NULL || EBIT_TEST(ftpState->flags, FTP_ISDIR)) { | |
1264 | ftpSendCwd(ftpState); | |
1265 | } else { | |
1266 | debug(9, 3) ("final component is probably a file\n"); | |
1267 | ftpSendMdtm(ftpState); | |
1268 | return; | |
1269 | } | |
1270 | } | |
1271 | ||
1272 | static void | |
1273 | ftpSendCwd(FtpStateData * ftpState) | |
1274 | { | |
1275 | char *path = ftpState->filepath; | |
1276 | debug(9, 3) ("ftpSendCwd\n"); | |
1277 | if (!strcmp(path, "..") || !strcmp(path, "/")) { | |
13e80e5b | 1278 | EBIT_SET(ftpState->flags, FTP_NO_DOTDOT); |
1279 | } else { | |
1280 | EBIT_CLR(ftpState->flags, FTP_NO_DOTDOT); | |
1281 | } | |
969c39b9 | 1282 | snprintf(cbuf, 1024, "CWD %s\r\n", path); |
1283 | ftpWriteCommand(cbuf, ftpState); | |
1284 | ftpState->state = SENT_CWD; | |
3fdadc70 | 1285 | } |
77a30ebb | 1286 | |
3fdadc70 | 1287 | static void |
1288 | ftpReadCwd(FtpStateData * ftpState) | |
1289 | { | |
1290 | int code = ftpState->ctrl.replycode; | |
a3d5953d | 1291 | debug(9, 3) ("This is ftpReadCwd\n"); |
3fdadc70 | 1292 | if (code >= 200 && code < 300) { |
969c39b9 | 1293 | /* CWD OK */ |
3fdadc70 | 1294 | if (ftpState->cwd_message) |
1295 | wordlistDestroy(&ftpState->cwd_message); | |
1296 | ftpState->cwd_message = ftpState->ctrl.message; | |
1297 | ftpState->ctrl.message = NULL; | |
969c39b9 | 1298 | /* Continue to traverse the path */ |
1299 | ftpTraverseDirectory(ftpState); | |
3fdadc70 | 1300 | } else { |
1301 | /* CWD FAILED */ | |
969c39b9 | 1302 | ftpFail(ftpState); |
c021888f | 1303 | } |
3fdadc70 | 1304 | } |
1305 | ||
969c39b9 | 1306 | static void |
1307 | ftpSendMdtm(FtpStateData * ftpState) | |
1308 | { | |
1309 | assert(*ftpState->filepath != '\0'); | |
1310 | snprintf(cbuf, 1024, "MDTM %s\r\n", ftpState->filepath); | |
1311 | ftpWriteCommand(cbuf, ftpState); | |
1312 | ftpState->state = SENT_MDTM; | |
1313 | } | |
1314 | ||
3fdadc70 | 1315 | static void |
1316 | ftpReadMdtm(FtpStateData * ftpState) | |
1317 | { | |
1318 | int code = ftpState->ctrl.replycode; | |
a3d5953d | 1319 | debug(9, 3) ("This is ftpReadMdtm\n"); |
3fdadc70 | 1320 | if (code == 213) { |
b9916917 | 1321 | ftpState->mdtm = parse_iso3307_time(ftpState->ctrl.last_reply); |
3fdadc70 | 1322 | } else if (code < 0) { |
1323 | ftpFail(ftpState); | |
77a30ebb | 1324 | } |
969c39b9 | 1325 | ftpSendSize(ftpState); |
1326 | } | |
1327 | ||
1328 | static void | |
1329 | ftpSendSize(FtpStateData * ftpState) | |
1330 | { | |
1331 | /* Only send SIZE for binary transfers. The returned size | |
1332 | * is useless on ASCII transfers */ | |
1333 | if (!EBIT_TEST(ftpState->flags, FTP_BINARY)) { | |
1334 | assert(ftpState->filepath != NULL); | |
1335 | assert(*ftpState->filepath != '\0'); | |
1336 | snprintf(cbuf, 1024, "SIZE %s\r\n", ftpState->filepath); | |
1337 | ftpWriteCommand(cbuf, ftpState); | |
1338 | ftpState->state = SENT_SIZE; | |
1339 | } else | |
1340 | /* Skip to next state no non-binary transfers */ | |
1341 | ftpSendPasv(ftpState); | |
3fdadc70 | 1342 | } |
1343 | ||
1344 | static void | |
1345 | ftpReadSize(FtpStateData * ftpState) | |
1346 | { | |
1347 | int code = ftpState->ctrl.replycode; | |
a3d5953d | 1348 | debug(9, 3) ("This is ftpReadSize\n"); |
3fdadc70 | 1349 | if (code == 213) { |
b9916917 | 1350 | ftpState->size = atoi(ftpState->ctrl.last_reply); |
3fdadc70 | 1351 | } else if (code < 0) { |
1352 | ftpFail(ftpState); | |
1353 | } | |
4939c5da | 1354 | ftpSendPasv(ftpState); |
3fdadc70 | 1355 | } |
1356 | ||
1357 | static void | |
1358 | ftpSendPasv(FtpStateData * ftpState) | |
1359 | { | |
1360 | int fd; | |
a57512fa | 1361 | if (ftpState->data.fd >= 0) { |
1362 | /* We are already connected, reuse this connection. */ | |
1363 | ftpRestOrList(ftpState); | |
1364 | return; | |
1365 | } | |
3fdadc70 | 1366 | assert(ftpState->data.fd < 0); |
1367 | if (!EBIT_TEST(ftpState->flags, FTP_PASV_SUPPORTED)) { | |
1368 | ftpSendPort(ftpState); | |
1369 | return; | |
30a4f2a8 | 1370 | } |
3fdadc70 | 1371 | fd = comm_open(SOCK_STREAM, |
16b204c4 | 1372 | 0, |
3fdadc70 | 1373 | Config.Addrs.tcp_outgoing, |
30a4f2a8 | 1374 | 0, |
3fdadc70 | 1375 | COMM_NONBLOCKING, |
9fb13bb6 | 1376 | storeUrl(ftpState->entry)); |
3fdadc70 | 1377 | if (fd < 0) { |
1378 | ftpFail(ftpState); | |
1379 | return; | |
1380 | } | |
ff008d84 | 1381 | /* |
1382 | * No comm_add_close_handler() here. If we have both ctrl and | |
1383 | * data FD's call ftpStateFree() upon close, then we have | |
1384 | * to delete the close handler which did NOT get called | |
1385 | * to prevent ftpStateFree() getting called twice. | |
1386 | * Instead we'll always call comm_close() on the ctrl FD. | |
1387 | */ | |
3fdadc70 | 1388 | ftpState->data.fd = fd; |
042461c3 | 1389 | snprintf(cbuf, 1024, "PASV\r\n"); |
3fdadc70 | 1390 | ftpWriteCommand(cbuf, ftpState); |
1391 | ftpState->state = SENT_PASV; | |
1392 | } | |
1393 | ||
1394 | static void | |
1395 | ftpReadPasv(FtpStateData * ftpState) | |
1396 | { | |
1397 | int code = ftpState->ctrl.replycode; | |
1398 | int h1, h2, h3, h4; | |
1399 | int p1, p2; | |
1400 | int n; | |
1401 | u_short port; | |
1402 | int fd = ftpState->data.fd; | |
b9916917 | 1403 | char *buf = ftpState->ctrl.last_reply; |
3fdadc70 | 1404 | LOCAL_ARRAY(char, junk, 1024); |
a3d5953d | 1405 | debug(9, 3) ("This is ftpReadPasv\n"); |
3fdadc70 | 1406 | if (code != 227) { |
a3d5953d | 1407 | debug(9, 3) ("PASV not supported by remote end\n"); |
969c39b9 | 1408 | /* XXX Shouldn't we get rid of the PASV socket? */ |
3fdadc70 | 1409 | ftpSendPort(ftpState); |
1410 | return; | |
1411 | } | |
1412 | if (strlen(buf) > 1024) { | |
2a1bc30a | 1413 | debug(9, 1) ("ftpReadPasv: Avoiding potential buffer overflow\n"); |
3fdadc70 | 1414 | ftpSendPort(ftpState); |
1415 | return; | |
1416 | } | |
1417 | /* 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). */ | |
1418 | /* ANSI sez [^0-9] is undefined, it breaks on Watcom cc */ | |
a3d5953d | 1419 | debug(9, 5) ("scanning: %s\n", buf); |
3fdadc70 | 1420 | n = sscanf(buf, "%[^0123456789]%d,%d,%d,%d,%d,%d", |
1421 | junk, &h1, &h2, &h3, &h4, &p1, &p2); | |
1422 | if (n != 7 || p1 < 0 || p2 < 0 || p1 > 255 || p2 > 255) { | |
a3d5953d | 1423 | debug(9, 3) ("Bad 227 reply\n"); |
1424 | debug(9, 3) ("n=%d, p1=%d, p2=%d\n", n, p1, p2); | |
3fdadc70 | 1425 | ftpSendPort(ftpState); |
1426 | return; | |
1427 | } | |
56878878 | 1428 | snprintf(junk, 1024, "%d.%d.%d.%d", h1, h2, h3, h4); |
3fdadc70 | 1429 | if (!safe_inet_addr(junk, NULL)) { |
a3d5953d | 1430 | debug(9, 1) ("unsafe address (%s)\n", junk); |
3fdadc70 | 1431 | ftpSendPort(ftpState); |
1432 | return; | |
1433 | } | |
1434 | port = ((p1 << 8) + p2); | |
a3d5953d | 1435 | debug(9, 5) ("ftpReadPasv: connecting to %s, port %d\n", junk, port); |
9b312a19 | 1436 | ftpState->data.port = port; |
1437 | ftpState->data.host = xstrdup(junk); | |
3fdadc70 | 1438 | commConnectStart(fd, junk, port, ftpPasvCallback, ftpState); |
1439 | } | |
1440 | ||
1441 | static void | |
1442 | ftpPasvCallback(int fd, int status, void *data) | |
1443 | { | |
1444 | FtpStateData *ftpState = data; | |
9b312a19 | 1445 | request_t *request = ftpState->request; |
1446 | ErrorState *err; | |
a3d5953d | 1447 | debug(9, 3) ("ftpPasvCallback\n"); |
9b312a19 | 1448 | if (status != COMM_OK) { |
fe40a877 | 1449 | err = errorCon(ERR_CONNECT_FAIL, HTTP_SERVICE_UNAVAILABLE); |
c45ed9ad | 1450 | err->xerrno = errno; |
9b312a19 | 1451 | err->host = xstrdup(ftpState->data.host); |
1452 | err->port = ftpState->data.port; | |
1453 | err->request = requestLink(request); | |
1454 | errorAppendEntry(ftpState->entry, err); | |
ff008d84 | 1455 | comm_close(ftpState->ctrl.fd); |
3fdadc70 | 1456 | return; |
1457 | } | |
1458 | ftpRestOrList(ftpState); | |
1459 | } | |
1460 | ||
1461 | static void | |
1462 | ftpSendPort(FtpStateData * ftpState) | |
1463 | { | |
a3d5953d | 1464 | debug(9, 3) ("This is ftpSendPort\n"); |
d3f89c29 | 1465 | EBIT_CLR(ftpState->flags, FTP_PASV_SUPPORTED); |
969c39b9 | 1466 | /* XXX Not implemented? ftpFail??? */ |
3fdadc70 | 1467 | } |
1468 | ||
1469 | static void | |
79d39a72 | 1470 | ftpReadPort(FtpStateData * ftpStateNotUsed) |
3fdadc70 | 1471 | { |
a3d5953d | 1472 | debug(9, 3) ("This is ftpReadPort\n"); |
969c39b9 | 1473 | /* XXX Not implemented? */ |
3fdadc70 | 1474 | } |
1475 | ||
1476 | static void | |
1477 | ftpRestOrList(FtpStateData * ftpState) | |
1478 | { | |
a3d5953d | 1479 | debug(9, 3) ("This is ftpRestOrList\n"); |
9e242e02 | 1480 | if (ftpState->typecode == 'D') { |
1481 | ftpSendNlst(ftpState); /* sec 3.2.2 of RFC 1738 */ | |
1482 | EBIT_SET(ftpState->flags, FTP_ISDIR); | |
1483 | } else if (EBIT_TEST(ftpState->flags, FTP_ISDIR)) | |
969c39b9 | 1484 | ftpSendList(ftpState); |
1485 | else if (ftpState->restart_offset > 0) | |
1486 | ftpSendRest(ftpState); | |
1487 | else | |
1488 | ftpSendRetr(ftpState); | |
1489 | } | |
1490 | ||
1491 | static void | |
1492 | ftpSendRest(FtpStateData * ftpState) | |
1493 | { | |
1494 | snprintf(cbuf, 1024, "REST %d\r\n", ftpState->restart_offset); | |
1495 | ftpWriteCommand(cbuf, ftpState); | |
1496 | ftpState->state = SENT_REST; | |
3fdadc70 | 1497 | } |
1498 | ||
1499 | static void | |
1500 | ftpReadRest(FtpStateData * ftpState) | |
1501 | { | |
1502 | int code = ftpState->ctrl.replycode; | |
a3d5953d | 1503 | debug(9, 3) ("This is ftpReadRest\n"); |
3fdadc70 | 1504 | assert(ftpState->restart_offset > 0); |
1505 | if (code == 350) { | |
969c39b9 | 1506 | ftpSendRetr(ftpState); |
3fdadc70 | 1507 | } else if (code > 0) { |
a3d5953d | 1508 | debug(9, 3) ("ftpReadRest: REST not supported\n"); |
d3f89c29 | 1509 | EBIT_CLR(ftpState->flags, FTP_REST_SUPPORTED); |
3fdadc70 | 1510 | } else { |
1511 | ftpFail(ftpState); | |
1512 | } | |
1513 | } | |
1514 | ||
969c39b9 | 1515 | static void |
1516 | ftpSendList(FtpStateData * ftpState) | |
1517 | { | |
1518 | snprintf(cbuf, 1024, "LIST\r\n"); | |
1519 | ftpWriteCommand(cbuf, ftpState); | |
1520 | ftpState->state = SENT_LIST; | |
1521 | } | |
1522 | ||
3fdadc70 | 1523 | static void |
1524 | ftpReadList(FtpStateData * ftpState) | |
1525 | { | |
1526 | int code = ftpState->ctrl.replycode; | |
a3d5953d | 1527 | debug(9, 3) ("This is ftpReadList\n"); |
3fdadc70 | 1528 | if (code == 150 || code == 125) { |
1529 | ftpAppendSuccessHeader(ftpState); | |
1530 | commSetSelect(ftpState->data.fd, | |
30a4f2a8 | 1531 | COMM_SELECT_READ, |
3fdadc70 | 1532 | ftpReadData, |
1533 | ftpState, | |
2acf4be6 | 1534 | Config.Timeout.read); |
70a9dab4 | 1535 | commSetDefer(ftpState->data.fd, protoCheckDeferRead, ftpState->entry); |
3fdadc70 | 1536 | ftpState->state = READING_DATA; |
a0eff6be | 1537 | /* Cancel the timeout on the Control socket and establish one |
1538 | * on the data socket */ | |
1539 | commSetTimeout(ftpState->ctrl.fd, -1, NULL, NULL); | |
1540 | commSetTimeout(ftpState->data.fd, Config.Timeout.read, ftpTimeout, ftpState); | |
3fdadc70 | 1541 | return; |
1542 | } else if (!EBIT_TEST(ftpState->flags, FTP_TRIED_NLST)) { | |
969c39b9 | 1543 | ftpSendNlst(ftpState); |
3fdadc70 | 1544 | } else { |
1545 | ftpFail(ftpState); | |
1546 | return; | |
1547 | } | |
1548 | } | |
1549 | ||
969c39b9 | 1550 | static void |
1551 | ftpSendNlst(FtpStateData * ftpState) | |
1552 | { | |
1553 | EBIT_SET(ftpState->flags, FTP_TRIED_NLST); | |
9e242e02 | 1554 | if (ftpState->filepath) |
1555 | snprintf(cbuf, 1024, "NLST %s\r\n", ftpState->filepath); | |
1556 | else | |
1557 | snprintf(cbuf, 1024, "NLST\r\n"); | |
969c39b9 | 1558 | ftpWriteCommand(cbuf, ftpState); |
1559 | ftpState->state = SENT_NLST; | |
1560 | } | |
1561 | ||
1562 | static void | |
1563 | ftpSendRetr(FtpStateData * ftpState) | |
1564 | { | |
1565 | assert(ftpState->filepath != NULL); | |
1566 | snprintf(cbuf, 1024, "RETR %s\r\n", ftpState->filepath); | |
1567 | ftpWriteCommand(cbuf, ftpState); | |
1568 | ftpState->state = SENT_RETR; | |
1569 | } | |
1570 | ||
3fdadc70 | 1571 | static void |
1572 | ftpReadRetr(FtpStateData * ftpState) | |
1573 | { | |
1574 | int code = ftpState->ctrl.replycode; | |
a3d5953d | 1575 | debug(9, 3) ("This is ftpReadRetr\n"); |
3fdadc70 | 1576 | if (code >= 100 && code < 200) { |
a57512fa | 1577 | debug(9, 3) ("ftpReadRetr: reading data channel\n"); |
3fdadc70 | 1578 | ftpAppendSuccessHeader(ftpState); |
1579 | commSetSelect(ftpState->data.fd, | |
1580 | COMM_SELECT_READ, | |
1581 | ftpReadData, | |
1582 | ftpState, | |
2acf4be6 | 1583 | Config.Timeout.read); |
70a9dab4 | 1584 | commSetDefer(ftpState->data.fd, protoCheckDeferRead, ftpState->entry); |
3fdadc70 | 1585 | ftpState->state = READING_DATA; |
2acf4be6 | 1586 | /* Cancel the timeout on the Control socket and establish one |
1587 | * on the data socket */ | |
1588 | commSetTimeout(ftpState->ctrl.fd, -1, NULL, NULL); | |
1589 | commSetTimeout(ftpState->data.fd, Config.Timeout.read, ftpTimeout, ftpState); | |
3fdadc70 | 1590 | } else { |
969c39b9 | 1591 | if (!EBIT_TEST(ftpState->flags, FTP_TRY_SLASH_HACK)) { |
1592 | /* Try this as a directory missing trailing slash... */ | |
1593 | ftpHackShortcut(ftpState, ftpSendCwd); | |
1594 | } else { | |
1595 | ftpFail(ftpState); | |
1596 | } | |
3fdadc70 | 1597 | } |
1598 | } | |
1599 | ||
1600 | static void | |
1601 | ftpReadTransferDone(FtpStateData * ftpState) | |
1602 | { | |
1603 | int code = ftpState->ctrl.replycode; | |
a3d5953d | 1604 | debug(9, 3) ("This is ftpReadTransferDone\n"); |
3fdadc70 | 1605 | if (code != 226) { |
e33ba616 | 1606 | debug(9, 1) ("ftpReadTransferDone: Got code %d after reading data\n"); |
9fb13bb6 | 1607 | debug(9, 1) ("--> releasing '%s'\n", storeUrl(ftpState->entry)); |
3fdadc70 | 1608 | storeReleaseRequest(ftpState->entry); |
1609 | } | |
02be0294 | 1610 | ftpDataTransferDone(ftpState); |
3fdadc70 | 1611 | } |
1612 | ||
1613 | static void | |
1614 | ftpDataTransferDone(FtpStateData * ftpState) | |
1615 | { | |
a3d5953d | 1616 | debug(9, 3) ("This is ftpDataTransferDone\n"); |
033fa114 | 1617 | if (ftpState->data.fd > -1) { |
a3d5953d | 1618 | comm_close(ftpState->data.fd); |
1619 | ftpState->data.fd = -1; | |
033fa114 | 1620 | } |
969c39b9 | 1621 | ftpSendQuit(ftpState); |
1622 | } | |
1623 | ||
1624 | static void | |
1625 | ftpSendQuit(FtpStateData * ftpState) | |
1626 | { | |
033fa114 | 1627 | assert(ftpState->ctrl.fd > -1); |
56878878 | 1628 | snprintf(cbuf, 1024, "QUIT\r\n"); |
3fdadc70 | 1629 | ftpWriteCommand(cbuf, ftpState); |
1630 | ftpState->state = SENT_QUIT; | |
1631 | } | |
1632 | ||
1633 | static void | |
1634 | ftpReadQuit(FtpStateData * ftpState) | |
1635 | { | |
1636 | comm_close(ftpState->ctrl.fd); | |
1637 | } | |
1638 | ||
969c39b9 | 1639 | static void |
1640 | ftpTrySlashHack(FtpStateData * ftpState) | |
1641 | { | |
1642 | char *path; | |
1643 | EBIT_SET(ftpState->flags, FTP_TRY_SLASH_HACK); | |
1644 | /* Free old paths */ | |
1645 | if (ftpState->pathcomps) | |
1646 | wordlistDestroy(&ftpState->pathcomps); | |
1647 | safe_free(ftpState->filepath); | |
1648 | /* Build the new path (urlpath begins with /) */ | |
1649 | path = xstrdup(ftpState->request->urlpath); | |
1650 | rfc1738_unescape(path); | |
1651 | ftpState->filepath = path; | |
1652 | /* And off we go */ | |
1653 | ftpSendMdtm(ftpState); | |
1654 | } | |
1655 | ||
1656 | static void | |
1657 | ftpHackShortcut(FtpStateData * ftpState, FTPSM * nextState) | |
1658 | { | |
a57512fa | 1659 | /* Leave the data connection open for future use */ |
969c39b9 | 1660 | /* Save old error message */ |
1661 | ftpState->old_request = ftpState->ctrl.last_command; | |
1662 | ftpState->ctrl.last_command = NULL; | |
1663 | ftpState->old_reply = ftpState->ctrl.last_reply; | |
1664 | ftpState->ctrl.last_reply = NULL; | |
1665 | /* Jump to the "hack" state */ | |
1666 | nextState(ftpState); | |
1667 | } | |
1668 | ||
3fdadc70 | 1669 | static void |
1670 | ftpFail(FtpStateData * ftpState) | |
1671 | { | |
b9916917 | 1672 | ErrorState *err; |
a3d5953d | 1673 | debug(9, 3) ("ftpFail\n"); |
969c39b9 | 1674 | /* Try the / hack to support "Netscape" FTP URL's |
1675 | * only if we failed on CWD or RETR, !IS_DIR */ | |
1676 | if (!EBIT_TEST(ftpState->flags, FTP_ISDIR) && | |
1677 | !EBIT_TEST(ftpState->flags, FTP_TRY_SLASH_HACK)) { | |
1678 | switch (ftpState->state) { | |
1679 | case SENT_CWD: | |
1680 | case SENT_RETR: | |
1681 | /* Try the / hack */ | |
1682 | ftpHackShortcut(ftpState, ftpTrySlashHack); | |
1683 | return; | |
1684 | default: | |
1685 | break; | |
1686 | } | |
1687 | } | |
1931735b | 1688 | err = errorCon(ERR_FTP_FAILURE, HTTP_INTERNAL_SERVER_ERROR); |
b9916917 | 1689 | err->request = requestLink(ftpState->request); |
969c39b9 | 1690 | if (ftpState->old_request) |
1691 | err->ftp.request = ftpState->old_request; | |
1692 | else | |
1693 | err->ftp.request = ftpState->ctrl.last_command; | |
1694 | if (ftpState->old_reply) | |
1695 | err->ftp.reply = ftpState->old_reply; | |
1696 | else | |
1697 | err->ftp.reply = ftpState->ctrl.last_reply; | |
b9916917 | 1698 | errorAppendEntry(ftpState->entry, err); |
3fdadc70 | 1699 | comm_close(ftpState->ctrl.fd); |
1700 | } | |
1701 | ||
1702 | static void | |
1703 | ftpAppendSuccessHeader(FtpStateData * ftpState) | |
1704 | { | |
1705 | char *mime_type = NULL; | |
1706 | char *mime_enc = NULL; | |
1707 | char *urlpath = ftpState->request->urlpath; | |
1708 | char *filename = NULL; | |
1709 | char *t = NULL; | |
1710 | StoreEntry *e = ftpState->entry; | |
9e975e4e | 1711 | http_reply *reply = e->mem_obj->reply; |
3fdadc70 | 1712 | if (EBIT_TEST(ftpState->flags, FTP_HTTP_HEADER_SENT)) |
1713 | return; | |
9e975e4e | 1714 | EBIT_SET(ftpState->flags, FTP_HTTP_HEADER_SENT); |
8350fe9b | 1715 | assert(e->mem_obj->inmem_hi == 0); |
3fdadc70 | 1716 | filename = (t = strrchr(urlpath, '/')) ? t + 1 : urlpath; |
1717 | if (EBIT_TEST(ftpState->flags, FTP_ISDIR)) { | |
1718 | mime_type = "text/html"; | |
1719 | } else { | |
1720 | mime_type = mimeGetContentType(filename); | |
1721 | mime_enc = mimeGetContentEncoding(filename); | |
1722 | } | |
438fc1e3 | 1723 | storeBuffer(e); |
3fdadc70 | 1724 | storeAppendPrintf(e, "HTTP/1.0 200 Gatewaying\r\n"); |
e6b02cfc | 1725 | reply->code = 200; |
1726 | reply->version = 1.0; | |
3fdadc70 | 1727 | storeAppendPrintf(e, "Date: %s\r\n", mkrfc1123(squid_curtime)); |
e6b02cfc | 1728 | reply->date = squid_curtime; |
3fdadc70 | 1729 | storeAppendPrintf(e, "MIME-Version: 1.0\r\n"); |
1730 | storeAppendPrintf(e, "Server: Squid %s\r\n", version_string); | |
e6b02cfc | 1731 | if (ftpState->size > 0) { |
3fdadc70 | 1732 | storeAppendPrintf(e, "Content-Length: %d\r\n", ftpState->size); |
a3d5953d | 1733 | reply->content_length = ftpState->size; |
e6b02cfc | 1734 | } |
1735 | if (mime_type) { | |
3fdadc70 | 1736 | storeAppendPrintf(e, "Content-Type: %s\r\n", mime_type); |
a3d5953d | 1737 | xstrncpy(reply->content_type, mime_type, HTTP_REPLY_FIELD_SZ); |
e6b02cfc | 1738 | } |
3fdadc70 | 1739 | if (mime_enc) |
1740 | storeAppendPrintf(e, "Content-Encoding: %s\r\n", mime_enc); | |
e6b02cfc | 1741 | if (ftpState->mdtm > 0) { |
3fdadc70 | 1742 | storeAppendPrintf(e, "Last-Modified: %s\r\n", mkrfc1123(ftpState->mdtm)); |
e6b02cfc | 1743 | reply->last_modified = ftpState->mdtm; |
1744 | } | |
3fdadc70 | 1745 | storeAppendPrintf(e, "\r\n"); |
438fc1e3 | 1746 | storeBufferFlush(e); |
2acf4be6 | 1747 | reply->hdr_sz = e->mem_obj->inmem_hi; |
e6b02cfc | 1748 | storeTimestampsSet(e); |
e6b02cfc | 1749 | storeSetPublicKey(e); |
77a30ebb | 1750 | } |
bfcaf585 | 1751 | |
1752 | static void | |
1753 | ftpAbort(void *data) | |
1754 | { | |
1755 | FtpStateData *ftpState = data; | |
9fb13bb6 | 1756 | debug(9, 2) ("ftpAbort: %s\n", storeUrl(ftpState->entry)); |
15888cf7 | 1757 | if (ftpState->data.fd >= 0) { |
270b86af | 1758 | comm_close(ftpState->data.fd); |
15888cf7 | 1759 | ftpState->data.fd = -1; |
1760 | } | |
7b2cdd7b | 1761 | comm_close(ftpState->ctrl.fd); |
bfcaf585 | 1762 | } |
9b312a19 | 1763 | |
9b312a19 | 1764 | static char * |
1765 | ftpAuthRequired(const request_t * request, const char *realm) | |
1766 | { | |
1767 | LOCAL_ARRAY(char, content, AUTH_MSG_SZ); | |
2761b2be | 1768 | LOCAL_ARRAY(char, buf, AUTH_MSG_SZ); |
9b312a19 | 1769 | char *hdr; |
1770 | int s = AUTH_MSG_SZ; | |
1771 | int l = 0; | |
1772 | /* Generate the reply body */ | |
2761b2be | 1773 | l += snprintf(content + l, s - l, |
1774 | "<HTML><HEAD><TITLE>Authorization needed</TITLE>\n" | |
1775 | "</HEAD><BODY><H1>Authorization needed</H1>\n" | |
1776 | "<P>Sorry, you have to authorize yourself to request:\n" | |
1777 | "<PRE> ftp://%s@%s%256.256s</PRE>\n" | |
1778 | "<P>from this cache. Please check with the\n" | |
1779 | "<A HREF=\"mailto:%s\">cache administrator</A>\n" | |
1780 | "if you believe this is incorrect.\n" | |
1781 | "<P>\n" | |
1782 | "%s\n" | |
1783 | "<HR>\n" | |
1784 | "<ADDRESS>\n" | |
1785 | "Generated by %s/%s@%s\n" | |
1786 | "</ADDRESS></BODY></HTML>\n" | |
1787 | "\n", | |
9b312a19 | 1788 | request->login, |
1789 | request->host, | |
1790 | request->urlpath, | |
2761b2be | 1791 | Config.adminEmail, |
1792 | Config.errHtmlText, | |
9b312a19 | 1793 | appname, |
1794 | version_string, | |
1795 | getMyHostname()); | |
1796 | /* Now generate reply headers with correct content length */ | |
1797 | hdr = httpReplyHeader(1.0, HTTP_UNAUTHORIZED, | |
1798 | "text/html", | |
1799 | strlen(content), | |
1800 | squid_curtime, | |
1801 | squid_curtime + Config.negativeTtl); | |
1802 | /* Now stuff them together and add Authenticate header */ | |
1803 | l = 0; | |
1804 | s = AUTH_MSG_SZ; | |
2761b2be | 1805 | l += snprintf(buf + l, s - l, "%s", hdr); |
1806 | l += snprintf(buf + l, s - l, | |
9b312a19 | 1807 | "WWW-Authenticate: Basic realm=\"%s\"\r\n", |
1808 | realm); | |
2761b2be | 1809 | l += snprintf(buf + l, s - l, "\r\n%s", content); |
1810 | return buf; | |
9b312a19 | 1811 | } |
8f872bb6 | 1812 | |
1813 | char * | |
1814 | ftpUrlWith2f(const request_t * request) | |
1815 | { | |
1816 | LOCAL_ARRAY(char, buf, MAX_URL); | |
1817 | LOCAL_ARRAY(char, loginbuf, MAX_LOGIN_SZ + 1); | |
1818 | LOCAL_ARRAY(char, portbuf, 32); | |
1819 | char *t; | |
1820 | portbuf[0] = '\0'; | |
23d92c64 | 1821 | if (request->protocol != PROTO_FTP) |
1822 | return NULL; | |
8f872bb6 | 1823 | if (request->port != urlDefaultPort(request->protocol)) |
1824 | snprintf(portbuf, 32, ":%d", request->port); | |
1825 | loginbuf[0] = '\0'; | |
1826 | if (strlen(request->login) > 0) { | |
1827 | strcpy(loginbuf, request->login); | |
1828 | if ((t = strchr(loginbuf, ':'))) | |
1829 | *t = '\0'; | |
1830 | strcat(loginbuf, "@"); | |
1831 | } | |
1832 | snprintf(buf, MAX_URL, "%s://%s%s%s%s%s", | |
1833 | ProtocolStr[request->protocol], | |
1834 | loginbuf, | |
1835 | request->host, | |
1836 | portbuf, | |
1837 | "/%2f", | |
1838 | request->urlpath); | |
1839 | if ((t = strchr(buf, '?'))) | |
1840 | *t = '\0'; | |
1841 | return buf; | |
1842 | } |