Commit 27b0f54c authored by Daniel Salzman's avatar Daniel Salzman

Merge branch 'timestamp_io_refactor' into 'master'

Timestamp io refactor

See merge request !752
parents 5fc62cd4 0489393e
......@@ -53,27 +53,28 @@ Print the program help.
\fB\-V\fP, \fB\-\-version\fP
Print the program version.
.TP
\fB\-t\fP \fBtsig_name\fP [\fItsig_algorithm\fP] [\fItsig_bits\fP]
Generates TSIG key. TSIG algorithm can be specified by string (default: hmac\-sha256),
\fB\-t\fP, \fB\-\-tsig\fP \fItsig_name\fP [\fItsig_algorithm\fP] [\fItsig_bits\fP]
Generates a TSIG key. TSIG algorithm can be specified by string (default: hmac\-sha256),
bit length of the key by number (default: optimal length given by algorithm).
.UNINDENT
.SS Config options
.INDENT 0.0
.TP
\fB\-c\fP
Use specified Knot DNS configuration file path.
\fB\-c\fP, \fB\-\-config\fP \fIfile\fP
Use a textual configuration file (default is \fB@config_dir@/knot.conf\fP).
.TP
\fB\-C\fP
Use specified Knot DNS configuration database path. The default configuration
database, if exists, has a preference to the default configuration file.
\fB\-C\fP, \fB\-\-confdb\fP \fIdirectory\fP
Use a binary configuration database directory (default is \fB@storage_dir@/confdb\fP).
The default configuration database, if exists, has a preference to the default
configuration file.
.TP
\fB\-d\fP
\fB\-d\fP, \fB\-\-dir\fP \fIpath\fP
Use specified KASP database path and default configuration.
.UNINDENT
.SS Commands
.INDENT 0.0
.TP
\fBlist\fP
\fBlist\fP [\fItimestamp_format\fP]
Prints the list of key IDs and parameters of keys belonging to the zone.
.TP
\fBgenerate\fP [\fIarguments\fP\&...]
......@@ -148,15 +149,27 @@ Timestamp for key ot be deleted.
Zero timestamp means infinite future.
.TP
\fIUNIX_time\fP
Positive number of seconds since 1970.
Positive number of seconds since 1970 UTC.
.TP
\fIYYYYMMDDHHMMSS\fP
Date and time in this format without any punctuation.
.TP
\fIrelative_timestamp\fP
The word "now" followed by sign (+, \-), a number and a shortcut for time unit
(y, mo, d, h, mi, (nothing = seconds)), e.g. now+1mi, now\-2mo, now+10,
now+0, now\-1y, ...
A sign character (\fB+\fP, \fB\-\fP), a number, and an optional time unit
(\fBy\fP, \fBmo\fP, \fBd\fP, \fBh\fP, \fBmi\fP, \fBs\fP). The default unit is one second.
E.g. +1mi, \-2mo.
.UNINDENT
.SS Output timestamp formats
.INDENT 0.0
.TP
(none)
The timestamps are printed as UNIX timestamp.
.TP
\fBhuman\fP
The timestamps are printed relatively to now using time units (e.g. \-2y5mo, +1h13s).
.TP
\fBiso\fP
The timestamps are printed in the ISO8601 format (e.g. 2016\-12\-31T23:59:00).
.UNINDENT
.SH EXAMPLES
.INDENT 0.0
......@@ -180,7 +193,7 @@ Generate new DNSSEC key:
.nf
.ft C
$ keymgr example.com. generate algorithm=ECDSAP256SHA256 size=256 \e
ksk=true created=1488034625 publish=20170223205611 retire=now+10mo remove=now+1y
ksk=true created=1488034625 publish=20170223205611 retire=+10mo remove=+1y
.ft P
.fi
.UNINDENT
......@@ -204,7 +217,7 @@ Configure key timing:
.sp
.nf
.ft C
$ keymgr example.com. set 4208 active=now+2mi retire=now+4mi remove=now+5mi
$ keymgr example.com. set 4208 active=+2mi retire=+4mi remove=+5mi
.ft P
.fi
.UNINDENT
......
......@@ -30,27 +30,28 @@ Basic options
**-V**, **--version**
Print the program version.
**-t** **tsig_name** [*tsig_algorithm*] [*tsig_bits*]
Generates TSIG key. TSIG algorithm can be specified by string (default: hmac-sha256),
**-t**, **--tsig** *tsig_name* [*tsig_algorithm*] [*tsig_bits*]
Generates a TSIG key. TSIG algorithm can be specified by string (default: hmac-sha256),
bit length of the key by number (default: optimal length given by algorithm).
Config options
..............
**-c**
Use specified Knot DNS configuration file path.
**-c**, **--config** *file*
Use a textual configuration file (default is :file:`@config_dir@/knot.conf`).
**-C**
Use specified Knot DNS configuration database path. The default configuration
database, if exists, has a preference to the default configuration file.
**-C**, **--confdb** *directory*
Use a binary configuration database directory (default is :file:`@storage_dir@/confdb`).
The default configuration database, if exists, has a preference to the default
configuration file.
**-d**
**-d**, **--dir** *path*
Use specified KASP database path and default configuration.
Commands
........
**list**
**list** [*timestamp_format*]
Prints the list of key IDs and parameters of keys belonging to the zone.
**generate** [*arguments*...]
......@@ -125,15 +126,27 @@ Timestamps
Zero timestamp means infinite future.
*UNIX_time*
Positive number of seconds since 1970.
Positive number of seconds since 1970 UTC.
*YYYYMMDDHHMMSS*
Date and time in this format without any punctuation.
*relative_timestamp*
The word "now" followed by sign (+, -), a number and a shortcut for time unit
(y, mo, d, h, mi, (nothing = seconds)), e.g. now+1mi, now-2mo, now+10,
now+0, now-1y, ...
A sign character (**+**, **-**), a number, and an optional time unit
(**y**, **mo**, **d**, **h**, **mi**, **s**). The default unit is one second.
E.g. +1mi, -2mo.
Output timestamp formats
........................
(none)
The timestamps are printed as UNIX timestamp.
**human**
The timestamps are printed relatively to now using time units (e.g. -2y5mo, +1h13s).
**iso**
The timestamps are printed in the ISO8601 format (e.g. 2016-12-31T23:59:00).
Examples
--------
......@@ -145,7 +158,7 @@ Examples
2. Generate new DNSSEC key::
$ keymgr example.com. generate algorithm=ECDSAP256SHA256 size=256 \
ksk=true created=1488034625 publish=20170223205611 retire=now+10mo remove=now+1y
ksk=true created=1488034625 publish=20170223205611 retire=+10mo remove=+1y
3. Import a DNSSEC key from BIND::
......@@ -153,7 +166,7 @@ Examples
4. Configure key timing::
$ keymgr example.com. set 4208 active=now+2mi retire=now+4mi remove=now+5mi
$ keymgr example.com. set 4208 active=+2mi retire=+4mi remove=+5mi
5. Share a KSK from another zone::
......
......@@ -14,6 +14,14 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <assert.h>
#include <ctype.h>
#include <limits.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "contrib/time.h"
#ifndef HAVE_CLOCK_GETTIME
#include <sys/time.h>
......@@ -56,3 +64,333 @@ double time_diff_ms(const struct timespec *begin, const struct timespec *end)
return (result.tv_sec * 1e3) + (result.tv_nsec / 1e6);
}
typedef struct {
const char *format;
const char *timespec;
const char *parsed;
knot_timediff_t offset;
char offset_sign;
char offset_unit;
struct tm calendar;
int error;
} time_ctx_t;
// After casting (struct tm) to (int []), we can use indexes...
static int calendar_index(char ind)
{
switch (ind) {
case 'Y': return 5;
case 'M': return 4;
case 'D': return 3;
case 'h': return 2;
case 'm': return 1;
case 's': return 0;
default: assert(0); return 6;
}
}
static size_t calendar_digits(int index)
{
return index == 5 ? 4 : 2;
}
static size_t unit_value(char unit)
{
size_t val = 1;
switch (unit) {
case 'M':
return 3600 * 24 * 30;
case 'Y':
val *= 365;
case 'D':
val *= 24;
case 'h':
val *= 60;
case 'm':
val *= 60;
case 's':
default:
return val;
}
}
static knot_time_t time_ctx_finalize(time_ctx_t *ctx)
{
if (ctx->offset_sign) {
ctx->offset *= unit_value(ctx->offset_unit);
return knot_time_add(knot_time(), (ctx->offset_sign == '-' ? -1 : 1) * ctx->offset);
} else if (ctx->offset) {
return (knot_time_t)ctx->offset;
} else if (ctx->calendar.tm_year != 0) {
ctx->calendar.tm_isdst = -1;
ctx->calendar.tm_year -= 1900;
ctx->calendar.tm_mon -= 1;
// Set UTC timezone before using mktime
putenv("TZ=UTC");
tzset();
return (knot_time_t)mktime(&ctx->calendar);
} else {
return (knot_time_t)0;
}
}
static void time_ctx_reset(time_ctx_t *ctx)
{
ctx->parsed = ctx->timespec;
ctx->offset = 0;
ctx->offset_sign = 0;
memset(&ctx->calendar, 0, sizeof(ctx->calendar));
ctx->error = 0;
}
static void parse_quote(time_ctx_t *ctx)
{
while (*ctx->format != '|' && *ctx->format != '\0') {
if (*ctx->format == '\'') {
ctx->format++;
return;
}
if (*ctx->format++ != *ctx->parsed++) {
ctx->error = -1;
return;
}
}
ctx->error = -2;
return;
}
static void parse_offset(time_ctx_t *ctx)
{
ctx->offset = 0;
ctx->error = -1;
while (isdigit(*ctx->parsed)) {
ctx->offset *= 10;
ctx->offset += *ctx->parsed++ - '0';
ctx->error = 0;
}
}
static void parse_calendar(time_ctx_t *ctx, int index)
{
int *cal_arr = (int *)&ctx->calendar;
cal_arr[index] = 0;
for (size_t i = 0; i < calendar_digits(index); i++) {
if (!isdigit(*ctx->parsed)) {
ctx->error = -1;
return;
}
cal_arr[index] *= 10;
cal_arr[index] += *ctx->parsed++ - '0';
}
}
static void parse_sign(time_ctx_t *ctx)
{
char sign1 = *(ctx->format - 1), sign2 = *ctx->format;
bool use_sign2 = (sign2 == '+' || sign2 == '-');
bool allow_plus = (sign1 == '+' || (sign1 == '-' && sign2 == '+'));
bool allow_minus = (sign1 == '-' || (sign1 == '+' && sign2 == '-'));
assert(sign1 == '+' || sign1 == '-');
if ((*ctx->parsed == '+' && allow_plus) || (*ctx->parsed == '-' && allow_minus)) {
ctx->offset_sign = *ctx->parsed++;
ctx->format += (use_sign2 ? 1 : 0);
} else {
ctx->error = -11;
}
}
static void parse_unit1(time_ctx_t *ctx)
{
char u = *ctx->parsed++;
switch (u) {
case 'Y':
case 'M':
case 'D':
case 'h':
case 'm':
case 's':
ctx->offset_unit = u;
break;
default:
ctx->error = -1;
}
}
static void parse_unit2(time_ctx_t *ctx)
{
char u = *ctx->parsed++;
switch (u) {
case 'y':
case 'd':
ctx->offset_unit = toupper(u);
break;
case 'h':
case 's':
ctx->offset_unit = u;
break;
case 'm':
switch (*ctx->parsed++) {
case 'o':
ctx->offset_unit = 'M';
break;
case 'i':
ctx->offset_unit = 'm';
break;
default:
ctx->error = -1;
}
break;
default:
ctx->error = -1;
}
}
int knot_time_parse(const char *format, const char *timespec, knot_time_t *time)
{
if (format == NULL || timespec == NULL || time == NULL) {
return -1;
}
time_ctx_t ctx = {
.format = format,
.timespec = timespec,
.parsed = timespec,
.offset = 0,
.offset_sign = 0,
// we hope that .calendar is zeroed by default
.error = 0,
};
while (ctx.error == 0 && *ctx.format != '\0') {
switch (*ctx.format++) {
case '|':
if (*ctx.parsed == '\0') {
*time = time_ctx_finalize(&ctx);
return 0;
} else {
time_ctx_reset(&ctx);
}
break;
case '\'':
parse_quote(&ctx);
break;
case '#':
parse_offset(&ctx);
break;
case 'Y':
case 'M':
case 'D':
case 'h':
case 'm':
case 's':
parse_calendar(&ctx, calendar_index(*(ctx.format - 1)));
break;
case '+':
case '-':
parse_sign(&ctx);
break;
case 'U':
parse_unit1(&ctx);
break;
case 'u':
parse_unit2(&ctx);
break;
default:
return -1;
}
if (ctx.error < 0) {
while (*ctx.format != '|' && *ctx.format != '\0') {
ctx.format++;
}
time_ctx_reset(&ctx);
ctx.error = (*ctx.format == '\0' ? -1 : 0);
}
}
if (ctx.error == 0 && *ctx.parsed == '\0') {
*time = time_ctx_finalize(&ctx);
return 0;
}
return -1;
}
static char *unit_names_mixed[] = { "Y", "M", "D", "h", "m", "s" };
static char *unit_names_lower[] = { "y", "mo", "d", "h", "mi", "s" };
static size_t unit_sizes[] = { 3600*24*365, 3600*24*30, 3600*24, 3600, 60, 1 };
static const size_t unit_count = 6;
static int print_unit(char *dst, size_t dst_len, char *unit_names[unit_count],
size_t max_units, knot_time_t time)
{
int ret;
if (time == 0) {
ret = snprintf(dst, dst_len, "0");
return (ret < 0 || ret >= dst_len ? -1 : 0);
}
knot_timediff_t diff = knot_time_diff(time, knot_time());
if (dst_len-- < 1) {
return -1;
}
*dst++ = (diff < 0 ? '-' : '+');
if (diff < 0) {
diff = -diff;
} else if (diff == 0) {
ret = snprintf(dst, dst_len, "0%s", unit_names[unit_count - 1]);
return (ret < 0 || ret >= dst_len ? -1 : 0);
}
size_t curr_unit = 0, used_units = 0;
while (curr_unit < unit_count && used_units < max_units) {
if (diff >= unit_sizes[curr_unit]) {
ret = snprintf(dst, dst_len, "%"KNOT_TIMEDIFF_PRINTF"%s",
diff / unit_sizes[curr_unit],
unit_names[curr_unit]);
if (ret < 0 || ret >= dst_len) {
return -1;
}
dst += ret;
dst_len -= ret;
used_units++;
diff %= unit_sizes[curr_unit];
}
curr_unit++;
}
return 0;
}
int knot_time_print(knot_time_print_t format, knot_time_t time, char *dst, size_t dst_len)
{
if (dst == NULL) {
return -1;
}
int ret;
switch (format) {
case TIME_PRINT_UNIX:
ret = snprintf(dst, dst_len, "%"KNOT_TIME_PRINTF, time);
return ((ret >= 0 && ret < dst_len) ? 0 : -1);
case TIME_PRINT_ISO8601:
if (time > LONG_MAX) {
return -1;
}
struct tm lt;
time_t tt = (time_t)time;
ret = (localtime_r(&tt, &lt) == NULL ? -1 :
strftime(dst, dst_len, "%Y-%m-%dT%H:%M:%S", &lt));
return (ret > 0 ? 0 : -1);
case TIME_PRINT_RELSEC:
ret = snprintf(dst, dst_len, "%+"KNOT_TIMEDIFF_PRINTF,
knot_time_diff(time, knot_time()));
return ((ret >= 0 && ret < dst_len) ? 0 : -1);
case TIME_PRINT_HUMAN_MIXED:
return print_unit(dst, dst_len, unit_names_mixed, unit_count, time);
case TIME_PRINT_HUMAN_LOWER:
return print_unit(dst, dst_len, unit_names_lower, unit_count, time);
default:
return -1;
}
}
......@@ -18,6 +18,18 @@
#include <stdint.h>
#include <time.h>
#include <inttypes.h>
/*!
* \brief Specify output format for knot_time_print().
*/
typedef enum {
TIME_PRINT_UNIX, // numeric UNIX time
TIME_PRINT_ISO8601, // 2016-12-31T23:59:00
TIME_PRINT_RELSEC, // relative +6523
TIME_PRINT_HUMAN_MIXED, // relative with mixed-case units
TIME_PRINT_HUMAN_LOWER, // relative with lower-case units
} knot_time_print_t;
/*!
* \brief Get current time.
......@@ -50,6 +62,9 @@ typedef int64_t knot_timediff_t;
#define KNOT_TIMEDIFF_MIN INT64_MIN
#define KNOT_TIMEDIFF_MAX INT64_MAX
#define KNOT_TIME_PRINTF PRIu64
#define KNOT_TIMEDIFF_PRINTF PRId64
/*!
* \brief Returns current time sice epoch.
*/
......@@ -115,3 +130,42 @@ inline static knot_time_t knot_time_from_u32(uint32_t u32time)
{
return (knot_time_t)u32time;
}
/*!
* \brief Parse a text-formatted timestamp to knot_time_t using format specification.
*
* \param format The timestamp text format specification.
* \param timespec Text-formatted timestamp.
* \param time The parsed timestamp.
*
* The format specification basics:
* <format 1>|<format2> - The pipe sign separates two time format specifications. Leftmost
* specification matching the timespec is used.
* '<a string>' - Matches exactly <a string> (not containing apostrophes) in timespec.
* # - Hashtag matches for a number in timespec, stands for either a UNIX timestamp,
* or, within a context of an unit, as a number of such units.
* Y, M, D, h, m, s - Matches a number, stands for a number of years, months, days, hours,
* minutes and seconds, respectively.
* +, - - The + and - signs declaring that following timespec is relative to "now".
* A single sign can be used to limit the timestamp being in future or in past,
* or both +- allow the timestamp to select any (just one) of them.
* U - Matches one of Y, M, D, h, m, s in the timespec standing for a time unit.
* u - Like U, but the unit in the timestamp is from: y, mo, d, h, mi, s.
*
* \retval -1 An error occured, out_time has no sense.
* \return 0 OK, timestamp parsed successfully.
*/
int knot_time_parse(const char *format, const char *timespec, knot_time_t *time);
/*!
* \brief Print the timestamp in specified format into a string buffer.
*
* \param format The timestamp text format specification.
* \param time The timestamp to be printed.
* \param dst The destination buffer pointer with text-formatted timestamp.
* \param dst_len The destination buffer length.
*
* \retval -1 An error occured, the buffer may be filled with nonsense.
* \return 0 OK, timestamp printed successfully.
*/
int knot_time_print(knot_time_print_t format, knot_time_t time, char *dst, size_t dst_len);
......@@ -254,11 +254,8 @@ static int zone_status(zone_t *zone, ctl_args_t *args)
} else if (ev_time <= time(NULL)) {
ret = snprintf(buff, sizeof(buff), "pending");
} else {
ev_time -= time(NULL);
ret = snprintf(buff, sizeof(buff), "in %lldh%lldm%llds",
(long long)(ev_time / 3600),
(long long)(ev_time % 3600) / 60,
(long long)(ev_time % 60));
ret = knot_time_print(TIME_PRINT_HUMAN_MIXED,
ev_time, buff, sizeof(buff));
}
if (ret < 0 || ret >= sizeof(buff)) {
return KNOT_ESPACE;
......
......@@ -32,57 +32,6 @@
#include "zscanner/scanner.h"
#include "contrib/base64.h"
static time_t arg_timestamp(const char *arg)
{
if (isdigit((int)arg[0]) && strlen(arg) < 12) {
return atol(arg); // unixtime
}
if (isdigit((int)arg[0]) && strlen(arg) == 14) {
struct tm tm = { 0 };
char *end = strptime(arg, "%Y%m%d%H%M%S", &tm);
if (end == NULL || *end != '\0') {
return -1;
}
return mktime(&tm); // time format
}
long amount;
if (strncasecmp(arg, "now+", 4) == 0) {
amount = atol(arg + 4);
} else if (strncasecmp(arg, "now-", 4) == 0) {
amount = 0 - atol(arg + 4);
} else if (strncasecmp(arg, "t+", 2) == 0) {
amount = atol(arg + 2);
} else if (strncasecmp(arg, "t-", 2) == 0) {
amount = 0 - atol(arg + 2);
} else if (arg[0] == '+' || arg[0] == '-') {
amount = atol(arg);
} else {
return -1;
}
char *unit = strrchr(arg, '0' + (labs(amount) % 10));
if (unit++ == NULL) {
return -1;
}
time_t now = time(NULL);
switch ((*unit == 'm') ? 'm' + *(unit + 1) : *unit) {
case 'm' + 'i':
return now + amount * 60;
case 'h':
return now + amount * 3600;
case 'd':
return now + amount * 3600 * 24;
case 'w':
return now + amount * 3600 * 24 * 7;
case 'm' + 'o':
return now + amount * 3600 * 24 * 30; // this is lame but same as keymgr
case 'y':
return now + amount * 3600 * 24 * 365;
case '\0':
return now + amount;
}
return -1;
}
static bool genkeyargs(int argc, char *argv[], bool just_timing,
bool *isksk, dnssec_key_algorithm_t *algorithm,
uint16_t *keysize, knot_kasp_key_timing_t *timing)
......@@ -134,29 +83,31 @@ static bool genkeyargs(int argc, char *argv[], bool just_timing,
strncasecmp(argv[i], "active=", 7) == 0 ||
strncasecmp(argv[i], "retire=", 7) == 0 ||
strncasecmp(argv[i], "remove=", 7) == 0) {
time_t stamp = arg_timestamp(strchr(argv[i], '=') + 1);
if (stamp < 0) {
knot_time_t stamp;
int ret = knot_time_parse("YMDhms|'now'+-#u|'t'+-#u|+-#u|'t'+-#|+-#|#",
strchr(argv[i], '=') + 1, &stamp);
if (ret < 0) {
printf("Invalid timestamp: %s\n", argv[i]);
return false;
}
switch ((argv[i][0] == 'r') ? argv[i][3] : argv[i][0]) {
case 'c':
timing->created = (knot_time_t)stamp;
timing->created = stamp;
break;
case 'a':
timing->active = (knot_time_t)stamp;
timing->active = stamp;
break;
case 'd':
timing->ready = (knot_time_t)stamp;
timing->ready = stamp;
break;
case 'p':
timing->publish = (knot_time_t)stamp;
timing->publish = stamp;
break;
case 'i':
timing->retire = (knot_time_t)stamp;
timing->retire = stamp;
break;
case 'o':
timing->remove = (knot_time_t)stamp;
timing->remove = stamp;
break;
}
} else {
......@@ -613,17 +564,30 @@ int keymgr_set_timing(knot_kasp_key_t *key, int argc, char *argv[])
return KNOT_EINVAL;
}
int keymgr_list_keys(kdnssec_ctx_t *ctx)
static void print_timer(const char *name, knot_time_t t, knot_time_print_t format,
char separator)
{
static char buff[100];
if (knot_time_print(format, t, buff, sizeof(buff)) < 0) {
printf("%s=(error)%c", name, separator); // shall not happen
} else {
printf("%s=%s%c", name, buff, separator);
}
}
int keymgr_list_keys(kdnssec_ctx_t *ctx, knot_time_print_t format)
{
for (size_t i = 0; i < ctx->zone->num_keys; i++) {
knot_kasp_key_t *key = &ctx->zone->keys[i];
printf("%s ksk=%s tag=%05d algorithm=%d created=%lld publish=%lld ready=%lld"
" active=%lld retire=%lld remove=%lld\n", key->id,
printf("%s ksk=%s tag=%05d algorithm=%d ", key->id,
((dnssec_key_get_flags(key->key) == dnskey_flags(true)) ? "yes" : "no "),
dnssec_key_get_keytag(key->key), (int)dnssec_key_get_algorithm(key->key),
(long long)key->timing.created, (long long)key->timing.publish,
(long long)key->timing.ready, (long long)key->timing.active,
(long long)key->timing.retire, (long long)key->timing.remove);
dnssec_key_get_keytag(key->key), (int)dnssec_key_get_algorithm(key->key));
print_timer("created", key->timing.created, format, ' ');