diff options
-rw-r--r-- | server/Makefile.am | 20 | ||||
-rw-r--r-- | server/providers/ipa/ipa_timerules.c | 1186 | ||||
-rw-r--r-- | server/providers/ipa/ipa_timerules.h | 56 | ||||
-rw-r--r-- | server/tests/ipa_timerules-tests.c | 579 |
4 files changed, 1841 insertions, 0 deletions
diff --git a/server/Makefile.am b/server/Makefile.am index 800837c3..0c894a66 100644 --- a/server/Makefile.am +++ b/server/Makefile.am @@ -68,6 +68,7 @@ if HAVE_CHECK resolv-tests \ krb5-utils-tests \ check_and_open-tests \ + ipa_timerules-tests \ files-tests \ refcount-tests \ fail_over-tests @@ -294,6 +295,7 @@ dist_noinst_HEADERS = \ providers/ldap/sdap_async_private.h \ providers/ipa/ipa_common.h \ providers/ipa/ipa_access.h \ + providers/ipa/ipa_timerules.h \ tools/tools_util.h \ tools/sss_sync_ops.h \ resolv/async_resolv.h \ @@ -497,6 +499,24 @@ fail_over_tests_LDADD = \ $(SSSD_LIBS) \ $(CHECK_LIBS) \ $(CARES_LIBS) + +ipa_timerules_tests_SOURCES = \ + providers/ipa/ipa_timerules.c \ + tests/ipa_timerules-tests.c \ + tests/common.c \ + $(SSSD_DEBUG_OBJ) +ipa_timerules_tests_CFLAGS = \ + $(AM_CFLAGS) \ + $(POPT_CFLAGS) \ + $(TALLOC_CFLAGS) \ + $(PCRE_CFLAGS) \ + $(CHECK_CFLAGS) +ipa_timerules_tests_LDADD = \ + $(POPT_LIBS) \ + $(PCRE_LIBS) \ + $(TALLOC_LIBS) \ + $(CHECK_LIBS) + endif stress_tests_SOURCES = \ diff --git a/server/providers/ipa/ipa_timerules.c b/server/providers/ipa/ipa_timerules.c new file mode 100644 index 00000000..1a52eef1 --- /dev/null +++ b/server/providers/ipa/ipa_timerules.c @@ -0,0 +1,1186 @@ +/* + SSSD + + IPA Provider Time Rules Parsing + + Authors: + Jakub Hrozek <jhrozek@redhat.com> + + Copyright (C) Red Hat, Inc 2009 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#define _XOPEN_SOURCE /* strptime() needs this */ + +#include <pcre.h> +#include <talloc.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <time.h> +#include <stdbool.h> +#include <limits.h> + +#include "providers/ipa/ipa_timerules.h" +#include "util/util.h" + +#define JMP_NEOK(variable) do { \ + if (variable != EOK) goto done; \ +} while (0) + +#define JMP_NEOK_LABEL(variable, label) do { \ + if (variable != EOK) goto label; \ +} while (0) + +#define CHECK_PTR(ptr) do { \ + if (ptr == NULL) { \ + return ENOMEM; \ + } \ +} while (0) + +#define CHECK_PTR_JMP(ptr) do { \ + if (ptr == NULL) { \ + ret = ENOMEM; \ + goto done; \ + } \ +} while (0) + +#define BUFFER_OR_JUMP(ctx, ptr, count) do { \ + ptr = talloc_array(ctx, unsigned char, count); \ + if (ptr == NULL) { \ + return ENOMEM; \ + } \ + memset(ptr, 0, sizeof(unsigned char)*count); \ +} while (0) + +#define TEST_BIT_RANGE(bitfield, index, resptr) do { \ + if (bitfield) { \ + if (test_bit(&bitfield, index) == 0) { \ + *resptr = false; \ + return EOK; \ + } \ + } \ +} while (0) + +#define TEST_BIT_RANGE_PTR(bitfield, index, resptr) do { \ + if (bitfield) { \ + if (test_bit(bitfield, index) == 0) { \ + *resptr = false; \ + return EOK; \ + } \ + } \ +} while (0) + +/* number of match offsets when matching pcre regexes */ +#define OVEC_SIZE 30 + +/* regular expressions describing syntax of our HBAC grammar */ +#define RGX_WEEKLY "day (?P<day_of_week>(0|1|2|3|4|5|6|7|Mon|Tue|Wed|Thu|Fri|Sat|Sun|,|-)+)" + +#define RGX_MDAY "(?P<mperspec_day>day) (?P<interval_day>[0-9,-]+) " +#define RGX_MWEEK "(?P<mperspec_week>week) (?P<interval_week>[0-9,-]+) "RGX_WEEKLY +#define RGX_MONTHLY RGX_MDAY"|"RGX_MWEEK + +#define RGX_YDAY "(?P<yperspec_day>day) (?P<day_of_year>[0-9,-]+) " +#define RGX_YWEEK "(?P<yperspec_week>week) (?P<week_of_year>[0-9,-]+) "RGX_WEEKLY +#define RGX_YMONTH "(?P<yperspec_month>month) (?P<month_number>[0-9,-]+) (?P<m_period>.*?)$" +#define RGX_YEARLY RGX_YMONTH"|"RGX_YWEEK"|"RGX_YDAY + +#define RGX_TIMESPEC "(?P<timeFrom>[0-9]{4}) ~ (?P<timeTo>[0-9]{4})" + +#define RGX_GENERALIZED "(?P<year>[0-9]{4})(?P<month>[0-9]{2})(?P<day>[0-9]{2})(?P<hour>[0-9]{2})?(?P<minute>[0-9]{2})?(?P<second>[0-9]{2})?" + +#define RGX_PERIODIC "^periodic (?P<perspec>daily|weekly|monthly|yearly) (?P<period>.*?)"RGX_TIMESPEC"$" +#define RGX_ABSOLUTE "^absolute (?P<from>\\S+) ~ (?P<to>\\S+)$" + +/* limits on various parameters */ +#define DAY_OF_WEEK_MAX 7 +#define DAY_OF_MONTH_MAX 31 +#define WEEK_OF_MONTH_MAX 5 +#define WEEK_OF_YEAR_MAX 54 +#define DAY_OF_YEAR_MAX 366 +#define MONTH_MAX 12 +#define HOUR_MAX 23 +#define MINUTE_MAX 59 + +/* limits on sizes of buffers for bit arrays */ +#define DAY_OF_MONTH_BUFSIZE 8 +#define DAY_OF_YEAR_BUFSIZE 44 +#define WEEK_OF_YEAR_BUFSIZE 13 +#define MONTH_BUFSIZE 2 +#define HOUR_BUFSIZE 4 +#define MINUTE_BUFSIZE 8 + +/* Lookup tables for translating names of days and months */ +static const char *names_day_of_week[] = + { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun", NULL }; +static const char *names_months[] = + { "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Nov", "Dec", NULL }; + +/* + * Timelib knows two types of ranges - periodic and absolute + */ +enum rangetypes { + TYPE_ABSOLUTE, + TYPE_PERIODIC +}; + +struct absolute_range { + time_t time_from; + time_t time_to; +}; + +struct periodic_range { + unsigned char day_of_week; + unsigned char *day_of_month; + unsigned char *day_of_year; + unsigned char week_of_month; + unsigned char *week_of_year; + unsigned char *month; + unsigned char *hour; + unsigned char *minute; +}; + +/* + * Context of one time rule being analyzed + */ +struct range_ctx { + /* main context with precompiled patterns */ + struct time_rules_ctx *trctx; + /* enum rangetypes */ + enum rangetypes type; + + struct absolute_range *abs; + struct periodic_range *per; +}; + + +/* + * The context of one regular expression + */ +struct parse_ctx { + /* the regular expression used for one parsing */ + pcre *re; + /* number of matches */ + int matches; + /* vector of matches */ + int *ovec; +}; + +/* indexes to the array of precompiled regexes */ +enum timelib_rgx { + LP_RGX_GENERALIZED, + LP_RGX_MDAY, + LP_RGX_MWEEK, + LP_RGX_YEARLY, + LP_RGX_WEEKLY, + LP_RGX_ABSOLUTE, + LP_RGX_PERIODIC, + LP_RGX_MAX, +}; + +/* matches the indexes */ +static const char *lookup_table[] = { + RGX_GENERALIZED, + RGX_MDAY, + RGX_MWEEK, + RGX_YEARLY, + RGX_WEEKLY, + RGX_ABSOLUTE, + RGX_PERIODIC, + NULL, +}; + +/* + * Main struct passed outside + * holds precompiled regular expressions + */ +struct time_rules_ctx { + pcre *re[LP_RGX_MAX]; +}; + +/******************************************************************* + * helper function - bit arrays * + *******************************************************************/ + +/* set a single bit in a bitmap */ +static void set_bit(unsigned char *bitmap, unsigned int bit) +{ + bitmap[bit/CHAR_BIT] |= 1 << (bit%CHAR_BIT); +} + +/* + * This function is based on bit_nset macro written originally by Paul Vixie, + * copyrighted by The Regents of the University of California, as found + * in tarball of fcron, file bitstring.h + */ +static void set_bit_range(unsigned char *bitmap, unsigned int start, + unsigned int stop) +{ + int startbyte = start/CHAR_BIT; + int stopbyte = stop/CHAR_BIT; + + if (startbyte == stopbyte) { + bitmap[startbyte] |= ((0xff << (start & 0x7)) & + (0xff >> (CHAR_BIT- 1 - (stop & 0x7)))); + } else { + bitmap[startbyte] |= 0xff << (start & 0x7); + while (++startbyte < stopbyte) { + bitmap[startbyte] |= 0xff; + } + bitmap[stopbyte] |= 0xff >> (CHAR_BIT- 1 - (stop & 0x7)); + } +} + +static int test_bit(unsigned char *bitmap, unsigned int bit) +{ + return (int)(bitmap[bit/CHAR_BIT] >> (bit%CHAR_BIT)) & 1; +} + +/******************************************************************* + * parsing intervals * + *******************************************************************/ + +/* + * Some ranges allow symbolic names, like Mon..Sun for names of day. + * This routine takes a list of symbolic names as NAME_ARRAY and the + * one we're looking for as KEY and returns its index or -1 when not + * found. The last member of NAME_ARRAY must be NULL. + */ +static int name_index(const char **name_array, const char *key, int min) +{ + int index = 0; + const char *one; + + if (name_array == NULL) { + return -1; + } + + while ((one = name_array[index]) != NULL) { + if (strcmp(key,one) == 0) { + return index+min; + } + index++; + } + + return -1; +} + +/* + * Sets appropriate bits given by an interval in STR (in form of 1,5-7,10) to + * a bitfield given in OUT. Does no boundary checking. STR can also contain + * symbolic names, these would be given in TRANSLATE. + */ +static int interval2bitfield(TALLOC_CTX *mem_ctx, + unsigned char *out, + const char *str, + int min, int max, + const char **translate) +{ + char *copy; + char *next, *token; + int tokval, tokmax; + char *end_ptr; + int ret; + char *dash; + + DEBUG(9, ("Converting '%s' to interval\n", str)); + + copy = talloc_strdup(mem_ctx, str); + CHECK_PTR(copy); + + next = copy; + while (next) { + token = next; + next = strchr(next, ','); + if (next) { + *next = '\0'; + next++; + } + + errno = 0; + tokval = strtol(token, &end_ptr, 10); + if (*end_ptr == '\0' && errno == 0) { + if (tokval <= max && tokval >= 0) { + set_bit(out, tokval); + continue; + } else { + ret = ERANGE; + goto done; + } + } else if ((dash = strchr(token, '-')) != NULL){ + *dash = '\0'; + ++dash; + + errno = 0; + tokval = strtol(token, &end_ptr, 10); + if (*end_ptr != '\0' || errno != 0) { + tokval = name_index(translate, token, min); + if (tokval == -1) { + ret = ERANGE; + goto done; + } + } + errno = 0; + tokmax = strtol(dash, &end_ptr, 10); + if (*end_ptr != '\0' || errno != 0) { + tokmax = name_index(translate, dash, min); + if (tokmax == -1) { + ret = ERANGE; + goto done; + } + } + + if (tokval <= max && tokmax <= max && + tokval >= min && tokmax >= min) { + if (tokmax > tokval) { + DEBUG(7, ("Setting interval %d-%d\n", tokval, tokmax)); + DEBUG(9, ("interval: %p\n", out)); + set_bit_range(out, tokval, tokmax); + } else { + /* Interval wraps around - i.e. from 18.00 to 06.00 */ + DEBUG(7, ("Setting inverted interval %d-%d\n", tokval, tokmax)); + DEBUG(9, ("interval: %p\n", out)); + set_bit_range(out, min, tokmax); + set_bit_range(out, tokval, max); + } + continue; + } else { + /* tokval or tokmax are not between <min, max> */ + ret = ERANGE; + goto done; + } + } else if ((tokval = name_index(translate, token, min)) != -1) { + /* Try to translate one token by name */ + if (tokval <= max) { + set_bit(out, tokval); + continue; + } else { + ret = ERANGE; + goto done; + } + } else { + ret = EINVAL; + goto done; + } + } + + ret = EOK; +done: + talloc_free(copy); + return ret; +} + +/******************************************************************* + * wrappers around regexp handling * + *******************************************************************/ + +/* + * Copies a named substring SUBSTR_NAME from string STR using the parsing + * information from PCTX. The context PCTX is also used as a talloc context. + * + * The resulting string is stored in OUT. + * Return value is EOK on no error or ENOENT on error capturing the substring + */ +static int copy_substring(struct parse_ctx *pctx, + const char *str, + const char *substr_name, + char **out) +{ + const char *result = NULL; + int ret; + char *o = NULL; + + result = NULL; + + ret = pcre_get_named_substring(pctx->re, str, pctx->ovec, + pctx->matches, substr_name, &result); + if (ret < 0 || result == NULL) { + DEBUG(5, ("named substring '%s' does not exist in '%s'\n", + substr_name, str)); + return ENOENT; + } + + o = talloc_strdup(pctx, result); + pcre_free_substring(result); + if (o == NULL) { + return ENOMEM; + } + + DEBUG(9, ("Copied substring named '%s' value '%s'\n", substr_name, o)); + + *out = o; + return EOK; +} + +/* + * Copies a named substring SUBSTR_NAME from string STR using the parsing + * information from PCTX and converts it to an integer. + * The context PCTX is also used as a talloc context. + * + * The resulting string is stored in OUT. + * Return value is EOK on no error or ENOENT on error capturing the substring + */ +static int substring_strtol(struct parse_ctx *pctx, + const char *str, + const char *substr_name, + int *out) +{ + char *substr = NULL; + int ret; + int val; + char *err_ptr; + + ret = copy_substring(pctx, str, substr_name, &substr); + if (ret != EOK) { + DEBUG(5, ("substring '%s' does not exist\n", substr_name)); + return ret; + } + + errno = 0; + val = strtol(substr, &err_ptr, 10); + if (substr == '\0' || *err_ptr != '\0' || errno != 0) { + DEBUG(5, ("substring '%s' does not contain an integerexist\n", + substr)); + talloc_free(substr); + return EINVAL; + } + + *out = val; + talloc_free(substr); + return EOK; +} + +/* + * Compiles a regular expression REGEXP and tries to match it against the + * string STR. Fills in structure _PCTX with info about matching. + * + * Returns EOK on no error, EFAULT on bad regexp, EINVAL when it cannot + * match the regexp. + */ +static int matches_regexp(TALLOC_CTX *ctx, + struct time_rules_ctx *trctx, + const char *str, + enum timelib_rgx regex, + struct parse_ctx **_pctx) +{ + int ret; + struct parse_ctx *pctx = NULL; + + pctx = talloc_zero(ctx, struct parse_ctx); + CHECK_PTR(pctx); + pctx->ovec = talloc_array(pctx, int, OVEC_SIZE); + CHECK_PTR_JMP(pctx->ovec); + pctx->re = trctx->re[regex]; + + ret = pcre_exec(pctx->re, NULL, str, strlen(str), 0, PCRE_NOTEMPTY, pctx->ovec, OVEC_SIZE); + if (ret <= 0) { + DEBUG(8, ("string '%s' did *NOT* match regexp '%s'\n", str, lookup_table[regex])); + ret = EINVAL; + goto done; + } + DEBUG(8, ("string '%s' matched regexp '%s'\n", str, lookup_table[regex])); + + pctx->matches = ret; + *_pctx = pctx; + return EOK; + +done: + talloc_free(pctx); + return ret; +} + +/******************************************************************* + * date/time helper functions * + *******************************************************************/ + +/* + * Returns week number as an integer + * This may seem ugly, but I think it's actually less error prone + * than writing my own routine + */ +static int weeknum(const struct tm *t) +{ + char buf[3]; + + if (!strftime(buf, 3, "%U", t)) { + return -1; + } + + /* %U returns 0-53, we want 1-54 */ + return atoi(buf)+1; +} + +/* + * Return the week of the month + * Range is 1 to 5 + */ +static int get_week_of_month(const struct tm *t) +{ + int fs; /* first sunday */ + + fs = (t->tm_mday % 7) - t->tm_wday; + if (fs <= 0) { + fs += 7; + } + + return (t->tm_mday <= fs) ? 1 : (2 + (t->tm_mday - fs - 1) / 7); +} + +/* + * Normalize differencies between our HBAC definition and semantics of + * struct tm + */ +static void abs2tm(struct tm *t) +{ + /* tm defines tm_year as num of yrs since 1900, we have absolute number */ + t->tm_year %= 1900; + /* struct tm defines tm_mon as number of month since January */ + t->tm_mon--; +} + +/* + * Normalize differencies between our HBAC definition and semantics of + * struct tm + */ +static void tm2abs(struct tm *t) +{ + /* tm defines tm_year as num of yrs since 1900, we have absolute number */ + t->tm_year += 1900; + /* struct tm defines tm_mon as number of month since January */ + t->tm_mon++; +} + +/******************************************************************* + * parsing of HBAC rules themselves * + *******************************************************************/ + +/* + * Parses generalized time string given in STR and fills the + * information into OUT. + */ +static int parse_generalized_time(struct parse_ctx *pctx, + struct time_rules_ctx *trctx, + const char *str, + time_t *out) +{ + int ret; + struct parse_ctx *gctx = NULL; + struct tm tm; + + memset(&tm, 0, sizeof(tm)); + tm.tm_isdst = -1; + + ret = matches_regexp(pctx, trctx, str, LP_RGX_GENERALIZED, &gctx); + JMP_NEOK(ret); + + /* compulsory */ + ret = substring_strtol(gctx, str, "year", &tm.tm_year); + JMP_NEOK(ret); + ret = substring_strtol(gctx, str, "month", &tm.tm_mon); + JMP_NEOK(ret); + ret = substring_strtol(gctx, str, "day", &tm.tm_mday); + JMP_NEOK(ret); + /* optional */ + ret = substring_strtol(gctx, str, "hour", &tm.tm_hour); + JMP_NEOK_LABEL(ret, enoent); + ret = substring_strtol(gctx, str, "minute", &tm.tm_min); + JMP_NEOK_LABEL(ret, enoent); + ret = substring_strtol(gctx, str, "second", &tm.tm_sec); + JMP_NEOK_LABEL(ret, enoent); + +enoent: + if (ret == ENOENT) { + ret = EOK; + } + + abs2tm(&tm); + + *out = mktime(&tm); + DEBUG(3, ("converted to time: '%s'\n", ctime(out))); + if (*out == -1) { + ret = EINVAL; + } +done: + talloc_free(gctx); + return ret; +} + +/* + * Parses absolute timerange string given in STR and fills the + * information into ABS. + */ +static int parse_absolute(struct absolute_range *absr, + struct time_rules_ctx *trctx, + struct parse_ctx *pctx, + const char *str) +{ + char *from = NULL, *to = NULL; + int ret; + + ret = copy_substring(pctx, str, "from", &from); + if (ret != EOK) { + DEBUG(1, ("Missing required part 'from' in absolute timespec\n")); + ret = EINVAL; + goto done; + } + ret = copy_substring(pctx, str, "to", &to); + if (ret != EOK) { + DEBUG(1, ("Missing required part 'to' in absolute timespec\n")); + ret = EINVAL; + goto done; + } + + ret = parse_generalized_time(pctx, trctx, from, &absr->time_from); + if (ret != EOK) { + DEBUG(1, ("Cannot parse generalized time - first part\n")); + goto done; + } + + ret = parse_generalized_time(pctx, trctx, to, &absr->time_to); + if (ret != EOK) { + DEBUG(1, ("Cannot parse generalized time - second part\n")); + goto done; + } + + if (difftime(absr->time_to, absr->time_from) < 0) { + DEBUG(1, ("Not a valid interval\n")); + ret = EINVAL; + } + + ret = EOK; +done: + talloc_free(from); + talloc_free(to); + return ret; +} + +static int parse_hhmm(const char *str, int *hour, int *min) +{ + struct tm t; + char *err; + + err = strptime(str, "%H%M", &t); + if (*err != '\0') { + return EINVAL; + } + + *hour = t.tm_hour; + *min = t.tm_min; + + return EOK; +} + +/* + * Parses monthly periodic timerange given in STR. + * Fills the information into PER. + */ +static int parse_periodic_monthly(TALLOC_CTX *ctx, + struct time_rules_ctx *trctx, + struct periodic_range *per, + const char *str) +{ + int ret; + struct parse_ctx *mpctx = NULL; + char *match = NULL; + char *mperspec = NULL; + + /* This code would be much less ugly if RHEL5 PCRE knew about PCRE_DUPNAMES */ + ret = matches_regexp(ctx, trctx, str, LP_RGX_MDAY, &mpctx); + if (ret == EOK) { + ret = copy_substring(mpctx, str, "mperspec_day", &mperspec); + JMP_NEOK(ret); + ret = copy_substring(mpctx, str, "interval_day", &match); + JMP_NEOK(ret); + BUFFER_OR_JUMP(ctx, per->day_of_month, DAY_OF_MONTH_BUFSIZE); + ret = interval2bitfield(mpctx, per->day_of_month, match, + 1, DAY_OF_MONTH_MAX, NULL); + JMP_NEOK(ret); + } else { + ret = matches_regexp(ctx, trctx, str, LP_RGX_MWEEK, &mpctx); + JMP_NEOK(ret); + ret = copy_substring(mpctx, str, "mperspec_week", &mperspec); + JMP_NEOK(ret); + + ret = copy_substring(mpctx, str, "interval_week", &match); + JMP_NEOK(ret); + ret = interval2bitfield(mpctx, &per->week_of_month, match, + 1, WEEK_OF_MONTH_MAX, NULL); + JMP_NEOK(ret); + + ret = copy_substring(mpctx, str, "day_of_week", &match); + JMP_NEOK(ret); + ret = interval2bitfield(mpctx, &per->day_of_week, match, + 1, DAY_OF_WEEK_MAX, names_day_of_week); + JMP_NEOK(ret); + } + +done: + talloc_free(mpctx); + return ret; +} + +/* + * Parses yearly periodic timerange given in STR. + * Fills the information into PER. + */ +static int parse_periodic_yearly(TALLOC_CTX *ctx, + struct time_rules_ctx *trctx, + struct periodic_range *per, + const char *str) +{ + int ret; + struct parse_ctx *ypctx = NULL; + char *match = NULL; + char *yperspec = NULL; + + ret = matches_regexp(ctx, trctx, str, LP_RGX_YEARLY, &ypctx); + JMP_NEOK(ret); + ret = copy_substring(ypctx, str, "yperspec_day", &yperspec); + if (ret == EOK) { + ret = copy_substring(ypctx, str, "day_of_year", &match); + JMP_NEOK(ret); + BUFFER_OR_JUMP(ypctx, per->day_of_year, DAY_OF_YEAR_BUFSIZE); + ret = interval2bitfield(ypctx, per->day_of_year, match, + 1, DAY_OF_YEAR_MAX, NULL); + JMP_NEOK(ret); + } + + if (ret != ENOENT) goto done; + + ret = copy_substring(ypctx, str, "yperspec_week", &yperspec); + if (ret == EOK) { + ret = copy_substring(ypctx, str, "week_of_year", &match); + JMP_NEOK(ret); + BUFFER_OR_JUMP(ypctx, per->week_of_year, WEEK_OF_YEAR_BUFSIZE); + ret = interval2bitfield(ypctx, per->week_of_year, match, + 1, WEEK_OF_YEAR_MAX, NULL); + JMP_NEOK(ret); + + talloc_free(match); + ret = copy_substring(ypctx, str, "day_of_week", &match); + JMP_NEOK(ret); + ret = interval2bitfield(ypctx, &per->day_of_week, match, + 1, DAY_OF_WEEK_MAX, names_day_of_week); + JMP_NEOK(ret); + } + + if (ret != ENOENT) goto done; + + ret = copy_substring(ypctx, str, "yperspec_month", &yperspec); + JMP_NEOK(ret); + + talloc_free(match); + ret = copy_substring(ypctx, str, "month_number", &match); + JMP_NEOK(ret); + BUFFER_OR_JUMP(ypctx, per->month, MONTH_BUFSIZE); + ret = interval2bitfield(ypctx, per->month, match, + 1, MONTH_MAX, names_months); + JMP_NEOK(ret); + + talloc_free(match); + ret = copy_substring(ypctx, str, "m_period", &match); + JMP_NEOK(ret); + DEBUG(7, ("Monthly year period - calling parse_periodic_monthly()\n")); + ret = parse_periodic_monthly(ypctx, trctx, per, match); + JMP_NEOK(ret); + +done: + talloc_free(ypctx); + return ret; +} + +/* + * Parses weekly periodic timerange given in STR. + * Fills the information into PER. + */ +static int parse_periodic_weekly(TALLOC_CTX *ctx, + struct time_rules_ctx *trctx, + struct periodic_range *per, + const char *str) +{ + int ret; + struct parse_ctx *wpctx = NULL; + char *dow = NULL; + + ret = matches_regexp(ctx, trctx, str, LP_RGX_WEEKLY, &wpctx); + JMP_NEOK(ret); + + ret = copy_substring(wpctx, str, "day_of_week", &dow); + JMP_NEOK(ret); + DEBUG(8, ("day_of_week = '%s'\n", dow)); + + ret = interval2bitfield(wpctx, &per->day_of_week, dow, + 1, DAY_OF_WEEK_MAX, names_day_of_week); + +done: + talloc_free(wpctx); + return ret; +} + +static int parse_periodic_time(struct periodic_range *per, + struct parse_ctx *pctx, + const char *str) +{ + char *substr = NULL; + int ret; + + int hour_from; + int hour_to; + int min_from; + int min_to; + + /* parse out the time */ + ret = copy_substring(pctx, str, "timeFrom", &substr); + JMP_NEOK(ret); + parse_hhmm(substr, &hour_from, &min_from); + DEBUG(7, ("Parsed timeFrom: %d:%d\n", hour_from, min_from)); + JMP_NEOK(ret); + + talloc_free(substr); + ret = copy_substring(pctx, str, "timeTo", &substr); + JMP_NEOK(ret); + parse_hhmm(substr, &hour_to, &min_to); + DEBUG(7, ("Parsed timeTo: %d:%d\n", hour_to, min_to)); + JMP_NEOK(ret); + + /* set the interval */ + if (hour_from > hour_to ) { + set_bit_range(per->hour, 0, hour_to); + set_bit_range(per->hour, hour_from, HOUR_MAX); + } else { + set_bit_range(per->hour, hour_from, hour_to); + } + + if (min_from > min_to) { + set_bit_range(per->minute, 0, min_to); + set_bit_range(per->minute, min_from, MINUTE_MAX); + } else { + set_bit_range(per->minute, min_from, min_to); + } + + + ret = EOK; +done: + talloc_free(substr); + return ret; +} + +/* + * Parses periodic timerange given in STR. + * Fills the information into PER. + */ +static int parse_periodic(struct periodic_range *per, + struct time_rules_ctx *trctx, + struct parse_ctx *pctx, + const char *str) +{ + char *substr = NULL; + char *period = NULL; + int ret; + + /* These are mandatory */ + BUFFER_OR_JUMP(per, per->hour, HOUR_BUFSIZE); + BUFFER_OR_JUMP(per, per->minute, MINUTE_BUFSIZE); + + ret = copy_substring(pctx, str, "perspec", &substr); + JMP_NEOK(ret); + ret = copy_substring(pctx, str, "period", &period); + JMP_NEOK(ret); + + if (strcmp(substr, "yearly") == 0) { + DEBUG(5, ("periodic yearly\n")); + ret = parse_periodic_yearly(pctx, trctx, per, period); + JMP_NEOK(ret); + } else if (strcmp(substr, "monthly") == 0) { + DEBUG(5, ("periodic monthly\n")); + ret = parse_periodic_monthly(pctx, trctx, per, period); + JMP_NEOK(ret); + } else if (strcmp(substr, "weekly") == 0) { + DEBUG(5, ("periodic weekly\n")); + ret = parse_periodic_weekly(pctx, trctx, per, period); + JMP_NEOK(ret); + } else if (strcmp(substr, "daily") == 0) { + DEBUG(5, ("periodic daily\n")); + } else { + DEBUG(1, ("Cannot determine periodic rule type" + "(perspec = '%s', period = '%s')\n", substr, period)); + ret = EINVAL; + goto done; + } + + talloc_free(period); + + ret = parse_periodic_time(per, pctx, str); + JMP_NEOK(ret); + + ret = EOK; +done: + talloc_free(substr); + return ret; +} + +/* + * Parses time specification given in string RULE into range_ctx + * context CTX. + */ +static int parse_timespec(struct range_ctx *ctx, const char *rule) +{ + int ret; + struct parse_ctx *pctx = NULL; + + if (matches_regexp(ctx, ctx->trctx, rule, LP_RGX_ABSOLUTE, &pctx) == EOK) { + DEBUG(5, ("Matched absolute range\n")); + ctx->type = TYPE_ABSOLUTE; + ctx->abs = talloc_zero(ctx, struct absolute_range); + CHECK_PTR_JMP(ctx->abs); + + ret = parse_absolute(ctx->abs, ctx->trctx, pctx, rule); + JMP_NEOK(ret); + } else if (matches_regexp(ctx, ctx->trctx, rule, LP_RGX_PERIODIC, &pctx) == EOK) { + DEBUG(5, ("Matched periodic range\n")); + ctx->type = TYPE_PERIODIC; + ctx->per = talloc_zero(ctx, struct periodic_range); + CHECK_PTR_JMP(ctx->per); + + ret = parse_periodic(ctx->per, ctx->trctx, pctx, rule); + JMP_NEOK(ret); + } else { + DEBUG(1, ("Cannot determine rule type\n")); + ret = EINVAL; + goto done; + } + + ret = EOK; +done: + talloc_free(pctx); + return ret; +} + +/******************************************************************* + * validation of rules against time_t * + *******************************************************************/ + +static int absolute_timerange_valid(struct absolute_range *absr, + const time_t now, + bool *result) +{ + if (difftime(absr->time_from, now) > 0) { + DEBUG(3, ("Absolute timerange invalid (before interval)\n")); + *result = false; + return EOK; + } + + if (difftime(absr->time_to, now) < 0) { + DEBUG(3, ("Absolute timerange invalid (after interval)\n")); + *result = false; + return EOK; + } + + DEBUG(3, ("Absolute timerange valid\n")); + *result = true; + return EOK; +} + +static int periodic_timerange_valid(struct periodic_range *per, + const time_t now, + bool *result) +{ + struct tm tm_now; + int wnum; + int wom; + + memset(&tm_now, 0, sizeof(struct tm)); + if (localtime_r(&now, &tm_now) == NULL) { + DEBUG(0, ("Cannot convert time_t to struct tm\n")); + return EFAULT; + } + DEBUG(9, ("Got struct tm value %s", asctime(&tm_now))); + tm2abs(&tm_now); + + wnum = weeknum(&tm_now); + if (wnum == -1) { + DEBUG(7, ("Cannot get week number")); + return EINVAL; + } + DEBUG(9, ("Week number is %d\n", wnum)); + + wom = get_week_of_month(&tm_now); + if (wnum == -1) { + DEBUG(7, ("Cannot get week of number")); + return EINVAL; + } + DEBUG(9, ("Week of month number is %d\n", wom)); + + /* The validation itself */ + TEST_BIT_RANGE(per->day_of_week, tm_now.tm_wday, result); + DEBUG(9, ("day of week OK\n")); + TEST_BIT_RANGE_PTR(per->day_of_month, tm_now.tm_mday, result); + DEBUG(9, ("day of month OK\n")); + TEST_BIT_RANGE(per->week_of_month, wom, result); + DEBUG(9, ("week of month OK\n")); + TEST_BIT_RANGE_PTR(per->week_of_year, wnum, result); + DEBUG(9, ("week of year OK\n")); + TEST_BIT_RANGE_PTR(per->month, tm_now.tm_mon, result); + DEBUG(9, ("month OK\n")); + TEST_BIT_RANGE_PTR(per->day_of_year, tm_now.tm_yday, result); + DEBUG(9, ("day of year OK\n")); + TEST_BIT_RANGE_PTR(per->hour, tm_now.tm_hour, result); + DEBUG(9, ("hour OK\n")); + TEST_BIT_RANGE_PTR(per->minute, tm_now.tm_min, result); + DEBUG(9, ("minute OK\n")); + + DEBUG(3, ("Periodic timerange valid\n")); + *result = true; + return EOK; +} + +/* + * Returns EOK if the timerange in range_ctx context is valid compared against a + * given time_t value in NOW, returns ERANGE if the time value is outside the + * specified range. + */ +static int timerange_valid(struct range_ctx *ctx, + const time_t now, + bool *result) +{ + int ret; + + switch(ctx->type) { + case TYPE_ABSOLUTE: + DEBUG(7, ("Checking absolute range\n")); + ret = absolute_timerange_valid(ctx->abs, now, result); + break; + + case TYPE_PERIODIC: + DEBUG(7, ("Checking periodic range\n")); + ret = periodic_timerange_valid(ctx->per, now, result); + break; + + default: + DEBUG(1, ("Unknown range type (%d)\n", ctx->type)); + ret = EINVAL; + break; + } + + return ret; +} + +/******************************************************************* + * public interface * + *******************************************************************/ + +/* + * This is actually the meat of the library. The function takes a string + * representation of a time rule in STR and time to check against (usually that + * would be current time) in NOW. + * + * It returns EOK if the rule is valid in the current time, ERANGE if not and + * EINVAL if the rule cannot be parsed + */ +int check_time_rule(TALLOC_CTX *mem_ctx, + struct time_rules_ctx *trctx, + const char *str, + const time_t now, + bool *result) +{ + int ret; + struct range_ctx *ctx; + + ctx = talloc_zero(mem_ctx, struct range_ctx); + CHECK_PTR_JMP(ctx); + ctx->trctx = trctx; + + DEBUG(9, ("Got time_t value %s", ctime(&now))); + + ret = parse_timespec(ctx, str); + if (ret != EOK) { + DEBUG(1, ("Cannot parse the time specification (%d)\n", ret)); + goto done; + } + + ret = timerange_valid(ctx, now, result); + if (ret != EOK) { + DEBUG(1, ("Cannot check the time range (%d)\n", ret)); + goto done; + } + + ret = EOK; +done: + talloc_free(ctx); + return EOK; +} + +/* + * Frees the resources taken by the precompiled rules + */ +static int time_rules_parser_destructor(struct time_rules_ctx *ctx) +{ + int i; + + for (i = 0; i< LP_RGX_MAX; ++i) { + pcre_free(ctx->re[i]); + ctx->re[i] = NULL; + } + + return 0; +} + +/* + * Initializes the parser by precompiling the regular expressions + * for later use + */ +int init_time_rules_parser(TALLOC_CTX *mem_ctx, + struct time_rules_ctx **_out) +{ + const char *errstr; + int errval; + int errpos; + int ret; + int i; + struct time_rules_ctx *ctx = NULL; + + ctx = talloc_zero(mem_ctx, struct time_rules_ctx); + CHECK_PTR(ctx); + talloc_set_destructor(ctx, time_rules_parser_destructor); + + /* Precompile regular expressions */ + for (i = LP_RGX_GENERALIZED; i< LP_RGX_MAX; ++i) { + ctx->re[i] = pcre_compile2(lookup_table[i], + 0, + &errval, + &errstr, + &errpos, + NULL); + + if (ctx->re[i] == NULL) { + DEBUG(0, ("Invalid Regular Expression pattern '%s' at position %d" + " (Error: %d [%s])\n", lookup_table[i], + errpos, errval, errstr)); + ret = EFAULT; + goto done; + } + + } + + *_out = ctx; + return EOK; +done: + talloc_free(ctx); + return ret; +} + diff --git a/server/providers/ipa/ipa_timerules.h b/server/providers/ipa/ipa_timerules.h new file mode 100644 index 00000000..e1beaa22 --- /dev/null +++ b/server/providers/ipa/ipa_timerules.h @@ -0,0 +1,56 @@ +/* + SSSD + + IPA Provider Time Rules Parsing + + Authors: + Jakub Hrozek <jhrozek@redhat.com> + + Copyright (C) Red Hat, Inc 2009 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __IPA_TIMERULES_H_ +#define __IPA_TIMERULES_H_ + +#include <stdbool.h> +#include <talloc.h> + +/* Opaque structure given after init */ +struct time_rules_ctx; + +/* + * Init the parser. Destroy the allocated resources by simply + * talloc_free()-ing the time_rules_ctx + */ +int init_time_rules_parser(TALLOC_CTX *mem_ctx, + struct time_rules_ctx **_out); + +/* + * This is actually the meat of the library. The function takes a string + * representation of a time rule in STR and time to check against (usually that + * would be current time) in NOW. + * + * It returns EOK if the rule can be parsed, error code if not. If the time + * given in the NOW parameter would be accepted by the rule, it stores true in + * RESULT, false otherwise. + */ +int check_time_rule(TALLOC_CTX *mem_ctx, + struct time_rules_ctx *trctx, + const char *str, + const time_t now, + bool *result); + +#endif /* __IPA_TIMERULES_H_ */ diff --git a/server/tests/ipa_timerules-tests.c b/server/tests/ipa_timerules-tests.c new file mode 100644 index 00000000..12ce6650 --- /dev/null +++ b/server/tests/ipa_timerules-tests.c @@ -0,0 +1,579 @@ +/* + Timelib + + test_timelib.c + + Copyright (C) Jakub Hrozek <jhrozek@redhat.com> 2009 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#define _XOPEN_SOURCE /* strptime */ + +#include <check.h> +#include <popt.h> +#include <time.h> +#include <errno.h> +#include <stdlib.h> +#include <stdbool.h> + +#include "providers/ipa/ipa_timerules.h" +#include "util/util.h" +#include "tests/common.h" + +#define CHECK_TIME_RULE_LEAK(ctx, tctx, str, now, result) do { \ + check_leaks_push(ctx); \ + ret = check_time_rule(ctx, tctx, str, now, result); \ + check_leaks_pop(ctx); \ +} while (0) + +static TALLOC_CTX *ctx; + +static void usage(poptContext pc, const char *error) +{ + poptPrintUsage(pc, stderr, 0); + if (error) fprintf(stderr, "%s", error); +} + +int str2time_t(const char *fmt, const char *str, time_t *out) +{ + char *err; + struct tm stm; + memset(&stm, 0, sizeof(struct tm)); + + err = strptime(str, fmt, &stm); + if(!err || err[0] != '\0') + return EINVAL; + + DEBUG(9, ("after strptime: %s", asctime(&stm))); + stm.tm_isdst = -1; + *out = mktime(&stm); + DEBUG(9, ("after mktime: %s", ctime(out))); + return (*out == -1) ? EINVAL : EOK; +} + +/* Fixtures - open the time library before every test, close it afterwards */ +void setup(void) +{ + leak_check_setup(); + + ctx = talloc_new(NULL); + fail_if(ctx == NULL); +} + +void teardown(void) +{ + leak_check_teardown(); +} + +/* Test that timelib detects a time rule inside the absolute range */ +START_TEST(test_timelib_absolute_in_range) +{ + time_t now; + int ret; + bool result; + static struct time_rules_ctx *tctx = NULL; + + + ret = init_time_rules_parser(ctx, &tctx); + fail_if(ret != EOK); + + fail_unless(str2time_t("%F", "2000-1-1", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "absolute 199412161032.5 ~ 200512161032,5", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule"); + fail_unless(result == true, "Range check error"); + + talloc_free(tctx); +} +END_TEST + +/* Test that timelib detects a time rule outside the absolute range */ +START_TEST(test_timelib_absolute_out_of_range) +{ + time_t now; + int ret; + bool result; + static struct time_rules_ctx *tctx = NULL; + + ret = init_time_rules_parser(ctx, &tctx); + fail_if(ret != EOK); + + fail_unless(str2time_t("%F", "2007-1-1", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "absolute 199412161032.5 ~ 200512161032,5", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule"); + fail_unless(result == false, "Range check error"); + + talloc_free(tctx); +} +END_TEST + +/* Test that absolute timeranges work OK with only minimal data supplied */ +START_TEST(test_timelib_absolute_minimal) +{ + time_t now; + int ret; + bool result; + static struct time_rules_ctx *tctx = NULL; + + ret = init_time_rules_parser(ctx, &tctx); + fail_if(ret != EOK); + + fail_unless(str2time_t("%F", "2000-1-1", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "absolute 19941216 ~ 20051216", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule"); + fail_unless(result == true, "Range check error"); + + talloc_free(tctx); +} +END_TEST + +/* Test a time value "right off the edge" of the time specifier */ +START_TEST(test_timelib_absolute_one_off) +{ + time_t now; + int ret; + bool result; + static struct time_rules_ctx *tctx = NULL; + + ret = init_time_rules_parser(ctx, &tctx); + fail_if(ret != EOK); + + fail_unless(str2time_t("%Y-%m-%d-%H-%M-%S", "1994-12-16-10-32-29", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "absolute 19941216103230 ~ 19941216103231", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule"); + fail_unless(result == false, "Range check error"); + + fail_unless(str2time_t("%Y-%m-%d-%H-%M-%S", "1994-12-16-10-32-32", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "absolute 19941216103230 ~ 19941216103231", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule"); + fail_unless(result == false, "Range check error"); + + talloc_free(tctx); +} +END_TEST + + +/* Test a time value "right on the edge" of the time specifier */ +START_TEST(test_timelib_absolute_one_on) +{ + time_t now; + int ret; + bool result; + static struct time_rules_ctx *tctx = NULL; + + ret = init_time_rules_parser(ctx, &tctx); + fail_if(ret != EOK); + + fail_unless(str2time_t("%Y-%m-%d-%H-%M-%S", "1994-12-16-10-32-30", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "absolute 19941216103230 ~ 19941216103231", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule"); + fail_unless(result == true, "Range check error"); + + fail_unless(str2time_t("%Y-%m-%d-%H-%M-%S", "1994-12-16-10-32-31", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "absolute 19941216103230 ~ 19941216103231", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule"); + fail_unless(result == true, "Range check error"); + + talloc_free(tctx); +} +END_TEST + +START_TEST(test_timelib_periodic_daily_in) +{ + time_t now; + int ret; + bool result; + static struct time_rules_ctx *tctx = NULL; + + ret = init_time_rules_parser(ctx, &tctx); + fail_if(ret != EOK); + + fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-03-30-12-00", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic daily 0900 ~ 1800", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule"); + fail_unless(result == true, "Range check error"); + + /* test edges */ + fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-03-30-09-30", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic daily 0930 ~ 1830", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule (edge1)"); + fail_unless(result == true, "Range check error"); + + fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-03-30-18-30", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic daily 0930 ~ 1830", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule (edge2)"); + fail_unless(result == true, "Range check error"); + + /* test wrap around */ + fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-03-30-16-00", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic daily 1500 ~ 0600", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule (edge1)"); + fail_unless(result == true, "Range check error1"); + + fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-03-30-15-00", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic daily 1500 ~ 0600", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule (edge1)"); + fail_unless(result == true, "Range check error1"); + + fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-03-30-06-00", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic daily 1500 ~ 0600", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule (edge1)"); + fail_unless(result == true, "Range check error1"); + + talloc_free(tctx); +} +END_TEST + +START_TEST(test_timelib_periodic_daily_out) +{ + time_t now; + int ret; + bool result; + static struct time_rules_ctx *tctx = NULL; + + ret = init_time_rules_parser(ctx, &tctx); + fail_if(ret != EOK); + + fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-03-30-21-00", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic daily 0900 ~ 1800", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule"); + fail_unless(result == false, "Range check error"); + + /* test one-off errors */ + fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-03-30-09-29", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic daily 0930 ~ 1830", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule (one-off 1)"); + fail_unless(result == false, "Range check error"); + + fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-03-30-18-31", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic daily 0930 ~ 1830", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule (one-off 2)"); + fail_unless(result == false, "Range check error"); + + /* test wrap around */ + fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-03-30-10-00", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic daily 1500 ~ 0600", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule (edge1)"); + fail_unless(result == false, "Range check error"); + + fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-03-30-14-59", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic daily 1500 ~ 0600", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule (edge1)"); + fail_unless(result == false, "Range check error1"); + + fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-03-30-06-01", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic daily 1500 ~ 0600", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule (edge1)"); + fail_unless(result == false, "Range check error2"); + + talloc_free(tctx); +} +END_TEST + +START_TEST(test_timelib_periodic_weekly_in) +{ + time_t now; + int ret; + bool result; + static struct time_rules_ctx *tctx = NULL; + + ret = init_time_rules_parser(ctx, &tctx); + fail_if(ret != EOK); + + fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-04-02-12-00", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic weekly day Mon-Fri 0900 ~ 1800", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule"); + fail_unless(result == true, "Range check error1"); + + /* test edges - monday */ + fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-06-22-12-00", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic weekly day Mon-Fri 0900 ~ 1800", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule"); + fail_unless(result == true, "Range check error2"); + + /* test edges - friday */ + fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-06-26-12-00", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic weekly day Mon-Fri 0900 ~ 1800", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule"); + fail_unless(result == true, "Range check error3"); + + /* test wrap around */ + fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-11-03-12-00", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic weekly day Fri-Tue 0900 ~ 1800", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule"); + fail_unless(result == true, "Range check error2"); + + fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-11-06-12-00", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic weekly day Fri-Tue 0900 ~ 1800", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule"); + fail_unless(result == true, "Range check error3"); + + talloc_free(tctx); +} +END_TEST + +START_TEST(test_timelib_periodic_weekly_out) +{ + time_t now; + int ret; + bool result; + static struct time_rules_ctx *tctx = NULL; + + ret = init_time_rules_parser(ctx, &tctx); + fail_if(ret != EOK); + + fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-04-04-12-00", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic weekly day Mon-Fri 0900 ~ 1800", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule"); + fail_unless(result == false, "Range check error"); + + /* test one-off error - monday */ + fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-06-22-12-00", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic weekly day Tue-Thu 0900 ~ 1800", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule"); + fail_unless(result == false, "Range check error"); + + /* test one-off error - friday */ + fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-06-26-12-00", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic weekly day Tue-Thu 0900 ~ 1800", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule"); + fail_unless(result == false, "Range check error"); + + /* test wrap around */ + fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-11-04-12-00", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic weekly day Fri-Tue 0900 ~ 1800", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule"); + fail_unless(result == false, "Range check error2"); + + fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-11-05-12-00", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic weekly day Fri-Tue 0900 ~ 1800", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule"); + fail_unless(result == false, "Range check error3"); + + talloc_free(tctx); +} +END_TEST + +START_TEST(test_timelib_periodic_monthly_in) +{ + time_t now; + int ret; + bool result; + static struct time_rules_ctx *tctx = NULL; + + ret = init_time_rules_parser(ctx, &tctx); + fail_if(ret != EOK); + + fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-04-07-12-00", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic monthly week 1,2 day Mon,Tue 0900 ~ 1800", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule1 (ret = %d: %s)", ret, strerror(ret)); + fail_unless(result == true, "Range check error"); + + fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-04-05-12-00", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic monthly day 1-5,10,15,20-25 0900 ~ 1800", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule2 (ret = %d: %s)", ret, strerror(ret)); + fail_unless(result == true, "Range check error"); + + /* edges - week in */ + fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-06-13-12-00", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic monthly week 1,2 day Sat 0900 ~ 1800", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule (week edge 1)"); + fail_unless(result == true, "Range check error"); + + fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-06-29-12-00", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic monthly week 5 day Mon,Tue 0900 ~ 1800", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule (week edge 2)"); + fail_unless(result == true, "Range check error"); + + /* edges - day in */ + fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-04-01-12-00", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic monthly day 1-10 0900 ~ 1800", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule (day edge 1)"); + fail_unless(result == true, "Range check error"); + + fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-04-10-12-00", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic monthly day 1-10 0900 ~ 1800", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule (day edge 2)"); + fail_unless(result == true, "Range check error"); + + talloc_free(tctx); +} +END_TEST + +START_TEST(test_timelib_periodic_monthly_out) +{ + time_t now; + int ret; + bool result; + static struct time_rules_ctx *tctx = NULL; + + ret = init_time_rules_parser(ctx, &tctx); + fail_if(ret != EOK); + + fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-06-03-12-00", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic monthly week 1,2 day Mon,Tue 0900 ~ 1800", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule (ret = %d)", ret); + fail_unless(result == false, "Range check error"); + + /* one-off error - week out */ + fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-06-15-12-00", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic monthly week 1 day Sun-Sat 0900 ~ 1800", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule (week edge 1)"); + fail_unless(result == false, "Range check error"); + + fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-06-28-12-00", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic monthly week 5 day Mon,Tue 0900 ~ 1800", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule (week edge 2)"); + fail_unless(result == false, "Range check error"); + + /* one-off error - day out */ + fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-04-01-12-00", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic monthly day 2-10 0900 ~ 1800", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule (day edge 1)"); + fail_unless(result == false, "Range check error"); + + fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-04-11-12-00", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic monthly day 1-10 0900 ~ 1800", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule (day edge 2)"); + fail_unless(result == false, "Range check error"); + + talloc_free(tctx); +} +END_TEST + +START_TEST(test_timelib_periodic_yearly_in) +{ + time_t now; + int ret; + bool result; + static struct time_rules_ctx *tctx = NULL; + + ret = init_time_rules_parser(ctx, &tctx); + fail_if(ret != EOK); + + fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-08-03-12-00", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic yearly month 1,7-8 day 1-10 0900 ~ 1800", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule (ret = %d)", ret); + fail_unless(result == true, "Range check error1"); + + fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-01-01-12-00", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic yearly month 1,7-8 day 1-10 0900 ~ 1800", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule (ret = %d)", ret); + fail_unless(result == true, "Range check error2"); + + fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-01-01-12-00", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic yearly week 1 day 1-7 0900 ~ 1800", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule (ret = %d)", ret); + fail_unless(result == true, "Range check error3"); + + fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-07-10-12-00", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic yearly month 1,7-8 day 1-10 0900 ~ 1800", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule (ret = %d)", ret); + fail_unless(result == true, "Range check error4"); + + talloc_free(tctx); +} +END_TEST + +START_TEST(test_timelib_periodic_yearly_out) +{ + time_t now; + int ret; + bool result; + static struct time_rules_ctx *tctx = NULL; + + ret = init_time_rules_parser(ctx, &tctx); + fail_if(ret != EOK); + + fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-06-13-12-00", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic yearly month 7-8 day 1-10 0900 ~ 1800", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule1 (ret = %d)", ret); + fail_unless(result == false, "Range check error"); + + fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-09-13-12-00", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic yearly month 7-8 day 1-10 0900 ~ 1800", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule2 (ret = %d)", ret); + fail_unless(result == false, "Range check error"); + + fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-01-11-12-00", &now) == 0, "Cannot parse time spec"); + CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic yearly month 1,7-8 day 1-10 0900 ~ 1800", now, &result); + fail_unless(ret == EOK, "Fail to check the time rule3 (ret = %d)", ret); + fail_unless(result == false, "Range check error"); + + talloc_free(tctx); +} +END_TEST + +Suite *create_timelib_suite(void) +{ + Suite *s = suite_create("timelib"); + + TCase *tc_timelib = tcase_create("Timelib Tests"); + + + /* Add setup() and teardown() methods */ + tcase_add_checked_fixture(tc_timelib, setup, teardown); + + /* Do some testing */ + tcase_add_test(tc_timelib, test_timelib_absolute_in_range); + tcase_add_test(tc_timelib, test_timelib_absolute_out_of_range); + tcase_add_test(tc_timelib, test_timelib_absolute_minimal); + tcase_add_test(tc_timelib, test_timelib_absolute_one_off); + tcase_add_test(tc_timelib, test_timelib_absolute_one_on); + + tcase_add_test(tc_timelib, test_timelib_periodic_daily_in); + tcase_add_test(tc_timelib, test_timelib_periodic_daily_out); + tcase_add_test(tc_timelib, test_timelib_periodic_weekly_in); + tcase_add_test(tc_timelib, test_timelib_periodic_weekly_out); + tcase_add_test(tc_timelib, test_timelib_periodic_monthly_in); + tcase_add_test(tc_timelib, test_timelib_periodic_monthly_out); + tcase_add_test(tc_timelib, test_timelib_periodic_yearly_in); + tcase_add_test(tc_timelib, test_timelib_periodic_yearly_out); + + /* Add all test cases to the test suite */ + suite_add_tcase(s, tc_timelib); + + return s; +} + +int main(int argc, const char *argv[]) +{ + int ret; + poptContext pc; + int failure_count; + Suite *timelib_suite; + SRunner *sr; + int debug = 0; + + struct poptOption long_options[] = { + POPT_AUTOHELP + { "debug-level", 'd', POPT_ARG_INT, &debug, 0, "Set debug level", NULL }, + POPT_TABLEEND + }; + + pc = poptGetContext(NULL, argc, (const char **) argv, long_options, 0); + if((ret = poptGetNextOpt(pc)) < -1) { + usage(pc, poptStrerror(ret)); + return EXIT_FAILURE; + } + debug_level = debug; + + timelib_suite = create_timelib_suite(); + sr = srunner_create(timelib_suite); + srunner_run_all(sr, CK_VERBOSE); + failure_count = srunner_ntests_failed(sr); + srunner_free(sr); + return (failure_count==0 ? EXIT_SUCCESS : EXIT_FAILURE); +} + |