/src/httrack/src/htsftp.c
Line | Count | Source |
1 | | /* ------------------------------------------------------------ */ |
2 | | /* |
3 | | HTTrack Website Copier, Offline Browser for Windows and Unix |
4 | | Copyright (C) 1998 Xavier Roche and other contributors |
5 | | |
6 | | SPDX-License-Identifier: GPL-3.0-or-later |
7 | | |
8 | | This program is free software: you can redistribute it and/or modify |
9 | | it under the terms of the GNU General Public License as published by |
10 | | the Free Software Foundation, either version 3 of the License, or |
11 | | (at your option) any later version. |
12 | | |
13 | | This program is distributed in the hope that it will be useful, |
14 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
16 | | GNU General Public License for more details. |
17 | | |
18 | | You should have received a copy of the GNU General Public License |
19 | | along with this program. If not, see <http://www.gnu.org/licenses/>. |
20 | | |
21 | | Ethical use: we kindly ask that you NOT use this software to harvest email |
22 | | addresses or to collect any other private information about people. Doing so |
23 | | would dishonor our work and waste the many hours we have spent on it. |
24 | | |
25 | | Please visit our Website: http://www.httrack.com |
26 | | */ |
27 | | |
28 | | /* ------------------------------------------------------------ */ |
29 | | /* File: basic FTP protocol manager */ |
30 | | /* Author: Xavier Roche */ |
31 | | /* ------------------------------------------------------------ */ |
32 | | |
33 | | /* Internal engine bytecode */ |
34 | | #define HTS_INTERNAL_BYTECODE |
35 | | |
36 | | // Gestion protocole ftp |
37 | | // Version .05 (01/2000) |
38 | | |
39 | | #include "htsftp.h" |
40 | | |
41 | | #include "htscore.h" |
42 | | #include "htsio.h" |
43 | | #include "htsthread.h" |
44 | | #include "htscrashtest.h" |
45 | | |
46 | | #include <limits.h> |
47 | | #include <stdint.h> |
48 | | |
49 | | #ifdef _WIN32 |
50 | | #else |
51 | | //inet_ntoa |
52 | | #include <arpa/inet.h> |
53 | | #endif |
54 | | |
55 | | #ifdef _WIN32 |
56 | | #ifndef __cplusplus |
57 | | // DOS |
58 | | #include <process.h> /* _beginthread, _endthread */ |
59 | | #endif |
60 | | #endif |
61 | | |
62 | | /* Passive FTP; overridable so CI can compile the active branch (#1091). */ |
63 | | #ifndef FTP_PASV |
64 | | #define FTP_PASV 1 |
65 | | #endif |
66 | | |
67 | | #define FTP_DEBUG 0 |
68 | | |
69 | | #if USE_BEGINTHREAD |
70 | | |
71 | | /* Live FTP workers. Each writes through its backlog slot and reads opt for its |
72 | | whole run, so neither may be freed while the list is not empty (#1051). |
73 | | Registration happens at spawn, not at thread entry: a worker that has not |
74 | | been scheduled yet must already hold the list against a teardown. */ |
75 | | static FTPDownloadStruct *ftp_workers = NULL; |
76 | | static htsmutex ftp_workers_mutex = HTSMUTEX_INIT; |
77 | | |
78 | 0 | static void ftp_worker_register(FTPDownloadStruct *worker) { |
79 | 0 | hts_mutexlock(&ftp_workers_mutex); |
80 | 0 | worker->pNext = ftp_workers; |
81 | 0 | ftp_workers = worker; |
82 | 0 | hts_mutexrelease(&ftp_workers_mutex); |
83 | 0 | } |
84 | | |
85 | | /* Leave the live list. The caller holds ftp_workers_mutex. */ |
86 | 0 | static void ftp_worker_unlink(FTPDownloadStruct *worker) { |
87 | 0 | FTPDownloadStruct **prev; |
88 | |
|
89 | 0 | for (prev = &ftp_workers; *prev != NULL; prev = &(*prev)->pNext) { |
90 | 0 | if (*prev == worker) { |
91 | 0 | *prev = worker->pNext; |
92 | 0 | break; |
93 | 0 | } |
94 | 0 | } |
95 | 0 | } |
96 | | |
97 | | /* Whether any worker is still registered, as ftp_stop_workers() sees it. That |
98 | | walk dereferences every entry, so one left behind is a use-after-free. */ |
99 | 0 | static hts_boolean ftp_workers_live(void) { |
100 | 0 | hts_boolean live; |
101 | |
|
102 | 0 | hts_mutexlock(&ftp_workers_mutex); |
103 | 0 | live = ftp_workers != NULL; |
104 | 0 | hts_mutexrelease(&ftp_workers_mutex); |
105 | 0 | return live; |
106 | 0 | } |
107 | | |
108 | 0 | void ftp_stop_workers(void) { |
109 | 0 | int wait; |
110 | |
|
111 | 0 | do { |
112 | 0 | FTPDownloadStruct *worker; |
113 | |
|
114 | 0 | hts_mutexlock(&ftp_workers_mutex); |
115 | | /* Idempotent, and re-raised each pass so a worker registered meanwhile is |
116 | | not missed. A registered worker's slot is still live by construction: |
117 | | back_delete_all() drains before back_free(). */ |
118 | 0 | for (worker = ftp_workers; worker != NULL; worker = worker->pNext) |
119 | 0 | hts_store_release_int(&worker->pBack->stop_ftp, HTS_TRUE); |
120 | 0 | wait = ftp_workers != NULL; |
121 | 0 | hts_mutexrelease(&ftp_workers_mutex); |
122 | 0 | if (wait) |
123 | 0 | Sleep(100); |
124 | 0 | } while (wait); |
125 | 0 | } |
126 | | |
127 | | /* Hand the slot back to the crawl thread, as 'error' where the transfer did not |
128 | | finish. Nobody would ever reap one left in STATUS_FTP_TRANSFER. */ |
129 | 0 | static void ftp_worker_release(FTPDownloadStruct *worker, const char *error) { |
130 | 0 | if (error != NULL) { |
131 | 0 | strcpybuff(worker->pBack->r.msg, error); |
132 | 0 | worker->pBack->r.statuscode = STATUSCODE_INVALID; |
133 | 0 | } |
134 | | /* Publish and unlink as one step. A teardown that still holds this worker |
135 | | would otherwise set stop_ftp on a slot the crawl thread now owns. That |
136 | | needs a second engine in this process, because ftp_workers is static. This |
137 | | is also the worker's last touch of the slot and of opt. */ |
138 | 0 | hts_mutexlock(&ftp_workers_mutex); |
139 | 0 | hts_store_release_int(&worker->pBack->status, STATUS_FTP_READY); |
140 | 0 | ftp_worker_unlink(worker); |
141 | 0 | hts_mutexrelease(&ftp_workers_mutex); |
142 | 0 | freet(worker); |
143 | 0 | } |
144 | | |
145 | | /* Runs as every worker's tail, even where a recovered fault cut the body short |
146 | | (see hts_newthread_tail). */ |
147 | 0 | static void ftp_worker_done(void *pP) { |
148 | 0 | FTPDownloadStruct *const worker = (FTPDownloadStruct *) pP; |
149 | |
|
150 | 0 | ftp_worker_release(worker, |
151 | 0 | worker->body_returned ? NULL : "FTP transfer interrupted"); |
152 | 0 | } |
153 | | |
154 | 0 | void back_launch_ftp(void *pP) { |
155 | 0 | FTPDownloadStruct *pStruct = (FTPDownloadStruct *) pP; |
156 | |
|
157 | 0 | if (pStruct == NULL) { |
158 | | #if FTP_DEBUG |
159 | | printf("[ftp error: no args]\n"); |
160 | | #endif |
161 | 0 | return; |
162 | 0 | } |
163 | | |
164 | 0 | hts_crash_test_worker(HTS_CRASH_WORKER_FTP); |
165 | | |
166 | | /* Initialize */ |
167 | 0 | hts_init(); |
168 | | |
169 | | // lancer ftp |
170 | | #if FTP_DEBUG |
171 | | printf("[Launching main ftp routine]\n"); |
172 | | #endif |
173 | 0 | run_launch_ftp(pStruct); |
174 | | |
175 | | /* Uninitialize */ |
176 | 0 | hts_uninit(); |
177 | |
|
178 | 0 | pStruct->body_returned = HTS_TRUE; |
179 | 0 | } |
180 | | |
181 | | /* See htsftp.h. The tail is called here as the thread layer calls it, since a |
182 | | worker that faults for real cannot be had on demand. */ |
183 | 0 | int ftp_worker_selftests(void) { |
184 | 0 | static const char kept[] = "what the transfer said"; |
185 | 0 | int err = 0; |
186 | 0 | int round; |
187 | |
|
188 | 0 | for (round = 0; round < 2; round++) { |
189 | 0 | const hts_boolean returned = (round == 0) ? HTS_FALSE : HTS_TRUE; |
190 | 0 | FTPDownloadStruct *const worker = calloct(1, sizeof(*worker)); |
191 | 0 | lien_back back; |
192 | |
|
193 | 0 | assertf(worker != NULL); |
194 | 0 | memset(&back, 0, sizeof(back)); |
195 | 0 | back.status = STATUS_FTP_TRANSFER; |
196 | 0 | back.r.statuscode = 200; |
197 | 0 | strcpybuff(back.r.msg, kept); |
198 | 0 | worker->pBack = &back; |
199 | 0 | worker->body_returned = returned; |
200 | 0 | ftp_worker_register(worker); |
201 | 0 | ftp_worker_done(worker); |
202 | |
|
203 | 0 | if (ftp_workers != NULL) { |
204 | 0 | fprintf(stderr, "ftp-worker-selftest: the tail left the worker " |
205 | 0 | "registered, so ftp_stop_workers() would never return\n"); |
206 | 0 | err++; |
207 | 0 | } |
208 | 0 | if (back_status(&back) != STATUS_FTP_READY) { |
209 | 0 | fprintf(stderr, "ftp-worker-selftest: slot status %d, expected %d\n", |
210 | 0 | back_status(&back), STATUS_FTP_READY); |
211 | 0 | err++; |
212 | 0 | } |
213 | 0 | if (returned) { |
214 | 0 | if (back.r.statuscode != 200 || strcmp(back.r.msg, kept) != 0) { |
215 | 0 | fprintf(stderr, |
216 | 0 | "ftp-worker-selftest: the tail overwrote a finished transfer " |
217 | 0 | "with %d '%s'\n", |
218 | 0 | back.r.statuscode, back.r.msg); |
219 | 0 | err++; |
220 | 0 | } |
221 | 0 | } else if (back.r.statuscode != STATUSCODE_INVALID || |
222 | 0 | strcmp(back.r.msg, "FTP transfer interrupted") != 0) { |
223 | 0 | fprintf(stderr, |
224 | 0 | "ftp-worker-selftest: a cut-short transfer reads %d '%s'\n", |
225 | 0 | back.r.statuscode, back.r.msg); |
226 | 0 | err++; |
227 | 0 | } |
228 | 0 | } |
229 | 0 | return err; |
230 | 0 | } |
231 | | |
232 | | /* --- The slot handoff, across a real worker thread (see htsftp.h) --- */ |
233 | | |
234 | | /* Enough rounds that a publish lands between two back_wait() passes rather |
235 | | than always before the first one. */ |
236 | 0 | #define FTP_HANDOFF_ROUNDS 32 |
237 | | /* The settle tells a blocked tail from one the scheduler never ran. */ |
238 | | #define FTP_HANDOFF_SETTLE 500 |
239 | | /* Per wait, and at 1ms a spin: the whole test must stay inside the suite's |
240 | | 600s guard even when every wait runs out. */ |
241 | 0 | #define FTP_HANDOFF_SPINS 2000 |
242 | | |
243 | | /* The payload a worker body leaves behind. Negative, so back_wait()'s reap |
244 | | skips back_finalize(): what this pins is the read, not the cache write after |
245 | | it. A slot nobody published reads 0 and "" instead. */ |
246 | 0 | #define FTP_HANDOFF_CODE STATUSCODE_INVALID |
247 | | static const char ftp_handoff_msg[] = "Transfer complete"; |
248 | | |
249 | | /* Stands in for run_launch_ftp(), writing the payload back_wait() reads. */ |
250 | 0 | static void ftp_handoff_body(void *pP) { |
251 | 0 | FTPDownloadStruct *const worker = (FTPDownloadStruct *) pP; |
252 | |
|
253 | 0 | strcpybuff(worker->pBack->r.msg, ftp_handoff_msg); |
254 | 0 | worker->pBack->r.statuscode = FTP_HANDOFF_CODE; |
255 | 0 | worker->pBack->r.size = (LLint) sizeof(ftp_handoff_msg); |
256 | 0 | worker->body_returned = HTS_TRUE; |
257 | 0 | } |
258 | | |
259 | | static int ftp_handoff_at_tail = 0; |
260 | | |
261 | | /* Raise the flag one call before ftp_worker_done() publishes the slot, so a |
262 | | test waiting on it gains no ordering over the publish that follows. */ |
263 | 0 | static void ftp_handoff_tail(void *pP) { |
264 | 0 | hts_store_release_int(&ftp_handoff_at_tail, 1); |
265 | 0 | ftp_worker_done(pP); |
266 | 0 | } |
267 | | |
268 | | /* Spawn a worker on the registered slot. The caller releases it on a refusal, |
269 | | since ftp_worker_release() wants a lock the second phase already holds. */ |
270 | | static hts_boolean ftp_handoff_spawn(FTPDownloadStruct *worker, |
271 | 0 | void (*tail)(void *)) { |
272 | 0 | if (hts_newthread_tail(ftp_handoff_body, worker, tail) == 0) |
273 | 0 | return HTS_TRUE; |
274 | 0 | fprintf(stderr, "ftp-handoff-selftest: cannot spawn a worker\n"); |
275 | 0 | return HTS_FALSE; |
276 | 0 | } |
277 | | |
278 | | /* Wait for *flag to read 'value'. HTS_FALSE once the spin budget is out. */ |
279 | 0 | static hts_boolean ftp_handoff_await(const int *flag, int value) { |
280 | 0 | int spins; |
281 | |
|
282 | 0 | for (spins = 0; hts_load_acquire_int(flag) != value; spins++) { |
283 | 0 | if (spins >= FTP_HANDOFF_SPINS) |
284 | 0 | return HTS_FALSE; |
285 | 0 | Sleep(1); |
286 | 0 | } |
287 | 0 | return HTS_TRUE; |
288 | 0 | } |
289 | | |
290 | | /* Put a fresh worker on the slot and on the live list, not yet running. The |
291 | | caller spawns it, because the second phase takes the list lock in between. */ |
292 | 0 | static FTPDownloadStruct *ftp_handoff_register(lien_back *slot) { |
293 | 0 | FTPDownloadStruct *const worker = calloct(1, sizeof(*worker)); |
294 | |
|
295 | 0 | assertf(worker != NULL); |
296 | 0 | slot->status = STATUS_FTP_TRANSFER; |
297 | 0 | worker->pBack = slot; |
298 | 0 | ftp_handoff_at_tail = 0; |
299 | 0 | ftp_worker_register(worker); |
300 | 0 | return worker; |
301 | 0 | } |
302 | | |
303 | | /* Drive back_wait() until the worker has left the list, and once more after |
304 | | that. back_wait() is the reader this pins, so the loop must not synchronize |
305 | | with the worker ahead of it: ftp_workers_live() takes the lock the publish |
306 | | happens under, and a lock that still finds the worker listed took it first, |
307 | | so it carries nothing from the publish. */ |
308 | | static void ftp_handoff_drive(struct_back *sback, httrackp *opt, |
309 | 0 | cache_back *cache) { |
310 | 0 | int spins; |
311 | |
|
312 | 0 | for (spins = 0; spins < FTP_HANDOFF_SPINS; spins++) { |
313 | 0 | back_wait(sback, opt, cache, HTS_STAT.stat_timestart); |
314 | 0 | if (!ftp_workers_live()) |
315 | 0 | break; |
316 | 0 | Sleep(1); |
317 | 0 | } |
318 | 0 | back_wait(sback, opt, cache, HTS_STAT.stat_timestart); |
319 | 0 | } |
320 | | |
321 | | /* back_wait() reaped the slot, carrying the payload its worker published. */ |
322 | 0 | static int ftp_handoff_check_reaped(const lien_back *slot, int round) { |
323 | 0 | if (back_status(slot) != STATUS_READY) { |
324 | 0 | fprintf(stderr, |
325 | 0 | "ftp-handoff-selftest: round %d left status %d, so back_wait() " |
326 | 0 | "never reaped the slot\n", |
327 | 0 | round, back_status(slot)); |
328 | 0 | return 1; |
329 | 0 | } |
330 | 0 | if (slot->r.statuscode != FTP_HANDOFF_CODE || |
331 | 0 | strcmp(slot->r.msg, ftp_handoff_msg) != 0 || |
332 | 0 | slot->r.size != (LLint) sizeof(ftp_handoff_msg)) { |
333 | 0 | fprintf(stderr, |
334 | 0 | "ftp-handoff-selftest: round %d read %d '%s' " LLintP |
335 | 0 | " behind the status back_wait() reaped\n", |
336 | 0 | round, slot->r.statuscode, slot->r.msg, (LLint) slot->r.size); |
337 | 0 | return 1; |
338 | 0 | } |
339 | 0 | return 0; |
340 | 0 | } |
341 | | |
342 | | /* See htsftp.h. */ |
343 | 0 | int ftp_handoff_selftests(httrackp *opt) { |
344 | 0 | struct_back *sback = back_new(opt, 1); |
345 | 0 | cache_back cache; |
346 | 0 | int err = 0; |
347 | 0 | int round; |
348 | |
|
349 | 0 | if (sback == NULL) { |
350 | 0 | fprintf(stderr, "ftp-handoff-selftest: no backing table\n"); |
351 | 0 | return 1; |
352 | 0 | } |
353 | 0 | memset(&cache, 0, sizeof(cache)); |
354 | | |
355 | | /* url_sav stays empty for every round, which keeps back_clean()'s sweeps off |
356 | | the slot: they all want a save name. */ |
357 | 0 | for (round = 0; round < FTP_HANDOFF_ROUNDS && err == 0; round++) { |
358 | 0 | lien_back *const slot = &sback->lnk[0]; |
359 | |
|
360 | 0 | FTPDownloadStruct *const worker = ftp_handoff_register(slot); |
361 | |
|
362 | 0 | if (!ftp_handoff_spawn(worker, ftp_handoff_tail)) { |
363 | 0 | ftp_worker_release(worker, "Unable to launch FTP thread"); |
364 | 0 | err++; |
365 | 0 | break; |
366 | 0 | } |
367 | | /* Wait on the tail's flag rather than on the slot, so the wait does not |
368 | | stand in for the acquire back_wait() has to bring itself. */ |
369 | 0 | if (!ftp_handoff_await(&ftp_handoff_at_tail, 1)) { |
370 | 0 | fprintf(stderr, "ftp-handoff-selftest: round %d never reached the tail\n", |
371 | 0 | round); |
372 | 0 | err++; |
373 | 0 | } |
374 | 0 | ftp_handoff_drive(sback, opt, &cache); |
375 | 0 | htsthread_wait(); |
376 | 0 | if (ftp_workers_live()) { |
377 | 0 | fprintf(stderr, |
378 | 0 | "ftp-handoff-selftest: round %d left a worker registered, so " |
379 | 0 | "ftp_stop_workers() would walk a freed one\n", |
380 | 0 | round); |
381 | 0 | err++; |
382 | 0 | } else { |
383 | 0 | err += ftp_handoff_check_reaped(slot, round); |
384 | 0 | } |
385 | 0 | back_clear_entry(slot); |
386 | 0 | } |
387 | | |
388 | | /* No worker may hand its slot back while a teardown holds the live-worker |
389 | | list, because ftp_stop_workers() raises stop_ftp through a slot the crawl |
390 | | thread is free to recycle as soon as its status reads ready. */ |
391 | 0 | { |
392 | 0 | lien_back *const slot = &sback->lnk[0]; |
393 | 0 | FTPDownloadStruct *const worker = ftp_handoff_register(slot); |
394 | | |
395 | | /* Held before the worker exists, so its publish has nowhere to slip in. */ |
396 | 0 | hts_mutexlock(&ftp_workers_mutex); |
397 | 0 | if (!ftp_handoff_spawn(worker, ftp_handoff_tail)) { |
398 | 0 | hts_mutexrelease(&ftp_workers_mutex); |
399 | 0 | ftp_worker_release(worker, "Unable to launch FTP thread"); |
400 | 0 | back_free(&sback); |
401 | 0 | return err + 1; |
402 | 0 | } |
403 | 0 | if (!ftp_handoff_await(&ftp_handoff_at_tail, 1)) { |
404 | 0 | fprintf(stderr, "ftp-handoff-selftest: the tail never ran\n"); |
405 | 0 | err++; |
406 | 0 | } else { |
407 | 0 | Sleep(FTP_HANDOFF_SETTLE); |
408 | 0 | if (hts_load_acquire_int(&slot->status) != STATUS_FTP_TRANSFER) { |
409 | 0 | fprintf(stderr, "ftp-handoff-selftest: a worker handed its slot back " |
410 | 0 | "while the live-worker list was held\n"); |
411 | 0 | err++; |
412 | 0 | } |
413 | 0 | } |
414 | 0 | hts_mutexrelease(&ftp_workers_mutex); |
415 | 0 | htsthread_wait(); |
416 | 0 | if (ftp_workers_live()) { |
417 | 0 | fprintf(stderr, "ftp-handoff-selftest: the held round left a worker " |
418 | 0 | "registered\n"); |
419 | 0 | err++; |
420 | 0 | } |
421 | 0 | if (back_status(slot) != STATUS_FTP_READY) { |
422 | 0 | fprintf(stderr, |
423 | 0 | "ftp-handoff-selftest: the released list left status %d\n", |
424 | 0 | back_status(slot)); |
425 | 0 | err++; |
426 | 0 | } |
427 | 0 | back_clear_entry(slot); |
428 | 0 | } |
429 | 0 | back_free(&sback); |
430 | 0 | return err; |
431 | 0 | } |
432 | | |
433 | | // lancer en back |
434 | 0 | void launch_ftp(FTPDownloadStruct * params) { |
435 | | // DOS |
436 | | #if FTP_DEBUG |
437 | | printf("[Launching main ftp thread]\n"); |
438 | | #endif |
439 | 0 | params->body_returned = HTS_FALSE; |
440 | 0 | ftp_worker_register(params); |
441 | 0 | if (hts_newthread_tail(back_launch_ftp, (void *) params, ftp_worker_done) != |
442 | 0 | 0) { |
443 | | /* ftp_stop_workers() would never return on a slot left registered. */ |
444 | 0 | ftp_worker_release(params, "Unable to launch FTP thread"); |
445 | 0 | } |
446 | 0 | } |
447 | | |
448 | | #else |
449 | | #error No more supported |
450 | | #endif |
451 | | |
452 | | // pour l'arrêt du ftp |
453 | | #ifdef _WIN32 |
454 | | #define _T_SOC_close(soc) do { closesocket(soc); soc=INVALID_SOCKET; } while(0) |
455 | | #else |
456 | 0 | #define _T_SOC_close(soc) do { close(soc); soc=INVALID_SOCKET; } while(0) |
457 | | #endif |
458 | 0 | #define _HALT_FTP { \ |
459 | 0 | if ( soc_ctl != INVALID_SOCKET ) _T_SOC_close(soc_ctl); \ |
460 | 0 | if ( soc_servdat != INVALID_SOCKET ) _T_SOC_close(soc_servdat); \ |
461 | 0 | if ( soc_dat != INVALID_SOCKET ) _T_SOC_close(soc_dat); \ |
462 | 0 | } |
463 | | #define _CHECK_HALT_FTP \ |
464 | 0 | if (stop_ftp(back)) { \ |
465 | 0 | _HALT_FTP \ |
466 | 0 | return 0; \ |
467 | 0 | } |
468 | | |
469 | | /* Start of the authority in a URL address (see htsftp.h). */ |
470 | 0 | char *ftp_jump_authority(char *url_adr) { |
471 | 0 | char *a = jump_protocol(url_adr); |
472 | |
|
473 | 0 | while (*a == '/') |
474 | 0 | a++; |
475 | 0 | return a; |
476 | 0 | } |
477 | | |
478 | | /* Split "host[:port]" (see htsftp.h). */ |
479 | | hts_boolean ftp_split_hostport(const char *adr, char *host, size_t host_size, |
480 | 0 | int *port, char *err, size_t err_size) { |
481 | 0 | static const char portmsg[] = "Invalid port: "; |
482 | 0 | const char *const a = jump_toport_const(adr); |
483 | 0 | const size_t len = a != NULL ? (size_t) (a - adr) : strlen(adr); |
484 | | |
485 | | /* the caller owns err[], so a size too small for either message is a bug */ |
486 | 0 | assertf(host_size > 0 && err_size > sizeof("Host name too long")); |
487 | 0 | host[0] = err[0] = '\0'; |
488 | | /* no resolvable name is this long, and clipping would query another host */ |
489 | 0 | if (len >= host_size) { |
490 | 0 | strlcpybuff(err, "Host name too long", err_size); |
491 | 0 | return HTS_FALSE; |
492 | 0 | } |
493 | | /* folding a nonsense port into 1..65535 fetches one the link never named */ |
494 | 0 | if (a != NULL && a[1] != '\0' && !hts_parse_url_port(a + 1, port)) { |
495 | 0 | strlcpybuff(err, portmsg, err_size); |
496 | | /* wire data: clip the echoed port rather than abort on an over-long one */ |
497 | 0 | strlncatbuff(err, a + 1, err_size, err_size - sizeof(portmsg)); |
498 | 0 | return HTS_FALSE; |
499 | 0 | } |
500 | 0 | strlncatbuff(host, adr, host_size, len); |
501 | 0 | return HTS_TRUE; |
502 | 0 | } |
503 | | |
504 | | /* Split a hostile-URL "user[:pass]@" prefix (see htsftp.h). */ |
505 | | hts_boolean ftp_split_userpass(const char *src, const char *end, char *user, |
506 | 0 | size_t user_size, char *pass, size_t pass_size) { |
507 | 0 | size_t len = 0, user_len, pass_len; |
508 | 0 | const char *colon; |
509 | |
|
510 | 0 | assertf(user_size > 0 && pass_size > 0); /* the size-1 math underflows on 0 */ |
511 | 0 | assertf(end > src); /* end is one past the '@' */ |
512 | | |
513 | 0 | user[0] = pass[0] = '\0'; // fail safe for a caller that ignores the result |
514 | 0 | while (len < (size_t) (end - src) - 1 && src[len] != '\0') |
515 | 0 | len++; |
516 | 0 | colon = memchr(src, ':', len); |
517 | 0 | user_len = colon != NULL ? (size_t) (colon - src) : len; |
518 | 0 | pass_len = colon != NULL ? len - user_len - 1 : 0; |
519 | | /* a clipped name is another account's, so refuse rather than log in as it */ |
520 | 0 | if (user_len >= user_size || pass_len >= pass_size) |
521 | 0 | return HTS_FALSE; |
522 | 0 | memcpy(user, src, user_len); |
523 | 0 | user[user_len] = '\0'; |
524 | 0 | if (pass_len != 0) |
525 | 0 | memcpy(pass, colon + 1, pass_len); |
526 | 0 | pass[pass_len] = '\0'; |
527 | 0 | return HTS_TRUE; |
528 | 0 | } |
529 | | |
530 | | /* Build "<verb> <path>" (see htsftp.h). */ |
531 | | hts_boolean ftp_command(char *line, size_t line_size, const char *verb, |
532 | 0 | const char *path) { |
533 | 0 | int n; |
534 | | |
535 | | /* A leading '-' would reach a server that shells out to ls as a flag. */ |
536 | 0 | if (path[0] == '-' || strchr(path, ' ') != NULL || |
537 | 0 | strchr(path, '\"') != NULL || strchr(path, '\'') != NULL) |
538 | 0 | n = snprintf(line, line_size, "%s \"%s\"", verb, path); |
539 | 0 | else |
540 | 0 | n = snprintf(line, line_size, "%s %s", verb, path); |
541 | 0 | if (n < 0 || (size_t) n >= line_size) { |
542 | 0 | line[0] = '\0'; // fail safe for a caller that ignores the result |
543 | 0 | return HTS_FALSE; |
544 | 0 | } |
545 | 0 | return HTS_TRUE; |
546 | 0 | } |
547 | | |
548 | | /* Read 1..max_digits decimal digits at s into *value, if it is at most max. |
549 | | Returns the first byte past them, or NULL if it is empty, too long or over |
550 | | max. */ |
551 | | static const char *ftp_parse_decimal(const char *s, int max_digits, int max, |
552 | 0 | int *value) { |
553 | 0 | const char *end; |
554 | 0 | LLint n; |
555 | |
|
556 | 0 | if (!hts_parse_llint(s, &end, 0, max, &n) || end - s > max_digits) |
557 | 0 | return NULL; |
558 | 0 | *value = (int) n; |
559 | 0 | return end; |
560 | 0 | } |
561 | | |
562 | | hts_boolean ftp_parse_pasv(const char *line, char *ip, size_t ip_size, |
563 | 0 | int *port) { |
564 | 0 | const char *s = strchr(line, '('); |
565 | 0 | int n[6]; |
566 | 0 | int i, len; |
567 | |
|
568 | 0 | if (s == NULL) |
569 | 0 | return HTS_FALSE; |
570 | 0 | s++; |
571 | 0 | for (i = 0; i < 6; i++) { |
572 | 0 | s = ftp_parse_decimal(s, 3, 255, &n[i]); |
573 | 0 | if (s == NULL || *s != (i < 5 ? ',' : ')')) |
574 | 0 | return HTS_FALSE; |
575 | 0 | s++; |
576 | 0 | } |
577 | 0 | if ((n[4] | n[5]) == 0) // port 0 means no data connection |
578 | 0 | return HTS_FALSE; |
579 | 0 | len = snprintf(ip, ip_size, "%d.%d.%d.%d", n[0], n[1], n[2], n[3]); |
580 | 0 | if (len < 0 || (size_t) len >= ip_size) { |
581 | 0 | ip[0] = '\0'; |
582 | 0 | return HTS_FALSE; |
583 | 0 | } |
584 | 0 | *port = (n[4] << 8) + n[5]; |
585 | 0 | return HTS_TRUE; |
586 | 0 | } |
587 | | |
588 | 0 | hts_boolean ftp_parse_epsv(const char *line, int *port) { |
589 | 0 | const char *s = strchr(line, '('); |
590 | 0 | char delim; |
591 | 0 | int p; |
592 | |
|
593 | 0 | if (s == NULL) |
594 | 0 | return HTS_FALSE; |
595 | | // RFC 2428: "(<d><d><d><port><d>)", d a printable non-digit |
596 | 0 | delim = s[1]; |
597 | 0 | if (delim < 33 || delim > 126 || s[2] != delim || s[3] != delim) |
598 | 0 | return HTS_FALSE; |
599 | 0 | s = ftp_parse_decimal(s + 4, 5, 65535, &p); |
600 | 0 | if (s == NULL || s[0] != delim || s[1] != ')' || p == 0) |
601 | 0 | return HTS_FALSE; |
602 | 0 | *port = p; |
603 | 0 | return HTS_TRUE; |
604 | 0 | } |
605 | | |
606 | 0 | hts_boolean ftp_parse_size(const char *line, LLint *size) { |
607 | 0 | const char *s = strchr(line, ' '); |
608 | |
|
609 | 0 | return s != NULL && hts_scan_llint(&s, 0, INT64_MAX, size) == HTS_SCAN_OK; |
610 | 0 | } |
611 | | |
612 | | /* MDTM reply "213 YYYYMMDDHHMMSS[.frac]" (RFC 3659, UTC) into tm_time. */ |
613 | 0 | static hts_boolean ftp_parse_mdtm(const char *line, struct tm *tm_time) { |
614 | 0 | int year, mon, mday, hour, min, sec; |
615 | |
|
616 | 0 | if (sscanf(line, "213 %4d%2d%2d%2d%2d%2d", &year, &mon, &mday, &hour, &min, |
617 | 0 | &sec) != 6) |
618 | 0 | return HTS_FALSE; |
619 | 0 | if (year < 1900 || mon < 1 || mon > 12 || mday < 1 || mday > 31 || hour < 0 || |
620 | 0 | hour > 23 || min < 0 || min > 59 || sec < 0 || sec > 60) |
621 | 0 | return HTS_FALSE; |
622 | 0 | memset(tm_time, 0, sizeof(*tm_time)); |
623 | 0 | tm_time->tm_year = year - 1900; |
624 | 0 | tm_time->tm_mon = mon - 1; |
625 | 0 | tm_time->tm_mday = mday; |
626 | 0 | tm_time->tm_hour = hour; |
627 | 0 | tm_time->tm_min = min; |
628 | 0 | tm_time->tm_sec = sec; |
629 | 0 | tm_time->tm_isdst = 0; |
630 | 0 | return HTS_TRUE; |
631 | 0 | } |
632 | | |
633 | | /* Whether the local copy can still be a prefix of the remote file. FTP has no |
634 | | conditional fetch, so an unprovable answer has to be no (#823). */ |
635 | | static hts_boolean ftp_may_resume(httrackp *opt, const lien_back *back, |
636 | 0 | time_t remote_mtime) { |
637 | 0 | const time_t local_mtime = get_filetime(back->url_sav); |
638 | |
|
639 | 0 | if (back->r.totalsize <= back->range_req_size) { |
640 | 0 | hts_log_print(opt, LOG_DEBUG, |
641 | 0 | "FTP: not resuming %s%s, the local copy is not shorter", |
642 | 0 | adr_nologin(opt, back->url_adr), back->url_fil); |
643 | 0 | return HTS_FALSE; |
644 | 0 | } |
645 | | /* Equality, not ordering: a copy this code did not stamp carries a local |
646 | | clock time newer than any MDTM, which "not newer" would wave through. */ |
647 | 0 | if (remote_mtime == (time_t) -1 || local_mtime == (time_t) -1 || |
648 | 0 | remote_mtime != local_mtime) { |
649 | 0 | hts_log_print(opt, LOG_DEBUG, |
650 | 0 | "FTP: not resuming %s%s, the copy is not dated as the remote", |
651 | 0 | adr_nologin(opt, back->url_adr), back->url_fil); |
652 | 0 | return HTS_FALSE; |
653 | 0 | } |
654 | 0 | return HTS_TRUE; |
655 | 0 | } |
656 | | |
657 | | /* Clip a wait to what is left of --max-time: back_wait() can't abort this slot |
658 | | the way it aborts an HTTP one. */ |
659 | 0 | static int ftp_wait_left(const httrackp *opt, int timeout) { |
660 | 0 | if (opt != NULL && opt->maxtime > 0) { |
661 | | /* rounded up, so the wait ends at the cap rather than before it */ |
662 | 0 | const TStamp left_ms = |
663 | 0 | (TStamp) opt->maxtime * 1000 - hts_mirror_elapsed_ms(); |
664 | 0 | const TStamp left = left_ms > 0 ? (left_ms + 999) / 1000 : 0; |
665 | |
|
666 | 0 | if (left < (TStamp) timeout) |
667 | 0 | timeout = (int) left; |
668 | 0 | } |
669 | 0 | return timeout; |
670 | 0 | } |
671 | | |
672 | | /* Connect honoring the stop flag, --timeout and --max-time: a blocking |
673 | | connect() to a black hole sits for the kernel's own two minutes (#1059). */ |
674 | | static hts_boolean ftp_connect(lien_back *back, T_SOC soc, SOCaddr *server, |
675 | 0 | int timeout, const httrackp *opt) { |
676 | 0 | const TStamp started = time_local(); |
677 | |
|
678 | 0 | timeout = ftp_wait_left(opt, timeout); |
679 | 0 | if (!socket_set_nonblocking(soc, HTS_TRUE)) |
680 | 0 | return HTS_FALSE; |
681 | 0 | if (hts_socket_connect(soc, &SOCaddr_sockaddr(*server), SOCaddr_size(*server), |
682 | 0 | opt) != 0) { |
683 | | #ifdef _WIN32 |
684 | | if (WSAGetLastError() != WSAEWOULDBLOCK) |
685 | | #else |
686 | 0 | if (errno != EINPROGRESS && errno != EINTR) |
687 | 0 | #endif |
688 | 0 | return HTS_FALSE; |
689 | | /* state.stop counts here, unlike in the waits below: a mirror the user |
690 | | stopped has nothing to finish on a connection never established. */ |
691 | 0 | while (check_socket_connect(soc) == 0) { |
692 | 0 | if (hts_load_acquire_int(&back->stop_ftp) || |
693 | 0 | (opt != NULL && hts_load_acquire_int(&opt->state.stop)) || |
694 | 0 | (int) (time_local() - started) >= timeout) |
695 | 0 | return HTS_FALSE; |
696 | 0 | Sleep(100); |
697 | 0 | } |
698 | 0 | if (connect_socket_error(soc) != 0) |
699 | 0 | return HTS_FALSE; |
700 | 0 | } |
701 | | /* the rest of the session reads and writes this socket blocking */ |
702 | 0 | return socket_set_nonblocking(soc, HTS_FALSE); |
703 | 0 | } |
704 | | |
705 | | #if !FTP_PASV |
706 | | /* The active-mode counterpart: an unbounded accept() waits for a data |
707 | | connection the server may never make (#1074). */ |
708 | | static T_SOC ftp_accept(lien_back *back, T_SOC soc, int timeout, |
709 | | const httrackp *opt) { |
710 | | const TStamp started = time_local(); |
711 | | T_SOC soc_dat; |
712 | | |
713 | | timeout = ftp_wait_left(opt, timeout); |
714 | | if (!socket_set_nonblocking(soc, HTS_TRUE)) |
715 | | return INVALID_SOCKET; |
716 | | /* state.stop counts as in ftp_connect: nothing is established yet */ |
717 | | while (check_socket(soc) == 0) { |
718 | | if (hts_load_acquire_int(&back->stop_ftp) || |
719 | | (opt != NULL && hts_load_acquire_int(&opt->state.stop)) || |
720 | | (int) (time_local() - started) >= timeout) |
721 | | return INVALID_SOCKET; |
722 | | Sleep(100); |
723 | | } |
724 | | soc_dat = accept(soc, NULL, NULL); |
725 | | /* BSD hands the listener's non-blocking flag down to the accepted socket, |
726 | | which the transfer below reads blocking. */ |
727 | | if (soc_dat != INVALID_SOCKET && |
728 | | !socket_set_nonblocking(soc_dat, HTS_FALSE)) { |
729 | | deletesoc(soc_dat); |
730 | | return INVALID_SOCKET; |
731 | | } |
732 | | return soc_dat; |
733 | | } |
734 | | #endif |
735 | | |
736 | | /* Resolve, likewise: the shared resolver bounds itself by opt->timeout alone |
737 | | and takes no stop flag. */ |
738 | | static SOCaddr *ftp_dns_resolve(httrackp *opt, lien_back *back, |
739 | | const char *host, SOCaddr *addr, int timeout, |
740 | 0 | const char **error) { |
741 | 0 | const int left = ftp_wait_left(opt, timeout); |
742 | |
|
743 | 0 | SOCaddr_clear(*addr); |
744 | 0 | if (left <= 0) { |
745 | 0 | if (error != NULL) |
746 | 0 | *error = "mirror deadline reached"; |
747 | 0 | return NULL; |
748 | 0 | } |
749 | 0 | if (hts_dns_resolve_all_bounded(opt, host, addr, 1, left, &back->stop_ftp, |
750 | 0 | error) > 0) |
751 | 0 | return SOCaddr_is_valid(*addr) ? addr : NULL; |
752 | 0 | return NULL; |
753 | 0 | } |
754 | | |
755 | | // la véritable fonction une fois lancées les routines thread/fork |
756 | 0 | int run_launch_ftp(FTPDownloadStruct * pStruct) { |
757 | 0 | lien_back *back = pStruct->pBack; |
758 | 0 | httrackp *opt = pStruct->pOpt; |
759 | 0 | char user[256] = "anonymous"; |
760 | 0 | char pass[256] = "user@"; |
761 | 0 | char line_retr[FTP_LINE_SIZE]; |
762 | 0 | int port = 21; |
763 | |
|
764 | 0 | #if FTP_PASV |
765 | 0 | int port_pasv = 0; |
766 | 0 | #endif |
767 | 0 | char BIGSTK adr_ip[1024]; |
768 | 0 | char *adr, *real_adr; |
769 | 0 | char BIGSTK ftp_path[CATBUFF_SIZE]; // the decoded URL path, screened once |
770 | | /* opt->timeout == 0 means unlimited; use INT_MAX so 0 doesn't make the read |
771 | | return instantly. */ |
772 | 0 | const int timeout = opt->timeout > 0 ? opt->timeout : INT_MAX; |
773 | 0 | int timeout_onfly = 8; // attente réponse supplémentaire |
774 | 0 | int transfer_list = 0; // directory |
775 | 0 | int rest_understood = 0; // rest command understood |
776 | | |
777 | | // |
778 | 0 | T_SOC soc_ctl = INVALID_SOCKET; |
779 | 0 | T_SOC soc_servdat = INVALID_SOCKET; |
780 | 0 | T_SOC soc_dat = INVALID_SOCKET; |
781 | 0 | SOCaddr server_data; |
782 | | |
783 | | // |
784 | 0 | line_retr[0] = adr_ip[0] = ftp_path[0] = '\0'; |
785 | | |
786 | | // effacer |
787 | 0 | strcpybuff(back->r.msg, ""); |
788 | 0 | back->r.statuscode = 0; |
789 | 0 | hts_store_relaxed_llint(&back->r.size, 0); |
790 | 0 | back->r.lastmodified[0] = '\0'; // a retry must not stamp the previous MDTM |
791 | |
|
792 | 0 | real_adr = ftp_jump_authority(back->url_adr); |
793 | 0 | if ((adr = jump_identification(real_adr)) != real_adr) { // user |
794 | 0 | if (!ftp_split_userpass_buf(real_adr, adr, user, pass)) { |
795 | 0 | strcpybuff(back->r.msg, "FTP user name or password too long"); |
796 | 0 | back->r.statuscode = STATUSCODE_INVALID; |
797 | 0 | _HALT_FTP return 0; |
798 | 0 | } |
799 | 0 | } |
800 | | // Calculer RETR <nom> |
801 | 0 | { |
802 | 0 | char *a; |
803 | |
|
804 | 0 | a = back->url_fil; |
805 | 0 | if (a != NULL && *a != '\0') { |
806 | 0 | if (strnotempty(a)) { |
807 | 0 | const size_t len_a = |
808 | 0 | strlen(unescape_http(ftp_path, sizeof(ftp_path), a)); |
809 | 0 | hts_boolean fits; |
810 | |
|
811 | 0 | if (len_a > 0 && |
812 | 0 | ftp_path[len_a - 1] == '/') { /* obviously a directory listing */ |
813 | 0 | transfer_list = 1; |
814 | 0 | fits = ftp_command_line(line_retr, "LIST -A", ftp_path); |
815 | 0 | } else { |
816 | 0 | fits = ftp_command_line(line_retr, "RETR", ftp_path); |
817 | 0 | } |
818 | 0 | if (!fits) { |
819 | 0 | strcpybuff(back->r.msg, "FTP path too long"); |
820 | 0 | back->r.statuscode = STATUSCODE_INVALID; |
821 | 0 | _HALT_FTP return 0; |
822 | 0 | } |
823 | 0 | } else { |
824 | 0 | transfer_list = 1; |
825 | 0 | snprintf(line_retr, sizeof(line_retr), "LIST -A"); |
826 | 0 | } |
827 | 0 | } else { |
828 | 0 | strcpybuff(back->r.msg, "Unexpected PORT error"); |
829 | 0 | back->r.statuscode = STATUSCODE_INVALID; |
830 | 0 | } |
831 | 0 | } |
832 | | |
833 | | // fail here, or send_line() drops the command and the reply read times out. |
834 | | // Every command below is a literal verb plus one of these three. |
835 | 0 | if (!hts_is_control_free(user) || !hts_is_control_free(pass) || |
836 | 0 | !hts_is_control_free(ftp_path)) { |
837 | 0 | strcpybuff(back->r.msg, "Invalid control character in FTP URL"); |
838 | 0 | back->r.statuscode = STATUSCODE_INVALID; |
839 | 0 | _HALT_FTP return 0; |
840 | 0 | } |
841 | | |
842 | | #if FTP_DEBUG |
843 | | printf("Connecting to %s...\n", adr); |
844 | | #endif |
845 | | |
846 | | // connexion |
847 | 0 | { |
848 | 0 | SOCaddr server; |
849 | 0 | char _adr[256]; |
850 | 0 | char err[128]; |
851 | 0 | const char *error = "unknown error"; |
852 | |
|
853 | 0 | memset(&server, 0, sizeof(server)); |
854 | |
|
855 | 0 | if (!ftp_split_hostport(adr, _adr, sizeof(_adr), &port, err, sizeof(err))) { |
856 | 0 | htsblk_failf(&back->r, "%s", err); |
857 | 0 | back->r.statuscode = STATUSCODE_INVALID; // permanent, unlike a DNS miss |
858 | 0 | _HALT_FTP return 0; |
859 | 0 | } |
860 | | |
861 | | // récupérer adresse résolue |
862 | 0 | strcpybuff(back->info, "host name"); |
863 | 0 | if (ftp_dns_resolve(opt, back, _adr, &server, timeout, &error) == NULL) { |
864 | 0 | htsblk_failf(&back->r, "Unable to get server's address: %s", error); |
865 | 0 | back->r.statuscode = STATUSCODE_NON_FATAL; |
866 | 0 | _HALT_FTP return 0; |
867 | 0 | } |
868 | 0 | _CHECK_HALT_FTP; |
869 | | |
870 | | // copie adresse pour cnx data |
871 | 0 | SOCaddr_copy_SOCaddr(server_data, server); |
872 | | |
873 | | // créer ("attachement") une socket (point d'accès) internet,en flot |
874 | 0 | soc_ctl = hts_socket_client(SOCaddr_sinfamily(server), opt, HTS_FALSE); |
875 | 0 | if (soc_ctl == INVALID_SOCKET) { |
876 | 0 | strcpybuff(back->r.msg, "Unable to create a socket"); |
877 | 0 | back->r.statuscode = STATUSCODE_INVALID; |
878 | 0 | _HALT_FTP return 0; |
879 | 0 | } |
880 | | |
881 | 0 | SOCaddr_initport(server, port); |
882 | |
|
883 | 0 | strcpybuff(back->info, "connect"); |
884 | 0 | hts_log_print(opt, LOG_DEBUG, "FTP: connecting to %s:%d", |
885 | 0 | adr_nologin(opt, _adr), port); |
886 | |
|
887 | 0 | if (!ftp_connect(back, soc_ctl, &server, timeout, opt)) { |
888 | 0 | strcpybuff(back->r.msg, "Unable to connect to the server"); |
889 | 0 | back->r.statuscode = STATUSCODE_INVALID; |
890 | 0 | _HALT_FTP return 0; |
891 | 0 | } |
892 | 0 | _CHECK_HALT_FTP; |
893 | |
|
894 | 0 | { |
895 | 0 | char BIGSTK line[FTP_LINE_SIZE]; |
896 | | |
897 | | /* line_retr is copied here verbatim; a narrower line[] would clip it. */ |
898 | 0 | HTS_COMPILE_ASSERT(sizeof(line) == sizeof(line_retr)); |
899 | | |
900 | | // envoi du login |
901 | | |
902 | | // --USER-- |
903 | 0 | get_ftp_line(back, soc_ctl, line, sizeof(line), timeout, opt); // en tête |
904 | 0 | _CHECK_HALT_FTP; |
905 | |
|
906 | 0 | if (line[0] == '2') { // ok, connecté |
907 | 0 | strcpybuff(back->info, "login: user"); |
908 | 0 | snprintf(line, sizeof(line), "USER %s", user); |
909 | 0 | send_line(soc_ctl, line); |
910 | 0 | get_ftp_line(back, soc_ctl, line, sizeof(line), timeout, opt); |
911 | 0 | _CHECK_HALT_FTP; |
912 | 0 | if ((line[0] == '3') || (line[0] == '2')) { |
913 | | // --PASS-- |
914 | 0 | if (line[0] == '3') { |
915 | 0 | strcpybuff(back->info, "login: pass"); |
916 | 0 | snprintf(line, sizeof(line), "PASS %s", pass); |
917 | 0 | send_line(soc_ctl, line); |
918 | 0 | get_ftp_line(back, soc_ctl, line, sizeof(line), timeout, opt); |
919 | 0 | _CHECK_HALT_FTP; |
920 | 0 | } |
921 | 0 | if (line[0] == '2') { // ok |
922 | 0 | send_line(soc_ctl, "TYPE I"); |
923 | 0 | get_ftp_line(back, soc_ctl, line, sizeof(line), timeout, opt); |
924 | 0 | _CHECK_HALT_FTP; |
925 | 0 | if (line[0] == '2') { |
926 | | // ok |
927 | 0 | } else { |
928 | 0 | strcpybuff(back->r.msg, "TYPE I error"); |
929 | 0 | back->r.statuscode = STATUSCODE_INVALID; |
930 | 0 | } |
931 | |
|
932 | 0 | } else { |
933 | 0 | htsblk_failf(&back->r, "Bad password: %s", linejmp(line)); |
934 | 0 | back->r.statuscode = STATUSCODE_INVALID; |
935 | 0 | } |
936 | 0 | } else { |
937 | 0 | htsblk_failf(&back->r, "Bad user name: %s", linejmp(line)); |
938 | 0 | back->r.statuscode = STATUSCODE_INVALID; |
939 | 0 | } |
940 | 0 | } else { |
941 | 0 | htsblk_failf(&back->r, "Connection refused: %s", linejmp(line)); |
942 | 0 | back->r.statuscode = STATUSCODE_INVALID; |
943 | 0 | } |
944 | | |
945 | | // ok, si on est prêts on écoute sur un port et on demande la sauce |
946 | 0 | if (back->r.statuscode != -1) { |
947 | | |
948 | | // |
949 | | // Pré-REST |
950 | | // |
951 | 0 | #if FTP_PASV |
952 | 0 | if (SOCaddr_getproto(server) == '1') { |
953 | 0 | strcpybuff(back->info, "pasv"); |
954 | 0 | snprintf(line, sizeof(line), "PASV"); |
955 | 0 | send_line(soc_ctl, line); |
956 | 0 | get_ftp_line(back, soc_ctl, line, sizeof(line), timeout, opt); |
957 | 0 | } else { /* ipv6 */ |
958 | 0 | line[0] = '\0'; |
959 | 0 | } |
960 | 0 | _CHECK_HALT_FTP; |
961 | 0 | if (line[0] == '2') { |
962 | | // e.g. 227 Entering Passive Mode (123,45,67,89,177,27) |
963 | 0 | if (!ftp_parse_pasv(line, adr_ip, sizeof(adr_ip), &port_pasv)) { |
964 | 0 | htsblk_failf(&back->r, "PASV incorrect: %s", linejmp(line)); |
965 | 0 | back->r.statuscode = STATUSCODE_INVALID; |
966 | 0 | } |
967 | 0 | } else { |
968 | | /* |
969 | | * try epsv (ipv6) * |
970 | | */ |
971 | 0 | strcpybuff(back->info, "pasv"); |
972 | 0 | snprintf(line, sizeof(line), "EPSV"); |
973 | 0 | send_line(soc_ctl, line); |
974 | 0 | get_ftp_line(back, soc_ctl, line, sizeof(line), timeout, opt); |
975 | 0 | _CHECK_HALT_FTP; |
976 | 0 | if (line[0] == '2') { /* got it */ |
977 | | // e.g. 229 Entering Extended Passive Mode (|||6446|) |
978 | 0 | if (!ftp_parse_epsv(line, &port_pasv)) { |
979 | 0 | htsblk_failf(&back->r, "EPSV incorrect: %s", linejmp(line)); |
980 | 0 | back->r.statuscode = STATUSCODE_INVALID; |
981 | 0 | } |
982 | 0 | } else { |
983 | 0 | htsblk_failf(&back->r, "PASV/EPSV error: %s", linejmp(line)); |
984 | 0 | back->r.statuscode = STATUSCODE_INVALID; |
985 | 0 | } // sinon on est prêts |
986 | 0 | } |
987 | | #else |
988 | | // rien à faire avant |
989 | | #endif |
990 | | |
991 | 0 | #if FTP_PASV |
992 | 0 | if (port_pasv) { |
993 | 0 | #endif |
994 | | // SIZE |
995 | 0 | if (back->r.statuscode != -1) { |
996 | | // a clipped probe would size and date a different file |
997 | 0 | if (!transfer_list && ftp_command_line(line, "SIZE", ftp_path)) { |
998 | | // SIZE? |
999 | 0 | strcpybuff(back->info, "size"); |
1000 | 0 | send_line(soc_ctl, line); |
1001 | 0 | get_ftp_line(back, soc_ctl, line, sizeof(line), timeout, opt); |
1002 | 0 | _CHECK_HALT_FTP; |
1003 | 0 | if (line[0] == '2') { // SIZE compris, ALORS tester REST (sinon pas tester: cf probleme des txt.gz decompresses a la volee) |
1004 | 0 | LLint size; |
1005 | 0 | time_t remote_mtime = (time_t) -1; |
1006 | 0 | struct tm remote_tm; |
1007 | |
|
1008 | 0 | if (ftp_parse_size(line, &size)) { |
1009 | 0 | hts_store_relaxed_llint(&back->r.totalsize, size); |
1010 | 0 | } |
1011 | | |
1012 | | // MDTM? |
1013 | 0 | if (ftp_command_line(line, "MDTM", ftp_path)) { |
1014 | 0 | strcpybuff(back->info, "mdtm"); |
1015 | 0 | send_line(soc_ctl, line); |
1016 | 0 | get_ftp_line(back, soc_ctl, line, sizeof(line), timeout, opt); |
1017 | 0 | _CHECK_HALT_FTP; |
1018 | 0 | if (ftp_parse_mdtm(line, &remote_tm)) { |
1019 | 0 | char date[256]; |
1020 | |
|
1021 | 0 | time_rfc822(date, &remote_tm); |
1022 | | /* Stamp the mirror as the HTTP path does, so a later pass |
1023 | | compares server-clock times instead of crossing clocks. |
1024 | | */ |
1025 | 0 | back->r.lastmodified[0] = '\0'; |
1026 | 0 | strlncatbuff(back->r.lastmodified, date, |
1027 | 0 | sizeof(back->r.lastmodified), |
1028 | 0 | sizeof(back->r.lastmodified) - 1); |
1029 | | // a clipped date cannot prove the copy matches |
1030 | 0 | if (!hts_time_exact(hts_utc_seconds(&remote_tm), |
1031 | 0 | &remote_mtime)) |
1032 | 0 | remote_mtime = (time_t) -1; |
1033 | 0 | } |
1034 | 0 | } |
1035 | | |
1036 | | /* Only over a copy back_add() judged partial: on --update every |
1037 | | mirrored file exists, and resuming a complete one splices the |
1038 | | old body into the new (#798). */ |
1039 | 0 | if (back->range_req_size > 0 && (transfer_list == 0) && |
1040 | 0 | ftp_may_resume(opt, back, remote_mtime)) { |
1041 | 0 | strcpybuff(back->info, "rest"); |
1042 | 0 | snprintf(line, sizeof(line), "REST " LLintP, |
1043 | 0 | (LLint) back->range_req_size); |
1044 | 0 | send_line(soc_ctl, line); |
1045 | 0 | get_ftp_line(back, soc_ctl, line, sizeof(line), timeout, opt); |
1046 | 0 | _CHECK_HALT_FTP; |
1047 | 0 | if ((line[0] == '3') || (line[0] == '2')) { // ok |
1048 | 0 | rest_understood = 1; |
1049 | 0 | } // else never mind |
1050 | 0 | } |
1051 | 0 | } // sinon tant pis |
1052 | 0 | } |
1053 | 0 | } |
1054 | 0 | #if FTP_PASV |
1055 | 0 | } |
1056 | 0 | #endif |
1057 | | |
1058 | | // |
1059 | | // Post-REST |
1060 | | // |
1061 | 0 | #if FTP_PASV |
1062 | | // Ok, se connecter |
1063 | 0 | if (port_pasv) { |
1064 | 0 | SOCaddr server; |
1065 | 0 | hts_boolean resolved; |
1066 | 0 | const char *error = "unknown error"; |
1067 | | |
1068 | | // effacer structure |
1069 | 0 | memset(&server, 0, sizeof(server)); |
1070 | | |
1071 | | // infos |
1072 | 0 | strcpybuff(back->info, "resolv"); |
1073 | | |
1074 | | // résoudre |
1075 | 0 | if (adr_ip[0]) { |
1076 | 0 | resolved = ftp_dns_resolve(opt, back, adr_ip, &server, timeout, |
1077 | 0 | &error) != NULL; |
1078 | 0 | } else { |
1079 | 0 | SOCaddr_copy_SOCaddr(server, server_data); |
1080 | 0 | resolved = HTS_TRUE; |
1081 | 0 | } |
1082 | | |
1083 | | // infos |
1084 | 0 | strcpybuff(back->info, "cnxdata"); |
1085 | | #if FTP_DEBUG |
1086 | | printf("Data: Connecting to %s:%d...\n", adr_ip, port_pasv); |
1087 | | #endif |
1088 | 0 | if (resolved) { |
1089 | | // socket |
1090 | 0 | soc_dat = |
1091 | 0 | hts_socket_client(SOCaddr_sinfamily(server), opt, HTS_FALSE); |
1092 | 0 | if (soc_dat != INVALID_SOCKET) { |
1093 | | // structure: connexion au domaine internet, port 80 (ou autre) |
1094 | 0 | SOCaddr_initport(server, port_pasv); |
1095 | 0 | if (ftp_connect(back, soc_dat, &server, timeout, opt)) { |
1096 | 0 | strcpybuff(back->info, "retr"); |
1097 | | // clip, never abort: this line is built from a crawled URL |
1098 | 0 | line[0] = '\0'; |
1099 | 0 | strlncatbuff(line, line_retr, sizeof(line), sizeof(line) - 1); |
1100 | 0 | send_line(soc_ctl, line); |
1101 | 0 | get_ftp_line(back, soc_ctl, line, sizeof(line), timeout, opt); |
1102 | 0 | _CHECK_HALT_FTP; |
1103 | 0 | if (line[0] == '1') { |
1104 | | // OK |
1105 | 0 | } else { |
1106 | 0 | deletesoc(soc_dat); |
1107 | 0 | soc_dat = INVALID_SOCKET; |
1108 | | // |
1109 | 0 | htsblk_failf(&back->r, "RETR command error: %s", |
1110 | 0 | linejmp(line)); |
1111 | 0 | back->r.statuscode = STATUSCODE_INVALID; |
1112 | 0 | } // sinon on est prêts |
1113 | 0 | } else { |
1114 | | #if FTP_DEBUG |
1115 | | printf("Data: unable to connect\n"); |
1116 | | #endif |
1117 | 0 | deletesoc(soc_dat); |
1118 | 0 | soc_dat = INVALID_SOCKET; |
1119 | | // |
1120 | 0 | strcpybuff(back->r.msg, "Unable to connect"); |
1121 | 0 | back->r.statuscode = STATUSCODE_INVALID; |
1122 | 0 | } // sinon on est prêts |
1123 | 0 | } else { |
1124 | 0 | strcpybuff(back->r.msg, "Unable to create a socket"); |
1125 | 0 | back->r.statuscode = STATUSCODE_INVALID; |
1126 | 0 | } // sinon on est prêts |
1127 | 0 | } else { |
1128 | 0 | htsblk_failf(&back->r, "Unable to resolve IP %s: %s", adr_ip, |
1129 | 0 | error); |
1130 | 0 | back->r.statuscode = STATUSCODE_INVALID; |
1131 | 0 | } // sinon on est prêts |
1132 | 0 | } else { |
1133 | 0 | htsblk_failf(&back->r, "PASV incorrect: %s", linejmp(line)); |
1134 | 0 | back->r.statuscode = STATUSCODE_INVALID; |
1135 | 0 | } // sinon on est prêts |
1136 | | #else |
1137 | | //T_SOC soc_servdat; |
1138 | | strcpybuff(back->info, "listening"); |
1139 | | if ((soc_servdat = get_datasocket(line, sizeof(line))) != INVALID_SOCKET) { |
1140 | | _CHECK_HALT_FTP; |
1141 | | send_line(soc_ctl, line); // envoi du RETR |
1142 | | get_ftp_line(back, soc_ctl, line, sizeof(line), timeout, opt); |
1143 | | _CHECK_HALT_FTP; |
1144 | | if (line[0] == '2') { // ok |
1145 | | strcpybuff(back->info, "retr"); |
1146 | | // clip, never abort: this line is built from a crawled URL |
1147 | | line[0] = '\0'; |
1148 | | strlncatbuff(line, line_retr, sizeof(line), sizeof(line) - 1); |
1149 | | send_line(soc_ctl, line); |
1150 | | get_ftp_line(back, soc_ctl, line, sizeof(line), timeout, opt); |
1151 | | _CHECK_HALT_FTP; |
1152 | | if (line[0] == '1') { |
1153 | | if ((soc_dat = ftp_accept(back, soc_servdat, timeout, opt)) == |
1154 | | INVALID_SOCKET) { |
1155 | | strcpybuff(back->r.msg, "Unable to accept connection"); |
1156 | | back->r.statuscode = STATUSCODE_INVALID; |
1157 | | } |
1158 | | } else { |
1159 | | htsblk_failf(&back->r, "RETR command error: %s", linejmp(line)); |
1160 | | back->r.statuscode = STATUSCODE_INVALID; |
1161 | | } |
1162 | | } else { |
1163 | | htsblk_failf(&back->r, "PORT command error: %s", linejmp(line)); |
1164 | | back->r.statuscode = STATUSCODE_INVALID; |
1165 | | } |
1166 | | /* invalidate, or the _CHECK_HALT_FTP below re-closes a stale fd */ |
1167 | | deletesoc(soc_servdat); |
1168 | | soc_servdat = INVALID_SOCKET; |
1169 | | } else { |
1170 | | strcpybuff(back->r.msg, "Unable to listen to a port"); |
1171 | | back->r.statuscode = STATUSCODE_INVALID; |
1172 | | } |
1173 | | #endif |
1174 | | |
1175 | | // |
1176 | | // Ok, connexion initiée |
1177 | | // |
1178 | 0 | if (soc_dat != INVALID_SOCKET) { |
1179 | 0 | if (rest_understood) { // REST sent and understood |
1180 | 0 | file_notify(opt, back->url_adr, back->url_fil, back->url_sav, 0, 1, |
1181 | 0 | 0); |
1182 | | /* The bytes already on disk count too, or the completeness check |
1183 | | below rejects every resumed transfer (#798). */ |
1184 | 0 | hts_store_relaxed_llint(&back->r.size, back->range_req_size); |
1185 | 0 | back->r.fp = fileappend(&opt->state.strc, back->url_sav); |
1186 | 0 | } else { |
1187 | 0 | file_notify(opt, back->url_adr, back->url_fil, back->url_sav, 1, 1, |
1188 | 0 | 0); |
1189 | | /* Every failure exit below would else leave the mirror truncated |
1190 | | (#771); the resume branch appends and needs no backup. */ |
1191 | 0 | back_refetch_backup(opt, back); |
1192 | 0 | back->r.fp = filecreate(&opt->state.strc, back->url_sav); |
1193 | 0 | } |
1194 | 0 | strcpybuff(back->info, "receiving"); |
1195 | 0 | if (back->r.fp != NULL) { |
1196 | 0 | char BIGSTK buff[1024]; |
1197 | 0 | int len = 1; |
1198 | 0 | int read_len = 1024; |
1199 | |
|
1200 | 0 | while((len > 0) && (!stop_ftp(back))) { |
1201 | | // attendre les données |
1202 | 0 | len = 1; // pas d'erreur pour le moment |
1203 | 0 | switch (wait_socket_receive(back, soc_dat, timeout, opt)) { |
1204 | 0 | case -1: |
1205 | 0 | strcpybuff(back->r.msg, "FTP read error"); |
1206 | 0 | back->r.statuscode = STATUSCODE_INVALID; |
1207 | 0 | len = 0; // fin |
1208 | 0 | break; |
1209 | 0 | case 0: |
1210 | | /* stop_ftp() names the real reason when it is the stop that |
1211 | | cut the wait short. */ |
1212 | 0 | if (!stop_ftp(back)) |
1213 | 0 | htsblk_failf(&back->r, "Time out (%d)", timeout); |
1214 | 0 | back->r.statuscode = STATUSCODE_INVALID; |
1215 | 0 | len = 0; // fin |
1216 | 0 | break; |
1217 | 0 | } |
1218 | | |
1219 | | // réception |
1220 | 0 | if (len) { |
1221 | 0 | len = recv(soc_dat, buff, read_len, 0); |
1222 | 0 | if (len > 0) { |
1223 | | /* This worker is the only writer, so its read is safe. */ |
1224 | 0 | hts_store_relaxed_llint(&back->r.size, back->r.size + len); |
1225 | 0 | hts_stat_recv_add(len); |
1226 | 0 | if (back->r.fp) { |
1227 | 0 | if (!hts_fwrite_exact(buff, (size_t) len, back->r.fp)) { |
1228 | | /* |
1229 | | int fcheck; |
1230 | | if ((fcheck=check_fatal_io_errno())) { |
1231 | | opt->state.exit_xh=-1; |
1232 | | } |
1233 | | */ |
1234 | 0 | strcpybuff(back->r.msg, "Write error"); |
1235 | 0 | back->r.statuscode = STATUSCODE_INVALID; |
1236 | 0 | len = 0; // error |
1237 | 0 | } |
1238 | 0 | } else { |
1239 | 0 | strcpybuff(back->r.msg, "Unexpected write error"); |
1240 | 0 | back->r.statuscode = STATUSCODE_INVALID; |
1241 | 0 | } |
1242 | 0 | } else { // Erreur ou terminé |
1243 | 0 | back->r.statuscode = 0; |
1244 | 0 | if (back->r.totalsize > 0 |
1245 | 0 | && back->r.size != back->r.totalsize) { |
1246 | 0 | back->r.statuscode = STATUSCODE_INVALID; |
1247 | 0 | strcpybuff(back->r.msg, "FTP file incomplete"); |
1248 | 0 | } |
1249 | 0 | } |
1250 | 0 | read_len = 1024; |
1251 | 0 | } |
1252 | 0 | } |
1253 | 0 | if (back->r.fp) { |
1254 | 0 | fclose(back->r.fp); |
1255 | 0 | back->r.fp = NULL; |
1256 | 0 | } |
1257 | | /* back_flush_output() stamps only r.is_write transfers, which FTP |
1258 | | never is; a partial needs the remote's date too (#823). */ |
1259 | 0 | if (strnotempty(back->r.lastmodified)) |
1260 | 0 | set_filetime_rfc822(back->url_sav, back->r.lastmodified); |
1261 | 0 | } else { |
1262 | 0 | strcpybuff(back->r.msg, "Unable to write file"); |
1263 | 0 | back->r.statuscode = STATUSCODE_INVALID; |
1264 | 0 | } |
1265 | | /* invalidate, or the _CHECK_HALT_FTP below re-closes a stale fd */ |
1266 | 0 | deletesoc(soc_dat); |
1267 | 0 | soc_dat = INVALID_SOCKET; |
1268 | | |
1269 | | // 226 Transfer complete? |
1270 | 0 | if (back->r.statuscode != -1) { |
1271 | 0 | if (wait_socket_receive(back, soc_ctl, timeout_onfly, opt) > 0) { |
1272 | | // récupérer 226 transfer complete |
1273 | 0 | get_ftp_line(back, soc_ctl, line, sizeof(line), timeout, opt); |
1274 | 0 | if (line[0] == '2') { // OK |
1275 | 0 | strcpybuff(back->r.msg, "OK"); |
1276 | 0 | back->r.statuscode = HTTP_OK; |
1277 | 0 | } else { |
1278 | 0 | htsblk_failf(&back->r, "RETR incorrect: %s", linejmp(line)); |
1279 | 0 | back->r.statuscode = STATUSCODE_INVALID; |
1280 | 0 | } |
1281 | 0 | } else { |
1282 | 0 | strcpybuff(back->r.msg, "FTP read error"); |
1283 | 0 | back->r.statuscode = STATUSCODE_INVALID; |
1284 | 0 | } |
1285 | 0 | } |
1286 | |
|
1287 | 0 | } |
1288 | |
|
1289 | 0 | } |
1290 | |
|
1291 | 0 | } |
1292 | | |
1293 | 0 | _CHECK_HALT_FTP; |
1294 | 0 | strcpybuff(back->info, "quit"); |
1295 | 0 | send_line(soc_ctl, "QUIT"); // bye bye |
1296 | | /* A stopped mirror skips the second --timeout window this courtesy reply |
1297 | | costs on a silent server; its text is discarded anyway (#1096). */ |
1298 | 0 | if (!hts_load_acquire_int(&opt->state.stop)) |
1299 | 0 | get_ftp_line(back, soc_ctl, NULL, 0, timeout, opt); |
1300 | | #ifdef _WIN32 |
1301 | | closesocket(soc_ctl); |
1302 | | #else |
1303 | 0 | close(soc_ctl); |
1304 | 0 | #endif |
1305 | 0 | } |
1306 | | |
1307 | 0 | if (back->r.statuscode != -1) { |
1308 | 0 | back->r.statuscode = HTTP_OK; |
1309 | 0 | strcpybuff(back->r.msg, "OK"); |
1310 | 0 | } |
1311 | 0 | return 0; |
1312 | 0 | } |
1313 | | |
1314 | | // ouverture d'un port |
1315 | 0 | T_SOC get_datasocket(char *to_send, size_t to_send_size) { |
1316 | 0 | T_SOC soc = INVALID_SOCKET; |
1317 | 0 | char h_loc[256 + 2]; |
1318 | |
|
1319 | 0 | to_send[0] = '\0'; |
1320 | 0 | if (gethostname(h_loc, 256) == 0) { // host name |
1321 | 0 | SOCaddr server; |
1322 | |
|
1323 | 0 | if (hts_dns_resolve_nocache(h_loc, &server) != NULL) { // notre host |
1324 | 0 | if ((soc = |
1325 | 0 | (T_SOC) socket(SOCaddr_sinfamily(server), SOCK_STREAM, |
1326 | 0 | 0)) != INVALID_SOCKET) { |
1327 | |
|
1328 | 0 | if (bind(soc, &SOCaddr_sockaddr(server), SOCaddr_size(server)) == 0) { |
1329 | 0 | SOCaddr server2; |
1330 | 0 | SOClen len = SOCaddr_capacity(server2); |
1331 | |
|
1332 | 0 | if (getsockname(soc, &SOCaddr_sockaddr(server2), &len) == 0) { |
1333 | | // *port=ntohs(server.sin_port); // récupérer port |
1334 | 0 | if (listen(soc, 1) >= 0) { |
1335 | | #if HTS_INET6==0 |
1336 | | unsigned short int a, n1, n2; |
1337 | | |
1338 | | // calculer port |
1339 | | a = SOCaddr_sinport(server2); |
1340 | | n1 = (a & 0xff); |
1341 | | n2 = ((a >> 8) & 0xff); |
1342 | | { |
1343 | | char dots[256 + 2]; |
1344 | | char dot[256 + 2]; |
1345 | | char *a; |
1346 | | |
1347 | | SOCaddr_inetntoa(dot, 256, server2); |
1348 | | // |
1349 | | dots[0] = '\0'; |
1350 | | strncatbuff(dots, dot, 128); |
1351 | | while((a = strchr(dots, '.'))) |
1352 | | *a = ','; // virgules! |
1353 | | while((a = strchr(dots, ':'))) |
1354 | | *a = ','; // virgules! |
1355 | | snprintf(to_send, to_send_size, "PORT %s,%d,%d", dots, n1, n2); |
1356 | | } |
1357 | | #else |
1358 | | /* |
1359 | | EPRT |1|132.235.1.2|6275| |
1360 | | EPRT |2|1080::8:800:200C:417A|5282| |
1361 | | */ |
1362 | 0 | { |
1363 | 0 | char dot[256 + 2]; |
1364 | |
|
1365 | 0 | SOCaddr_inetntoa(dot, 256, server2); |
1366 | 0 | snprintf(to_send, to_send_size, "EPRT |%c|%s|%d|", |
1367 | 0 | SOCaddr_getproto(server2), dot, |
1368 | 0 | SOCaddr_sinport(server2)); |
1369 | 0 | } |
1370 | 0 | #endif |
1371 | |
|
1372 | 0 | } else { |
1373 | | #ifdef _WIN32 |
1374 | | closesocket(soc); |
1375 | | #else |
1376 | 0 | close(soc); |
1377 | 0 | #endif |
1378 | 0 | soc = INVALID_SOCKET; |
1379 | 0 | } |
1380 | |
|
1381 | 0 | } else { |
1382 | | #ifdef _WIN32 |
1383 | | closesocket(soc); |
1384 | | #else |
1385 | 0 | close(soc); |
1386 | 0 | #endif |
1387 | 0 | soc = INVALID_SOCKET; |
1388 | 0 | } |
1389 | |
|
1390 | 0 | } else { |
1391 | | #ifdef _WIN32 |
1392 | | closesocket(soc); |
1393 | | #else |
1394 | 0 | close(soc); |
1395 | 0 | #endif |
1396 | 0 | soc = INVALID_SOCKET; |
1397 | 0 | } |
1398 | 0 | } |
1399 | 0 | } |
1400 | 0 | } |
1401 | |
|
1402 | 0 | return soc; |
1403 | 0 | } |
1404 | | |
1405 | | #if FTP_DEBUG |
1406 | | FILE *dd = NULL; |
1407 | | #endif |
1408 | | |
1409 | | // routines de réception/émission |
1410 | | // 0 = ERROR |
1411 | 0 | int send_line(T_SOC soc, const char *data) { |
1412 | 0 | char BIGSTK line[FTP_LINE_SIZE + 2]; // room for the CRLF of a maximal command |
1413 | 0 | int n; |
1414 | | |
1415 | | // backstop: the driver fails earlier, but no injected byte reaches the wire |
1416 | 0 | if (!hts_is_control_free(data)) |
1417 | 0 | return 0; |
1418 | | |
1419 | 0 | if (_DEBUG_HEAD) { |
1420 | 0 | if (ioinfo) { |
1421 | 0 | if (strncmp(data, "PASS ", 5) == 0) { |
1422 | 0 | fprintf(ioinfo, "---> PASS "); |
1423 | 0 | fprint_redacted(ioinfo, strlen(data + 5)); |
1424 | 0 | fprintf(ioinfo, "\x0d\x0a"); |
1425 | 0 | } else |
1426 | 0 | fprintf(ioinfo, "---> %s\x0d\x0a", data); |
1427 | 0 | fflush(ioinfo); |
1428 | 0 | } |
1429 | 0 | } |
1430 | | #if FTP_DEBUG |
1431 | | if (dd == NULL) |
1432 | | dd = fopen("toto.txt", "w"); |
1433 | | fprintf(dd, "---> %s\x0d\x0a", data); |
1434 | | fflush(dd); |
1435 | | printf("---> %s", data); |
1436 | | fflush(stdout); |
1437 | | #endif |
1438 | | // an unterminated command would blend into whatever the server reads next |
1439 | 0 | n = snprintf(line, sizeof(line), "%s\x0d\x0a", data); |
1440 | 0 | if (n < 0 || n >= (int) sizeof(line)) |
1441 | 0 | return 0; |
1442 | 0 | if (check_socket_connect(soc) != 1) { |
1443 | | #if FTP_DEBUG |
1444 | | printf("!SOC WRITE ERROR\n"); |
1445 | | #endif |
1446 | 0 | return 0; // erreur, plus connecté! |
1447 | 0 | } |
1448 | | #if FTP_DEBUG |
1449 | | { |
1450 | | int r = (hts_send_nosignal(soc, line, (int) strlen(line)) == |
1451 | | (int) strlen(line)); |
1452 | | |
1453 | | printf("%s\x0d\x0a", data); |
1454 | | fflush(stdout); |
1455 | | return r; |
1456 | | } |
1457 | | #else |
1458 | 0 | return (hts_send_nosignal(soc, line, (int) strlen(line)) == |
1459 | 0 | (int) strlen(line)); |
1460 | 0 | #endif |
1461 | 0 | } |
1462 | | |
1463 | | int get_ftp_line(lien_back *back, T_SOC soc, char *ptrline, size_t line_size, |
1464 | 0 | int timeout, const httrackp *opt) { |
1465 | 0 | char BIGSTK data[1024]; |
1466 | 0 | int i, ok, multiline; |
1467 | |
|
1468 | | #if FTP_DEBUG |
1469 | | if (dd == NULL) |
1470 | | dd = fopen("toto.txt", "w"); |
1471 | | #endif |
1472 | |
|
1473 | 0 | data[0] = '\0'; |
1474 | 0 | i = ok = multiline = 0; |
1475 | 0 | data[3] = '\0'; |
1476 | 0 | do { |
1477 | 0 | char b; |
1478 | | |
1479 | | // vérifier données |
1480 | 0 | switch (wait_socket_receive(back, soc, timeout, opt)) { |
1481 | 0 | case -1: // erreur de lecture |
1482 | 0 | if (ptrline) |
1483 | 0 | snprintf(ptrline, line_size, "500 *read error"); |
1484 | 0 | return 0; |
1485 | 0 | break; |
1486 | 0 | case 0: |
1487 | 0 | if (ptrline) |
1488 | 0 | snprintf(ptrline, line_size, "500 *read timeout (%d)", timeout); |
1489 | 0 | return 0; |
1490 | 0 | break; |
1491 | 0 | } |
1492 | | |
1493 | 0 | switch (recv(soc, &b, 1, 0)) { |
1494 | 0 | case 1: |
1495 | 0 | hts_stat_recv_add(1); // compter flux entrant |
1496 | 0 | if ((b != 10) && (b != 13) && (i < (int) sizeof(data) - 1)) |
1497 | 0 | data[i++] = b; // truncate hostile over-long reply lines |
1498 | 0 | break; |
1499 | 0 | default: |
1500 | 0 | if (ptrline) |
1501 | 0 | snprintf(ptrline, line_size, "500 *read error"); |
1502 | 0 | return 0; // error |
1503 | 0 | break; |
1504 | 0 | } |
1505 | 0 | if (((b == 13) || (b == 10)) && (i > 0)) { // CR/LF |
1506 | 0 | if ((data[3] == '-') |
1507 | 0 | || ((multiline) && (!isdigit((unsigned char) data[0]))) |
1508 | 0 | ) { |
1509 | 0 | data[3] = '\0'; |
1510 | 0 | i = 0; |
1511 | 0 | multiline = 1; |
1512 | 0 | } else |
1513 | 0 | ok = 1; // sortir |
1514 | 0 | } |
1515 | 0 | } while(!ok); |
1516 | 0 | data[i++] = '\0'; |
1517 | |
|
1518 | 0 | if (_DEBUG_HEAD) { |
1519 | 0 | if (ioinfo) { |
1520 | 0 | fprintf(ioinfo, "<--- %s\x0d\x0a", data); |
1521 | 0 | fflush(ioinfo); |
1522 | 0 | } |
1523 | 0 | } |
1524 | | #if FTP_DEBUG |
1525 | | fprintf(dd, "<--- %s\n", data); |
1526 | | fflush(dd); |
1527 | | printf("<--- %s\n", data); |
1528 | | #endif |
1529 | 0 | if (ptrline) |
1530 | 0 | snprintf(ptrline, line_size, "%s", data); |
1531 | 0 | return (strnotempty(data)); |
1532 | 0 | } |
1533 | | |
1534 | | // sauter NNN |
1535 | 0 | char *linejmp(char *line) { |
1536 | 0 | if (strlen(line) > 4) |
1537 | 0 | return line + 4; |
1538 | 0 | else |
1539 | 0 | return line; |
1540 | 0 | } |
1541 | | |
1542 | | // test socket: |
1543 | | // 0 : no data |
1544 | | // 1 : data detected |
1545 | | // -1: error |
1546 | 0 | int check_socket(T_SOC soc) { |
1547 | 0 | fd_set fds, fds_e; // poll structures |
1548 | 0 | struct timeval tv; // structure for select |
1549 | |
|
1550 | 0 | FD_ZERO(&fds); |
1551 | 0 | FD_ZERO(&fds_e); |
1552 | | // socket read |
1553 | 0 | FD_SET(soc, &fds); |
1554 | | // socket error |
1555 | 0 | FD_SET(soc, &fds_e); |
1556 | 0 | tv.tv_sec = 0; |
1557 | 0 | tv.tv_usec = 0; |
1558 | | // poll! |
1559 | 0 | select((int) soc + 1, &fds, NULL, &fds_e, &tv); |
1560 | 0 | if (FD_ISSET(soc, &fds_e)) { // error detected |
1561 | 0 | return -1; |
1562 | 0 | } else if (FD_ISSET(soc, &fds)) { |
1563 | 0 | return 1; |
1564 | 0 | } |
1565 | 0 | return 0; |
1566 | 0 | } |
1567 | | |
1568 | | // check if connected |
1569 | 0 | int check_socket_connect(T_SOC soc) { |
1570 | 0 | fd_set fds, fds_e; // poll structures |
1571 | 0 | struct timeval tv; // structure for select |
1572 | |
|
1573 | 0 | FD_ZERO(&fds); |
1574 | 0 | FD_ZERO(&fds_e); |
1575 | | // socket write |
1576 | 0 | FD_SET(soc, &fds); |
1577 | | // socket error |
1578 | 0 | FD_SET(soc, &fds_e); |
1579 | 0 | tv.tv_sec = 0; |
1580 | 0 | tv.tv_usec = 0; |
1581 | | // poll! |
1582 | 0 | select((int) soc + 1, NULL, &fds, &fds_e, &tv); |
1583 | 0 | if (FD_ISSET(soc, &fds_e)) { // error detected |
1584 | 0 | return -1; |
1585 | 0 | } else if (FD_ISSET(soc, &fds)) { |
1586 | 0 | return 1; |
1587 | 0 | } |
1588 | 0 | return 0; |
1589 | 0 | } |
1590 | | |
1591 | | // attendre des données |
1592 | | int wait_socket_receive(lien_back *back, T_SOC soc, int timeout, |
1593 | 0 | const httrackp *opt) { |
1594 | 0 | TStamp ltime = time_local(); |
1595 | 0 | int r; |
1596 | |
|
1597 | 0 | timeout = ftp_wait_left(opt, timeout); |
1598 | |
|
1599 | | #if FTP_DEBUG |
1600 | | printf("\x0dWaiting for data "); |
1601 | | fflush(stdout); |
1602 | | #endif |
1603 | | /* A stop raised by the engine ends the wait on the next 100ms tick instead of |
1604 | | after the full timeout, so teardown does not sit on a silent server. */ |
1605 | 0 | while ((!(r = check_socket(soc))) && |
1606 | 0 | (back == NULL || !hts_load_acquire_int(&back->stop_ftp)) && |
1607 | 0 | (((int) ((TStamp) (time_local() - ltime))) < timeout)) { |
1608 | 0 | Sleep(100); |
1609 | | #if FTP_DEBUG |
1610 | | printf("."); |
1611 | | fflush(stdout); |
1612 | | #endif |
1613 | 0 | } |
1614 | | #if FTP_DEBUG |
1615 | | printf("\x0dreturn: %d\x0d", r); |
1616 | | fflush(stdout); |
1617 | | #endif |
1618 | 0 | return r; |
1619 | 0 | } |
1620 | | |
1621 | | // cancel reçu? |
1622 | 0 | int stop_ftp(lien_back * back) { |
1623 | 0 | if (hts_load_acquire_int(&back->stop_ftp)) { |
1624 | 0 | strcpybuff(back->r.msg, "Cancelled by User"); |
1625 | 0 | back->r.statuscode = STATUSCODE_INVALID; |
1626 | 0 | return 1; |
1627 | 0 | } |
1628 | 0 | return 0; |
1629 | 0 | } |