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); } }
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); }
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 */ }
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); } }
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 */ }
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; }
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; }
/* 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 }
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; }
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; }
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; }
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; }
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; }
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 */ }
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; }