int pr_session_set_protocol(const char *sess_proto) { int count, res; if (sess_proto == NULL) { errno = EINVAL; return -1; } count = pr_table_exists(session.notes, "protocol"); if (count > 0) { res = pr_table_set(session.notes, pstrdup(session.pool, "protocol"), pstrdup(session.pool, sess_proto), 0); if (res == 0) { /* Update the scoreboard entry for this session with the protocol. */ pr_scoreboard_entry_update(session.pid, PR_SCORE_PROTOCOL, sess_proto, NULL); } return res; } res = pr_table_add(session.notes, pstrdup(session.pool, "protocol"), pstrdup(session.pool, sess_proto), 0); if (res == 0) { /* Update the scoreboard entry for this session with the protocol. */ pr_scoreboard_entry_update(session.pid, PR_SCORE_PROTOCOL, sess_proto, NULL); } return res; }
END_TEST START_TEST (cmd_get_errno_test) { int res, *xerrno = NULL; cmd_rec *cmd = NULL; res = pr_cmd_get_errno(NULL); fail_unless(res == -1, "Failed to handle null cmd_rec"); fail_unless(errno == EINVAL, "Failed to set errno to EINVAL"); cmd = pr_cmd_alloc(p, 1, "foo"); res = pr_cmd_get_errno(cmd); fail_unless(res == 0, "Expected errno 0, got %d", res); (void) pr_table_remove(cmd->notes, "errno", NULL); res = pr_cmd_get_errno(cmd); fail_unless(res < 0, "Failed to handle missing 'errno' note"); fail_unless(errno == ENOENT, "Expected ENOENT (%d), got %s (%d)", ENOENT, strerror(errno), errno); xerrno = pcalloc(cmd->pool, sizeof(int)); (void) pr_table_add(cmd->notes, "errno", xerrno, sizeof(int)); res = pr_cmd_set_errno(NULL, ENOENT); fail_unless(res < 0, "Failed to handle null arguments"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); res = pr_cmd_set_errno(cmd, ENOENT); fail_unless(res == 0, "Failed to stash errno ENOENT: %s", strerror(errno)); res = pr_cmd_get_errno(cmd); fail_unless(res == ENOENT, "Expected errno ENOENT, got %s (%d)", strerror(res), res); }
int proxy_db_prepare_stmt(pool *p, const char *stmt) { sqlite3_stmt *pstmt = NULL; int res; if (stmt == NULL) { errno = EINVAL; return -1; } pstmt = pr_table_get(prepared_stmts, stmt, NULL); if (pstmt != NULL) { res = sqlite3_reset(pstmt); if (res != SQLITE_OK) { pr_trace_msg(trace_channel, 3, "error resetting prepared statement '%s': %s", stmt, sqlite3_errmsg(proxy_dbh)); errno = EPERM; return -1; } return 0; } res = sqlite3_prepare_v2(proxy_dbh, stmt, -1, &pstmt, NULL); if (res != SQLITE_OK) { pr_trace_msg(trace_channel, 4, "error preparing statement '%s': %s", stmt, sqlite3_errmsg(proxy_dbh)); errno = EINVAL; return -1; } /* The prepared statement handling here relies on this cache, thus if we fail * to stash the prepared statement here, it will cause problems later. */ res = pr_table_add(prepared_stmts, pstrdup(db_pool, stmt), pstmt, sizeof(sqlite3_stmt *)); if (res < 0) { int xerrno = errno; pr_trace_msg(trace_channel, 4, "error stashing prepared statement '%s': %s", stmt, strerror(xerrno)); errno = xerrno; return -1; } return 0; }
MODRET copy_cpfr(cmd_rec *cmd) { register unsigned int i; int res; char *path = ""; unsigned char *authenticated = NULL; if (copy_engine == FALSE) { return PR_DECLINED(cmd); } if (cmd->argc < 3 || strncasecmp(cmd->argv[1], "CPFR", 5) != 0) { return PR_DECLINED(cmd); } authenticated = get_param_ptr(cmd->server->conf, "authenticated", FALSE); if (authenticated == NULL || *authenticated == FALSE) { pr_response_add_err(R_530, _("Please login with USER and PASS")); pr_cmd_set_errno(cmd, EPERM); errno = EPERM; return PR_ERROR(cmd); } CHECK_CMD_MIN_ARGS(cmd, 3); /* Construct the target file name by concatenating all the parameters after * the "SITE CPFR", separating them with spaces. */ for (i = 2; i <= cmd->argc-1; i++) { char *decoded_path; decoded_path = pr_fs_decode_path2(cmd->tmp_pool, cmd->argv[i], FSIO_DECODE_FL_TELL_ERRORS); if (decoded_path == NULL) { int xerrno = errno; pr_log_debug(DEBUG8, "'%s' failed to decode properly: %s", (char *) cmd->argv[i], strerror(xerrno)); pr_response_add_err(R_550, _("%s: Illegal character sequence in filename"), cmd->arg); pr_cmd_set_errno(cmd, xerrno); errno = xerrno; return PR_ERROR(cmd); } path = pstrcat(cmd->tmp_pool, path, *path ? " " : "", decoded_path, NULL); } res = pr_filter_allow_path(CURRENT_CONF, path); switch (res) { case 0: break; case PR_FILTER_ERR_FAILS_ALLOW_FILTER: pr_log_debug(DEBUG2, MOD_COPY_VERSION ": 'CPFR %s' denied by PathAllowFilter", path); pr_response_add_err(R_550, _("%s: Forbidden filename"), path); pr_cmd_set_errno(cmd, EPERM); errno = EPERM; return PR_ERROR(cmd); case PR_FILTER_ERR_FAILS_DENY_FILTER: pr_log_debug(DEBUG2, MOD_COPY_VERSION ": 'CPFR %s' denied by PathDenyFilter", path); pr_response_add_err(R_550, _("%s: Forbidden filename"), path); pr_cmd_set_errno(cmd, EPERM); errno = EPERM; return PR_ERROR(cmd); } /* Allow renaming a symlink, even a dangling one. */ path = dir_canonical_vpath(cmd->tmp_pool, path); if (!path || !dir_check_canon(cmd->tmp_pool, cmd, cmd->group, path, NULL) || !exists2(cmd->tmp_pool, path)) { int xerrno = errno; pr_response_add_err(R_550, "%s: %s", path, strerror(xerrno)); pr_cmd_set_errno(cmd, xerrno); errno = xerrno; return PR_ERROR(cmd); } if (pr_table_add(session.notes, "mod_copy.cpfr-path", pstrdup(session.pool, path), 0) < 0) { pr_trace_msg(trace_channel, 4, "error adding 'mod_copy.cpfr-path' note: %s", strerror(errno)); } pr_response_add(R_350, _("File or directory exists, ready for destination name")); return PR_HANDLED(cmd); }
int pr_trace_set_levels(const char *channel, int min_level, int max_level) { if (channel == NULL) { void *v; if (trace_tab == NULL) { errno = EINVAL; return -1; } v = pr_table_remove(trace_tab, channel, NULL); if (v == NULL) { errno = EINVAL; return -1; } return 0; } if (min_level > max_level) { errno = EINVAL; return -1; } if (trace_tab == NULL && min_level < 0) { return 0; } if (trace_pool == NULL) { trace_pool = make_sub_pool(permanent_pool); pr_pool_tag(trace_pool, "Trace API"); trace_tab = pr_table_alloc(trace_pool, 0); /* Register a handler for churning the log pool during HUP. */ pr_event_register(NULL, "core.restart", trace_restart_ev, NULL); } if (min_level >= 0) { struct trace_levels *levels; levels = pcalloc(trace_pool, sizeof(struct trace_levels)); levels->min_level = min_level; levels->max_level = max_level; if (strcmp(channel, PR_TRACE_DEFAULT_CHANNEL) != 0) { int count = pr_table_exists(trace_tab, channel); if (count <= 0) { if (pr_table_add(trace_tab, pstrdup(trace_pool, channel), levels, sizeof(struct trace_levels)) < 0) { return -1; } } else { if (pr_table_set(trace_tab, pstrdup(trace_pool, channel), levels, sizeof(struct trace_levels)) < 0) return -1; } } else { register unsigned int i; for (i = 0; trace_channels[i]; i++) { (void) pr_trace_set_levels(trace_channels[i], min_level, max_level); } } } else { if (strcmp(channel, PR_TRACE_DEFAULT_CHANNEL) != 0) { (void) pr_table_remove(trace_tab, channel, NULL); } else { register unsigned int i; for (i = 0; trace_channels[i]; i++) { (void) pr_table_remove(trace_tab, trace_channels[i], NULL); } } } return 0; }
struct passwd *pr_auth_getpwnam(pool *p, const char *name) { cmd_rec *cmd = NULL; modret_t *mr = NULL; struct passwd *res = NULL; module *m = NULL; cmd = make_cmd(p, 1, name); mr = dispatch_auth(cmd, "getpwnam", &m); if (MODRET_ISHANDLED(mr) && MODRET_HASDATA(mr)) res = mr->data; if (cmd->tmp_pool) { destroy_pool(cmd->tmp_pool); cmd->tmp_pool = NULL; } /* Sanity check */ if (res == NULL) { errno = ENOENT; return NULL; } /* Make sure the UID and GID are not -1 */ if (res->pw_uid == (uid_t) -1) { pr_log_pri(PR_LOG_ERR, "error: UID of -1 not allowed"); return NULL; } if (res->pw_gid == (gid_t) -1) { pr_log_pri(PR_LOG_ERR, "error: GID of -1 not allowed"); return NULL; } if ((auth_caching & PR_AUTH_CACHE_FL_AUTH_MODULE) && !auth_tab && auth_pool) { auth_tab = pr_table_alloc(auth_pool, 0); } if (m && auth_tab) { int count = 0; void *value = NULL; value = palloc(auth_pool, sizeof(module *)); *((module **) value) = m; count = pr_table_exists(auth_tab, name); if (count <= 0) { if (pr_table_add(auth_tab, pstrdup(auth_pool, name), value, sizeof(module *)) < 0) { pr_trace_msg(trace_channel, 3, "error adding module 'mod_%s.c' for user '%s' to the authcache: %s", m->name, name, strerror(errno)); } else { pr_trace_msg(trace_channel, 5, "stashed module 'mod_%s.c' for user '%s' in the authcache", m->name, name); } } else { if (pr_table_set(auth_tab, pstrdup(auth_pool, name), value, sizeof(module *)) < 0) { pr_trace_msg(trace_channel, 3, "error setting module 'mod_%s.c' for user '%s' in the authcache: %s", m->name, name, strerror(errno)); } else { pr_trace_msg(trace_channel, 5, "stashed module 'mod_%s.c' for user '%s' in the authcache", m->name, name); } } } uidcache_add(res->pw_uid, name); /* Get the (possibly rewritten) home directory. */ res->pw_dir = pr_auth_get_home(p, res->pw_dir); pr_log_debug(DEBUG10, "retrieved UID %lu for user '%s'", (unsigned long) res->pw_uid, name); return res; }
int pr_var_set(pool *p, const char *name, const char *desc, int vtype, void *val, void *data, size_t datasz) { struct var *v; size_t namelen = 0; if (var_tab == NULL) { errno = EPERM; return -1; } if (p == NULL || name == NULL || val == NULL) { errno = EINVAL; return -1; } /* The length of the key must be greater than 3 characters (for "%{}"). */ namelen = strlen(name); if (namelen < 4) { errno = EINVAL; return -1; } /* If the given variable type is not recognized, reject. */ if (vtype != PR_VAR_TYPE_STR && vtype != PR_VAR_TYPE_FUNC) { errno = EINVAL; return -1; } /* Specifying data, but no length for that data, is an error. */ if (data != NULL && datasz == 0) { errno = EINVAL; return -1; } /* Specifying no data, but providing a non-zero length for that data, is an * error. */ if (data == NULL && datasz > 0) { errno = EINVAL; return -1; } /* Variable names MUST start with '%{', and end in '}'. */ if (strncmp(name, "%{", 2) != 0 || name[namelen-1] != '}') { errno = EINVAL; return -1; } /* Remove any previously registered value for this name. For names whose * values change rapidly (e.g. session.xfer.total_bytes), a callback * function should be used, rather than always setting the same name as an * update; using a callback avoids the memory consumption that setting does * (set always allocates a new struct var *). */ (void) pr_var_delete(name); /* Note: if var_pool was used for allocating the struct var *, rather * than the given pool, then deleting an entry would not necessarily * lead to such memory consumption (assuming it would even be a problem). * However, if this was the case, then a churn counter would be needed, * and var_pool would need to be churned occasionally to limit memory * growth. */ switch (vtype) { case PR_VAR_TYPE_STR: v = pcalloc(p, sizeof(struct var)); if (desc) { v->v_desc = (const char *) pstrdup(p, desc); } v->v_type = PR_VAR_TYPE_STR; v->v_val = pstrdup(p, (char *) val); v->v_datasz = strlen((char *) val); break; case PR_VAR_TYPE_FUNC: v = pcalloc(p, sizeof(struct var)); if (desc) { v->v_desc = (const char *) pstrdup(p, desc); } v->v_type = PR_VAR_TYPE_FUNC; v->v_val = val; if (data) { v->v_data = data; v->v_datasz = datasz; } break; default: errno = EINVAL; return -1; } return pr_table_add(var_tab, name, v, sizeof(struct var)); }
int proxy_ftp_sess_get_feat(pool *p, struct proxy_session *proxy_sess) { pool *tmp_pool; int res, xerrno = 0; cmd_rec *cmd; pr_response_t *resp; unsigned int resp_nlines = 0; char *feats, *token; size_t token_len = 0; tmp_pool = make_sub_pool(p); cmd = pr_cmd_alloc(tmp_pool, 1, C_FEAT); res = proxy_ftp_ctrl_send_cmd(tmp_pool, proxy_sess->backend_ctrl_conn, cmd); if (res < 0) { xerrno = errno; pr_trace_msg(trace_channel, 4, "error sending %s to backend: %s", (char *) cmd->argv[0], strerror(xerrno)); destroy_pool(tmp_pool); errno = xerrno; return -1; } resp = proxy_ftp_ctrl_recv_resp(tmp_pool, proxy_sess->backend_ctrl_conn, &resp_nlines); if (resp == NULL) { xerrno = errno; pr_trace_msg(trace_channel, 4, "error receiving %s response from backend: %s", (char *) cmd->argv[0], strerror(xerrno)); destroy_pool(tmp_pool); errno = xerrno; return -1; } if (resp->num[0] != '2') { pr_trace_msg(trace_channel, 4, "received unexpected %s response code %s from backend", (char *) cmd->argv[0], resp->num); /* Note: If the UseProxyProtocol ProxyOption is enabled, AND if the * response message mentions a "PROXY" command, we might read an * error response here that is NOT actually for the FEAT command we just * sent. * * A backend FTP server which does not understand the PROXY protocol * will treat it as a normal FTP command, and respond. And that will * put us, the client, out of lockstep with the server, for how do we know * that we need to read that error response FIRST, then send another * command? */ destroy_pool(tmp_pool); errno = EPERM; return -1; } proxy_sess->backend_features = pr_table_nalloc(p, 0, 4); feats = resp->msg; token = pr_str_get_token2(&feats, (char *) feat_crlf, &token_len); while (token != NULL) { pr_signals_handle(); if (token_len > 0) { /* The FEAT response lines in which we are interested all start with * a single space, per RFC spec. Ignore any other lines. */ if (token[0] == ' ') { char *key, *val, *ptr; /* Find the next space in the string, to delimit our key/value pairs. */ ptr = strchr(token + 1, ' '); if (ptr != NULL) { key = pstrndup(p, token + 1, ptr - token - 1); val = pstrdup(p, ptr + 1); } else { key = pstrdup(p, token + 1); val = pstrdup(p, ""); } pr_table_add(proxy_sess->backend_features, key, val, 0); } } feats = token + token_len + 1; token = pr_str_get_token2(&feats, (char *) feat_crlf, &token_len); } destroy_pool(tmp_pool); return 0; }
conn_t *proxy_conn_get_server_conn(pool *p, struct proxy_session *proxy_sess, pr_netaddr_t *remote_addr) { pr_netaddr_t *bind_addr = NULL, *local_addr = NULL; const char *remote_ipstr = NULL; unsigned int remote_port; conn_t *server_conn, *ctrl_conn; int res; if (proxy_sess->connect_timeout > 0) { const char *notes_key = "mod_proxy.proxy-connect-address"; proxy_sess->connect_timerno = pr_timer_add(proxy_sess->connect_timeout, -1, &proxy_module, proxy_conn_connect_timeout_cb, "ProxyTimeoutConnect"); (void) pr_table_remove(session.notes, notes_key, NULL); if (pr_table_add(session.notes, notes_key, remote_addr, sizeof(pr_netaddr_t)) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error stashing proxy connect address note: %s", strerror(errno)); } } remote_ipstr = pr_netaddr_get_ipstr(remote_addr); remote_port = ntohs(pr_netaddr_get_port(remote_addr)); /* Check the family of the retrieved address vs what we'll be using * to connect. If there's a mismatch, we need to get an addr with the * matching family. */ if (pr_netaddr_get_family(session.c->local_addr) == pr_netaddr_get_family(remote_addr)) { local_addr = session.c->local_addr; } else { /* In this scenario, the proxy has an IPv6 socket, but the remote/backend * server has an IPv4 (or IPv4-mapped IPv6) address. OR it's the proxy * which has an IPv4 socket, and the remote/backend server has an IPv6 * address. */ if (pr_netaddr_get_family(session.c->local_addr) == AF_INET) { char *ip_str; /* Convert the local address from an IPv4 to an IPv6 addr. */ ip_str = pcalloc(p, INET6_ADDRSTRLEN + 1); snprintf(ip_str, INET6_ADDRSTRLEN, "::ffff:%s", pr_netaddr_get_ipstr(session.c->local_addr)); local_addr = pr_netaddr_get_addr(p, ip_str, NULL); } else { local_addr = pr_netaddr_v6tov4(p, session.c->local_addr); if (local_addr == NULL) { pr_trace_msg(trace_channel, 4, "error converting IPv6 local address %s to IPv4 address: %s", pr_netaddr_get_ipstr(session.c->local_addr), strerror(errno)); } } if (local_addr == NULL) { local_addr = session.c->local_addr; } } bind_addr = proxy_sess->src_addr; if (bind_addr == NULL) { bind_addr = local_addr; } /* Note: IF mod_proxy is running on localhost, and the connection to be * made is to a public IP address, then this connect(2) attempt would most * likely fail with ENETUNREACH, since localhost is a loopback network, * and of course not reachable from a public IP. Thus we check for this * edge case (which happens often for development). */ if (pr_netaddr_is_loopback(bind_addr) == TRUE) { const char *local_name; pr_netaddr_t *local_addr; local_name = pr_netaddr_get_localaddr_str(p); local_addr = pr_netaddr_get_addr(p, local_name, NULL); if (local_addr != NULL) { pr_trace_msg(trace_channel, 14, "%s is a loopback address, and unable to reach %s; using %s instead", pr_netaddr_get_ipstr(bind_addr), remote_ipstr, pr_netaddr_get_ipstr(local_addr)); bind_addr = local_addr; } } server_conn = pr_inet_create_conn(p, -1, bind_addr, INPORT_ANY, FALSE); if (server_conn == NULL) { int xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error creating connection to %s: %s", pr_netaddr_get_ipstr(bind_addr), strerror(xerrno)); pr_timer_remove(proxy_sess->connect_timerno, &proxy_module); errno = xerrno; return NULL; } pr_trace_msg(trace_channel, 11, "connecting to backend address %s#%u from %s", remote_ipstr, remote_port, pr_netaddr_get_ipstr(bind_addr)); res = pr_inet_connect_nowait(p, server_conn, remote_addr, ntohs(pr_netaddr_get_port(remote_addr))); if (res < 0) { int xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error starting connect to %s#%u: %s", remote_ipstr, remote_port, strerror(xerrno)); pr_timer_remove(proxy_sess->connect_timerno, &proxy_module); errno = xerrno; return NULL; } if (res == 0) { pr_netio_stream_t *nstrm; int nstrm_mode = PR_NETIO_IO_RD; if (proxy_opts & PROXY_OPT_USE_PROXY_PROTOCOL) { /* Rather than waiting for the stream to be readable (because the * other end sent us something), wait for the stream to be writable * so that we can send something to the other end). */ nstrm_mode = PR_NETIO_IO_WR; } /* Not yet connected. */ nstrm = proxy_netio_open(p, PR_NETIO_STRM_OTHR, server_conn->listen_fd, nstrm_mode); if (nstrm == NULL) { int xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error opening stream to %s#%u: %s", remote_ipstr, remote_port, strerror(xerrno)); pr_timer_remove(proxy_sess->connect_timerno, &proxy_module); pr_inet_close(p, server_conn); errno = xerrno; return NULL; } proxy_netio_set_poll_interval(nstrm, 1); switch (proxy_netio_poll(nstrm)) { case 1: { /* Aborted, timed out. Note that we shouldn't reach here. */ pr_timer_remove(proxy_sess->connect_timerno, &proxy_module); proxy_netio_close(nstrm); pr_inet_close(p, server_conn); errno = ETIMEDOUT; return NULL; } case -1: { /* Error */ int xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error connecting to %s#%u: %s", remote_ipstr, remote_port, strerror(xerrno)); pr_timer_remove(proxy_sess->connect_timerno, &proxy_module); proxy_netio_close(nstrm); pr_inet_close(p, server_conn); errno = xerrno; return NULL; } default: { /* Connected */ server_conn->mode = CM_OPEN; pr_timer_remove(proxy_sess->connect_timerno, &proxy_module); pr_table_remove(session.notes, "mod_proxy.proxy-connect-addr", NULL); res = pr_inet_get_conn_info(server_conn, server_conn->listen_fd); if (res < 0) { int xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error obtaining local socket info on fd %d: %s", server_conn->listen_fd, strerror(xerrno)); proxy_netio_close(nstrm); pr_inet_close(p, server_conn); errno = xerrno; return NULL; } break; } } } pr_trace_msg(trace_channel, 5, "successfully connected to %s#%u from %s#%d", remote_ipstr, remote_port, pr_netaddr_get_ipstr(server_conn->local_addr), ntohs(pr_netaddr_get_port(server_conn->local_addr))); ctrl_conn = proxy_inet_openrw(p, server_conn, NULL, PR_NETIO_STRM_CTRL, -1, -1, -1, FALSE); if (ctrl_conn == NULL) { int xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to open control connection to %s#%u: %s", remote_ipstr, remote_port, strerror(xerrno)); pr_inet_close(p, server_conn); errno = xerrno; return NULL; } return ctrl_conn; }
pr_netio_stream_t *pr_netio_open(pool *parent_pool, int strm_type, int fd, int mode) { pr_netio_stream_t *nstrm = NULL; if (parent_pool == NULL) { errno = EINVAL; return NULL; } /* Create a new stream object, then pass that the NetIO open handler. */ nstrm = netio_stream_alloc(parent_pool); switch (strm_type) { case PR_NETIO_STRM_CTRL: nstrm->strm_type = PR_NETIO_STRM_CTRL; nstrm->strm_mode = mode; if (ctrl_netio != NULL) { pr_table_add(nstrm->notes, pstrdup(nstrm->strm_pool, "core.netio"), ctrl_netio, sizeof(pr_netio_t *)); return (ctrl_netio->open)(nstrm, fd, mode); } else { pr_table_add(nstrm->notes, pstrdup(nstrm->strm_pool, "core.netio"), default_ctrl_netio, sizeof(pr_netio_t *)); return (default_ctrl_netio->open)(nstrm, fd, mode); } case PR_NETIO_STRM_DATA: nstrm->strm_type = PR_NETIO_STRM_DATA; nstrm->strm_mode = mode; if (data_netio != NULL) { pr_table_add(nstrm->notes, pstrdup(nstrm->strm_pool, "core.netio"), data_netio, sizeof(pr_netio_t *)); return (data_netio->open)(nstrm, fd, mode); } else { pr_table_add(nstrm->notes, pstrdup(nstrm->strm_pool, "core.netio"), default_data_netio, sizeof(pr_netio_t *)); return (default_data_netio->open)(nstrm, fd, mode); } case PR_NETIO_STRM_OTHR: nstrm->strm_type = PR_NETIO_STRM_OTHR; nstrm->strm_mode = mode; if (othr_netio != NULL) { pr_table_add(nstrm->notes, pstrdup(nstrm->strm_pool, "core.netio"), othr_netio, sizeof(pr_netio_t *)); return (othr_netio->open)(nstrm, fd, mode); } else { pr_table_add(nstrm->notes, pstrdup(nstrm->strm_pool, "core.netio"), default_othr_netio, sizeof(pr_netio_t *)); return (default_othr_netio->open)(nstrm, fd, mode); } } destroy_pool(nstrm->strm_pool); nstrm->strm_pool = NULL; errno = EPERM; return NULL; }