Coverage Report

Created: 2026-10-28 06:47

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}