/* close == successful transfer */ void pr_data_close(int quiet) { nstrm = NULL; if (session.d) { pr_inet_lingering_close(session.pool, session.d, timeout_linger); session.d = NULL; } /* Aborts no longer necessary */ signal(SIGURG, SIG_IGN); if (timeout_noxfer) { pr_timer_reset(PR_TIMER_NOXFER, ANY_MODULE); } if (timeout_stalled) { pr_timer_remove(PR_TIMER_STALLED, ANY_MODULE); } session.sf_flags &= (SF_ALL^SF_PASSIVE); session.sf_flags &= (SF_ALL^(SF_ABORT|SF_XFER|SF_PASSIVE|SF_ASCII_OVERRIDE)); pr_session_set_idle(); if (!quiet) pr_response_add(R_226, _("Transfer complete")); }
static void dynmasq_restart_ev(const void *event_data, void *user_data) { #ifdef PR_USE_CTRLS register unsigned int i; #endif /* PR_USE_CTRLS */ if (dynmasq_timer_id != -1) { pr_timer_remove(dynmasq_timer_id, &dynmasq_module); dynmasq_timer_id = -1; } #ifdef PR_USE_CTRLS if (dynmasq_act_pool) { destroy_pool(dynmasq_act_pool); dynmasq_act_pool = NULL; } dynmasq_act_pool = make_sub_pool(permanent_pool); pr_pool_tag(dynmasq_act_pool, "DynMasq Controls Pool"); /* Re-create the controls ACLs. */ for (i = 0; dynmasq_acttab[i].act_action; i++) { dynmasq_acttab[i].act_acl = palloc(dynmasq_act_pool, sizeof(ctrls_acl_t)); pr_ctrls_init_acl(dynmasq_acttab[i].act_acl); } #endif /* PR_USE_CTRLS */ }
END_TEST START_TEST (timer_remove_multi_test) { int res; module m; /* By providing a negative timerno, the return value should be the * dynamically generated timerno, which is greater than or equal to * 1024. */ res = pr_timer_add(3, -1, &m, timers_test_cb, "test1"); fail_unless(res >= 1024, "Failed to add timer (%d): %s", res, strerror(errno)); res = pr_timer_add(3, -1, &m, timers_test_cb, "test2"); fail_unless(res >= 1024, "Failed to add timer (%d): %s", res, strerror(errno)); res = pr_timer_add(3, -1, &m, timers_test_cb, "test3"); fail_unless(res >= 1024, "Failed to add timer (%d): %s", res, strerror(errno)); res = pr_timer_remove(-1, &m); fail_unless(res == 3, "Failed to remove timers (%d): %s", res, strerror(errno)); }
static int dynmasq_sess_init(void) { /* Ensure that the timer only fires on the daemon process. */ pr_timer_remove(dynmasq_timer_id, &dynmasq_module); dynmasq_timer_id = -1; pr_event_unregister(&dynmasq_module, "core.restart", NULL); return 0; }
/* * cmd_close: attempts to close the named connection. * * Inputs: * cmd->argv[0]: connection name * Optional: * cmd->argv[1]: close immediately * * Returns: * either a properly filled error modret_t if a connection could not be * closed, or a simple non-error modret_t. For the case of mod_sql_postgres, * there are no error codes returned by the close call; other backends * may be able to return a useful error message. * * Notes: * mod_sql depends on these semantics -- a backend should not open * a connection unless mod_sql requests it, nor close one unless * mod_sql requests it. Connection counting is *REQUIRED* for complete * compatibility; a connection should not be closed unless the count * reaches 0, and should not need to be re-opened for counts > 1. * * If argv[1] exists and is not NULL, the connection should be immediately * closed and the connection count should be reset to 0. */ MODRET cmd_close(cmd_rec *cmd) { conn_entry_t *entry = NULL; db_conn_t *conn = NULL; sql_log(DEBUG_FUNC, "%s", "entering \tpostgres cmd_close"); _sql_check_cmd(cmd, "cmd_close"); if ((cmd->argc < 1) || (cmd->argc > 2)) { sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_close"); return PR_ERROR_MSG(cmd, MOD_SQL_POSTGRES_VERSION, "badly formed request"); } /* get the named connection */ if (!(entry = _sql_get_connection(cmd->argv[0]))) { sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_close"); return PR_ERROR_MSG(cmd, MOD_SQL_POSTGRES_VERSION, "unknown named connection"); } conn = (db_conn_t *) entry->data; /* if we're closed already (connections == 0) return HANDLED */ if (entry->connections == 0) { sql_log(DEBUG_INFO, "connection '%s' count is now %d", entry->name, entry->connections); sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_close"); return PR_HANDLED(cmd); } /* decrement connections. If our count is 0 or we received a second arg * close the connection, explicitly set the counter to 0, and remove any * timers. */ if (((--entry->connections) == 0 ) || ((cmd->argc == 2) && (cmd->argv[1]))) { PQfinish(conn->postgres); conn->postgres = NULL; entry->connections = 0; if (entry->timer) { pr_timer_remove(entry->timer, &sql_postgres_module); entry->timer = 0; sql_log(DEBUG_INFO, "connection '%s' - timer stopped", entry->name); } sql_log(DEBUG_INFO, "connection '%s' closed", entry->name); } sql_log(DEBUG_INFO, "connection '%s' count is now %d", entry->name, entry->connections); sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_close"); return PR_HANDLED(cmd); }
int proxy_forward_handle_pass(cmd_rec *cmd, struct proxy_session *proxy_sess, int *successful, int *block_responses) { int res = -1, xerrno = 0; /* Look at our proxy method to see what we should do here. */ switch (proxy_method) { case PROXY_FORWARD_METHOD_USER_NO_PROXY_AUTH: res = forward_handle_pass_passthru(cmd, proxy_sess, successful); xerrno = errno; if (res == 1) { pr_timer_remove(PR_TIMER_LOGIN, ANY_MODULE); } break; case PROXY_FORWARD_METHOD_USER_WITH_PROXY_AUTH: res = forward_handle_pass_userwithproxyauth(cmd, proxy_sess, successful, block_responses); xerrno = errno; if (res == 1) { pr_timer_remove(PR_TIMER_LOGIN, ANY_MODULE); } break; case PROXY_FORWARD_METHOD_PROXY_USER_WITH_PROXY_AUTH: res = forward_handle_pass_proxyuserwithproxyauth(cmd, proxy_sess, successful, block_responses); xerrno = errno; if (res == 1) { pr_timer_remove(PR_TIMER_LOGIN, ANY_MODULE); } break; default: xerrno = ENOSYS; res = -1; } errno = xerrno; return res; }
END_TEST START_TEST (timer_remove_test) { int res; res = pr_timer_remove(0, NULL); fail_unless(res == 0); res = pr_timer_add(1, 0, NULL, timers_test_cb, "test"); fail_unless(res == 0, "Failed to add timer (%d): %s", res, strerror(errno)); res = pr_timer_remove(1, NULL); fail_unless(res == -1, "Failed to return -1 for non-matching timer ID"); fail_unless(errno == ENOENT, "Failed to set errno to ENOENT"); res = pr_timer_remove(0, NULL); fail_unless(res == 0, "Failed to remove timer (%d): %s", res, strerror(errno)); fail_unless(timer_triggered_count == 0, "Expected trigger count of 0, got %u", timer_triggered_count); }
static void dynmasq_mod_unload_ev(const void *event_data, void *user_data) { if (strcmp("mod_dynmasq.c", (const char *) event_data) == 0) { pr_event_unregister(&dynmasq_module, NULL, NULL); # ifdef PR_USE_CTRLS /* Unregister any control actions. */ pr_ctrls_unregister(&dynmasq_module, "dynmasq"); destroy_pool(dynmasq_act_pool); dynmasq_act_pool = NULL; # endif /* PR_USE_CTRLS */ pr_timer_remove(dynmasq_timer_id, &dynmasq_module); dynmasq_timer_id = -1; } }
int sftp_tap_set_policy(const char *policy) { register unsigned int i; if (tap_pool) { /* Special case: IFF the existing policy is 'none' AND the given * policy is 'rogaway', just return. The 'none' policy must have been * explicitly configured, and it should override the automatic use of * the 'rogaway' policy. */ if (strncmp(curr_policy.policy, "none", 5) == 0 && strncasecmp(policy, "rogaway", 8) == 0) { (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION, "'none' traffic policy explicitly configured, ignoring '%s' policy", policy); return 0; } destroy_pool(tap_pool); if (tap_timerno > 0) { pr_timer_remove(tap_timerno, &sftp_module); tap_timerno = -1; } } tap_pool = make_sub_pool(sftp_pool); pr_pool_tag(tap_pool, "SFTP TAP Pool"); memset(&curr_policy, 0, sizeof(struct sftp_tap_policy)); for (i = 0; tap_policies[i].policy; i++) { if (strcasecmp(tap_policies[i].policy, policy) == 0) { copy_policy(&curr_policy, &(tap_policies[i])); set_policy_chance(&curr_policy); set_policy_timer(&curr_policy); return 0; } } errno = ENOENT; return -1; }
static bool is_cache_exits(memcached_st *mmc, const char *key) { int timer_id; memcached_return rc; char *cached_value; size_t value_len; uint32_t flag; /* todo */ timer_id = pr_timer_add(3, -1, NULL, lmd_timeout_callback, "memcached_get"); cached_value = memcached_get(mmc, key, strlen(key), &value_len, &flag, &rc); pr_timer_remove(timer_id, NULL); /* no cache */ if(MEMCACHED_NOTFOUND == rc) return false; /* failed by other reason */ if(MEMCACHED_SUCCESS != rc && MEMCACHED_NOTFOUND != rc) { pr_log_auth(PR_LOG_NOTICE, "%s: failed memcached_get() %s. but IGNORE", MODULE_NAME, memcached_strerror(mmc, rc)); return false; } /* cache not fond */ if(NULL == cached_value) return false; /* something wrong */ if(0 == value_len) return false; free(cached_value); return true; }
/* * cmd_open: attempts to open a named connection to the database. * * Inputs: * cmd->argv[0]: connection name * * Returns: * either a properly filled error modret_t if a connection could not be * opened, or a simple non-error modret_t. * * Notes: * mod_sql depends on these semantics -- a backend should not open * a connection unless mod_sql requests it, nor close one unless * mod_sql requests it. Connection counting is *REQUIRED* for complete * compatibility; a connection should not be closed unless the count * reaches 0, and ideally will not need to be re-opened for counts > 1. */ MODRET cmd_open(cmd_rec *cmd) { conn_entry_t *entry = NULL; db_conn_t *conn = NULL; const char *server_version = NULL; sql_log(DEBUG_FUNC, "%s", "entering \tpostgres cmd_open"); _sql_check_cmd(cmd, "cmd_open" ); if (cmd->argc < 1) { sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_open"); return PR_ERROR_MSG(cmd, MOD_SQL_POSTGRES_VERSION, "badly formed request"); } /* get the named connection */ if (!(entry = _sql_get_connection(cmd->argv[0]))) { sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_open"); return PR_ERROR_MSG(cmd, MOD_SQL_POSTGRES_VERSION, "unknown named connection"); } conn = (db_conn_t *) entry->data; /* if we're already open (connections > 0) increment connections * reset our timer if we have one, and return HANDLED */ if (entry->connections > 0) { if (PQstatus(conn->postgres) == CONNECTION_OK) { entry->connections++; if (entry->timer) { pr_timer_reset(entry->timer, &sql_postgres_module); } sql_log(DEBUG_INFO, "connection '%s' count is now %d", entry->name, entry->connections); sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_open"); return PR_HANDLED(cmd); } else { char *reason; size_t reason_len; /* Unless we've been told not to reconnect, try to reconnect now. * We only try once; if it fails, we return an error. */ if (!(pr_sql_opts & SQL_OPT_NO_RECONNECT)) { PQreset(conn->postgres); if (PQstatus(conn->postgres) == CONNECTION_OK) { entry->connections++; if (entry->timer) { pr_timer_reset(entry->timer, &sql_postgres_module); } sql_log(DEBUG_INFO, "connection '%s' count is now %d", entry->name, entry->connections); sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_open"); return PR_HANDLED(cmd); } } reason = PQerrorMessage(conn->postgres); reason_len = strlen(reason); /* Postgres might give us an empty string as the reason; not helpful. */ if (reason_len == 0) { reason = "(unknown)"; reason_len = strlen(reason); } /* The error message returned by Postgres is usually appended with * a newline. Let's prettify it by removing the newline. Note * that yes, we are overwriting the pointer given to us by Postgres, * but it's OK. The Postgres docs say that we're not supposed to * free the memory associated with the returned string anyway. */ reason = pstrdup(session.pool, reason); if (reason[reason_len-1] == '\n') { reason[reason_len-1] = '\0'; reason_len--; } sql_log(DEBUG_INFO, "lost connection to database: %s", reason); entry->connections = 0; if (entry->timer) { pr_timer_remove(entry->timer, &sql_postgres_module); entry->timer = 0; } sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_open"); return PR_ERROR_MSG(cmd, MOD_SQL_POSTGRES_VERSION, "lost connection to database"); } } /* make sure we have a new conn struct */ conn->postgres = PQconnectdb(conn->connectstring); if (PQstatus(conn->postgres) == CONNECTION_BAD) { /* if it didn't work, return an error */ sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_open"); return _build_error( cmd, conn ); } #if defined(PG_VERSION_STR) sql_log(DEBUG_FUNC, "Postgres client: %s", PG_VERSION_STR); #endif server_version = PQparameterStatus(conn->postgres, "server_version"); if (server_version != NULL) { sql_log(DEBUG_FUNC, "Postgres server version: %s", server_version); } #ifdef PR_USE_NLS if (pr_encode_get_encoding() != NULL) { const char *encoding; encoding = get_postgres_encoding(pr_encode_get_encoding()); /* Configure the connection for the current local character set. */ if (PQsetClientEncoding(conn->postgres, encoding) < 0) { /* if it didn't work, return an error */ sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_open"); return _build_error(cmd, conn); } sql_log(DEBUG_FUNC, "Postgres connection character set now '%s' " "(from '%s')", pg_encoding_to_char(PQclientEncoding(conn->postgres)), pr_encode_get_encoding()); } #endif /* !PR_USE_NLS */ /* bump connections */ entry->connections++; if (pr_sql_conn_policy == SQL_CONN_POLICY_PERSESSION) { /* If the connection policy is PERSESSION... */ if (entry->connections == 1) { /* ...and we are actually opening the first connection to the database; * we want to make sure this connection stays open, after this first use * (as per Bug#3290). To do this, we re-bump the connection count. */ entry->connections++; } } else if (entry->ttl > 0) { /* Set up our timer if necessary */ entry->timer = pr_timer_add(entry->ttl, -1, &sql_postgres_module, sql_timer_cb, "postgres connection ttl"); sql_log(DEBUG_INFO, "connection '%s' - %d second timer started", entry->name, entry->ttl); /* Timed connections get re-bumped so they don't go away when cmd_close * is called. */ entry->connections++; } /* return HANDLED */ sql_log(DEBUG_INFO, "connection '%s' opened", entry->name); sql_log(DEBUG_INFO, "connection '%s' count is now %d", entry->name, entry->connections); sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_open"); return PR_HANDLED(cmd); }
int pr_module_unload(module *m) { char buf[256]; if (m == NULL || m->name == NULL) { errno = EINVAL; return -1; } /* Make sure this module has been loaded. We can't unload a module that * has not been loaded, now can we? */ memset(buf, '\0', sizeof(buf)); snprintf(buf, sizeof(buf), "mod_%s.c", m->name); buf[sizeof(buf)-1] = '\0'; if (pr_module_get(buf) == NULL) { errno = ENOENT; return -1; } /* Generate an event. */ pr_event_generate("core.module-unload", buf); /* Remove the module from the loaded_modules list. */ if (m->prev) { m->prev->next = m->next; } else { /* This module is the start of the loaded_modules list (prev is NULL), * so we need to update that pointer, too. */ loaded_modules = m->next; } if (m->next) m->next->prev = m->prev; m->prev = m->next = NULL; /* Remove the module's config, cmd, and auth tables. */ if (m->conftable) { conftable *conftab; for (conftab = m->conftable; conftab->directive; conftab++) { pr_stash_remove_symbol(PR_SYM_CONF, conftab->directive, conftab->m); } } if (m->cmdtable) { cmdtable *cmdtab; for (cmdtab = m->cmdtable; cmdtab->command; cmdtab++) { if (cmdtab->cmd_type == HOOK) { pr_stash_remove_symbol(PR_SYM_HOOK, cmdtab->command, cmdtab->m); } else { /* All other cmd_types are for CMDs: PRE_CMD, CMD, POST_CMD, etc. */ pr_stash_remove_symbol(PR_SYM_CMD, cmdtab->command, cmdtab->m); } } } if (m->authtable) { authtable *authtab; for (authtab = m->authtable; authtab->name; authtab++) { pr_stash_remove_symbol(PR_SYM_AUTH, authtab->name, authtab->m); } } /* Remove any callbacks that the module may have registered, i.e.: * * ctrls * events * timers * * Ideally we would also automatically unregister other callbacks that * the module may have registered, such as FSIO, NetIO, variables, and * response handlers. However, these APIs do not yet allow for * removal of all callbacks for a given module. */ #ifdef PR_USE_CTRLS pr_ctrls_unregister(m, NULL); #endif /* PR_USE_CTRLS */ pr_event_unregister(m, NULL, NULL); pr_timer_remove(-1, m); return 0; }
int proxy_session_setup_env(pool *p, const char *user, int flags) { struct passwd *pw; config_rec *c; int i, res = 0, xerrno = 0; const char *xferlog = NULL; session.hide_password = TRUE; /* Note: the given user name may not be known locally on the proxy; thus * having pr_auth_getpwnam() returning NULL here is not an unexpected * use case. */ pw = pr_auth_getpwnam(p, user); if (pw != NULL) { if (pw->pw_uid == PR_ROOT_UID) { int root_login = FALSE; pr_event_generate("mod_auth.root-login", NULL); c = find_config(main_server->conf, CONF_PARAM, "RootLogin", FALSE); if (c != NULL) { root_login = *((int *) c->argv[0]); } if (root_login == FALSE) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "root login attempted, denied by RootLogin configuration"); pr_log_auth(PR_LOG_NOTICE, "SECURITY VIOLATION: Root login attempted."); return -1; } pr_log_auth(PR_LOG_WARNING, "ROOT proxy login successful"); } res = pr_auth_is_valid_shell(main_server->conf, pw->pw_shell); if (res == FALSE) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "authentication for user '%s' failed: Invalid shell", user); pr_log_auth(PR_LOG_NOTICE, "USER %s (Login failed): Invalid shell: '%s'", user, pw->pw_shell); errno = EPERM; return -1; } res = pr_auth_banned_by_ftpusers(main_server->conf, pw->pw_name); if (res == TRUE) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "authentication for user '%s' failed: User in " PR_FTPUSERS_PATH, user); pr_log_auth(PR_LOG_NOTICE, "USER %s (Login failed): User in " PR_FTPUSERS_PATH, pw->pw_name); errno = EPERM; return -1; } session.user = pstrdup(p, pw->pw_name); session.group = pstrdup(p, pr_auth_gid2name(p, pw->pw_gid)); session.login_uid = pw->pw_uid; session.login_gid = pw->pw_gid; } else { session.user = pstrdup(session.pool, user); /* XXX What should session.group, session.login_uid, session.login_gid * be? Kept as is? */ } if (session.gids == NULL && session.groups == NULL) { res = pr_auth_getgroups(p, session.user, &session.gids, &session.groups); if (res < 1 && errno != ENOENT) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "no supplemental groups found for user '%s'", session.user); } } if (flags & PROXY_SESSION_FL_CHECK_LOGIN_ACL) { int login_acl; login_acl = login_check_limits(main_server->conf, FALSE, TRUE, &i); if (!login_acl) { pr_log_auth(PR_LOG_NOTICE, "USER %s (Login failed): Limit configuration " "denies login", user); return -1; } } /* XXX Will users want wtmp logging for a proxy login? */ session.wtmp_log = FALSE; c = find_config(main_server->conf, CONF_PARAM, "TransferLog", FALSE); if (c == NULL) { xferlog = PR_XFERLOG_PATH; } else { xferlog = c->argv[0]; } PRIVS_ROOT if (strncasecmp(xferlog, "none", 5) == 0) { xferlog_open(NULL); } else { xferlog_open(xferlog); } res = xerrno = 0; if (pw != NULL) { res = set_groups(p, pw->pw_gid, session.gids); xerrno = errno; } PRIVS_RELINQUISH if (res < 0) { pr_log_pri(PR_LOG_WARNING, "unable to set process groups: %s", strerror(xerrno)); } session.disable_id_switching = TRUE; session.proc_prefix = pstrdup(session.pool, session.c->remote_name); session.sf_flags = 0; pr_scoreboard_entry_update(session.pid, PR_SCORE_USER, session.user, PR_SCORE_CWD, pr_fs_getcwd(), NULL); if (session.group != NULL) { session.group = pstrdup(session.pool, session.group); } if (session.groups != NULL) { session.groups = copy_array_str(session.pool, session.groups); } proxy_sess_state |= PROXY_SESS_STATE_PROXY_AUTHENTICATED; pr_timer_remove(PR_TIMER_LOGIN, ANY_MODULE); return 0; }
char *pr_ident_lookup(pool *p, conn_t *c) { char *ret = "UNKNOWN"; pool *tmp_pool = NULL; conn_t *ident_conn = NULL, *ident_io = NULL; char buf[256] = {'\0'}, *tok = NULL, *tmp = NULL; int timerno, i = 0; int ident_port = pr_inet_getservport(p, "ident", "tcp"); tmp_pool = make_sub_pool(p); ident_timeout = 0; nstrm = NULL; if (ident_port == -1) { destroy_pool(tmp_pool); return pstrdup(p, ret); } /* Set up our timer before going any further. */ timerno = pr_timer_add(PR_TUNABLE_TIMEOUTIDENT, -1, NULL, (callback_t) ident_timeout_cb, "ident lookup"); if (timerno <= 0) { destroy_pool(tmp_pool); return pstrdup(p, ret); } ident_conn = pr_inet_create_connection(tmp_pool, NULL, -1, c->local_addr, INPORT_ANY, FALSE); pr_inet_set_nonblock(tmp_pool, ident_conn); i = pr_inet_connect_nowait(tmp_pool, ident_conn, c->remote_addr, ident_port); if (i < 0) { int xerrno = errno; pr_timer_remove(timerno, ANY_MODULE); pr_inet_close(tmp_pool, ident_conn); pr_trace_msg(trace_channel, 5, "connection to %s, port %d failed: %s", pr_netaddr_get_ipstr(c->remote_addr), ident_port, strerror(xerrno)); destroy_pool(tmp_pool); errno = xerrno; return pstrdup(p, ret); } if (!i) { /* Not yet connected. */ nstrm = pr_netio_open(p, PR_NETIO_STRM_OTHR, ident_conn->listen_fd, PR_NETIO_IO_RD); pr_netio_set_poll_interval(nstrm, 1); switch (pr_netio_poll(nstrm)) { /* Aborted, timed out */ case 1: { if (ident_timeout) { pr_timer_remove(timerno, ANY_MODULE); pr_netio_close(nstrm); pr_inet_close(tmp_pool, ident_conn); pr_trace_msg(trace_channel, 5, "lookup timed out, returning '%s'", ret); destroy_pool(tmp_pool); return pstrdup(p, ret); } break; } /* Error. */ case -1: { int xerrno = errno; pr_timer_remove(timerno, ANY_MODULE); pr_netio_close(nstrm); pr_inet_close(tmp_pool, ident_conn); pr_trace_msg(trace_channel, 6, "lookup failed (%s), returning '%s'", strerror(xerrno), ret); destroy_pool(tmp_pool); errno = xerrno; return pstrdup(p, ret); } /* Connected. */ default: { ident_conn->mode = CM_OPEN; if (pr_inet_get_conn_info(ident_conn, ident_conn->listen_fd) < 0) { int xerrno = errno; pr_timer_remove(timerno, ANY_MODULE); pr_netio_close(nstrm); pr_inet_close(tmp_pool, ident_conn); pr_trace_msg(trace_channel, 2, "lookup timed out (%s), returning '%s'", strerror(xerrno), ret); destroy_pool(tmp_pool); errno = xerrno; return pstrdup(p, ret); } break; } } } ident_io = pr_inet_openrw(tmp_pool, ident_conn, NULL, PR_NETIO_STRM_OTHR, -1, -1, -1, FALSE); if (ident_io == NULL) { int xerrno = errno; pr_timer_remove(timerno, ANY_MODULE); pr_inet_close(tmp_pool, ident_conn); pr_trace_msg(trace_channel, 3, "failed opening read/write connection: %s", strerror(xerrno)); destroy_pool(tmp_pool); errno = xerrno; return pstrdup(p, ret); } nstrm = ident_io->instrm; pr_inet_set_nonblock(tmp_pool, ident_io); pr_netio_set_poll_interval(ident_io->instrm, 1); pr_netio_set_poll_interval(ident_io->outstrm, 1); pr_netio_printf(ident_io->outstrm, "%d, %d\r\n", c->remote_port, c->local_port); /* If the timer fires while in netio_gets(), netio_gets() will simply return * either a partial string, or NULL. This works because ident_timeout_cb * aborts the stream from which we are reading. netio_set_poll_interval() is * used to make sure significant delays don't occur on systems that * automatically restart syscalls after the SIGALRM signal. */ pr_trace_msg(trace_channel, 4, "reading response from remote ident server"); if (pr_netio_gets(buf, sizeof(buf), ident_io->instrm)) { strip_end(buf, "\r\n"); pr_trace_msg(trace_channel, 6, "received '%s' from remote ident server", buf); tmp = buf; tok = get_token(&tmp, ":"); if (tok && (tok = get_token(&tmp, ":"))) { while (*tok && isspace((int) *tok)) { pr_signals_handle(); tok++; } strip_end(tok, " \t"); if (strcasecmp(tok, "ERROR") == 0) { if (tmp) { while (*tmp && isspace((int) *tmp)) { pr_signals_handle(); tmp++; } strip_end(tmp, " \t"); if (strcasecmp(tmp, "HIDDEN-USER") == 0) ret = "HIDDEN-USER"; } } else if (strcasecmp(tok, "USERID") == 0) { if (tmp && (tok = get_token(&tmp, ":"))) { if (tmp) { while (*tmp && isspace((int) *tmp)) { pr_signals_handle(); tmp++; } strip_end(tmp, " \t"); ret = tmp; } } } } } pr_timer_remove(timerno, ANY_MODULE); pr_inet_close(tmp_pool, ident_io); pr_inet_close(tmp_pool, ident_conn); destroy_pool(tmp_pool); return pstrdup(p, ret); }
static int forward_handle_user_passthru(cmd_rec *cmd, struct proxy_session *proxy_sess, int *successful, int flags) { int res, xerrno; char *user = NULL; cmd_rec *user_cmd = NULL; pr_response_t *resp = NULL; unsigned int resp_nlines = 0; if (flags & PROXY_FORWARD_USER_PASSTHRU_FL_PARSE_DSTADDR) { struct proxy_conn *pconn = NULL; pr_netaddr_t *remote_addr = NULL; array_header *other_addrs = NULL; res = forward_cmd_parse_dst(cmd->tmp_pool, cmd->arg, &user, &pconn); if (res < 0) { errno = EINVAL; return -1; } remote_addr = proxy_conn_get_addr(pconn, &other_addrs); /* Ensure that the requested remote address is NOT (blatantly) ourselves, * i.e. the proxy itself. This prevents easy-to-detect proxy loops. */ if (pr_netaddr_cmp(remote_addr, session.c->local_addr) == 0 && pr_netaddr_get_port(remote_addr) == pr_netaddr_get_port(session.c->local_addr)) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "requested destination %s#%u is local address %s#%u, rejecting", pr_netaddr_get_ipstr(remote_addr), ntohs(pr_netaddr_get_port(remote_addr)), pr_netaddr_get_ipstr(session.c->local_addr), ntohs(pr_netaddr_get_port(session.c->local_addr))); pr_response_send(R_530, _("Unable to connect to %s: %s"), proxy_conn_get_hostport(pconn), strerror(EPERM)); return 1; } proxy_sess->dst_addr = remote_addr; proxy_sess->other_addrs = other_addrs; proxy_sess->dst_pconn = pconn; /* Change the command so that it no longer includes the proxy info. */ user_cmd = pr_cmd_alloc(cmd->pool, 2, C_USER, user); user_cmd->arg = user; } else { user_cmd = cmd; } if (flags & PROXY_FORWARD_USER_PASSTHRU_FL_CONNECT_DSTADDR) { pr_response_t *banner = NULL; unsigned int banner_nlines = 0; res = forward_connect(proxy_pool, proxy_sess, &banner, &banner_nlines); if (res < 0) { xerrno = errno; *successful = FALSE; /* Send a failed USER response to our waiting frontend client, but do * not necessarily close the frontend connection. */ resp = pcalloc(cmd->tmp_pool, sizeof(pr_response_t)); resp->num = R_530; if (banner != NULL) { resp->msg = banner->msg; resp_nlines = banner_nlines; } else { resp->msg = pstrcat(cmd->tmp_pool, "Unable to connect to ", proxy_conn_get_hostport(proxy_sess->dst_pconn), ": ", strerror(xerrno), NULL); resp_nlines = 1; } res = proxy_ftp_ctrl_send_resp(cmd->tmp_pool, proxy_sess->frontend_ctrl_conn, resp, resp_nlines); if (res < 0) { xerrno = errno; pr_response_block(TRUE); errno = xerrno; return -1; } return 1; } } res = proxy_ftp_ctrl_send_cmd(cmd->tmp_pool, proxy_sess->backend_ctrl_conn, user_cmd); if (res < 0) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error sending %s to backend: %s", (char *) user_cmd->argv[0], strerror(xerrno)); errno = xerrno; return -1; } resp = proxy_ftp_ctrl_recv_resp(cmd->tmp_pool, proxy_sess->backend_ctrl_conn, &resp_nlines); if (resp == NULL) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error receiving %s response from backend: %s", (char *) cmd->argv[0], strerror(xerrno)); errno = xerrno; return -1; } if (resp->num[0] == '2' || resp->num[0] == '3') { *successful = TRUE; if (strcmp(resp->num, R_232) == 0) { proxy_sess_state |= PROXY_SESS_STATE_BACKEND_AUTHENTICATED; pr_timer_remove(PR_TIMER_LOGIN, ANY_MODULE); } } /* XXX TODO: Concatenate the banner from the connect with the USER response * message here, and send the entire kit to the frontend client, e.g.: * * Name (gatekeeper:you): [email protected] * 331-(----GATEWAY CONNECTED TO ftp.uu.net----) * 331-(220 ftp.uu.net FTP server (SunOS 4.1) ready. * 331 Guest login ok, send ident as password. * Password: ###### * 230 Guest login ok, access restrictions apply. * ftp> dir */ res = proxy_ftp_ctrl_send_resp(cmd->tmp_pool, proxy_sess->frontend_ctrl_conn, resp, resp_nlines); if (res < 0) { xerrno = errno; pr_response_block(TRUE); errno = xerrno; return -1; } return 1; }
/* In order to avoid clearing the transfer counters in session.xfer, we don't * clear session.xfer here, it should be handled by the appropriate * LOG_CMD/LOG_CMD_ERR handler calling pr_data_cleanup(). */ void pr_data_abort(int err, int quiet) { int true_abort = XFER_ABORTED; nstrm = NULL; if (session.d) { if (!true_abort) pr_inet_lingering_close(session.pool, session.d, timeout_linger); else pr_inet_lingering_abort(session.pool, session.d, timeout_linger); session.d = NULL; } if (timeout_noxfer) { pr_timer_reset(PR_TIMER_NOXFER, ANY_MODULE); } if (timeout_stalled) { pr_timer_remove(PR_TIMER_STALLED, ANY_MODULE); } session.sf_flags &= (SF_ALL^SF_PASSIVE); session.sf_flags &= (SF_ALL^(SF_XFER|SF_PASSIVE|SF_ASCII_OVERRIDE)); pr_session_set_idle(); /* Aborts no longer necessary */ signal(SIGURG, SIG_IGN); if (timeout_noxfer) pr_timer_reset(PR_TIMER_NOXFER, ANY_MODULE); if (!quiet) { char *respcode = R_426; char *msg = NULL; char msgbuf[64]; switch (err) { case 0: respcode = R_426; msg = _("Data connection closed"); break; #ifdef ENXIO case ENXIO: respcode = R_451; msg = _("Unexpected streams hangup"); break; #endif #ifdef EAGAIN case EAGAIN: /* FALLTHROUGH */ #endif #ifdef ENOMEM case ENOMEM: #endif #if defined(EAGAIN) || defined(ENOMEM) respcode = R_451; msg = _("Insufficient memory or file locked"); break; #endif #ifdef ETXTBSY case ETXTBSY: /* FALLTHROUGH */ #endif #ifdef EBUSY case EBUSY: #endif #if defined(ETXTBSY) || defined(EBUSY) respcode = R_451; break; #endif #ifdef ENOSPC case ENOSPC: respcode = R_452; break; #endif #ifdef EDQUOT case EDQUOT: /* FALLTHROUGH */ #endif #ifdef EFBIG case EFBIG: #endif #if defined(EDQUOT) || defined(EFBIG) respcode = R_552; break; #endif #ifdef ECOMM case ECOMM: /* FALLTHROUGH */ #endif #ifdef EDEADLK case EDEADLK: /* FALLTHROUGH */ #endif #ifdef EDEADLOCK # if !defined(EDEADLK) || (EDEADLOCK != EDEADLK) case EDEADLOCK: /* FALLTHROUGH */ # endif #endif #ifdef EXFULL case EXFULL: /* FALLTHROUGH */ #endif #ifdef ENOSR case ENOSR: /* FALLTHROUGH */ #endif #ifdef EPROTO case EPROTO: /* FALLTHROUGH */ #endif #ifdef ETIME case ETIME: /* FALLTHROUGH */ #endif #ifdef EIO case EIO: /* FALLTHROUGH */ #endif #ifdef EFAULT case EFAULT: /* FALLTHROUGH */ #endif #ifdef ESPIPE case ESPIPE: /* FALLTHROUGH */ #endif #ifdef EPIPE case EPIPE: #endif #if defined(ECOMM) || defined(EDEADLK) || defined(EDEADLOCK) \ || defined(EXFULL) || defined(ENOSR) || defined(EPROTO) \ || defined(ETIME) || defined(EIO) || defined(EFAULT) \ || defined(ESPIPE) || defined(EPIPE) respcode = R_451; break; #endif #ifdef EREMCHG case EREMCHG: /* FALLTHROUGH */ #endif #ifdef ESRMNT case ESRMNT: /* FALLTHROUGH */ #endif #ifdef ESTALE case ESTALE: /* FALLTHROUGH */ #endif #ifdef ENOLINK case ENOLINK: /* FALLTHROUGH */ #endif #ifdef ENOLCK case ENOLCK: /* FALLTHROUGH */ #endif #ifdef ENETRESET case ENETRESET: /* FALLTHROUGH */ #endif #ifdef ECONNABORTED case ECONNABORTED: /* FALLTHROUGH */ #endif #ifdef ECONNRESET case ECONNRESET: /* FALLTHROUGH */ #endif #ifdef ETIMEDOUT case ETIMEDOUT: #endif #if defined(EREMCHG) || defined(ESRMNT) || defined(ESTALE) \ || defined(ENOLINK) || defined(ENOLCK) || defined(ENETRESET) \ || defined(ECONNABORTED) || defined(ECONNRESET) || defined(ETIMEDOUT) respcode = R_450; msg = _("Link to file server lost"); break; #endif } if (msg == NULL && (msg = strerror(err)) == NULL ) { if (snprintf(msgbuf, sizeof(msgbuf), _("Unknown or out of range errno [%d]"), err) > 0) msg = msgbuf; } pr_log_pri(PR_LOG_NOTICE, "notice: user %s: aborting transfer: %s", session.user, msg); /* If we are aborting, then a 426 response has already been sent, * and we don't want to add another to the error queue. */ if (!true_abort) pr_response_add_err(respcode, _("Transfer aborted. %s"), msg ? msg : ""); } if (true_abort) session.sf_flags |= SF_POST_ABORT; }
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; }