/* SSSD IPA Provider Time Rules Parsing Authors: Jakub Hrozek 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 . */ #define _XOPEN_SOURCE /* strptime() needs this */ #include #include #include #include #include #include #include #include #include #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(0|1|2|3|4|5|6|7|Mon|Tue|Wed|Thu|Fri|Sat|Sun|,|-)+)" #define RGX_MDAY "(?Pday) (?P[0-9,-]+) " #define RGX_MWEEK "(?Pweek) (?P[0-9,-]+) "RGX_WEEKLY #define RGX_MONTHLY RGX_MDAY"|"RGX_MWEEK #define RGX_YDAY "(?Pday) (?P[0-9,-]+) " #define RGX_YWEEK "(?Pweek) (?P[0-9,-]+) "RGX_WEEKLY #define RGX_YMONTH "(?Pmonth) (?P[0-9,-]+) (?P.*?)$" #define RGX_YEARLY RGX_YMONTH"|"RGX_YWEEK"|"RGX_YDAY #define RGX_TIMESPEC "(?P[0-9]{4}) ~ (?P[0-9]{4})" #define RGX_GENERALIZED "(?P[0-9]{4})(?P[0-9]{2})(?P[0-9]{2})(?P[0-9]{2})?(?P[0-9]{2})?(?P[0-9]{2})?" #define RGX_PERIODIC "^periodic (?Pdaily|weekly|monthly|yearly) (?P.*?)"RGX_TIMESPEC"$" #define RGX_ABSOLUTE "^absolute (?P\\S+) ~ (?P\\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 */ 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; }