* Update dictionary.aruba
* Added "radsecret" program which generates strong secrets. See the
top of the "clients.conf" file for more information.
+ * Add "time_since" xlat to calculate elapsed time in seconds, milliseconds
+ and microseconds.
Bug fixes
* Fix corner case with empty defaults in rlm_files. Fixes #5035
# default = no
# utc = yes
}
+
+#
+# The date module also provides the %{time_since:} xlat, which
+# makes it possible to both:
+# - get the time since the epoch in seconds, milliseconds or
+# microseconds; and
+# - calculate the time elapsed since a given time.
+#
+# Syntax is: %{time_since:BASE[:(number|&attribute)]}
+# where "BASE" is "s", "ms" or "us".
+#
+# Examples:
+# %{time_since:s}
+# - time in seconds since the epoch, same as %c
+#
+# %{time_since:s:1695753388}
+# - time in seconds since Tue 26 Sep 19:36:28 BST 2023
+# (which is 1695753388 in UNIX time)
+#
+# %{time_since:s:&Tmp-Integer-0}
+# - Time since the number of seconds in Tmp-Integer-0
+#
+# %{time_since:ms}
+# - Milliseconds since the epoch
+#
+# %{time_since:us}
+# - Microseconds since the epoch
+#
+# The provided attribute should be an Integer (or Integer64 for
+# ms or us bases). However, other attributes will be converted if
+# possible, with a warning given. The only one that might make
+# sense is a Date attribute (which will be scaled appropriately
+# according to the base, as Date is always in seconds).
+#
+# Primary usage would be for taking latenct measurements, for
+# example to calculate the number of microseconds an LDAP call
+# took:
+#
+# update request {
+# &Tmp-Integer64-0 := %{time_since:us}"
+# }
+# ldap
+# update request {
+# &Tmp-Integer64-1 := %{time_since:us:&Tmp-Integer64-0}"
+# }
* @brief Translates timestrings between formats.
*
* @author Artur Malinowski <artur@wow.com>
+ * @author Matthew Newton
*
* @copyright 2013 Artur Malinowski <artur@wow.com>
- * @copyright 1999-2013 The FreeRADIUS Server Project.
+ * @copyright 1999-2023 The FreeRADIUS Server Project.
*/
#include <freeradius-devel/radiusd.h>
}
DIAG_ON(format-nonliteral)
+
+/** Get time in ms since either epoch or another value
+ *
+ * %{time_since:s} - return seconds since epoch
+ * %{time_since:ms:0} - return milliseconds since epoch
+ * %{time_since:us:1695745763034443} - return microseconds since Tue 26 Sep 17:29:23.034443 BST 2023
+ * %{time_since:us:&Tmp-Integer64-1} - return microseconds since value in &Tmp-Integer64-1
+ */
+
+static ssize_t xlat_time_since(UNUSED void *instance, REQUEST *request, char const *fmt, char *out, size_t outlen)
+{
+ uint64_t time_now = 0;
+ uint64_t time_delta = 0;
+ uint64_t time_since = 0;
+ struct timeval tv;
+
+ enum timebase {
+ S = 1,
+ MS = 1000,
+ US = 1000000,
+ };
+ enum timebase time_base;
+
+ while (isspace((uint8_t) *fmt)) fmt++;
+
+ /*
+ * Work out what time base we are using, s, ms or us.
+ */
+ if (fmt[0] == 'm' && fmt[1] == 's') {
+ time_base = MS;
+ fmt += 2;
+ } else if (fmt[0] == 'u' && fmt[1] == 's') {
+ time_base = US;
+ fmt += 2;
+ } else if (fmt[0] == 's') {
+ time_base = S;
+ fmt++;
+ } else {
+ REDEBUG("Time base (ms, us, s) missing in time_since xlat");
+ error:
+ *out = '\0';
+ return -1;
+ }
+
+ if (fmt[0] != '\0' && fmt[0] != ':') {
+ REDEBUG("Invalid arguments passed to time_since xlat");
+ goto error;
+ }
+
+ if (fmt[0] == ':') fmt++;
+
+ /*
+ * Handle the different formats that we can be passed
+ */
+ if (fmt[0] == '\0') {
+ /*
+ * %{time_since:[mu]?s:} - epoch
+ */
+ time_since = 0;
+
+ } else if (fmt[0] == '&') {
+ /*
+ * We were provided with an attribute
+ *
+ * %{time_since:[mu]?s:&Attr-Name}
+ */
+ value_data_t outnum;
+ VALUE_PAIR *vp;
+
+ if ((radius_get_vp(&vp, request, fmt) < 0) || !vp) {
+ REDEBUG("Unable to parse attribute in time_ms_since xlat");
+ goto error;
+ }
+
+ if (vp->da->type == PW_TYPE_INTEGER64) {
+ /*
+ * Int64 is easy
+ */
+ time_since = vp->vp_integer64;
+ } else {
+ /*
+ * ...but not others - try and convert, but warn it's likely nonsensical.
+ */
+ if (value_data_cast(request, &outnum,
+ PW_TYPE_INTEGER64, NULL, vp->da->type, NULL,
+ &vp->data, vp->vp_length) < 0) {
+ REDEBUG("Unable to convert %s to integer", fmt);
+ goto error;
+ }
+ if (vp->da->type == PW_TYPE_DATE) {
+ /*
+ * Special case a Date - we know it's seconds
+ */
+ RDEBUG3("Attribute \"%s\" is a date; multiplying seconds by %d", fmt, time_base);
+ time_since = outnum.integer64 * time_base;
+ } else {
+ RWDEBUG("Attribute \"%s\" is not integer, conversion may not make sense", fmt);
+ time_since = outnum.integer64;
+ }
+ }
+
+ } else if (fmt[0] == '-') {
+ REDEBUG("time_since xlat only accepts positive integers");
+ goto error;
+
+ } else {
+ /*
+ * Otherwise we hope we were provided with an integer value
+ *
+ * %{time_since:[mu]?s:12345}
+ */
+ if (sscanf(fmt, "%" PRIu64, &time_since) != 1) {
+ REDEBUG("Failed parsing \"%s\" as integer", fmt);
+ goto error;
+ }
+ }
+
+ /*
+ * Get current time and add milli/micro component if needed
+ */
+ gettimeofday(&tv, NULL);
+
+ time_now = (uint64_t)tv.tv_sec * time_base;
+
+ if (time_base == MS) {
+ time_now += (uint64_t)tv.tv_usec / 1000;
+ } else if (time_base == US) {
+ time_now += (uint64_t)tv.tv_usec;
+ }
+
+ /*
+ * time_since needs to be in the past
+ */
+ if (time_since > time_now) {
+ REDEBUG("time provided to time_since needs to be in the past");
+ goto error;
+ }
+
+ /*
+ * Calculate time since provided value
+ */
+ time_delta = time_now - time_since;
+
+ /*
+ * Write out and return
+ */
+ if ((size_t)snprintf(out, outlen, "%" PRIu64, time_delta) >= outlen) {
+ REDEBUG("Insufficient space to write 64-bit time value");
+ goto error;
+ }
+
+ return 0;
+}
+
+
static int mod_bootstrap(CONF_SECTION *conf, void *instance)
{
rlm_date_t *inst = instance;
}
xlat_register(inst->xlat_name, xlat_date_convert, NULL, inst);
+ xlat_register("time_since", xlat_time_since, NULL, inst);
return 0;
}
--- /dev/null
+#
+# Test the "date" module
+#
--- /dev/null
+#
+# Input packet
+#
+Packet-Type = Access-Request
+User-Name = 'Bob'
+User-Password = 'Alice'
+Tmp-Integer-1 = "12345"
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Accept
+
--- /dev/null
+#
+# Selection of tests for the %{time_since:} xlat
+#
+# Somewhat limited in what we can do here, as it bases its
+# responses off the current system time. So we need to do some
+# comparisons rather than actual value checks.
+#
+
+#
+# %c and %{time_since:s:0} should match
+#
+update {
+ &Tmp-Integer-9 := 0
+}
+
+update {
+ &Tmp-Integer-0 := "%c"
+ &Tmp-Integer-1 := "%{time_since:s:0}"
+ &Tmp-Integer-2 := "%{time_since:s:&Tmp-Integer-9}"
+}
+
+if (&Tmp-Integer-0 != &Tmp-Integer-1) {
+ if (&Tmp-Integer-0 != "%{expr:&Tmp-Integer-1 - 1}") {
+ # at a push, %{time_since:s:0} might be one second later,
+ # depending on when the test ran
+ test_fail
+ }
+}
+
+if (&Tmp-Integer-1 != &Tmp-Integer-2) {
+ if (&Tmp-Integer-1 != "%{expr:&Tmp-Integer-2 - 1}") {
+ test_fail
+ }
+}
+
+#
+# If we run time_since 3 times, they should be the same or increasing
+#
+update {
+ &Tmp-Integer64-0 := "%{time_since:s:0}"
+}
+
+update {
+ &Tmp-Integer64-1 := "%{time_since:s:}"
+}
+
+update {
+ &Tmp-Integer64-2 := "%{time_since:s}"
+}
+
+if (&Tmp-Integer64-0 > &Tmp-Integer64-1 || \
+ &Tmp-Integer64-1 > &Tmp-Integer64-2 || \
+ &Tmp-Integer64-0 > &Tmp-Integer64-2) {
+ test_fail
+}
+
+#
+# It's way past the year 2020, so this should only fail if the
+# computer's clock is very wrong...
+#
+if (&Tmp-Integer64-0 < 1600000000) {
+ test_fail
+}
+
+
+#
+# Similar for milliseconds
+#
+update {
+ &Tmp-Integer64-3 := "%{time_since:ms:0}"
+}
+
+update {
+ &Tmp-Integer64-4 := "%{time_since:ms:}"
+}
+
+update {
+ &Tmp-Integer64-5 := "%{time_since:ms}"
+}
+
+if (&Tmp-Integer64-3 > &Tmp-Integer64-4 || \
+ &Tmp-Integer64-4 > &Tmp-Integer64-5 || \
+ &Tmp-Integer64-3 > &Tmp-Integer64-5) {
+ test_fail
+}
+
+
+#
+# ...and microseconds
+#
+update {
+ &Tmp-Integer64-6 := "%{time_since:us:0}"
+}
+
+update {
+ &Tmp-Integer64-7 := "%{time_since:us:}"
+}
+
+update {
+ &Tmp-Integer64-8 := "%{time_since:us}"
+}
+
+if (&Tmp-Integer64-6 > &Tmp-Integer64-7 || \
+ &Tmp-Integer64-7 > &Tmp-Integer64-8 || \
+ &Tmp-Integer64-6 > &Tmp-Integer64-8) {
+ test_fail
+}
+
+if ("%{expr:&Tmp-Integer64-7 - &Tmp-Integer64-6}" > 250) {
+ # you have a really slow computer if the time between
+ # getting these took more than 250us
+ test_fail
+}
+
+
+#
+# Seconds component * 1000 must always be same or less than
+# milliseconds, and microseconds.
+#
+if ("%{expr:%{time_since:s:0} * 1000}" > "%{time_since:ms:0}") {
+ test_fail
+}
+
+if ("%{expr:%{time_since:ms:0} * 1000}" > "%{time_since:us:0}") {
+ test_fail
+}
+
+if ("%{expr:%{time_since:s:0} * 1000000}" > "%{time_since:us:0}") {
+ test_fail
+}
+
+
+#
+# Test for some errors
+#
+
+# missing time base
+update {
+ &Tmp-Integer-0 := "%{time_since:}"
+}
+
+if (!(&Module-Failure-Message[*] == 'Time base (ms, us, s) missing in time_since xlat')) {
+ test_fail
+}
+
+update {
+ &Module-Failure-Message !* ANY
+}
+
+
+# invalid time base
+update {
+ &Tmp-Integer-0 := "%{time_since:bob}"
+}
+
+if (!(&Module-Failure-Message[*] == 'Time base (ms, us, s) missing in time_since xlat')) {
+ test_fail
+}
+
+update {
+ &Module-Failure-Message !* ANY
+}
+
+
+# negative values
+update {
+ &Tmp-Integer-0 := "%{time_since:ms:-1234}"
+}
+
+if (!(&Module-Failure-Message[*] == 'time_since xlat only accepts positive integers')) {
+ test_fail
+}
+
+update {
+ &Module-Failure-Message !* ANY
+}
+
+
+# invalid attribute
+update {
+ &Tmp-Integer-0 := "%{time_since:us:&Test-Non-Existant-Attr}"
+}
+
+if (!(&Module-Failure-Message[*] == 'Unable to parse attribute in time_ms_since xlat')) {
+ test_fail
+}
+
+update {
+ &Module-Failure-Message !* ANY
+}
+
+
+# silly text
+update {
+ &Tmp-Integer-0 := "%{time_since:us:test random text}"
+}
+
+if (!(&Module-Failure-Message[*] == 'Failed parsing "test random text" as integer')) {
+ test_fail
+}
+
+update {
+ &Module-Failure-Message !* ANY
+}
+
+
+test_pass
--- /dev/null
+#date unit test config
+date {
+}