mirror of
https://github.com/freebsd/freebsd-src.git
synced 2026-10-28 11:25:21 +01:00
Full release notes are available at https://www.openssh.com/txt/release-10.3 Selected highlights from the release notes: * ssh(1), sshd(8): remove bug compatibility for implementations that don't support rekeying. If such an implementation tries to interoperate with OpenSSH, it will now eventually fail when the transport needs rekeying. * ssh(1), sshd(8): support IANA-assigned codepoints for SSH agent forwarding, as per draft-ietf-sshm-ssh-agent. Support for the new names is advertised via the EXT_INFO message. If a server offers support for the new names, then they are used preferentially. * ssh(1): add a ~I escape option that shows information about the current SSH connection. * sshd(8): add 'invaliduser' penalty to PerSourcePenalties, which is applied to login attempts for usernames that do not match real accounts. Defaults to 5s to match 'authfail' but allows administrators to block such attempts for longer if desired. * Support the ed25519 signature scheme via libcrypto. Sponsored by: The FreeBSD Foundation Differential Revision: https://reviews.freebsd.org/D56999
657 lines
17 KiB
C
657 lines
17 KiB
C
/* $OpenBSD: log.c,v 1.67 2026/02/14 00:18:34 jsg Exp $ */
|
|
/*
|
|
* Author: Tatu Ylonen <[email protected]>
|
|
* Copyright (c) 1995 Tatu Ylonen <[email protected]>, Espoo, Finland
|
|
* All rights reserved
|
|
*
|
|
* As far as I am concerned, the code I have written for this software
|
|
* can be used freely for any purpose. Any derived versions of this
|
|
* software must be clearly marked as such, and if the derived work is
|
|
* incompatible with the protocol description in the RFC file, it must be
|
|
* called by a name other than "ssh" or "Secure Shell".
|
|
*/
|
|
/*
|
|
* Copyright (c) 2000 Markus Friedl. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "includes.h"
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <limits.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <syslog.h>
|
|
#include <unistd.h>
|
|
#if defined(HAVE_STRNVIS) && defined(HAVE_VIS_H) && !defined(BROKEN_STRNVIS)
|
|
# include <vis.h>
|
|
#endif
|
|
|
|
#include "log.h"
|
|
#include "match.h"
|
|
|
|
static LogLevel log_level = SYSLOG_LEVEL_INFO;
|
|
static int log_on_stderr = 1;
|
|
static int log_stderr_fd = STDERR_FILENO;
|
|
static int log_facility = LOG_AUTH;
|
|
static const char *argv0;
|
|
static log_handler_fn *log_handler;
|
|
static void *log_handler_ctx;
|
|
static char **log_verbose;
|
|
static size_t nlog_verbose;
|
|
extern char *__progname;
|
|
|
|
#define LOG_SYSLOG_VIS (VIS_CSTYLE|VIS_NL|VIS_TAB|VIS_OCTAL)
|
|
#define LOG_STDERR_VIS (VIS_SAFE|VIS_OCTAL)
|
|
|
|
/* textual representation of log-facilities/levels */
|
|
|
|
static struct {
|
|
const char *name;
|
|
SyslogFacility val;
|
|
} log_facilities[] = {
|
|
{ "DAEMON", SYSLOG_FACILITY_DAEMON },
|
|
{ "USER", SYSLOG_FACILITY_USER },
|
|
{ "AUTH", SYSLOG_FACILITY_AUTH },
|
|
#ifdef LOG_AUTHPRIV
|
|
{ "AUTHPRIV", SYSLOG_FACILITY_AUTHPRIV },
|
|
#endif
|
|
{ "LOCAL0", SYSLOG_FACILITY_LOCAL0 },
|
|
{ "LOCAL1", SYSLOG_FACILITY_LOCAL1 },
|
|
{ "LOCAL2", SYSLOG_FACILITY_LOCAL2 },
|
|
{ "LOCAL3", SYSLOG_FACILITY_LOCAL3 },
|
|
{ "LOCAL4", SYSLOG_FACILITY_LOCAL4 },
|
|
{ "LOCAL5", SYSLOG_FACILITY_LOCAL5 },
|
|
{ "LOCAL6", SYSLOG_FACILITY_LOCAL6 },
|
|
{ "LOCAL7", SYSLOG_FACILITY_LOCAL7 },
|
|
{ NULL, SYSLOG_FACILITY_NOT_SET }
|
|
};
|
|
|
|
static struct {
|
|
const char *name;
|
|
LogLevel val;
|
|
} log_levels[] =
|
|
{
|
|
{ "QUIET", SYSLOG_LEVEL_QUIET },
|
|
{ "FATAL", SYSLOG_LEVEL_FATAL },
|
|
{ "ERROR", SYSLOG_LEVEL_ERROR },
|
|
{ "INFO", SYSLOG_LEVEL_INFO },
|
|
{ "VERBOSE", SYSLOG_LEVEL_VERBOSE },
|
|
{ "DEBUG", SYSLOG_LEVEL_DEBUG1 },
|
|
{ "DEBUG1", SYSLOG_LEVEL_DEBUG1 },
|
|
{ "DEBUG2", SYSLOG_LEVEL_DEBUG2 },
|
|
{ "DEBUG3", SYSLOG_LEVEL_DEBUG3 },
|
|
{ NULL, SYSLOG_LEVEL_NOT_SET }
|
|
};
|
|
|
|
LogLevel
|
|
log_level_get(void)
|
|
{
|
|
return log_level;
|
|
}
|
|
|
|
SyslogFacility
|
|
log_facility_number(char *name)
|
|
{
|
|
int i;
|
|
|
|
if (name != NULL)
|
|
for (i = 0; log_facilities[i].name; i++)
|
|
if (strcasecmp(log_facilities[i].name, name) == 0)
|
|
return log_facilities[i].val;
|
|
return SYSLOG_FACILITY_NOT_SET;
|
|
}
|
|
|
|
const char *
|
|
log_facility_name(SyslogFacility facility)
|
|
{
|
|
u_int i;
|
|
|
|
for (i = 0; log_facilities[i].name; i++)
|
|
if (log_facilities[i].val == facility)
|
|
return log_facilities[i].name;
|
|
return NULL;
|
|
}
|
|
|
|
LogLevel
|
|
log_level_number(char *name)
|
|
{
|
|
int i;
|
|
|
|
if (name != NULL)
|
|
for (i = 0; log_levels[i].name; i++)
|
|
if (strcasecmp(log_levels[i].name, name) == 0)
|
|
return log_levels[i].val;
|
|
return SYSLOG_LEVEL_NOT_SET;
|
|
}
|
|
|
|
const char *
|
|
log_level_name(LogLevel level)
|
|
{
|
|
u_int i;
|
|
|
|
for (i = 0; log_levels[i].name != NULL; i++)
|
|
if (log_levels[i].val == level)
|
|
return log_levels[i].name;
|
|
return NULL;
|
|
}
|
|
|
|
void
|
|
log_verbose_add(const char *s)
|
|
{
|
|
char **tmp;
|
|
|
|
/* Ignore failures here */
|
|
if ((tmp = recallocarray(log_verbose, nlog_verbose, nlog_verbose + 1,
|
|
sizeof(*log_verbose))) != NULL) {
|
|
log_verbose = tmp;
|
|
if ((log_verbose[nlog_verbose] = strdup(s)) != NULL)
|
|
nlog_verbose++;
|
|
}
|
|
}
|
|
|
|
void
|
|
log_verbose_reset(void)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < nlog_verbose; i++)
|
|
free(log_verbose[i]);
|
|
free(log_verbose);
|
|
log_verbose = NULL;
|
|
nlog_verbose = 0;
|
|
}
|
|
|
|
/*
|
|
* Initialize the log.
|
|
*/
|
|
|
|
void
|
|
log_init(const char *av0, LogLevel level, SyslogFacility facility,
|
|
int on_stderr)
|
|
{
|
|
#if defined(HAVE_OPENLOG_R) && defined(SYSLOG_DATA_INIT)
|
|
struct syslog_data sdata = SYSLOG_DATA_INIT;
|
|
#endif
|
|
|
|
argv0 = av0;
|
|
|
|
if (log_change_level(level) != 0) {
|
|
fprintf(stderr, "Unrecognized internal syslog level code %d\n",
|
|
(int) level);
|
|
exit(1);
|
|
}
|
|
|
|
log_handler = NULL;
|
|
log_handler_ctx = NULL;
|
|
|
|
log_on_stderr = on_stderr;
|
|
if (on_stderr)
|
|
return;
|
|
|
|
switch (facility) {
|
|
case SYSLOG_FACILITY_DAEMON:
|
|
log_facility = LOG_DAEMON;
|
|
break;
|
|
case SYSLOG_FACILITY_USER:
|
|
log_facility = LOG_USER;
|
|
break;
|
|
case SYSLOG_FACILITY_AUTH:
|
|
log_facility = LOG_AUTH;
|
|
break;
|
|
#ifdef LOG_AUTHPRIV
|
|
case SYSLOG_FACILITY_AUTHPRIV:
|
|
log_facility = LOG_AUTHPRIV;
|
|
break;
|
|
#endif
|
|
case SYSLOG_FACILITY_LOCAL0:
|
|
log_facility = LOG_LOCAL0;
|
|
break;
|
|
case SYSLOG_FACILITY_LOCAL1:
|
|
log_facility = LOG_LOCAL1;
|
|
break;
|
|
case SYSLOG_FACILITY_LOCAL2:
|
|
log_facility = LOG_LOCAL2;
|
|
break;
|
|
case SYSLOG_FACILITY_LOCAL3:
|
|
log_facility = LOG_LOCAL3;
|
|
break;
|
|
case SYSLOG_FACILITY_LOCAL4:
|
|
log_facility = LOG_LOCAL4;
|
|
break;
|
|
case SYSLOG_FACILITY_LOCAL5:
|
|
log_facility = LOG_LOCAL5;
|
|
break;
|
|
case SYSLOG_FACILITY_LOCAL6:
|
|
log_facility = LOG_LOCAL6;
|
|
break;
|
|
case SYSLOG_FACILITY_LOCAL7:
|
|
log_facility = LOG_LOCAL7;
|
|
break;
|
|
default:
|
|
fprintf(stderr,
|
|
"Unrecognized internal syslog facility code %d\n",
|
|
(int) facility);
|
|
exit(1);
|
|
}
|
|
|
|
/*
|
|
* If an external library (eg libwrap) attempts to use syslog
|
|
* immediately after reexec, syslog may be pointing to the wrong
|
|
* facility, so we force an open/close of syslog here.
|
|
*/
|
|
#if defined(HAVE_OPENLOG_R) && defined(SYSLOG_DATA_INIT)
|
|
openlog_r(argv0 ? argv0 : __progname, LOG_PID, log_facility, &sdata);
|
|
closelog_r(&sdata);
|
|
#else
|
|
openlog(argv0 ? argv0 : __progname, LOG_PID, log_facility);
|
|
closelog();
|
|
#endif
|
|
}
|
|
|
|
int
|
|
log_change_level(LogLevel new_log_level)
|
|
{
|
|
/* no-op if log_init has not been called */
|
|
if (argv0 == NULL)
|
|
return 0;
|
|
|
|
switch (new_log_level) {
|
|
case SYSLOG_LEVEL_QUIET:
|
|
case SYSLOG_LEVEL_FATAL:
|
|
case SYSLOG_LEVEL_ERROR:
|
|
case SYSLOG_LEVEL_INFO:
|
|
case SYSLOG_LEVEL_VERBOSE:
|
|
case SYSLOG_LEVEL_DEBUG1:
|
|
case SYSLOG_LEVEL_DEBUG2:
|
|
case SYSLOG_LEVEL_DEBUG3:
|
|
log_level = new_log_level;
|
|
return 0;
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
int
|
|
log_is_on_stderr(void)
|
|
{
|
|
return log_on_stderr && log_stderr_fd == STDERR_FILENO;
|
|
}
|
|
|
|
/* redirect what would usually get written to stderr to specified file */
|
|
void
|
|
log_redirect_stderr_to(const char *logfile)
|
|
{
|
|
int fd;
|
|
|
|
if (logfile == NULL) {
|
|
if (log_stderr_fd != STDERR_FILENO) {
|
|
close(log_stderr_fd);
|
|
log_stderr_fd = STDERR_FILENO;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if ((fd = open(logfile, O_WRONLY|O_CREAT|O_APPEND, 0600)) == -1) {
|
|
fprintf(stderr, "Couldn't open logfile %s: %s\n", logfile,
|
|
strerror(errno));
|
|
exit(1);
|
|
}
|
|
log_stderr_fd = fd;
|
|
}
|
|
|
|
#define MSGBUFSIZ 1024
|
|
|
|
void
|
|
set_log_handler(log_handler_fn *handler, void *ctx)
|
|
{
|
|
log_handler = handler;
|
|
log_handler_ctx = ctx;
|
|
}
|
|
|
|
static void
|
|
do_log(LogLevel level, int force, const char *suffix, const char *fmt,
|
|
va_list args)
|
|
{
|
|
#if defined(HAVE_OPENLOG_R) && defined(SYSLOG_DATA_INIT)
|
|
struct syslog_data sdata = SYSLOG_DATA_INIT;
|
|
#endif
|
|
char msgbuf[MSGBUFSIZ];
|
|
char fmtbuf[MSGBUFSIZ];
|
|
char *txt = NULL;
|
|
int pri = LOG_INFO;
|
|
int saved_errno = errno;
|
|
log_handler_fn *tmp_handler;
|
|
const char *progname = argv0 != NULL ? argv0 : __progname;
|
|
|
|
if (!force && level > log_level)
|
|
return;
|
|
|
|
switch (level) {
|
|
case SYSLOG_LEVEL_FATAL:
|
|
if (!log_on_stderr)
|
|
txt = "fatal";
|
|
pri = LOG_CRIT;
|
|
break;
|
|
case SYSLOG_LEVEL_ERROR:
|
|
if (!log_on_stderr)
|
|
txt = "error";
|
|
pri = LOG_ERR;
|
|
break;
|
|
case SYSLOG_LEVEL_INFO:
|
|
pri = LOG_INFO;
|
|
break;
|
|
case SYSLOG_LEVEL_VERBOSE:
|
|
pri = LOG_INFO;
|
|
break;
|
|
case SYSLOG_LEVEL_DEBUG1:
|
|
txt = "debug1";
|
|
pri = LOG_DEBUG;
|
|
break;
|
|
case SYSLOG_LEVEL_DEBUG2:
|
|
txt = "debug2";
|
|
pri = LOG_DEBUG;
|
|
break;
|
|
case SYSLOG_LEVEL_DEBUG3:
|
|
txt = "debug3";
|
|
pri = LOG_DEBUG;
|
|
break;
|
|
default:
|
|
txt = "internal error";
|
|
pri = LOG_ERR;
|
|
break;
|
|
}
|
|
if (txt != NULL && log_handler == NULL) {
|
|
snprintf(fmtbuf, sizeof(fmtbuf), "%s: %s", txt, fmt);
|
|
vsnprintf(msgbuf, sizeof(msgbuf), fmtbuf, args);
|
|
} else {
|
|
vsnprintf(msgbuf, sizeof(msgbuf), fmt, args);
|
|
}
|
|
if (suffix != NULL) {
|
|
snprintf(fmtbuf, sizeof(fmtbuf), "%s: %s", msgbuf, suffix);
|
|
strlcpy(msgbuf, fmtbuf, sizeof(msgbuf));
|
|
}
|
|
strnvis(fmtbuf, msgbuf, sizeof(fmtbuf),
|
|
log_on_stderr ? LOG_STDERR_VIS : LOG_SYSLOG_VIS);
|
|
if (log_handler != NULL) {
|
|
/* Avoid recursion */
|
|
tmp_handler = log_handler;
|
|
log_handler = NULL;
|
|
/* Note: this sends the raw (i.e. no strnvis) log message */
|
|
tmp_handler(level, force, msgbuf, log_handler_ctx);
|
|
log_handler = tmp_handler;
|
|
} else if (log_on_stderr) {
|
|
snprintf(msgbuf, sizeof msgbuf, "%s%s%.*s\r\n",
|
|
(log_on_stderr > 1) ? progname : "",
|
|
(log_on_stderr > 1) ? ": " : "",
|
|
(int)sizeof msgbuf - 3, fmtbuf);
|
|
(void)write(log_stderr_fd, msgbuf, strlen(msgbuf));
|
|
} else {
|
|
#if defined(HAVE_OPENLOG_R) && defined(SYSLOG_DATA_INIT)
|
|
openlog_r(progname, LOG_PID, log_facility, &sdata);
|
|
syslog_r(pri, &sdata, "%.500s", fmtbuf);
|
|
closelog_r(&sdata);
|
|
#else
|
|
openlog(progname, LOG_PID, log_facility);
|
|
syslog(pri, "%.500s", fmtbuf);
|
|
closelog();
|
|
#endif
|
|
}
|
|
errno = saved_errno;
|
|
}
|
|
|
|
void
|
|
sshlog(const char *file, const char *func, int line, int showfunc,
|
|
LogLevel level, const char *suffix, const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
|
|
va_start(args, fmt);
|
|
sshlogv(file, func, line, showfunc, level, suffix, fmt, args);
|
|
va_end(args);
|
|
}
|
|
|
|
void
|
|
sshlogdie(const char *file, const char *func, int line, int showfunc,
|
|
LogLevel level, const char *suffix, const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
|
|
va_start(args, fmt);
|
|
sshlogv(file, func, line, showfunc, SYSLOG_LEVEL_INFO,
|
|
suffix, fmt, args);
|
|
va_end(args);
|
|
cleanup_exit(255);
|
|
}
|
|
|
|
void
|
|
sshlogv(const char *file, const char *func, int line, int showfunc,
|
|
LogLevel level, const char *suffix, const char *fmt, va_list args)
|
|
{
|
|
char tag[128], fmt2[MSGBUFSIZ + 128];
|
|
int forced = 0;
|
|
const char *cp;
|
|
size_t i;
|
|
|
|
/* short circuit processing early if we're not going to log anything */
|
|
if (nlog_verbose == 0 && level > log_level)
|
|
return;
|
|
|
|
snprintf(tag, sizeof(tag), "%.48s:%.48s():%d (bin=%s, pid=%ld)",
|
|
(cp = strrchr(file, '/')) == NULL ? file : cp + 1, func, line,
|
|
argv0 == NULL ? "UNKNOWN" : argv0, (long)getpid());
|
|
for (i = 0; i < nlog_verbose; i++) {
|
|
if (match_pattern_list(tag, log_verbose[i], 0) == 1) {
|
|
forced = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (forced)
|
|
snprintf(fmt2, sizeof(fmt2), "%s: %s", tag, fmt);
|
|
else if (showfunc)
|
|
snprintf(fmt2, sizeof(fmt2), "%s: %s", func, fmt);
|
|
else
|
|
strlcpy(fmt2, fmt, sizeof(fmt2));
|
|
|
|
do_log(level, forced, suffix, fmt2, args);
|
|
}
|
|
|
|
void
|
|
sshlogdirect(LogLevel level, int forced, const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
|
|
va_start(args, fmt);
|
|
do_log(level, forced, NULL, fmt, args);
|
|
va_end(args);
|
|
}
|
|
|
|
|
|
/*
|
|
* A simple system for ratelimiting aperiodic events such as logs, without
|
|
* needing to be hooked into a mainloop/timer. A running total of events is
|
|
* maintained and when it exceeds a threshold further events are dropped
|
|
* until the rate falls back below that threshold.
|
|
*
|
|
* To prevent flipping in and out of rate-limiting, there is a hysteresis
|
|
* timer that delays leaving the rate-limited state.
|
|
*
|
|
* While in the rate-limited state, events can be periodically allowed through
|
|
* and the number of dropped events since the last log obtained.
|
|
*
|
|
* XXX a moving average rate of events might be a better approach here rather
|
|
* than linear decay, which can suppress events for a while after large
|
|
* bursts.
|
|
*/
|
|
|
|
/* #define RATELIMIT_DEBUG 1 */
|
|
|
|
#ifdef RATELIMIT_DEBUG
|
|
# define RLDBG(x) do { \
|
|
printf("%s:%d %s: ", __FILE__, __LINE__, __func__); \
|
|
printf x; \
|
|
printf("\n"); \
|
|
fflush(stdout); \
|
|
} while (0)
|
|
#else
|
|
# define RLDBG(x)
|
|
#endif
|
|
|
|
/* set up a ratelimit */
|
|
void
|
|
log_ratelimit_init(struct log_ratelimit_ctx *rl, u_int threshold,
|
|
u_int max_accum, u_int hysteresis, u_int log_every)
|
|
{
|
|
memset(rl, 0, sizeof(*rl));
|
|
rl->threshold = threshold;
|
|
rl->max_accum = max_accum;
|
|
rl->hysteresis = hysteresis;
|
|
rl->log_every = log_every;
|
|
RLDBG(("called: rl=%p thresh=%u max=%u hys=%u log_every=%u",
|
|
rl, rl->threshold, rl->max_accum, rl->hysteresis, rl->log_every));
|
|
}
|
|
|
|
/*
|
|
* check whether a log event should be dropped because of rate-limiting.
|
|
* returns non-zero if the event should be dropped. If events_since_last
|
|
* is supplied then, for periodic logs, it will be set to the number of
|
|
* dropped events since the last message.
|
|
*/
|
|
int
|
|
log_ratelimit(struct log_ratelimit_ctx *rl, time_t now, int *active,
|
|
u_int *events_dropped)
|
|
{
|
|
time_t olast_event;
|
|
|
|
RLDBG(("called: rl=%p thresh=%u max=%u hys=%u log_every=%u "
|
|
"accum=%u since=%ld since_last=%u", rl, rl->threshold,
|
|
rl->max_accum, rl->hysteresis,
|
|
rl->log_every, rl->accumulated_events,
|
|
rl->last_event == 0 ? -1 : (long)(now - rl->last_event),
|
|
rl->ratelimited_events));
|
|
|
|
if (now < 0)
|
|
return 0;
|
|
if (events_dropped != NULL)
|
|
*events_dropped = 0;
|
|
if (active != NULL)
|
|
*active = rl->ratelimit_active;
|
|
|
|
/* First, decay accumulated events */
|
|
if (rl->last_event <= 0)
|
|
rl->last_event = now;
|
|
if (now > rl->last_event) {
|
|
uint64_t n = now - rl->last_event;
|
|
|
|
if (n > UINT_MAX)
|
|
n = UINT_MAX;
|
|
if (rl->accumulated_events < (u_int)n)
|
|
rl->accumulated_events = 0;
|
|
else
|
|
rl->accumulated_events -= (u_int)n;
|
|
RLDBG(("decay: accum=%u", rl->accumulated_events));
|
|
}
|
|
rl->accumulated_events++; /* add this event */
|
|
if (rl->accumulated_events > rl->max_accum)
|
|
rl->accumulated_events = rl->max_accum;
|
|
olast_event = rl->last_event;
|
|
rl->last_event = now;
|
|
RLDBG(("check threshold: accum=%u vs thresh=%u",
|
|
rl->accumulated_events, rl->threshold));
|
|
|
|
/* Are we under threshold? */
|
|
if (rl->accumulated_events < rl->threshold) {
|
|
if (!rl->ratelimit_active)
|
|
return 0;
|
|
RLDBG(("under threshold: hys=%u since_hys=%ld since_last=%ld",
|
|
rl->hysteresis, rl->hysteresis_start == 0 ? -1 :
|
|
(long)(now - rl->hysteresis_start),
|
|
olast_event == 0 ? -1 : (long)(now - olast_event)));
|
|
if (rl->hysteresis_start == 0) {
|
|
/* active, but under threshold; hysteresis */
|
|
if (olast_event + rl->hysteresis < now) {
|
|
/* hysteresis expired before this event */
|
|
RLDBG(("hysteresis preexpired"));
|
|
goto inactive;
|
|
}
|
|
RLDBG(("start hysteresis"));
|
|
rl->hysteresis_start = now;
|
|
} else if (rl->hysteresis_start + rl->hysteresis < now) {
|
|
/* Hysteresis period expired, transition to inactive */
|
|
RLDBG(("complete hysteresis"));
|
|
inactive:
|
|
if (events_dropped != NULL)
|
|
*events_dropped = rl->ratelimited_events;
|
|
if (active != NULL)
|
|
*active = 0;
|
|
rl->ratelimit_active = 0;
|
|
rl->ratelimit_start = 0;
|
|
rl->last_log = 0;
|
|
rl->hysteresis_start = 0;
|
|
rl->ratelimited_events = 0;
|
|
return 0;
|
|
}
|
|
/* ratelimiting active, but in hysteresis period */
|
|
} else if (!rl->ratelimit_active) {
|
|
/* Transition to rate-limiting */
|
|
RLDBG(("start ratelimit"));
|
|
rl->ratelimit_active = 1;
|
|
rl->ratelimit_start = now;
|
|
rl->last_log = now;
|
|
rl->hysteresis_start = 0;
|
|
rl->ratelimited_events = 1;
|
|
if (active != NULL)
|
|
*active = 1;
|
|
return 1;
|
|
} else if (rl->hysteresis_start != 0) {
|
|
/* active and over threshold; reset hysteresis timer */
|
|
RLDBG(("clear hysteresis"));
|
|
rl->hysteresis_start = 0;
|
|
}
|
|
|
|
/* over threshold or in hysteresis period; log periodically */
|
|
if (active != NULL)
|
|
*active = 1;
|
|
RLDBG(("log_every=%u since_log=%ld", rl->log_every,
|
|
(long)(now - rl->last_log)));
|
|
if (rl->log_every > 0 && now >= rl->last_log + rl->log_every) {
|
|
RLDBG(("periodic: since_last=%u", rl->ratelimited_events));
|
|
rl->last_log = now;
|
|
if (events_dropped != NULL) {
|
|
*events_dropped = rl->ratelimited_events;
|
|
rl->ratelimited_events = 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* drop event */
|
|
rl->ratelimited_events++;
|
|
RLDBG(("drop: ratelimited_events=%u", rl->ratelimited_events));
|
|
return 1;
|
|
}
|