Exemple #1
0
END_TEST

START_TEST (netaddr_is_v6_test) {
  int res;
  const char *name;

  res = pr_netaddr_is_v6(NULL);
  fail_unless(res == -1, "Failed to handle null arguments");
  fail_unless(errno == EINVAL, "Failed to set errno to EINVAL");

  name = "127.0.0.1";
  res = pr_netaddr_is_v6(name);
  fail_unless(res == FALSE, "Expected 'false' for IPv4 address '%s', got %d",
    name, res);

  name = "localhost";
  res = pr_netaddr_is_v6(name);
  fail_unless(res == FALSE, "Expected 'false' for DNS name '%s', got %d",
    name, res);

  pr_netaddr_enable_ipv6();

  if (pr_netaddr_use_ipv6() == TRUE) {
    name = "::1";
    res = pr_netaddr_is_v6(name);
    fail_unless(res == TRUE, "Expected 'true' for IPv6 address '%s', got %d",
      name, res);
  }
}
Exemple #2
0
END_TEST

START_TEST (netaddr_disable_ipv6_test) {
  unsigned char use_ipv6;

  use_ipv6 = pr_netaddr_use_ipv6();

#ifdef PR_USE_IPV6
  fail_unless(use_ipv6 == TRUE, "Expected %d, got %d", TRUE, use_ipv6);
#else
  fail_unless(use_ipv6 == FALSE, "Expected %d, got %d", FALSE, use_ipv6);
#endif

  pr_netaddr_disable_ipv6();

  use_ipv6 = pr_netaddr_use_ipv6();
  fail_unless(use_ipv6 == FALSE, "Expected %d, got %d", FALSE, use_ipv6);
}
Exemple #3
0
END_TEST

START_TEST (inet_connect_ipv6_test) {
#ifdef PR_USE_IPV6
  int res;
  conn_t *conn;
  const pr_netaddr_t *addr;
  unsigned char use_ipv6;

  use_ipv6 = pr_netaddr_use_ipv6();
  pr_netaddr_enable_ipv6();
  pr_inet_set_default_family(p, AF_INET6);

  conn = pr_inet_create_conn(p, -1, NULL, INPORT_ANY, FALSE);
  fail_unless(conn != NULL, "Failed to create conn: %s", strerror(errno));

  addr = pr_netaddr_get_addr(p, "::1", NULL);
  fail_unless(addr != NULL, "Failed to resolve '::1': %s", strerror(errno));

  mark_point();
  res = proxy_inet_connect(p, conn, addr, 80);
  fail_unless(res < 0, "Connected to 127.0.0.1#80 unexpectedly");
  fail_unless(errno == ECONNREFUSED || errno == ENETUNREACH,
    "Expected ECONNREFUSED (%d) or ENETUNREACH (%d), got %s (%d)",
    ECONNREFUSED, ENETUNREACH, strerror(errno), errno);
  proxy_inet_close(p, conn);

  /* Try connecting to Google's DNS server. */

  conn = pr_inet_create_conn(p, -1, NULL, INPORT_ANY, FALSE);
  fail_unless(conn != NULL, "Failed to create conn: %s", strerror(errno));

  addr = pr_netaddr_get_addr(p, "2001:4860:4860::8888", NULL);
  fail_unless(addr != NULL, "Failed to resolve '2001:4860:4860::8888': %s",
    strerror(errno));

  mark_point();
  res = proxy_inet_connect(p, conn, addr, 53);
  if (res < 0) {
    /* This could be expected, e.g. if there's no route. */
    fail_unless(errno == EHOSTUNREACH || errno == ENETUNREACH,
      "Expected EHOSTUNREACH (%d) or ENETUNREACH (%d), got %s (%d)",
      EHOSTUNREACH, ENETUNREACH, strerror(errno), errno);
  }

  mark_point();
  proxy_inet_close(p, conn);

  pr_inet_set_default_family(p, AF_INET);

  if (use_ipv6 == FALSE) {
    pr_netaddr_disable_ipv6();
  }
#endif /* PR_USE_IPV6 */
}
Exemple #4
0
static void set_up(void) {
  if (p == NULL) {
    p = permanent_pool = session.pool = make_sub_pool(NULL);
    session.c = NULL;
    session.notes = NULL;
  }

  init_netaddr();
  use_ipv6 = pr_netaddr_use_ipv6();

  if (getenv("TEST_VERBOSE") != NULL) {
    pr_trace_set_levels("proxy.ftp.msg", 1, 20);
  }
}
Exemple #5
0
END_TEST

START_TEST (inet_set_proto_opts_ipv6_test) {
#ifdef PR_USE_IPV6
  int fd, sockfd = -1, port = INPORT_ANY, res;
  conn_t *conn;
  unsigned char use_ipv6;

  use_ipv6 = pr_netaddr_use_ipv6();

  pr_netaddr_enable_ipv6();
  pr_inet_set_default_family(p, AF_INET6);

  conn = pr_inet_create_conn(p, sockfd, NULL, port, FALSE);
  fail_unless(conn != NULL, "Failed to create conn: %s", strerror(errno));

  mark_point();
  res = pr_inet_set_proto_opts(p, conn, 1, 1, 1, 1);
  fail_unless(res == 0, "Failed to set proto opts: %s", strerror(errno));

  mark_point();
  fd = conn->rfd;
  conn->rfd = 8;
  res = pr_inet_set_proto_opts(p, conn, 1, 1, 1, 1);
  fail_unless(res == 0, "Failed to set proto opts: %s", strerror(errno));
  conn->rfd = fd;

  mark_point();
  fd = conn->wfd;
  conn->wfd = 9;
  res = pr_inet_set_proto_opts(p, conn, 1, 1, 1, 1);
  fail_unless(res == 0, "Failed to set proto opts: %s", strerror(errno));
  conn->wfd = fd;

  mark_point();
  fd = conn->listen_fd;
  conn->listen_fd = 10;
  res = pr_inet_set_proto_opts(p, conn, 1, 1, 1, 1);
  fail_unless(res == 0, "Failed to set proto opts: %s", strerror(errno));
  conn->listen_fd = fd;

  pr_inet_close(p, conn);

  pr_inet_set_default_family(p, AF_INET);
  if (use_ipv6 == FALSE) {
    pr_netaddr_disable_ipv6();
  }
#endif /* PR_USE_IPV6 */
}
Exemple #6
0
const pr_netaddr_t *proxy_ftp_msg_parse_ext_addr(pool *p, const char *msg,
    const pr_netaddr_t *addr, int cmd_id, const char *net_proto) {
  pr_netaddr_t *res = NULL, na;
  int family = 0;
  unsigned short port = 0;
  char delim, *msg_str, *ptr;
  size_t msglen;

  if (p == NULL ||
      msg == NULL ||
      addr == NULL) {
    errno = EINVAL;
    return NULL;
  }

  if (cmd_id == PR_CMD_EPSV_ID) {
    /* First, find the opening '(' character. */
    ptr = strchr(msg, '(');
    if (ptr == NULL) {
      pr_trace_msg(trace_channel, 12,
        "missing starting '(' character for extended address in '%s'", msg);
      errno = EINVAL;
      return NULL;
    }

    /* Make sure that the last character is a closing ')'. */
    msglen = strlen(ptr);
    if (ptr[msglen-1] != ')') {
      pr_trace_msg(trace_channel, 12,
        "missing ending ')' character for extended address in '%s'", msg);
      errno = EINVAL;
      return NULL;
    }

    msg_str = pstrndup(p, ptr+1, msglen-2);

  } else {
    msg_str = pstrdup(p, msg);
  }

  /* Format is <d>proto<d>ip address<d>port<d> (ASCII in network order),
   * where <d> is an arbitrary delimiter character.
   */
  delim = *msg_str++;

  /* If the network protocol string (e.g. sent by client in EPSV command) is
   * null, then determine the protocol family from the address family we were
   * given.
   */

  /* XXX Hack to skip "all", e.g. "EPSV ALL" commands. */
  if (net_proto != NULL) {
    if (strncasecmp(net_proto, "all", 4) == 0) {
      net_proto = NULL;
    }
  }

  if (net_proto == NULL) {
    if (*msg_str == delim) {
      switch (pr_netaddr_get_family(addr)) {
        case AF_INET:
          family = 1;
          break;

#ifdef PR_USE_IPV6
        case AF_INET6:
          if (pr_netaddr_use_ipv6()) {
            family = 2;
            break;
          }
#endif /* PR_USE_IPV6 */

        default:
          break;
      }

    } else {
      family = atoi(msg_str);
    }

  } else {
    family = atoi(net_proto);
  }

  switch (family) {
    case 1:
      pr_trace_msg(trace_channel, 19, "parsed IPv4 address from '%s'", msg);
      break;

#ifdef PR_USE_IPV6
    case 2:
      pr_trace_msg(trace_channel, 19, "parsed IPv6 address from '%s'", msg);
      if (pr_netaddr_use_ipv6()) {
        break;
      }
#endif /* PR_USE_IPV6 */

    default:
      pr_trace_msg(trace_channel, 12,
        "unsupported network protocol %d", family);
      errno = EPROTOTYPE;
      return NULL;
  }

  /* Now, skip past those numeric characters that atoi() used. */
  while (PR_ISDIGIT(*msg_str)) {
    msg_str++;
  }

  /* If the next character is not the delimiter, it's a badly formatted
   * parameter.
   */
  if (*msg_str == delim) {
    msg_str++;

  } else {
    pr_trace_msg(trace_channel, 17, "rejecting badly formatted message '%s'",
      msg_str);
    errno = EPERM;
    return NULL;
  }

  pr_netaddr_clear(&na);

  /* If the next character IS the delimiter, then the address portion is
   * omitted (which is permissible).
   */
  if (*msg_str == delim) {
    pr_netaddr_set_family(&na, pr_netaddr_get_family(addr));
    pr_netaddr_set_sockaddr(&na, pr_netaddr_get_sockaddr(addr));
    msg_str++;

  } else {
    ptr = strchr(msg_str, delim);
    if (ptr == NULL) {
      /* Badly formatted message. */
      errno = EINVAL;
      return NULL;
    }

    /* Twiddle the string so that just the address portion will be processed
     * by pr_inet_pton().
     */
    *ptr = '\0';

    /* Use pr_inet_pton() to translate the address string into the address
     * value.
     */
    switch (family) {
      case 1: {
        struct sockaddr *sa = NULL;

        pr_netaddr_set_family(&na, AF_INET);
        sa = pr_netaddr_get_sockaddr(&na);
        if (sa) {
          sa->sa_family = AF_INET;
        }

        if (pr_inet_pton(AF_INET, msg_str, pr_netaddr_get_inaddr(&na)) <= 0) {
          pr_trace_msg(trace_channel, 2,
            "error converting IPv4 address '%s': %s", msg_str, strerror(errno));
          errno = EPERM;
          return NULL;
        }

        break;
      }

      case 2: {
        struct sockaddr *sa = NULL;

        pr_netaddr_set_family(&na, AF_INET6);
        sa = pr_netaddr_get_sockaddr(&na);
        if (sa) {
          sa->sa_family = AF_INET6;
        }

        if (pr_inet_pton(AF_INET6, msg_str, pr_netaddr_get_inaddr(&na)) <= 0) {
          pr_trace_msg(trace_channel, 2,
            "error converting IPv6 address '%s': %s", msg_str, strerror(errno));
          errno = EPERM;
          return NULL;
        }

        break;
      }
    }

    /* Advance past the address portion of the argument. */
    msg_str = ++ptr;
  }

  port = atoi(msg_str);

  while (PR_ISDIGIT(*msg_str)) {
    msg_str++;
  }

  /* If the next character is not the delimiter, it's a badly formatted
   * parameter.
   */
  if (*msg_str != delim) {
    pr_trace_msg(trace_channel, 17, "rejecting badly formatted message '%s'",
      msg_str);
    errno = EPERM;
    return NULL;
  }

  /* XXX Use a pool other than session.pool here, in the future. */ 
  res = pr_netaddr_dup(session.pool, &na);
  pr_netaddr_set_port(res, htons(port));

  return res;
}
Exemple #7
0
const pr_netaddr_t *proxy_ftp_msg_parse_addr(pool *p, const char *msg,
    int addr_family) {
  int valid_fmt = FALSE;
  const char *ptr;
  char *addr_buf;
  unsigned int h1, h2, h3, h4, p1, p2;
  unsigned short port;
  size_t addrlen;
  pr_netaddr_t *addr;

  if (p == NULL ||
      msg == NULL) {
    errno = EINVAL;
    return NULL;
  }

  /* Have to scan the message for the encoded address/port.  Note that we may
   * see some strange formats for PASV responses from FTP servers here.
   *
   * We can't predict where the expected address/port numbers start in the
   * string, so start from the beginning.
   */
  for (ptr = msg; *ptr; ptr++) {
    if (sscanf(ptr, "%u,%u,%u,%u,%u,%u", &h1, &h2, &h3, &h4, &p1, &p2) == 6) {
      valid_fmt = TRUE;
      break;
    }
  }

  if (valid_fmt == FALSE) {
    pr_trace_msg(trace_channel, 12,
      "unable to find PORT/PASV address/port format in '%s'", msg);
    errno = EPERM;
    return NULL;
  }

  if (h1 > 255 || h2 > 255 || h3 > 255 || h4 > 255 ||
      p1 > 255 || p2 > 255 ||
      (h1|h2|h3|h4) == 0 ||
      (p1|p2) == 0) {
    pr_trace_msg(trace_channel, 9,
      "message '%s' has invalid address/port value(s)", msg);
    errno = EINVAL;
    return NULL;
  }

  /* A dotted quad address has a maximum size of 16 bytes: 4 numbers of 3 digits
   * (max), 3 periods, and 1 terminating NUL.
   */
  addrlen = 16;

#ifdef PR_USE_IPV6
  /* Allow extra room for any necessary "::ffff:" prefix, for IPv6 sessions. */
  addrlen += 7;
#endif /* PR_USE_IPV6 */

  addr_buf = pcalloc(p, addrlen); 

#ifdef PR_USE_IPV6
  if (pr_netaddr_use_ipv6()) {
    if (addr_family == AF_INET6) {
      snprintf(addr_buf, addrlen-1, "::ffff:%u.%u.%u.%u", h1, h2, h3, h4);

    } else {
      snprintf(addr_buf, addrlen-1, "%u.%u.%u.%u", h1, h2, h3, h4);
    }

  } else {
    snprintf(addr_buf, addrlen-1, "%u.%u.%u.%u", h1, h2, h3, h4);
  }
#else
  snprintf(addr_buf, addrlen-1, "%u.%u.%u.%u", h1, h2, h3, h4);
#endif /* PR_USE_IPV6 */

  /* XXX Ideally we would NOT be using session pool here, but some other
   * pool.  These objects can't be destroyed (they have no pools of their own),
   * so they will just clutter up the session pool.  Perhaps we could have
   * a pool of addrs in this API, for reusing.
   */
  addr = (pr_netaddr_t *) pr_netaddr_get_addr(session.pool, addr_buf, NULL);
  if (addr == NULL) {
    int xerrno = errno;

    pr_trace_msg(trace_channel, 7,
      "unable to resolve '%s' from message '%s': %s", addr_buf, msg,
      strerror(xerrno));

    errno = xerrno;
    return NULL;
  }

  port = (p1 << 8) + p2;
  pr_netaddr_set_port2(addr, port);

  return addr;
}
Exemple #8
0
/* Initialize a new connection record, also creates a new subpool just for the
 * new connection.
 */
static conn_t *init_conn(pool *p, int fd, pr_netaddr_t *bind_addr,
    int port, int retry_bind, int reporting) {
  pool *sub_pool = NULL;
  conn_t *c;
  pr_netaddr_t na;
  int addr_family;
  int res = 0, one = 1, hold_errno;

  if (!inet_pool) {
    inet_pool = make_sub_pool(permanent_pool);
    pr_pool_tag(inet_pool, "Inet Pool");
  }

  /* Initialize the netaddr. */
  pr_netaddr_clear(&na);

  sub_pool = make_sub_pool(p);
  pr_pool_tag(sub_pool, "init_conn() subpool");

  c = (conn_t *) pcalloc(sub_pool, sizeof(conn_t));
  c->pool = sub_pool;

  c->local_port = port;
  c->rfd = c->wfd = -1;

  if (bind_addr) {
    addr_family = pr_netaddr_get_family(bind_addr);

  } else if (inet_family) {
    addr_family = inet_family;

  } else {

    /* If no default family has been set, then default to IPv6 (if IPv6
     * support is enabled), otherwise use IPv4.
     */
#ifdef PR_USE_IPV6
    if (pr_netaddr_use_ipv6())
      addr_family = AF_INET6;

    else
      addr_family = AF_INET;
#else
    addr_family = AF_INET;
#endif /* PR_USE_IPV6 */
  }

  /* If fd == -1, there is no currently open socket, so create one.
   */
  if (fd == -1) {
    socklen_t salen;
    register unsigned int i = 0;

    /* Certain versions of Solaris apparently require us to be root
     * in order to create a socket inside a chroot.
     *
     * FreeBSD 2.2.6 (possibly other versions as well), has a security
     * "feature" which disallows SO_REUSEADDR from working if the socket
     * owners don't match.  The easiest thing to do is simply make sure
     * the socket is created as root.  (Note: this "feature" seems to apply
     * to _all_ BSDs.)
     */

#if defined(SOLARIS2) || defined(FREEBSD2) || defined(FREEBSD3) || \
    defined(FREEBSD4) || defined(FREEBSD5) || defined(FREEBSD6) || \
    defined(FREEBSD7) || defined(FREEBSD8) || defined(FREEBSD9) || \
    defined(__OpenBSD__) || defined(__NetBSD__) || \
    defined(DARWIN6) || defined(DARWIN7) || defined(DARWIN8) || \
    defined(DARWIN9) || defined(DARWIN10) || defined(DARWIN11) || \
    defined(SCO3) || defined(CYGWIN) || defined(SYSV4_2MP) || \
    defined(SYSV5SCO_SV6) || defined(SYSV5UNIXWARE7)
# ifdef SOLARIS2
    if (port != INPORT_ANY && port < 1024) {
# endif
      pr_signals_block();
      PRIVS_ROOT
# ifdef SOLARIS2
    }
Exemple #9
0
pr_namebind_t *pr_namebind_find(const char *name, pr_netaddr_t *addr,
    unsigned int port, unsigned char skip_inactive) {
  pr_ipbind_t *ipbind = NULL;
  pr_namebind_t *namebind = NULL;

  if (name == NULL ||
      addr == NULL) {
    errno = EINVAL;
    return NULL;
  }

  /* First, find an active ipbind for the given addr/port */
  ipbind = pr_ipbind_find(addr, port, skip_inactive);

  if (ipbind == NULL) {
    pr_netaddr_t wildcard_addr;
    int addr_family;

    /* If not found, look for the wildcard address. */

    addr_family = pr_netaddr_get_family(addr);
    pr_netaddr_clear(&wildcard_addr);
    pr_netaddr_set_family(&wildcard_addr, addr_family);
    pr_netaddr_set_sockaddr_any(&wildcard_addr);

    ipbind = pr_ipbind_find(&wildcard_addr, port, FALSE);
#ifdef PR_USE_IPV6
    if (ipbind == FALSE &&
        addr_family == AF_INET6 &&
        pr_netaddr_use_ipv6()) {

      /* No IPv6 wildcard address found; try the IPv4 wildcard address. */
      pr_netaddr_clear(&wildcard_addr);
      pr_netaddr_set_family(&wildcard_addr, AF_INET);
      pr_netaddr_set_sockaddr_any(&wildcard_addr);

      ipbind = pr_ipbind_find(&wildcard_addr, port, FALSE);
    }
#endif /* PR_USE_IPV6 */
  }

  if (ipbind == NULL) {
    errno = ENOENT;
    return NULL;
  }

  if (!ipbind->ib_namebinds) {
    return NULL;

  } else {
    register unsigned int i = 0;
    pr_namebind_t **namebinds = (pr_namebind_t **) ipbind->ib_namebinds->elts;

    for (i = 0; i < ipbind->ib_namebinds->nelts; i++) {
      namebind = namebinds[i];

      /* Skip inactive namebinds */
      if (skip_inactive == TRUE &&
          namebind != NULL &&
          namebind->nb_isactive == FALSE) {
        continue;
      }

      /* At present, this looks for an exactly matching name.  In the future,
       * we may want to have something like Apache's matching scheme, which
       * looks for the most specific domain to the most general.  Note that
       * that scheme, however, is specific to DNS; should any other naming
       * scheme be desired, that sort of matching will be unnecessary.
       */
      if (namebind != NULL &&
          namebind->nb_name != NULL) {

        if (namebind->nb_iswildcard == FALSE) {
          if (strcasecmp(namebind->nb_name, name) == 0)
            return namebind;
          }

        } else {
          int match_flags = PR_FNM_NOESCAPE|PR_FNM_CASEFOLD;

          if (pr_fnmatch(namebind->nb_name, name, match_flags) == 0) {
            pr_trace_msg(trace_channel, 9,
              "matched name '%s' against pattern '%s'", name,
              namebind->nb_name);
            return namebind;
          }

          pr_trace_msg(trace_channel, 9,
            "failed to match name '%s' against pattern '%s'", name,
            namebind->nb_name);
        }
    }
  }

  return NULL;
}
Exemple #10
0
int pr_namebind_create(server_rec *server, const char *name,
    pr_netaddr_t *addr, unsigned int port) {
  pr_ipbind_t *ipbind = NULL;
  pr_namebind_t *namebind = NULL, **namebinds = NULL;

  if (server == NULL ||
      name == NULL) {
    errno = EINVAL;
    return -1;
  }

  /* First, find the ipbind to hold this namebind. */
  ipbind = pr_ipbind_find(addr, port, FALSE);

  if (ipbind == NULL) {
    pr_netaddr_t wildcard_addr;
    int addr_family;

    /* If not found, look for the wildcard address. */

    addr_family = pr_netaddr_get_family(addr);
    pr_netaddr_clear(&wildcard_addr);
    pr_netaddr_set_family(&wildcard_addr, addr_family);
    pr_netaddr_set_sockaddr_any(&wildcard_addr);

    ipbind = pr_ipbind_find(&wildcard_addr, port, FALSE);
#ifdef PR_USE_IPV6
    if (ipbind == FALSE &&
        addr_family == AF_INET6 &&
        pr_netaddr_use_ipv6()) {

      /* No IPv6 wildcard address found; try the IPv4 wildcard address. */
      pr_netaddr_clear(&wildcard_addr);
      pr_netaddr_set_family(&wildcard_addr, AF_INET);
      pr_netaddr_set_sockaddr_any(&wildcard_addr);

      ipbind = pr_ipbind_find(&wildcard_addr, port, FALSE);
    }
#endif /* PR_USE_IPV6 */
  }

  if (ipbind == NULL) {
    errno = ENOENT;
    return -1;
  }

  /* Make sure we can add this namebind. */
  if (!ipbind->ib_namebinds) {
    ipbind->ib_namebinds = make_array(binding_pool, 0, sizeof(pr_namebind_t *));

  } else {
    register unsigned int i = 0;
    namebinds = (pr_namebind_t **) ipbind->ib_namebinds->elts;

    /* See if there is already a namebind for the given name. */
    for (i = 0; i < ipbind->ib_namebinds->nelts; i++) {
      namebind = namebinds[i];
      if (namebind != NULL &&
          namebind->nb_name != NULL) {

        /* DNS names are case-insensitive, hence the case-insensitive check
         * here.
         *
         * XXX Ideally, we should check whether any existing namebinds which
         * are globs will match the newly added namebind as well.
         */

        if (strcasecmp(namebind->nb_name, name) == 0) {
          errno = EEXIST;
          return -1;
        }
      }
    }
  }

  namebind = (pr_namebind_t *) pcalloc(server->pool, sizeof(pr_namebind_t));
  namebind->nb_name = name;
  namebind->nb_server = server;
  namebind->nb_isactive = FALSE;

  if (pr_str_is_fnmatch(name) == TRUE) {
    namebind->nb_iswildcard = TRUE;
  }

  pr_trace_msg(trace_channel, 8,
    "created named binding '%s' for %s#%u, server %p", name,
    pr_netaddr_get_ipstr(server->addr), server->ServerPort, server->ServerName);

  /* The given server should already have the following populated:
   *
   *  server->ServerName
   *  server->ServerAddress
   *  server->ServerFQDN
   */

  /* These TCP socket tweaks will not apply to the control connection (it will
   * already have been established by the time this named vhost is used),
   * but WILL apply to any data connections established to this named vhost.
   */

#if 0
  namebind->nb_server->tcp_mss_len = (server->tcp_mss_len ?
    server->tcp_mss_len : main_server->tcp_mss_len);
  namebind->nb_server->tcp_rcvbuf_len = (server->tcp_rcvbuf_len ?
    server->tcp_rcvbuf_len : main_server->tcp_rcvbuf_len);
  namebind->nb_server->tcp_rcvbuf_override = (server->tcp_rcvbuf_override ?
    TRUE : main_server->tcp_rcvbuf_override);
  namebind->nb_server->tcp_sndbuf_len = (server->tcp_sndbuf_len ?
    server->tcp_sndbuf_len : main_server->tcp_sndbuf_len);
  namebind->nb_server->tcp_sndbuf_override = (server->tcp_sndbuf_override ?
    TRUE : main_server->tcp_sndbuf_override);

  /* XXX Shouldn't need these; the ipbind container handles all of the
   * connection (listener, port, addr) stuff.
   */

  namebind->nb_server->addr = (server->addr ? server->addr :
    main_server->addr);
  namebind->nb_server->ServerPort = (server->ServerPort ? server->ServerPort :
    main_server->ServerPort);
  namebind->nb_listener = (server->listen ? server->listen :
    main_server->listen);
#endif

  *((pr_namebind_t **) push_array(ipbind->ib_namebinds)) = namebind;
  return 0;
}
Exemple #11
0
server_rec *pr_ipbind_get_server(pr_netaddr_t *addr, unsigned int port) {
  pr_ipbind_t *ipbind = NULL;
  pr_netaddr_t wildcard_addr;
  int addr_family;

  /* If we've got a binding configured for this exact address, return it
   * straightaway.
   */
  ipbind = pr_ipbind_find(addr, port, TRUE);
  if (ipbind != NULL)
    return ipbind->ib_server;

  /* Look for a vhost bound to the wildcard address (i.e. INADDR_ANY).
   *
   * This allows for "<VirtualHost 0.0.0.0>" configurations, where the
   * IP address to which the client might connect is not known at
   * configuration time.  (Usually happens when the same config file
   * is deployed to multiple machines.)
   */

  addr_family = pr_netaddr_get_family(addr);

  pr_netaddr_clear(&wildcard_addr);
  pr_netaddr_set_family(&wildcard_addr, addr_family);
  pr_netaddr_set_sockaddr_any(&wildcard_addr);

  ipbind = pr_ipbind_find(&wildcard_addr, port, TRUE);
  if (ipbind != NULL) {
    pr_log_debug(DEBUG7, "no matching vhost found for %s#%u, using "
      "'%s' listening on wildcard address", pr_netaddr_get_ipstr(addr), port,
      ipbind->ib_server->ServerName);
    return ipbind->ib_server;

  } else {
#ifdef PR_USE_IPV6
    if (addr_family == AF_INET6 &&
        pr_netaddr_use_ipv6()) {

      /* The pr_ipbind_find() probably returned NULL because there aren't
       * any <VirtualHost> sections configured explicitly for the wildcard
       * IPv6 address of "::", just the IPv4 wildcard "0.0.0.0" address.
       *
       * So try the pr_ipbind_find() again, this time using the IPv4 wildcard.
       */
      pr_netaddr_clear(&wildcard_addr);
      pr_netaddr_set_family(&wildcard_addr, AF_INET);
      pr_netaddr_set_sockaddr_any(&wildcard_addr);

      ipbind = pr_ipbind_find(&wildcard_addr, port, TRUE);
      if (ipbind != NULL) {
        pr_log_debug(DEBUG7, "no matching vhost found for %s#%u, using "
          "'%s' listening on wildcard address", pr_netaddr_get_ipstr(addr),
          port, ipbind->ib_server->ServerName);
        return ipbind->ib_server;
      }
    }
#endif /* PR_USE_IPV6 */
  }

  /* Use the default server, if set. */
  if (ipbind_default_server &&
      ipbind_default_server->ib_isactive) {
    pr_log_debug(DEBUG7, "no matching vhost found for %s#%u, using "
      "DefaultServer '%s'", pr_netaddr_get_ipstr(addr), port,
      ipbind_default_server->ib_server->ServerName);
    return ipbind_default_server->ib_server;
  }

  /* Not found in binding list, and no DefaultServer, so see if it's the
   * loopback address
   */
  if (ipbind_localhost_server &&
      pr_netaddr_is_loopback(addr)) {
    return ipbind_localhost_server->ib_server;
  }

  return NULL;
}
Exemple #12
0
static int init_standalone_bindings(void) {
  int res = 0;
  server_rec *serv = NULL;
  unsigned char *default_server = NULL, is_default = FALSE;

  /* If a port is set to zero, the address/port is not bound to a socket
   * at all.
   */
  if (main_server->ServerPort) {

    /* If SocketBindTight is off, then pr_inet_create_conn() will
     * create and bind to a wildcard socket.  However, should it be an
     * IPv4 or an IPv6 wildcard socket?
     */
    if (!SocketBindTight) {
#ifdef PR_USE_IPV6
      if (pr_netaddr_use_ipv6()) {
        pr_inet_set_default_family(NULL, AF_INET6);

      } else {
        int default_family;

        default_family = pr_netaddr_get_family(main_server->addr);
        pr_inet_set_default_family(NULL, default_family);
      }
#else
      pr_inet_set_default_family(NULL,
        pr_netaddr_get_family(main_server->addr));
#endif /* PR_USE_IPV6 */
    }

    main_server->listen = pr_ipbind_get_listening_conn(main_server,
      (SocketBindTight ? main_server->addr : NULL), main_server->ServerPort);
    if (main_server->listen == NULL) {
      return -1;
    }

  } else {
    main_server->listen = NULL;
  }

  default_server = get_param_ptr(main_server->conf, "DefaultServer", FALSE);
  if (default_server != NULL &&
      *default_server == TRUE) {
    is_default = TRUE;
  }

  if (main_server->ServerPort ||
      is_default) {
    PR_CREATE_IPBIND(main_server, main_server->addr, main_server->ServerPort);
    PR_OPEN_IPBIND(main_server->addr, main_server->ServerPort,
      main_server->listen, is_default, TRUE, TRUE);
    PR_ADD_IPBINDS(main_server);
  }

  for (serv = main_server->next; serv; serv = serv->next) {
    config_rec *c;
    int is_namebind = FALSE;

    /* See if this server is a namebind, to be part of an existing ipbind. */
    c = find_config(serv->conf, CONF_PARAM, "ServerAlias", FALSE);
    while (c != NULL) {
      pr_signals_handle();

      res = pr_namebind_create(serv, c->argv[0], serv->addr, serv->ServerPort);
      if (res == 0) {
        is_namebind = TRUE;

        res = pr_namebind_open(c->argv[0], serv->addr, serv->ServerPort);
        if (res < 0) {
          pr_log_pri(PR_LOG_NOTICE,
            "%s:%d: notice: unable to open namebind '%s': %s", __FILE__,
            __LINE__, (char *) c->argv[0], strerror(errno));
        }

      } else {
        pr_log_pri(PR_LOG_NOTICE,
          "unable to create namebind for '%s' to %s#%u: %s",
          (char *) c->argv[0], pr_netaddr_get_ipstr(serv->addr),
          serv->ServerPort, strerror(errno));
      }

      c = find_config_next(c, c->next, CONF_PARAM, "ServerAlias", FALSE);
    }

    if (is_namebind == TRUE) {
      continue;
    }

    if (serv->ServerPort != main_server->ServerPort ||
        SocketBindTight ||
        !main_server->listen) {
      is_default = FALSE;

      default_server = get_param_ptr(serv->conf, "DefaultServer", FALSE);
      if (default_server != NULL &&
          *default_server == TRUE) {
        is_default = TRUE;
      }

      if (serv->ServerPort) {
        if (!SocketBindTight) {
#ifdef PR_USE_IPV6
          if (pr_netaddr_use_ipv6()) {
            pr_inet_set_default_family(NULL, AF_INET6);

          } else {
            pr_inet_set_default_family(NULL, pr_netaddr_get_family(serv->addr));
          }
#else
          pr_inet_set_default_family(NULL, pr_netaddr_get_family(serv->addr));
#endif /* PR_USE_IPV6 */
        }

        serv->listen = pr_ipbind_get_listening_conn(serv,
          (SocketBindTight ? serv->addr : NULL), serv->ServerPort);
        if (serv->listen == NULL) {
          return -1;
        }

        PR_CREATE_IPBIND(serv, serv->addr, serv->ServerPort);
        PR_OPEN_IPBIND(serv->addr, serv->ServerPort, serv->listen, is_default,
          FALSE, TRUE);
        PR_ADD_IPBINDS(serv);

      } else if (is_default) {
        serv->listen = NULL;

        PR_CREATE_IPBIND(serv, serv->addr, serv->ServerPort);
        PR_OPEN_IPBIND(serv->addr, serv->ServerPort, serv->listen, is_default,
          FALSE, TRUE);
        PR_ADD_IPBINDS(serv);

      } else {
        serv->listen = NULL;
      }

    } else {
      /* Because this server is sharing the connection with the main server,
       * we need a cleanup handler to remove the server's reference when the
       * original connection's pool is destroyed.
       */

      is_default = FALSE;
      default_server = get_param_ptr(serv->conf, "DefaultServer", FALSE);
      if (default_server != NULL &&
          *default_server == TRUE) {
        is_default = TRUE;
      }

      serv->listen = main_server->listen;
      register_cleanup(serv->listen->pool, &serv->listen, server_cleanup_cb,
        server_cleanup_cb);

      PR_CREATE_IPBIND(serv, serv->addr, serv->ServerPort);
      PR_OPEN_IPBIND(serv->addr, serv->ServerPort, NULL, is_default, FALSE,
        TRUE);
      PR_ADD_IPBINDS(serv);
    }
  }

  /* Any "unclaimed" listening conns can be removed and closed. */
  if (listening_conn_list) {
    struct listener_rec *lr, *lrn;

    for (lr = (struct listener_rec *) listening_conn_list->xas_list; lr; lr = lrn) {
      lrn = lr->next;

      if (!lr->claimed) {
        xaset_remove(listening_conn_list, (xasetmember_t *) lr);
        destroy_pool(lr->pool);
      }
    }
  }

  return 0;
}
Exemple #13
0
pr_netacl_t *pr_netacl_create(pool *p, char *aclstr) {
  pr_netacl_t *acl;
  char *cp, *aclstr_dup;

  if (p == NULL ||
      aclstr == NULL) {
    errno = EINVAL;
    return NULL;
  }

  if (strlen(aclstr) == 0) {
    errno = EINVAL;
    return NULL;
  }

  /* Parse the given rule into a netacl object. */
  acl = pcalloc(p, sizeof(pr_netacl_t));

  aclstr_dup = pstrdup(p, aclstr);

  if (strncasecmp(aclstr, "all", 4) == 0) {
    aclstr_dup = pstrdup(p, "all");
    acl->type = PR_NETACL_TYPE_ALL;

  } else if (strncasecmp(aclstr, "none", 5) == 0) {
    aclstr_dup = pstrdup(p, "none");
    acl->type = PR_NETACL_TYPE_NONE;

  } else if ((cp = strchr(aclstr, '/')) != NULL) {
    char *tmp;

    acl->type = PR_NETACL_TYPE_IPMASK;
    *cp = '\0';

    /* Check if the given rule is negated. */
    if (*aclstr == '!') {
      acl->negated = TRUE;
      aclstr++;
    }

    /* We have some type of IP/mask, either IPv4 or IPv6.  We know that colons
     * will only appear in IPv6 addresses, so...
     */

    if (strspn(aclstr, "0123456789ABCDEFabcdef.:") != strlen(aclstr)) {
      errno = EINVAL;
      return NULL;
    }

    acl->addr = pr_netaddr_get_addr(p, aclstr, NULL);
    if (acl->addr == NULL) {
      return NULL;
    }

    /* Determine what the given bitmask is. */
    acl->masklen = strtol(cp + 1, &tmp, 10);

    if (tmp && *tmp) {
      /* Invalid bitmask syntax. */
      errno = EINVAL;
      return NULL;
    }

    *cp = '/';

    /* Make sure the given mask length is appropriate for the address. */
    switch (pr_netaddr_get_family(acl->addr)) {
      case AF_INET: {
        /* Make sure that the given number of bits is not more than supported
         * for IPv4 addresses (32).
         */
        if (acl->masklen > 32) {
          errno = EINVAL;
          return NULL;
        }

        break;
      }

#ifdef PR_USE_IPV6
      case AF_INET6: {
        if (pr_netaddr_use_ipv6()) {
          if (acl->masklen > 128) {
            errno = EINVAL;
            return NULL;

          } else if (pr_netaddr_is_v4mappedv6(acl->addr) == TRUE &&
                     acl->masklen > 32) {
            /* The admin may be trying to use IPv6-style masks on IPv4-mapped
             * IPv6 addresses, which of course will not work as expected.
             * If the mask is 32 bits or more, warn the admin.
             */
            pr_log_pri(PR_LOG_WARNING, "warning: possibly using IPv6-style netmask on IPv4-mapped IPv6 address, which will not work as expected");
            pr_trace_msg(trace_channel, 1, "possibly using IPv6-style netmask on IPv4-mapped IPv6 address (%s), which will not work as expected", aclstr);

            break;
          }
        }
      }
#endif /* PR_USE_IPV6 */

      default:
        break;
    }

#ifdef PR_USE_IPV6
  } else if (pr_netaddr_use_ipv6() &&
             strspn(aclstr, "0123456789ABCDEFabcdef.:!") != strlen(aclstr)) {
#else
  } else if (strspn(aclstr, "0123456789.!") != strlen(aclstr)) {
#endif /* PR_USE_IPV6 */

    /* Check if the given rule is negated. */
    if (*aclstr == '!') {
      acl->negated = TRUE;
      aclstr++;
    }

    /* If there are any glob characters (e.g. '{', '[', '*', or '?'), or if the
     * first character is a '.', then treat the rule as a glob.
     */
    if (strpbrk(aclstr, "{[*?")) {
      register unsigned int i;
      size_t aclstr_len = strlen(aclstr);
      pr_netacl_type_t netacl_type = PR_NETACL_TYPE_IPGLOB;

      /* Is this a DNS glob, or an IP address glob?  To find out, see if there
       * are any non-IP characters (i.e. alphabetical characters, taking IPv6
       * into account).
       */
      for (i = 0; i < aclstr_len; i++) {
        if (PR_ISALPHA(aclstr[i])) {
#ifdef PR_USE_IPV6
          if (pr_netaddr_use_ipv6()) {
            if (aclstr[i] == 'A' || aclstr[i] == 'a' ||
                aclstr[i] == 'B' || aclstr[i] == 'b' ||
                aclstr[i] == 'C' || aclstr[i] == 'c' ||
                aclstr[i] == 'D' || aclstr[i] == 'd' ||
                aclstr[i] == 'E' || aclstr[i] == 'e' ||
                aclstr[i] == 'F' || aclstr[i] == 'f') {
              continue;
            }

            netacl_type = PR_NETACL_TYPE_DNSGLOB;
            break;

          } else {
            netacl_type = PR_NETACL_TYPE_DNSGLOB;
            break;
          }
#else
          netacl_type = PR_NETACL_TYPE_DNSGLOB;
          break;
#endif /* PR_USE_IPV6 */
        }
      }

      acl->type = netacl_type;
      acl->pattern = pstrdup(p, aclstr);

    } else if (*aclstr == '.') {
      acl->type = PR_NETACL_TYPE_DNSGLOB;
      acl->pattern = pstrcat(p, "*", aclstr, NULL);

    } else {
      acl->type = PR_NETACL_TYPE_DNSMATCH;
      acl->pattern = pstrdup(p, aclstr);
    }

  } else if (strchr(aclstr, '.') == NULL) {

    /* Check if the given rule is negated. */
    if (*aclstr == '!') {
      acl->negated = TRUE;
      aclstr++;
    }

    /* If there are any glob characters (e.g. '{', '[', '*', or '?'), or if the
     * first character is a '.', then treat the rule as a glob.
     */
    if (strpbrk(aclstr, "{[*?")) {
      acl->type = PR_NETACL_TYPE_DNSGLOB;
      acl->pattern = pstrdup(p, aclstr);

    } else {
      acl->type = PR_NETACL_TYPE_DNSMATCH;
      acl->pattern = pstrdup(p, aclstr);
    }

  } else {

    /* Check if the given rule is negated. */
    if (*aclstr == '!') {
      acl->negated = TRUE;
      aclstr++;
    }

    /* If the last character is a '.', then treat the rule as an IP glob. */
    if (aclstr[strlen(aclstr)-1] == '.') {
      acl->type = PR_NETACL_TYPE_IPGLOB;
      acl->pattern = pstrcat(p, aclstr, "*", NULL);

    } else {
      register unsigned int i;
      int use_glob = FALSE, use_dns = FALSE;
      size_t aclstr_len;

      /* Is this a DNS glob, DNS match, IP glob, or IP match?
       *
       * First, check for any glob characters.  After that, determine whether
       * it's a DNS or IP type ACL.
       */

      /* If there are any glob characters (e.g. '{', '[', '*', or '?'), or
       * if the first character is a '.', then treat the rule as a glob.
       */
      use_glob = (strpbrk(aclstr, "{[*?") != NULL);

      aclstr_len = strlen(aclstr);
      for (i = 0; i < aclstr_len; i++) {
        if (PR_ISALPHA(aclstr[i])) {
#ifdef PR_USE_IPV6
          if (pr_netaddr_use_ipv6()) {
            if (aclstr[i] == 'A' || aclstr[i] == 'a' ||
                aclstr[i] == 'B' || aclstr[i] == 'b' ||
                aclstr[i] == 'C' || aclstr[i] == 'c' ||
                aclstr[i] == 'D' || aclstr[i] == 'd' ||
                aclstr[i] == 'E' || aclstr[i] == 'e' ||
                aclstr[i] == 'F' || aclstr[i] == 'f') {
              continue;
            }

            use_dns = TRUE;
            break;

          } else {
            use_dns = TRUE;
            break;
          }
#else
          use_dns = TRUE;
          break;
#endif /* PR_USE_IPV6 */
        }
      }

      if (!use_dns) {
        acl->type = use_glob ? PR_NETACL_TYPE_IPGLOB : PR_NETACL_TYPE_IPMATCH;
        acl->addr = pr_netaddr_get_addr(p, aclstr, NULL);

        if (acl->addr == NULL) {
           return NULL;
        }

      } else {
        if (use_glob) {
          acl->type = PR_NETACL_TYPE_DNSGLOB;
          acl->pattern = pstrdup(p, aclstr);

        } else if (*aclstr == '.') {
          acl->type = PR_NETACL_TYPE_DNSGLOB;
          acl->pattern = pstrcat(p, "*", aclstr, NULL);

        } else {
          acl->type = PR_NETACL_TYPE_DNSMATCH;
          acl->pattern = pstrdup(p, aclstr);
        }
      }
    }
  }

  acl->aclstr = aclstr_dup;
  return acl;
}
Exemple #14
0
END_TEST

START_TEST (inet_connect_ipv6_test) {
#ifdef PR_USE_IPV6
  int sockfd = -1, port = INPORT_ANY, res;
  conn_t *conn;
  const pr_netaddr_t *addr;
  unsigned char use_ipv6;

  use_ipv6 = pr_netaddr_use_ipv6();

  pr_netaddr_enable_ipv6();
  pr_inet_set_default_family(p, AF_INET6);

  conn = pr_inet_create_conn(p, sockfd, NULL, port, FALSE);
  fail_unless(conn != NULL, "Failed to create conn: %s", strerror(errno));

  addr = pr_netaddr_get_addr(p, "::1", NULL);
  fail_unless(addr != NULL, "Failed to resolve '::1': %s",
    strerror(errno));

  res = pr_inet_connect(p, conn, addr, 80);
  fail_unless(res < 0, "Connected to ::1#80 unexpectedly");
  fail_unless(errno == ECONNREFUSED || errno == ENETUNREACH,
    "Expected ECONNREFUSED (%d) or ENETUNREACH (%d), got %s (%d)",
    ECONNREFUSED, ENETUNREACH, strerror(errno), errno);

  /* Try connecting to Google's DNS server. */

  addr = pr_netaddr_get_addr(p, "2001:4860:4860::8888", NULL);
  fail_unless(addr != NULL, "Failed to resolve '2001:4860:4860::8888': %s",
    strerror(errno));

  res = pr_inet_connect(p, conn, addr, 53);
  if (res < 0) {
    /* Note: We get EINVAL here because the socket already tried (and failed)
     * to connect to a different address.  Interestingly, trying to connect(2)
     * using that same fd to a different address yields EINVAL.
     */
    fail_unless(errno == EINVAL || errno == ENETUNREACH,
      "Expected EINVAL (%d) or ENETUNREACH (%d), got %s (%d)",
      EINVAL, ENETUNREACH, strerror(errno), errno);
  }
  pr_inet_close(p, conn);

  conn = pr_inet_create_conn(p, sockfd, NULL, port, FALSE);
  fail_unless(conn != NULL, "Failed to create conn: %s", strerror(errno));

  res = pr_inet_connect(p, conn, addr, 53);
  if (res < 0) {
    /* This could be expected, e.g. if there's no route. */
    fail_unless(errno == EHOSTUNREACH || errno == ENETUNREACH,
      "Expected EHOSTUNREACH (%d) or ENETUNREACH (%d), got %s (%d)",
      EHOSTUNREACH, ENETUNREACH, strerror(errno), errno);
  }

  res = pr_inet_connect(p, conn, addr, 53);
  fail_unless(res < 0, "Failed to connect to 2001:4860:4860::8888#53: %s",
    strerror(errno));
  fail_unless(errno == EISCONN || errno == EHOSTUNREACH || errno == ENETUNREACH,
    "Expected EISCONN (%d) or EHOSTUNREACH (%d) or ENETUNREACH (%d), "
    "got %s (%d)", EISCONN, EHOSTUNREACH, ENETUNREACH, strerror(errno), errno);
  pr_inet_close(p, conn);

  pr_inet_set_default_family(p, AF_INET);

  if (use_ipv6 == FALSE) {
    pr_netaddr_disable_ipv6();
  }
#endif /* PR_USE_IPV6 */
}
Exemple #15
0
int proxy_ftp_xfer_prepare_active(int policy_id, cmd_rec *cmd,
    const char *error_code, struct proxy_session *proxy_sess, int flags) {
  int backend_family, bind_family, res, xerrno = 0;
  cmd_rec *actv_cmd;
  const pr_netaddr_t *bind_addr = NULL;
  pr_response_t *resp;
  unsigned int resp_nlines = 0;
  conn_t *data_conn = NULL;
  char *active_cmd;
  const char *resp_msg = NULL;

  if (cmd == NULL ||
      error_code == NULL ||
      proxy_sess == NULL ||
      proxy_sess->backend_ctrl_conn == NULL) {
    errno = EINVAL;
    return -1;
  }

  switch (policy_id) {
    case PR_CMD_PORT_ID:
      active_cmd = C_PORT;
      break;

    case PR_CMD_EPRT_ID:
      /* If the remote host does not mention EPRT in its features, fall back
       * to using PORT.
       */
      active_cmd = C_EPRT;
      if (pr_table_get(proxy_sess->backend_features, C_EPRT, NULL) == NULL) {
        pr_trace_msg(trace_channel, 19,
          "EPRT not supported by backend server (via FEAT), using PORT");
        if (proxy_sess->dataxfer_policy == PR_CMD_EPRT_ID) {
          proxy_sess->dataxfer_policy = PR_CMD_PORT_ID;
        }

        active_cmd = C_PORT;
        policy_id = PR_CMD_PORT_ID;
      }
      break;

    default:
      /* In this case, the cmd we were given is the one we should send to
       * the backend server -- but only if it is either EPRT or PORT.
       */
      if (pr_cmd_cmp(cmd, PR_CMD_EPRT_ID) != 0 &&
          pr_cmd_cmp(cmd, PR_CMD_PORT_ID) != 0) {
        pr_trace_msg(trace_channel, 9,
          "illegal FTP active transfer command '%s'", (char *) cmd->argv[0]);
        errno = EINVAL;
        return -1;
      }

      active_cmd = cmd->argv[0];

      if (pr_cmd_cmp(cmd, PR_CMD_EPRT_ID) == 0) {
        /* If the remote host does not mention EPRT in its features, fall back
         * to using PORT.
         */
        if (pr_table_get(proxy_sess->backend_features, C_EPRT, NULL) == NULL) {
          pr_trace_msg(trace_channel, 19,
            "EPRT not supported by backend server (via FEAT), using PORT");
          if (proxy_sess->dataxfer_policy == PR_CMD_EPRT_ID) {
            proxy_sess->dataxfer_policy = PR_CMD_PORT_ID;
          }

          active_cmd = C_PORT;
          policy_id = PR_CMD_PORT_ID;
        }
      }

      break;
  }

  bind_addr = proxy_sess->src_addr;
  if (bind_addr == NULL) {
    bind_addr = session.c->local_addr;
  }

  if (pr_netaddr_is_loopback(bind_addr) == TRUE &&
      pr_netaddr_is_loopback(proxy_sess->backend_ctrl_conn->remote_addr) != TRUE) {
    const char *local_name;
    const pr_netaddr_t *local_addr;

    local_name = pr_netaddr_get_localaddr_str(cmd->pool);
    local_addr = pr_netaddr_get_addr(cmd->pool, local_name, NULL);

    if (local_addr != NULL) {
      pr_trace_msg(trace_channel, 14,
        "%s is a loopback address, using %s instead",
        pr_netaddr_get_ipstr(bind_addr), pr_netaddr_get_ipstr(local_addr));
      bind_addr = local_addr;
    }
  }

  /* Need to check the family of the bind addr against the family of the
   * backend server.
   */
  backend_family = pr_netaddr_get_family(proxy_sess->backend_ctrl_conn->remote_addr);
  bind_family = pr_netaddr_get_family(bind_addr);

  if (bind_family == backend_family) {
#ifdef PR_USE_IPV6
    if (pr_netaddr_use_ipv6()) {
      /* Make sure that the family is NOT IPv6, even though the local and
       * remote families match.  The PORT command cannot be used for IPv6
       * addresses -- but EPRT CAN be used for IPv6 addresses.
       */
      if (bind_family == AF_INET6 &&
          policy_id == PR_CMD_PORT_ID) {
        pr_netaddr_t *mapped_addr;

        mapped_addr = pr_netaddr_v6tov4(cmd->pool, bind_addr);
        if (mapped_addr != NULL) {
          pr_trace_msg(trace_channel, 9,
            "converting local IPv6 address '%s' to IPv4 address '%s' for "
            "active transfer using PORT", pr_netaddr_get_ipstr(bind_addr),
            pr_netaddr_get_ipstr(mapped_addr));
          bind_addr = mapped_addr;
        }
      }
    }
#endif /* PR_USE_IPV6 */
  } else {
    if (backend_family == AF_INET) {
      pr_netaddr_t *mapped_addr;

      /* In this scenario, the remote peer is an IPv4 (or IPv4-mapped IPv6)
       * peer, so make sure we use an IPv4 local address.
       */
      mapped_addr = pr_netaddr_v6tov4(cmd->pool, bind_addr);
      if (mapped_addr != NULL) {
        pr_trace_msg(trace_channel, 9,
          "converting local IPv6 address '%s' to IPv4 address '%s' for "
          "active transfer with IPv4 peer", pr_netaddr_get_ipstr(bind_addr),
          pr_netaddr_get_ipstr(mapped_addr));
        bind_addr = mapped_addr;
      }
    }
  }

  if (proxy_sess->backend_data_conn != NULL) {
    /* Make sure that we only have one backend data connection. */
    proxy_inet_close(session.pool, proxy_sess->backend_data_conn);
    proxy_sess->backend_data_conn = NULL;
  }

  data_conn = proxy_ftp_conn_listen(cmd->tmp_pool, bind_addr, FALSE);
  if (data_conn == NULL) {
    xerrno = errno;

    pr_response_add_err(error_code,
      _("Unable to build data connection: Internal error"));
    pr_response_flush(&resp_err_list);

    errno = xerrno;
    return -1;
  }

  proxy_sess->backend_data_conn = data_conn;

  switch (pr_cmd_get_id(active_cmd)) {
    case PR_CMD_PORT_ID:
      resp_msg = proxy_ftp_msg_fmt_addr(cmd->tmp_pool, data_conn->local_addr,
        data_conn->local_port, FALSE);
      break;

    case PR_CMD_EPRT_ID:
      resp_msg = proxy_ftp_msg_fmt_ext_addr(cmd->tmp_pool,
        data_conn->local_addr, data_conn->local_port, PR_CMD_EPRT_ID, FALSE);
      break;
  }

  actv_cmd = pr_cmd_alloc(cmd->tmp_pool, 2, active_cmd, resp_msg);
  actv_cmd->arg = (char *) resp_msg;

  pr_cmd_clear_cache(actv_cmd);

  res = proxy_ftp_ctrl_send_cmd(cmd->tmp_pool, proxy_sess->backend_ctrl_conn,
    actv_cmd);
  if (res < 0) {
    xerrno = errno;
    (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
      "error sending %s to backend: %s", (char *) actv_cmd->argv[0],
      strerror(xerrno));

    proxy_inet_close(session.pool, proxy_sess->backend_data_conn);
    proxy_sess->backend_data_conn = NULL;

    pr_response_add_err(error_code, "%s: %s", (char *) cmd->argv[0],
      strerror(xerrno));
    pr_response_flush(&resp_err_list);

    errno = xerrno;
    return -1;
  }

  resp = proxy_ftp_ctrl_recv_resp(cmd->tmp_pool, proxy_sess->backend_ctrl_conn,
    &resp_nlines, flags);
  if (resp == NULL) {
    xerrno = errno;
    (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
      "error receiving %s response from backend: %s",
      (char *) actv_cmd->argv[0], strerror(xerrno));

    proxy_inet_close(session.pool, proxy_sess->backend_data_conn);
    proxy_sess->backend_data_conn = NULL;

    pr_response_add_err(error_code, "%s: %s", (char *) cmd->argv[0],
      strerror(xerrno));
    pr_response_flush(&resp_err_list);

    errno = xerrno;
    return -1;
  }

  if (resp->num[0] != '2') {
    (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
      "received non-2xx response from backend for %s: %s %s",
      (char *) actv_cmd->argv[0], resp->num, resp->msg);

    proxy_inet_close(session.pool, proxy_sess->backend_data_conn);
    proxy_sess->backend_data_conn = NULL;

    if (policy_id == PR_CMD_EPRT_ID) {
      /* If using EPRT failed, try again using PORT, and switch the
       * DataTransferPolicy (if EPRT) to be PORT, for future attempts.
       */

      if (proxy_sess->dataxfer_policy == PR_CMD_EPRT_ID) {
        pr_trace_msg(trace_channel, 15,
          "falling back from EPRT to PORT DataTransferPolicy");
        proxy_sess->dataxfer_policy = PR_CMD_PORT_ID;
      }

      return proxy_ftp_xfer_prepare_active(PR_CMD_PORT_ID, cmd,
        error_code, proxy_sess, flags);
    }

    pr_response_add_err(error_code, "%s", resp->msg);
    pr_response_flush(&resp_err_list);

    errno = EINVAL;
    return -1;
  }

  return 0;
}