1/*
2 * Part of Very Secure FTPd
3 * Licence: GPL v2
4 * Author: Chris Evans
5 * ftpdataio.c
6 *
7 * Code to handle FTP data connections. This includes both PORT (server
8 * connects) and PASV (client connects) modes of data transfer. This
9 * includes sends and receives, files and directories.
10 */
11
12#include "ftpdataio.h"
13#include "session.h"
14#include "ftpcmdio.h"
15#include "ftpcodes.h"
16#include "utility.h"
17#include "tunables.h"
18#include "defs.h"
19#include "str.h"
20#include "strlist.h"
21#include "sysutil.h"
22#include "logging.h"
23#include "secbuf.h"
24#include "sysstr.h"
25#include "sysdeputil.h"
26#include "ascii.h"
27#include "oneprocess.h"
28#include "twoprocess.h"
29#include "ls.h"
30#include "ssl.h"
31#include "readwrite.h"
32
33static void init_data_sock_params(struct vsf_session* p_sess, int sock_fd);
34static filesize_t calc_num_send(int file_fd, filesize_t init_offset);
35static struct vsf_transfer_ret do_file_send_sendfile(
36  struct vsf_session* p_sess, int net_fd, int file_fd,
37  filesize_t curr_file_offset, filesize_t bytes_to_send);
38static struct vsf_transfer_ret do_file_send_rwloop(
39  struct vsf_session* p_sess, int file_fd, int is_ascii);
40static struct vsf_transfer_ret do_file_recv(
41  struct vsf_session* p_sess, int file_fd, int is_ascii);
42static void handle_sigalrm(void* p_private);
43static void start_data_alarm(struct vsf_session* p_sess);
44static void handle_io(int retval, int fd, void* p_private);
45static int transfer_dir_internal(
46  struct vsf_session* p_sess, int is_control, struct vsf_sysutil_dir* p_dir,
47  const struct mystr* p_base_dir_str, const struct mystr* p_option_str,
48  const struct mystr* p_filter_str, int is_verbose);
49static int write_dir_list(struct vsf_session* p_sess,
50                          struct mystr_list* p_dir_list,
51                          enum EVSFRWTarget target);
52static unsigned int get_chunk_size();
53
54void
55vsf_ftpdataio_dispose_transfer_fd(struct vsf_session* p_sess)
56{
57  int retval;
58  if (p_sess->data_fd == -1)
59  {
60    bug("no data descriptor in vsf_ftpdataio_dispose_transfer_fd");
61  }
62  /* Reset the data connection alarm so it runs anew with the blocking close */
63  start_data_alarm(p_sess);
64  vsf_sysutil_uninstall_io_handler();
65  if (p_sess->p_data_ssl != 0)
66  {
67    ssl_data_close(p_sess);
68  }
69  /* This close() blocks because we set SO_LINGER */
70  retval = vsf_sysutil_close_failok(p_sess->data_fd);
71  if (vsf_sysutil_retval_is_error(retval))
72  {
73    /* Do it again without blocking. */
74    vsf_sysutil_deactivate_linger_failok(p_sess->data_fd);
75    (void) vsf_sysutil_close_failok(p_sess->data_fd);
76  }
77  vsf_sysutil_clear_alarm();
78  p_sess->data_fd = -1;
79}
80
81int
82vsf_ftpdataio_get_pasv_fd(struct vsf_session* p_sess)
83{
84  int remote_fd;
85  struct vsf_sysutil_sockaddr* p_accept_addr = 0;
86  vsf_sysutil_sockaddr_alloc(&p_accept_addr);
87  remote_fd = vsf_sysutil_accept_timeout(p_sess->pasv_listen_fd, p_accept_addr,
88                                         tunable_accept_timeout);
89  if (vsf_sysutil_retval_is_error(remote_fd))
90  {
91    vsf_cmdio_write(p_sess, FTP_BADSENDCONN,
92                    "Failed to establish connection.");
93    vsf_sysutil_sockaddr_clear(&p_accept_addr);
94    return remote_fd;
95  }
96  /* SECURITY:
97   * Reject the connection if it wasn't from the same IP as the
98   * control connection.
99   */
100  if (!tunable_pasv_promiscuous)
101  {
102    if (!vsf_sysutil_sockaddr_addr_equal(p_sess->p_remote_addr, p_accept_addr))
103    {
104      vsf_cmdio_write(p_sess, FTP_BADSENDCONN, "Security: Bad IP connecting.");
105      vsf_sysutil_close(remote_fd);
106      vsf_sysutil_sockaddr_clear(&p_accept_addr);
107      return -1;
108    }
109  }
110  vsf_sysutil_sockaddr_clear(&p_accept_addr);
111  init_data_sock_params(p_sess, remote_fd);
112  return remote_fd;
113}
114
115int
116vsf_ftpdataio_get_port_fd(struct vsf_session* p_sess)
117{
118  int retval;
119  int remote_fd;
120  if (tunable_connect_from_port_20)
121  {
122    if (tunable_one_process_model)
123    {
124      remote_fd = vsf_one_process_get_priv_data_sock(p_sess);
125    }
126    else
127    {
128      remote_fd = vsf_two_process_get_priv_data_sock(p_sess);
129    }
130  }
131  else
132  {
133    static struct vsf_sysutil_sockaddr* s_p_addr;
134    remote_fd = vsf_sysutil_get_ipsock(p_sess->p_local_addr);
135    vsf_sysutil_sockaddr_clone(&s_p_addr, p_sess->p_local_addr);
136    vsf_sysutil_sockaddr_set_port(s_p_addr, 0);
137    retval = vsf_sysutil_bind(remote_fd, s_p_addr);
138    if (retval != 0)
139    {
140      die("vsf_sysutil_bind");
141    }
142  }
143  retval = vsf_sysutil_connect_timeout(remote_fd, p_sess->p_port_sockaddr,
144                                       tunable_connect_timeout);
145  if (vsf_sysutil_retval_is_error(retval))
146  {
147    vsf_cmdio_write(p_sess, FTP_BADSENDCONN,
148                    "Failed to establish connection.");
149    vsf_sysutil_close(remote_fd);
150    return -1;
151  }
152  init_data_sock_params(p_sess, remote_fd);
153  return remote_fd;
154}
155
156int
157vsf_ftpdataio_post_mark_connect(struct vsf_session* p_sess)
158{
159  if (p_sess->data_use_ssl)
160  {
161    if (!ssl_accept(p_sess, p_sess->data_fd))
162    {
163      vsf_cmdio_write(
164        p_sess, FTP_DATATLSBAD, "Secure connection negotiation failed.");
165      return 0;
166    }
167  }
168  return 1;
169}
170
171static void
172handle_sigalrm(void* p_private)
173{
174  struct vsf_session* p_sess = (struct vsf_session*) p_private;
175  if (!p_sess->data_progress)
176  {
177    vsf_cmdio_write_exit(p_sess, FTP_DATA_TIMEOUT,
178                         "Data timeout. Reconnect. Sorry.");
179  }
180  p_sess->data_progress = 0;
181  start_data_alarm(p_sess);
182}
183
184void
185start_data_alarm(struct vsf_session* p_sess)
186{
187  if (tunable_data_connection_timeout > 0)
188  {
189    vsf_sysutil_install_sighandler(kVSFSysUtilSigALRM, handle_sigalrm, p_sess);
190    vsf_sysutil_set_alarm(tunable_data_connection_timeout);
191  }
192}
193
194static void
195init_data_sock_params(struct vsf_session* p_sess, int sock_fd)
196{
197  if (p_sess->data_fd != -1)
198  {
199    bug("data descriptor still present in init_data_sock_params");
200  }
201  p_sess->data_fd = sock_fd;
202  p_sess->data_progress = 0;
203  vsf_sysutil_activate_keepalive(sock_fd);
204  /* And in the vague hope it might help... */
205  vsf_sysutil_set_iptos_throughput(sock_fd);
206  /* Set up lingering, so that we wait for all data to transfer, and report
207   * more accurate transfer rates.
208   */
209  vsf_sysutil_activate_linger(sock_fd);
210  /* Start the timeout monitor */
211  vsf_sysutil_install_io_handler(handle_io, p_sess);
212  start_data_alarm(p_sess);
213}
214
215static void
216handle_io(int retval, int fd, void* p_private)
217{
218  long curr_sec;
219  long curr_usec;
220  unsigned int bw_rate;
221  double elapsed;
222  double pause_time;
223  double rate_ratio;
224  struct vsf_session* p_sess = (struct vsf_session*) p_private;
225  if (p_sess->data_fd != fd || vsf_sysutil_retval_is_error(retval) ||
226      retval == 0)
227  {
228    return;
229  }
230  /* Note that the session hasn't stalled, i.e. don't time it out */
231  p_sess->data_progress = 1;
232  /* Apply bandwidth quotas via a little pause, if necessary */
233  if (p_sess->bw_rate_max == 0)
234  {
235    return;
236  }
237  /* Calculate bandwidth rate */
238  vsf_sysutil_update_cached_time();
239  curr_sec = vsf_sysutil_get_cached_time_sec();
240  curr_usec = vsf_sysutil_get_cached_time_usec();
241  elapsed = (double) (curr_sec - p_sess->bw_send_start_sec);
242  elapsed += (double) (curr_usec - p_sess->bw_send_start_usec) /
243             (double) 1000000;
244  if (elapsed <= (double) 0)
245  {
246    elapsed = (double) 0.01;
247  }
248  bw_rate = (unsigned int) ((double) retval / elapsed);
249  if (bw_rate <= p_sess->bw_rate_max)
250  {
251    p_sess->bw_send_start_sec = curr_sec;
252    p_sess->bw_send_start_usec = curr_usec;
253    return;
254  }
255  /* Tut! Rate exceeded, calculate a pause to bring things back into line */
256  rate_ratio = (double) bw_rate / (double) p_sess->bw_rate_max;
257  pause_time = (rate_ratio - (double) 1) * elapsed;
258  vsf_sysutil_sleep(pause_time);
259  vsf_sysutil_update_cached_time();
260  p_sess->bw_send_start_sec = vsf_sysutil_get_cached_time_sec();
261  p_sess->bw_send_start_usec = vsf_sysutil_get_cached_time_usec();
262}
263
264int
265vsf_ftpdataio_transfer_dir(struct vsf_session* p_sess, int is_control,
266                           struct vsf_sysutil_dir* p_dir,
267                           const struct mystr* p_base_dir_str,
268                           const struct mystr* p_option_str,
269                           const struct mystr* p_filter_str,
270                           int is_verbose)
271{
272  return transfer_dir_internal(p_sess, is_control, p_dir, p_base_dir_str,
273                               p_option_str, p_filter_str, is_verbose);
274}
275
276static int
277transfer_dir_internal(struct vsf_session* p_sess, int is_control,
278                      struct vsf_sysutil_dir* p_dir,
279                      const struct mystr* p_base_dir_str,
280                      const struct mystr* p_option_str,
281                      const struct mystr* p_filter_str,
282                      int is_verbose)
283{
284  struct mystr_list dir_list = INIT_STRLIST;
285  struct mystr_list subdir_list = INIT_STRLIST;
286  struct mystr dir_prefix_str = INIT_MYSTR;
287  struct mystr_list* p_subdir_list = 0;
288  struct str_locate_result loc_result = str_locate_char(p_option_str, 'R');
289  int failed = 0;
290  enum EVSFRWTarget target = kVSFRWData;
291  if (is_control)
292  {
293    target = kVSFRWControl;
294  }
295  if (loc_result.found && tunable_ls_recurse_enable)
296  {
297    p_subdir_list = &subdir_list;
298  }
299
300// 2007.05 James {
301  char *session_user = p_sess->user_str.PRIVATE_HANDS_OFF_p_buf;
302  vsf_ls_populate_dir_list(session_user, &dir_list, p_subdir_list, p_dir, p_base_dir_str,
303                           p_option_str, p_filter_str, is_verbose);
304// 2007.05 James }
305
306  if (p_subdir_list)
307  {
308    int retval;
309    str_copy(&dir_prefix_str, p_base_dir_str);
310    str_append_text(&dir_prefix_str, ":\r\n");
311    retval = ftp_write_str(p_sess, &dir_prefix_str, target);
312    if (retval != 0)
313    {
314      failed = 1;
315    }
316  }
317  if (!failed)
318  {
319    failed = write_dir_list(p_sess, &dir_list, target);
320  }
321  /* Recurse into the subdirectories if required... */
322  if (!failed)
323  {
324    struct mystr sub_str = INIT_MYSTR;
325    unsigned int num_subdirs = str_list_get_length(&subdir_list);
326    unsigned int subdir_index;
327    for (subdir_index = 0; subdir_index < num_subdirs; subdir_index++)
328    {
329      int retval;
330      struct vsf_sysutil_dir* p_subdir;
331      const struct mystr* p_subdir_str =
332        str_list_get_pstr(&subdir_list, subdir_index);
333      if (str_equal_text(p_subdir_str, ".") ||
334          str_equal_text(p_subdir_str, ".."))
335      {
336        continue;
337      }
338      str_copy(&sub_str, p_base_dir_str);
339      str_append_char(&sub_str, '/');
340      str_append_str(&sub_str, p_subdir_str);
341      p_subdir = str_opendir(&sub_str);
342      if (p_subdir == 0)
343      {
344        /* Unreadable, gone missing, etc. - no matter */
345        continue;
346      }
347      str_alloc_text(&dir_prefix_str, "\r\n");
348      retval = ftp_write_str(p_sess, &dir_prefix_str, target);
349      if (retval != 0)
350      {
351        failed = 1;
352        break;
353      }
354      retval = transfer_dir_internal(p_sess, is_control, p_subdir, &sub_str,
355                                     p_option_str, p_filter_str, is_verbose);
356      vsf_sysutil_closedir(p_subdir);
357      if (retval != 0)
358      {
359        failed = 1;
360        break;
361      }
362    }
363    str_free(&sub_str);
364  }
365  str_list_free(&dir_list);
366  str_list_free(&subdir_list);
367  str_free(&dir_prefix_str);
368  if (!failed)
369  {
370    return 0;
371  }
372  else
373  {
374    return -1;
375  }
376}
377
378/* XXX - really, this should be refactored into a "buffered writer" object */
379static int
380write_dir_list(struct vsf_session* p_sess, struct mystr_list* p_dir_list,
381               enum EVSFRWTarget target)
382{
383  /* This function writes out a list of strings to the client, over the
384   * data socket. We now coalesce the strings into fewer write() syscalls,
385   * which saved 33% CPU time writing a large directory.
386   */
387  int retval = 0;
388  unsigned int dir_index_max = str_list_get_length(p_dir_list);
389  unsigned int dir_index;
390  struct mystr buf_str = INIT_MYSTR;
391  str_reserve(&buf_str, VSFTP_DIR_BUFSIZE);
392  for (dir_index = 0; dir_index < dir_index_max; dir_index++)
393  {
394    str_append_str(&buf_str, str_list_get_pstr(p_dir_list, dir_index));
395    if (dir_index == dir_index_max - 1 ||
396        str_getlen(&buf_str) +
397          str_getlen(str_list_get_pstr(p_dir_list, dir_index + 1)) >
398            VSFTP_DIR_BUFSIZE)
399    {
400      /* Writeout needed - we're either at the end, or we filled the buffer */
401      int writeret = ftp_write_str(p_sess, &buf_str, target);
402      if (writeret != 0)
403      {
404        retval = 1;
405        break;
406      }
407      str_empty(&buf_str);
408    }
409  }
410  str_free(&buf_str);
411  return retval;
412}
413
414struct vsf_transfer_ret
415vsf_ftpdataio_transfer_file(struct vsf_session* p_sess, int remote_fd,
416                            int file_fd, int is_recv, int is_ascii)
417{
418  if (!is_recv)
419  {
420    if (is_ascii || p_sess->data_use_ssl)
421    {
422      return do_file_send_rwloop(p_sess, file_fd, is_ascii);
423    }
424    else
425    {
426      filesize_t curr_offset = vsf_sysutil_get_file_offset(file_fd);
427      filesize_t num_send = calc_num_send(file_fd, curr_offset);
428      return do_file_send_sendfile(
429        p_sess, remote_fd, file_fd, curr_offset, num_send);
430    }
431  }
432  else
433  {
434    return do_file_recv(p_sess, file_fd, is_ascii);
435  }
436}
437
438static struct vsf_transfer_ret
439do_file_send_rwloop(struct vsf_session* p_sess, int file_fd, int is_ascii)
440{
441  static char* p_readbuf;
442  static char* p_asciibuf;
443  struct vsf_transfer_ret ret_struct = { 0, 0 };
444  unsigned int chunk_size = get_chunk_size();
445  char* p_writefrom_buf;
446  if (p_readbuf == 0)
447  {
448    /* NOTE!! * 2 factor because we can double the data by doing our ASCII
449     * linefeed mangling
450     */
451    vsf_secbuf_alloc(&p_asciibuf, VSFTP_DATA_BUFSIZE * 2);
452    vsf_secbuf_alloc(&p_readbuf, VSFTP_DATA_BUFSIZE);
453  }
454  if (is_ascii)
455  {
456    p_writefrom_buf = p_asciibuf;
457  }
458  else
459  {
460    p_writefrom_buf = p_readbuf;
461  }
462  while (1)
463  {
464    unsigned int num_to_write;
465    int retval = vsf_sysutil_read(file_fd, p_readbuf, chunk_size);
466    if (vsf_sysutil_retval_is_error(retval))
467    {
468      ret_struct.retval = -1;
469      return ret_struct;
470    }
471    else if (retval == 0)
472    {
473      /* Success - cool */
474      return ret_struct;
475    }
476    if (is_ascii)
477    {
478      num_to_write = vsf_ascii_bin_to_ascii(p_readbuf, p_asciibuf,
479                                            (unsigned int) retval);
480    }
481    else
482    {
483      num_to_write = (unsigned int) retval;
484    }
485    retval = ftp_write_data(p_sess, p_writefrom_buf, num_to_write);
486    if (vsf_sysutil_retval_is_error(retval) ||
487        (unsigned int) retval != num_to_write)
488    {
489      ret_struct.retval = -2;
490      return ret_struct;
491    }
492    ret_struct.transferred += (unsigned int) retval;
493  }
494}
495
496static struct vsf_transfer_ret
497do_file_send_sendfile(struct vsf_session* p_sess, int net_fd, int file_fd,
498                      filesize_t curr_file_offset, filesize_t bytes_to_send)
499{
500  int retval;
501  unsigned int chunk_size = 0;
502  struct vsf_transfer_ret ret_struct = { 0, 0 };
503  filesize_t init_file_offset = curr_file_offset;
504  filesize_t bytes_sent;
505  if (p_sess->bw_rate_max)
506  {
507    chunk_size = get_chunk_size();
508  }
509  /* Just because I can ;-) */
510  retval = vsf_sysutil_sendfile(net_fd, file_fd, &curr_file_offset,
511                                bytes_to_send, chunk_size);
512  bytes_sent = curr_file_offset - init_file_offset;
513  ret_struct.transferred = bytes_sent;
514  if (vsf_sysutil_retval_is_error(retval))
515  {
516    ret_struct.retval = -2;
517    return ret_struct;
518  }
519  else if (bytes_sent != bytes_to_send)
520  {
521    ret_struct.retval = -2;
522    return ret_struct;
523  }
524  return ret_struct;
525}
526
527static filesize_t
528calc_num_send(int file_fd, filesize_t init_offset)
529{
530  static struct vsf_sysutil_statbuf* s_p_statbuf;
531  filesize_t bytes_to_send;
532  /* Work out how many bytes to send based on file size minus current offset */
533  vsf_sysutil_fstat(file_fd, &s_p_statbuf);
534  bytes_to_send = vsf_sysutil_statbuf_get_size(s_p_statbuf);
535  if (init_offset < 0 || bytes_to_send < 0)
536  {
537    die("calc_num_send: negative file offset or send count");
538  }
539  /* Don't underflow if some bonehead sets a REST greater than the file size */
540  if (init_offset > bytes_to_send)
541  {
542    bytes_to_send = 0;
543  }
544  else
545  {
546    bytes_to_send -= init_offset;
547  }
548  return bytes_to_send;
549}
550
551static struct vsf_transfer_ret
552do_file_recv(struct vsf_session* p_sess, int file_fd, int is_ascii)
553{
554  static char* p_recvbuf;
555  unsigned int num_to_write;
556  struct vsf_transfer_ret ret_struct = { 0, 0 };
557  unsigned int chunk_size = get_chunk_size();
558  int prev_cr = 0;
559  if (p_recvbuf == 0)
560  {
561    /* Now that we do ASCII conversion properly, the plus one is to cater for
562     * the fact we may need to stick a '\r' at the front of the buffer if the
563     * last buffer fragment eneded in a '\r' and the current buffer fragment
564     * does not start with a '\n'.
565     */
566    vsf_secbuf_alloc(&p_recvbuf, VSFTP_DATA_BUFSIZE + 1);
567  }
568  while (1)
569  {
570    const char* p_writebuf = p_recvbuf + 1;
571    int retval = ftp_read_data(p_sess, p_recvbuf + 1, chunk_size);
572    if (vsf_sysutil_retval_is_error(retval))
573    {
574      ret_struct.retval = -2;
575      return ret_struct;
576    }
577    else if (retval == 0 && !prev_cr)
578    {
579      /* Transfer done, nifty */
580      return ret_struct;
581    }
582    num_to_write = (unsigned int) retval;
583    ret_struct.transferred += num_to_write;
584    if (is_ascii)
585    {
586      /* Handle ASCII conversion if we have to. Note that using the same
587       * buffer for source and destination is safe, because the ASCII ->
588       * binary transform only ever results in a smaller file.
589       */
590      struct ascii_to_bin_ret ret =
591        vsf_ascii_ascii_to_bin(p_recvbuf, num_to_write, prev_cr);
592      num_to_write = ret.stored;
593      prev_cr = ret.last_was_cr;
594      p_writebuf = ret.p_buf;
595    }
596    retval = vsf_sysutil_write_loop(file_fd, p_writebuf, num_to_write);
597    if (vsf_sysutil_retval_is_error(retval) ||
598        (unsigned int) retval != num_to_write)
599    {
600      ret_struct.retval = -1;
601      return ret_struct;
602    }
603  }
604}
605
606static unsigned int
607get_chunk_size()
608{
609  unsigned int ret = VSFTP_DATA_BUFSIZE;
610  if (tunable_trans_chunk_size < VSFTP_DATA_BUFSIZE &&
611      tunable_trans_chunk_size > 0)
612  {
613    ret = tunable_trans_chunk_size;
614    if (ret < 4096)
615    {
616      ret = 4096;
617    }
618  }
619  return ret;
620}
621
622