示例#1
0
static void
handle_mkd(struct vsf_session* p_sess)
{
  int retval;
  vsf_log_start_entry(p_sess, kVSFLogEntryMkdir);
  str_copy(&p_sess->log_str, &p_sess->ftp_arg_str);
  prepend_path_to_filename(&p_sess->log_str);
  /* NOTE! Actual permissions will be governed by the tunable umask */
  retval = str_mkdir(&p_sess->ftp_arg_str, 0777);
  if (retval != 0)
  {
    vsf_log_do_log(p_sess, 0);
    vsf_cmdio_write(p_sess, FTP_FILEFAIL,
                    "Create directory operation failed.");
    return;
  }
  vsf_log_do_log(p_sess, 1);
  {
    static struct mystr s_mkd_res;
    static struct mystr s_tmp_str;
    str_copy(&s_tmp_str, &p_sess->ftp_arg_str);
    prepend_path_to_filename(&s_tmp_str);
    /* Double up double quotes */
    str_replace_text(&s_tmp_str, "\"", "\"\"");
    /* Build result string */
    str_alloc_text(&s_mkd_res, "\"");
    str_append_str(&s_mkd_res, &s_tmp_str);
    str_append_text(&s_mkd_res, "\" created");
    vsf_cmdio_write_str(p_sess, FTP_MKDIROK, &s_mkd_res);
  }
}
示例#2
0
static void
handle_mdtm(struct vsf_session* p_sess)
{
  static struct vsf_sysutil_statbuf* s_p_statbuf;
  int retval;
  if (!vsf_access_check_file(&p_sess->ftp_arg_str))
  {
    vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied.");
    return;
  }
  retval = str_stat(&p_sess->ftp_arg_str, &s_p_statbuf);
  if (retval != 0 || !vsf_sysutil_statbuf_is_regfile(s_p_statbuf))
  {
    vsf_cmdio_write(p_sess, FTP_FILEFAIL,
                    "Could not get file modification time.");
  }
  else
  {
    static struct mystr s_mdtm_res_str;
    str_alloc_text(&s_mdtm_res_str,
                   vsf_sysutil_statbuf_get_numeric_date(
                     s_p_statbuf, tunable_use_localtime));
    vsf_cmdio_write_str(p_sess, FTP_MDTMOK, &s_mdtm_res_str);
  }
}
示例#3
0
static void
handle_size(struct vsf_session* p_sess)
{
  /* Note - in ASCII mode, are supposed to return the size after taking into
   * account ASCII linefeed conversions. At least this is what wu-ftpd does in
   * version 2.6.1. Proftpd-1.2.0pre fails to do this.
   * I will not do it because it is a potential I/O DoS.
   */
  static struct vsf_sysutil_statbuf* s_p_statbuf;
  int retval;
  if (!vsf_access_check_file(&p_sess->ftp_arg_str))
  {
    vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied.");
    return;
  }
  retval = str_stat(&p_sess->ftp_arg_str, &s_p_statbuf);
  if (retval != 0 || !vsf_sysutil_statbuf_is_regfile(s_p_statbuf))
  {
    vsf_cmdio_write(p_sess, FTP_FILEFAIL, "Could not get file size.");
  }
  else
  {
    static struct mystr s_size_res_str;
    str_alloc_filesize_t(&s_size_res_str,
                         vsf_sysutil_statbuf_get_size(s_p_statbuf));
    vsf_cmdio_write_str(p_sess, FTP_SIZEOK, &s_size_res_str);
  }
}
示例#4
0
void
handle_auth(struct vsf_session* p_sess)
{
  str_upper(&p_sess->ftp_arg_str);
  if (str_equal_text(&p_sess->ftp_arg_str, "TLS") ||
      str_equal_text(&p_sess->ftp_arg_str, "TLS-C") ||
      str_equal_text(&p_sess->ftp_arg_str, "SSL") ||
      str_equal_text(&p_sess->ftp_arg_str, "TLS-P"))
  {
    vsf_cmdio_write(p_sess, FTP_AUTHOK, "Proceed with negotiation.");
    if (!ssl_session_init(p_sess))
    {
      struct mystr err_str = INIT_MYSTR;
      str_alloc_text(&err_str, "Negotiation failed: ");
      str_append_text(&err_str, get_ssl_error());
      vsf_cmdio_write_str(p_sess, FTP_TLS_FAIL, &err_str);
      vsf_sysutil_exit(0);
    }
    p_sess->control_use_ssl = 1;
    if (str_equal_text(&p_sess->ftp_arg_str, "SSL") ||
        str_equal_text(&p_sess->ftp_arg_str, "TLS-P"))
    {
      p_sess->data_use_ssl = 1;
    }
  }
  else
  {
    vsf_cmdio_write(p_sess, FTP_BADAUTH, "Unknown AUTH type.");
  }
}
示例#5
0
static void
handle_pwd(struct vsf_session* p_sess)
{
  static struct mystr s_cwd_buf_mangle_str;
  static struct mystr s_pwd_res_str;
  str_getcwd(&s_cwd_buf_mangle_str);
  /* Double up any double-quotes in the pathname! */
  str_replace_text(&s_cwd_buf_mangle_str, "\"", "\"\"");
  /* Enclose pathname in quotes */
  str_alloc_text(&s_pwd_res_str, "\"");
  str_append_str(&s_pwd_res_str, &s_cwd_buf_mangle_str);
  str_append_text(&s_pwd_res_str, "\"");
  vsf_cmdio_write_str(p_sess, FTP_PWDOK, &s_pwd_res_str);
}
示例#6
0
文件: ssl.c 项目: arrrbiter/flowftpd
void
ssl_control_handshake(struct vsf_session* p_sess)
{
  if (!ssl_session_init(p_sess))
  {
    struct mystr err_str = INIT_MYSTR;
    str_alloc_text(&err_str, "Negotiation failed: ");
    /* Technically, we shouldn't leak such detailed error messages. */
    str_append_text(&err_str, get_ssl_error());
    vsf_cmdio_write_str(p_sess, FTP_TLS_FAIL, &err_str);
    vsf_sysutil_exit(0);
  }
  p_sess->control_use_ssl = 1;
}
示例#7
0
static void
handle_rest(struct vsf_session* p_sess)
{
  static struct mystr s_rest_str;
  filesize_t val = str_a_to_filesize_t(&p_sess->ftp_arg_str);
  if (val < 0)
  {
    val = 0;
  }
  p_sess->restart_pos = val;
  str_alloc_text(&s_rest_str, "Restart position accepted (");
  str_append_filesize_t(&s_rest_str, val);
  str_append_text(&s_rest_str, ").");
  vsf_cmdio_write_str(p_sess, FTP_RESTOK, &s_rest_str);
}
示例#8
0
static void
handle_site_umask(struct vsf_session* p_sess, struct mystr* p_arg_str)
{
  static struct mystr s_umask_resp_str;
  if (str_isempty(p_arg_str))
  {
    /* Empty arg => report current umask */
    str_alloc_text(&s_umask_resp_str, "Your current UMASK is ");
    str_append_text(&s_umask_resp_str,
                    vsf_sysutil_uint_to_octal(vsf_sysutil_get_umask()));
  }
  else
  {
    /* Set current umask */
    unsigned int new_umask = str_octal_to_uint(p_arg_str);
    vsf_sysutil_set_umask(new_umask);
    str_alloc_text(&s_umask_resp_str, "UMASK set to ");
    str_append_text(&s_umask_resp_str,
                    vsf_sysutil_uint_to_octal(vsf_sysutil_get_umask()));
  }
  vsf_cmdio_write_str(p_sess, FTP_UMASKOK, &s_umask_resp_str);
}
示例#9
0
int
vsf_ftpdataio_post_mark_connect(struct vsf_session* p_sess)
{
  int ret = 0;
  if (!p_sess->data_use_ssl)
  {
    return 1;
  }
  if (!p_sess->ssl_slave_active)
  {
    ret = ssl_accept(p_sess, p_sess->data_fd);
  }
  else
  {
    int sock_ret;
    priv_sock_send_cmd(p_sess->ssl_consumer_fd, PRIV_SOCK_DO_SSL_HANDSHAKE);
    priv_sock_send_fd(p_sess->ssl_consumer_fd, p_sess->data_fd);
    sock_ret = priv_sock_get_result(p_sess->ssl_consumer_fd);
    if (sock_ret == PRIV_SOCK_RESULT_OK)
    {
      ret = 1;
    }
  }
  if (ret != 1)
  {
    static struct mystr s_err_msg;
    str_alloc_text(&s_err_msg, "SSL connection failed");
    if (tunable_require_ssl_reuse)
    {
      str_append_text(&s_err_msg, "; session reuse required");
      str_append_text(
          &s_err_msg, ": see require_ssl_reuse option in vsftpd.conf man page");
    }
    vsf_cmdio_write_str(p_sess, FTP_DATATLSBAD, &s_err_msg);
  }
  return ret;
}
示例#10
0
static void
handle_retr(struct vsf_session* p_sess)
{
  static struct mystr s_mark_str;
  static struct vsf_sysutil_statbuf* s_p_statbuf;
  struct vsf_transfer_ret trans_ret;
  int retval;
  int remote_fd;
  int opened_file;
  int is_ascii = 0;
  filesize_t offset = p_sess->restart_pos;
  p_sess->restart_pos = 0;
  if (!pasv_active(p_sess) && !port_active(p_sess))
  {
    vsf_cmdio_write(p_sess, FTP_BADSENDCONN, "Use PORT or PASV first.");
    return;
  }
  if (p_sess->is_ascii && offset != 0)
  {
    vsf_cmdio_write(p_sess, FTP_FILEFAIL,
                    "No support for resume of ASCII transfer.");
    return;
  }
  opened_file = str_open(&p_sess->ftp_arg_str, kVSFSysStrOpenReadOnly);
  if (vsf_sysutil_retval_is_error(opened_file))
  {
    vsf_cmdio_write(p_sess, FTP_FILEFAIL, "Failed to open file.");
    return;
  }
  vsf_sysutil_fstat(opened_file, &s_p_statbuf);
  /* No games please */
  if (!vsf_sysutil_statbuf_is_regfile(s_p_statbuf))
  {
    /* Note - pretend open failed */
    vsf_cmdio_write(p_sess, FTP_FILEFAIL, "Failed to open file.");
    goto file_close_out;
  }
  /* Optionally, we'll be paranoid and only serve publicly readable stuff */
  if (p_sess->is_anonymous && tunable_anon_world_readable_only &&
      !vsf_sysutil_statbuf_is_readable_other(s_p_statbuf))
  {
    vsf_cmdio_write(p_sess, FTP_FILEFAIL, "Failed to open file.");
    goto file_close_out;
  }
  /* Set the download offset (from REST) if any */
  if (offset != 0)
  {
    vsf_sysutil_lseek_to(opened_file, offset);
  }
  remote_fd = get_remote_transfer_fd(p_sess);
  if (vsf_sysutil_retval_is_error(remote_fd))
  {
    goto port_pasv_cleanup_out;
  }
  vsf_log_start_entry(p_sess, kVSFLogEntryDownload);
  str_copy(&p_sess->log_str, &p_sess->ftp_arg_str);
  prepend_path_to_filename(&p_sess->log_str);
  str_alloc_text(&s_mark_str, "Opening ");
  if (tunable_ascii_download_enable && p_sess->is_ascii)
  {
    str_append_text(&s_mark_str, "ASCII");
    is_ascii = 1;
  }
  else
  {
    str_append_text(&s_mark_str, "BINARY");
  }
  str_append_text(&s_mark_str, " mode data connection for ");
  str_append_str(&s_mark_str, &p_sess->ftp_arg_str);
  str_append_text(&s_mark_str, " (");
  str_append_filesize_t(&s_mark_str,
                        vsf_sysutil_statbuf_get_size(s_p_statbuf));
  str_append_text(&s_mark_str, " bytes).");
  vsf_cmdio_write_str(p_sess, FTP_DATACONN, &s_mark_str);
  trans_ret = vsf_ftpdataio_transfer_file(p_sess, remote_fd,
                                          opened_file, 0, is_ascii);
  p_sess->transfer_size = trans_ret.transferred;
  retval = dispose_remote_transfer_fd(p_sess);
  /* Log _after_ the blocking dispose call, so we get transfer times right */
  if (trans_ret.retval == 0 && retval == 0)
  {
    vsf_log_do_log(p_sess, 1);
  }
  else
  {
    vsf_log_do_log(p_sess, 0);
  }
port_pasv_cleanup_out:
  port_cleanup(p_sess);
  pasv_cleanup(p_sess);
file_close_out:
  vsf_sysutil_close(opened_file);
}
示例#11
0
static void
handle_pasv(struct vsf_session* p_sess)
{
  static struct mystr s_pasv_res_str;
  static struct vsf_sysutil_sockaddr* s_p_sockaddr;
  struct vsf_sysutil_ipv4port listen_port;
  struct vsf_sysutil_ipv4addr listen_ipaddr;
  int bind_retries = 10;
  pasv_cleanup(p_sess);
  port_cleanup(p_sess);
  p_sess->pasv_listen_fd = vsf_sysutil_get_ipv4_sock();
  while (--bind_retries)
  {
    int retval;
    unsigned short the_port;
    double scaled_port;
    /* IPPORT_RESERVED */
    unsigned short min_port = 1024;
    unsigned short max_port = 65535;
    if (tunable_pasv_min_port > min_port && tunable_pasv_min_port < max_port)
    {
      min_port = tunable_pasv_min_port;
    }
    if (tunable_pasv_max_port > min_port && tunable_pasv_max_port < max_port)
    {
      max_port = tunable_pasv_max_port;
    }
    the_port = vsf_sysutil_get_random_byte();
    the_port <<= 8;
    the_port |= vsf_sysutil_get_random_byte();
    scaled_port = (double) min_port;
    scaled_port += ((double) the_port / (double) 65535) *
                   ((double) max_port - min_port);
    the_port = (unsigned short) scaled_port;
    vsf_sysutil_sockaddr_alloc_ipv4(&s_p_sockaddr);
    vsf_sysutil_sockaddr_set_port(s_p_sockaddr,
                                  vsf_sysutil_ipv4port_from_int(the_port));
    /* Bind to same address we got the incoming connect on */
    vsf_sysutil_sockaddr_set_ipaddr(s_p_sockaddr,
      vsf_sysutil_sockaddr_get_ipaddr(p_sess->p_local_addr));
    retval = vsf_sysutil_bind(p_sess->pasv_listen_fd, s_p_sockaddr);
    if (!vsf_sysutil_retval_is_error(retval))
    {
      break;
    }
    if (vsf_sysutil_get_error() == kVSFSysUtilErrADDRINUSE)
    {
      continue;
    }
    die("vsf_sysutil_bind");
  }
  if (!bind_retries)
  {
    die("vsf_sysutil_bind");
  }
  vsf_sysutil_listen(p_sess->pasv_listen_fd, 1);
  /* Get the address of the bound socket, for the port */
  vsf_sysutil_getsockname(p_sess->pasv_listen_fd, &s_p_sockaddr);
  if (tunable_pasv_address != 0)
  {
    /* Report passive address as specified in configuration */
    if (vsf_sysutil_inet_aton(tunable_pasv_address, &listen_ipaddr) == 0)
    {
      die("invalid pasv_address");
    }
  }
  else
  {
    /* Use address of bound socket for passive address */
    listen_ipaddr = vsf_sysutil_sockaddr_get_ipaddr(s_p_sockaddr);
  }
  listen_port = vsf_sysutil_sockaddr_get_port(s_p_sockaddr);
  str_alloc_text(&s_pasv_res_str, "Entering Passive Mode (");
  str_append_ulong(&s_pasv_res_str, listen_ipaddr.data[0]);
  str_append_text(&s_pasv_res_str, ",");
  str_append_ulong(&s_pasv_res_str, listen_ipaddr.data[1]);
  str_append_text(&s_pasv_res_str, ",");
  str_append_ulong(&s_pasv_res_str, listen_ipaddr.data[2]);
  str_append_text(&s_pasv_res_str, ",");
  str_append_ulong(&s_pasv_res_str, listen_ipaddr.data[3]);
  str_append_text(&s_pasv_res_str, ",");
  str_append_ulong(&s_pasv_res_str, listen_port.data[0]);
  str_append_text(&s_pasv_res_str, ",");
  str_append_ulong(&s_pasv_res_str, listen_port.data[1]);
  str_append_text(&s_pasv_res_str, ")");
  vsf_cmdio_write_str(p_sess, FTP_PASVOK, &s_pasv_res_str);
}
示例#12
0
static void
handle_upload_common(struct vsf_session* p_sess, int is_append, int is_unique)
{
  static struct mystr s_filename;
  struct mystr* p_filename;
  struct vsf_transfer_ret trans_ret;
  int new_file_fd;
  int remote_fd;
  int retval;
  filesize_t offset = p_sess->restart_pos;
  p_sess->restart_pos = 0;
  if (!pasv_active(p_sess) && !port_active(p_sess))
  {
    vsf_cmdio_write(p_sess, FTP_BADSENDCONN, "Use PORT or PASV first.");
    return;
  }
  p_filename = &p_sess->ftp_arg_str;
  if (is_unique)
  {
    get_unique_filename(&s_filename, p_filename);
    p_filename = &s_filename;
  }
  if (!vsf_access_check_file(p_filename))
  {
    vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied.");
    return;
  }
  /* NOTE - actual file permissions will be governed by the tunable umask */
  /* XXX - do we care about race between create and chown() of anonymous
   * upload?
   */
  if (is_unique || (p_sess->is_anonymous && !tunable_anon_other_write_enable))
  {
    new_file_fd = str_create(p_filename);
  }
  else
  {
    /* For non-anonymous, allow open() to overwrite or append existing files */
    if (!is_append && offset == 0)
    {
      new_file_fd = str_create_overwrite(p_filename);
    }
    else
    {
      new_file_fd = str_create_append(p_filename);
    }
  }
  if (vsf_sysutil_retval_is_error(new_file_fd))
  {
    vsf_cmdio_write(p_sess, FTP_UPLOADFAIL, "Could not create file.");
    return;
  }
  /* Are we required to chown() this file for security? */
  if (p_sess->is_anonymous && tunable_chown_uploads)
  {
    vsf_sysutil_fchmod(new_file_fd, 0600);
    if (tunable_one_process_model)
    {
      vsf_one_process_chown_upload(p_sess, new_file_fd);
    }
    else
    {
      vsf_two_process_chown_upload(p_sess, new_file_fd);
    }
  }
  if (!is_append && offset != 0)
  {
    /* XXX - warning, allows seek past end of file! Check for seek > size? */
    vsf_sysutil_lseek_to(new_file_fd, offset);
  }
  remote_fd = get_remote_transfer_fd(p_sess);
  if (vsf_sysutil_retval_is_error(remote_fd))
  {
    goto port_pasv_cleanup_out;
  }
  if (is_unique)
  {
    struct mystr resp_str = INIT_MYSTR;
    str_alloc_text(&resp_str, "FILE: ");
    str_append_str(&resp_str, p_filename);
    vsf_cmdio_write_str(p_sess, FTP_DATACONN, &resp_str);
    str_free(&resp_str);
  }
  else
  {
    vsf_cmdio_write(p_sess, FTP_DATACONN, "Ok to send data.");
  }
  vsf_log_start_entry(p_sess, kVSFLogEntryUpload);
  str_copy(&p_sess->log_str, &p_sess->ftp_arg_str);
  prepend_path_to_filename(&p_sess->log_str);
  if (tunable_ascii_upload_enable && p_sess->is_ascii)
  {
    trans_ret = vsf_ftpdataio_transfer_file(p_sess, remote_fd,
                                            new_file_fd, 1, 1);
  }
  else
  {
    trans_ret = vsf_ftpdataio_transfer_file(p_sess, remote_fd,
                                            new_file_fd, 1, 0);
  }
  p_sess->transfer_size = trans_ret.transferred;
  /* XXX - handle failure, delete file? */
  retval = dispose_remote_transfer_fd(p_sess);
  /* Log _after_ the blocking dispose call, so we get transfer times right */
  if (trans_ret.retval == 0 && retval == 0)
  {
    vsf_log_do_log(p_sess, 1);
  }
  else
  {
    vsf_log_do_log(p_sess, 0);
  }
port_pasv_cleanup_out:
  port_cleanup(p_sess);
  pasv_cleanup(p_sess);
  vsf_sysutil_close(new_file_fd);
}
示例#13
0
static void
handle_pasv(struct vsf_session* p_sess, int is_epsv)
{
  static struct mystr s_pasv_res_str;
  static struct vsf_sysutil_sockaddr* s_p_sockaddr;
  int bind_retries = 10;
  unsigned short the_port = 0;
  int is_ipv6 = vsf_sysutil_sockaddr_is_ipv6(p_sess->p_local_addr);
  if (is_epsv && !str_isempty(&p_sess->ftp_arg_str))
  {
    int argval;
    str_upper(&p_sess->ftp_arg_str);
    if (str_equal_text(&p_sess->ftp_arg_str, "ALL"))
    {
      p_sess->epsv_all = 1;
      vsf_cmdio_write(p_sess, FTP_EPSVALLOK, "EPSV ALL ok.");
      return;
    }
    argval = vsf_sysutil_atoi(str_getbuf(&p_sess->ftp_arg_str));
    if (!is_ipv6 || argval != 2)
    {
      vsf_cmdio_write(p_sess, FTP_EPSVBAD, "Bad network protocol.");
      return;
    }
  }
  pasv_cleanup(p_sess);
  port_cleanup(p_sess);
  if (is_epsv && is_ipv6)
  {
    p_sess->pasv_listen_fd = vsf_sysutil_get_ipv6_sock();
  }
  else
  {
    p_sess->pasv_listen_fd = vsf_sysutil_get_ipv4_sock();
  }
  vsf_sysutil_activate_reuseaddr(p_sess->pasv_listen_fd);
  while (--bind_retries)
  {
    int retval;
    double scaled_port;
    /* IPPORT_RESERVED */
    unsigned short min_port = 1024;
    unsigned short max_port = 65535;
    if (tunable_pasv_min_port > min_port && tunable_pasv_min_port <= max_port)
    {
      min_port = tunable_pasv_min_port;
    }
    if (tunable_pasv_max_port >= min_port && tunable_pasv_max_port < max_port)
    {
      max_port = tunable_pasv_max_port;
    }
    the_port = vsf_sysutil_get_random_byte();
    the_port <<= 8;
    the_port |= vsf_sysutil_get_random_byte();
    scaled_port = (double) min_port;
    scaled_port += ((double) the_port / (double) 65536) *
                   ((double) max_port - min_port + 1);
    the_port = (unsigned short) scaled_port;
    vsf_sysutil_sockaddr_clone(&s_p_sockaddr, p_sess->p_local_addr);
    vsf_sysutil_sockaddr_set_port(s_p_sockaddr, the_port);
    retval = vsf_sysutil_bind(p_sess->pasv_listen_fd, s_p_sockaddr);
    if (!vsf_sysutil_retval_is_error(retval))
    {
      break;
    }
    if (vsf_sysutil_get_error() == kVSFSysUtilErrADDRINUSE)
    {
      continue;
    }
    die("vsf_sysutil_bind");
  }
  if (!bind_retries)
  {
    die("vsf_sysutil_bind");
  }
  vsf_sysutil_listen(p_sess->pasv_listen_fd, 1);
  if (is_epsv)
  {
    str_alloc_text(&s_pasv_res_str, "Entering Extended Passive Mode (|||");
    str_append_ulong(&s_pasv_res_str, (unsigned long) the_port);
    str_append_text(&s_pasv_res_str, "|)");
    vsf_cmdio_write_str(p_sess, FTP_EPSVOK, &s_pasv_res_str);
    return;
  }
  if (tunable_pasv_address != 0)
  {
    /* Report passive address as specified in configuration */
    if (vsf_sysutil_inet_aton(tunable_pasv_address, s_p_sockaddr) == 0)
    {
      die("invalid pasv_address");
    }
  }
  str_alloc_text(&s_pasv_res_str, "Entering Passive Mode (");
  if (!is_ipv6)
  {
    str_append_text(&s_pasv_res_str, vsf_sysutil_inet_ntop(s_p_sockaddr));
  }
  else
  {
    const void* p_v4addr = vsf_sysutil_sockaddr_ipv6_v4(s_p_sockaddr);
    if (p_v4addr)
    {
      str_append_text(&s_pasv_res_str, vsf_sysutil_inet_ntoa(p_v4addr));
    }
  }
  str_replace_char(&s_pasv_res_str, '.', ',');
  str_append_text(&s_pasv_res_str, ",");
  str_append_ulong(&s_pasv_res_str, the_port >> 8);
  str_append_text(&s_pasv_res_str, ",");
  str_append_ulong(&s_pasv_res_str, the_port & 255);
  str_append_text(&s_pasv_res_str, ")");
  vsf_cmdio_write_str(p_sess, FTP_PASVOK, &s_pasv_res_str);
}