PageRenderTime 49ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/release/src/router/vsftpd/ftpdataio.c

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