From dfe4e291c7b47fd97fbabc3ee300f60facb6e61f Mon Sep 17 00:00:00 2001
From: Stanislav Laznicka <slaznick@redhat.com>
Date: Wed, 30 Sep 2015 10:17:22 +0200
Subject: [PATCH 6/6] IPA: Added negative values to HBAC time policies

https://fedorahosted.org/freeipa/ticket/547
---
 src/providers/ipa/ipa_timerules.c | 186 +++++++++++++++++++++++++++++++++++---
 src/tests/ipa_hbac-tests.c        |  20 +++-
 2 files changed, 186 insertions(+), 20 deletions(-)

diff --git a/src/providers/ipa/ipa_timerules.c b/src/providers/ipa/ipa_timerules.c
index dc25c20caac15ef656d45d138df64cdff40ae97d..b6bb7f1f7115b7e1db9a1980eee1ea74efb60b6c 100644
--- a/src/providers/ipa/ipa_timerules.c
+++ b/src/providers/ipa/ipa_timerules.c
@@ -33,6 +33,7 @@
 #include "providers/ipa/ipa_common.h"
 
 #define NORANGE -10001
+#define LOW_VAL -10002
 
 #define TOD_STR "timeofday"
 #define DOW_STR "dayofweek"
@@ -120,7 +121,6 @@ hbac_evaluate_time_rules(struct hbac_time_rules *rule,
     struct tm *tmp_time;
     int i;
 
-
     /* No access time policies = always match */
     if(!(rule->accesstimes && rule->accesstimes[0]) &&
         !(rule->exceptions && rule->exceptions[0])) {
@@ -233,9 +233,12 @@ struct tm *time_to_timezone(time_t t, const char *tz, struct tm **result)
 }
 
 void get_token(const char *input, struct time_token *tok);
+int get_days_in_month(struct tm *t);
 errno_t check_range(int val, int low, int high);
 int get_week_of_month(struct tm *t);
+int get_weeks_of_month(struct tm *t);
 int get_week_of_year(struct tm *t);
+int get_weeks_of_year(int year, int yday, int wday);
 int parse_repeat(const char *str, struct repeat *rpt);
 errno_t check_repeat(struct repeat *rpt,
                      struct tm *cmp_time,
@@ -266,6 +269,7 @@ eval_time_rule(const char *rule,
     int year_week;
     /* year modifier for special cases when "weekofyear" is part of the policy */
     int year_mod = 0;
+    int zero_allowed = 0;
 
     prev = START;
     tok.type = START;
@@ -327,7 +331,8 @@ eval_time_rule(const char *rule,
 
                 break;
             case DOW:
-                if(tok.value_low > DOW_MAX || tok.value_high > DOW_MAX) {
+                if(abs(tok.value_low) > DOW_MAX ||
+                  (tok.value_high != NORANGE && abs(tok.value_high) > DOW_MAX)) {
                     ret = EINVAL;
                     goto finished;
                 }
@@ -337,11 +342,17 @@ eval_time_rule(const char *rule,
                     goto finished;
                 }
 
+                if(tok.value_low < 0)
+                    tok.value_low += DOW_MAX + 1;
+                if(tok.value_high != NORANGE && tok.value_high < 0)
+                    tok.value_high += DOW_MAX + 1;
+
                 checked_val = cmp_time->tm_wday;
 
                 break;
             case DOM:
-                if(tok.value_low > DOM_MAX || tok.value_high > DOM_MAX) {
+                if(abs(tok.value_low) > DOM_MAX ||
+                  (tok.value_high != NORANGE && abs(tok.value_high) > DOM_MAX)) {
                     ret = EINVAL;
                     goto finished;
                 }
@@ -350,11 +361,21 @@ eval_time_rule(const char *rule,
                     goto finished;
                 }
 
+                int tmp_mon_days = get_days_in_month(cmp_time);
+                if(tok.value_low < 0)
+                    tok.value_low += tmp_mon_days + 1;
+                if(tok.value_high != NORANGE && tok.value_high < 0)
+                    tok.value_high += tmp_mon_days + 1;
+
+                if(tok.value_low <= 0)
+                    tok.value_low = LOW_VAL;
+
                 checked_val = cmp_time->tm_mday;
 
                 break;
             case DOY:
-                if(tok.value_low > DOY_MAX || tok.value_high > DOY_MAX) {
+                if(abs(tok.value_low) > DOY_MAX ||
+                  (tok.value_high != NORANGE && abs(tok.value_high) > DOY_MAX)) {
                     ret = EINVAL;
                     goto finished;
                 }
@@ -363,11 +384,21 @@ eval_time_rule(const char *rule,
                     goto finished;
                 }
 
+                int tmp_yr_days = isleap(cmp_time->tm_year) ? DOY_MAX : (DOY_MAX-1);
+                if(tok.value_low < 0)
+                    tok.value_low += tmp_yr_days + 1;
+                if(tok.value_high != NORANGE && tok.value_high < 0)
+                    tok.value_high += tmp_yr_days + 1;
+
+                if(tok.value_low <= 0)
+                    tok.value_low = LOW_VAL;
+
                 checked_val = cmp_time->tm_yday;
 
                 break;
             case WOY:
-                if(tok.value_low > WOY_MAX || tok.value_high > WOY_MAX) {
+                if(abs(tok.value_low) > WOY_MAX ||
+                  (tok.value_high != NORANGE && abs(tok.value_high) > WOY_MAX)) {
                     ret = EINVAL;
                     goto finished;
                 }
@@ -376,20 +407,63 @@ eval_time_rule(const char *rule,
                     goto finished;
                 }
 
+                int tmp_yr_weeks;
+                if(year_mod == 0)
+                    tmp_yr_weeks = get_weeks_of_year(cmp_time->tm_year,
+                                                     cmp_time->tm_yday,
+                                                     cmp_time->tm_wday);
+                else if(year_mod == 1)
+                    tmp_yr_weeks = get_weeks_of_year(cmp_time->tm_year + 1,
+                                        cmp_time->tm_yday + 7 -
+                                        (isleap(cmp_time->tm_year) ? DOY_MAX : (DOY_MAX - 1)),
+                                        cmp_time->tm_wday);
+                else /* year_mod == -1 */
+                    tmp_yr_weeks = get_weeks_of_year(cmp_time->tm_year - 1,
+                                        cmp_time->tm_yday - 7 +
+                                        (isleap(cmp_time->tm_year - 1) ? DOY_MAX : (DOY_MAX - 1)),
+                                        cmp_time->tm_wday);
+
+                if(tok.value_low < 0)
+                    tok.value_low += tmp_yr_weeks + 1;
+                if(tok.value_high != NORANGE && tok.value_high < 0)
+                    tok.value_high += tmp_yr_weeks + 1;
+
+                if(tok.value_low < 0)
+                    tok.value_low = LOW_VAL;
+
                 checked_val = year_week;
 
                 break;
             case WOM:
+                zero_allowed = 1;
+
+                /* WOM has the exception that negative values might have higher abs. value than positive*/
                 if(tok.value_low > WOM_MAX || tok.value_high > WOM_MAX) {
                     ret = EINVAL;
                     goto finished;
                 }
 
+                if(tok.value_low < -(WOM_MAX+1) ||
+                  (tok.value_high != NORANGE && tok.value_high < -(WOM_MAX+1))) {
+                    ret = EINVAL;
+                    goto finished;
+                }
+
+                int tmp_mon_weeks = get_weeks_of_month(cmp_time);
+                if(tok.value_low < 0)
+                    tok.value_low += tmp_mon_weeks + 1;
+                if(tok.value_high != NORANGE && tok.value_high < 0)
+                    tok.value_high += tmp_mon_weeks + 1;
+
+                if(tok.value_low < 0)
+                    tok.value_low = LOW_VAL;
+
                 checked_val = month_week;
 
                 break;
             case MOY:
-                if(tok.value_low > MOY_MAX || tok.value_high > MOY_MAX) {
+                if(abs(tok.value_low) > MOY_MAX ||
+                  (tok.value_high != NORANGE && abs(tok.value_high) > MOY_MAX)) {
                     ret = EINVAL;
                     goto finished;
                 }
@@ -398,6 +472,11 @@ eval_time_rule(const char *rule,
                     goto finished;
                 }
 
+                if(tok.value_low < 0)
+                    tok.value_low += MOY_MAX + 1;
+                if(tok.value_high != NORANGE && tok.value_high < 0)
+                    tok.value_high += MOY_MAX + 1;
+
                 checked_val = cmp_time->tm_mon;
 
                 break;
@@ -457,6 +536,28 @@ eval_time_rule(const char *rule,
                 goto finished;
         }
 
+        if(tok.type != RPT && (tok.value_high != NORANGE &&
+            (tok.value_high + zero_allowed) <= 0))
+        {
+            /* higher border too low */
+            if(prev != tok.type) { /* this is a new keyword type */
+                if(*matched == true) {
+                    *matched = false;
+                    found_flags[prev] = true;
+                    prev = tok.type;
+                }
+                else {
+                    /* previous attribute did not match */
+                    ret = EOK;
+                    goto finished;
+                }
+            }
+            zero_allowed = 0;
+            /* don't check now */
+            continue;
+        }
+        zero_allowed = 0;
+
         /*
          * If RPT is found, it is evaluated in the end of the rule evaluation.
          * The current state is that prev != tok.type, *matched is set to
@@ -499,6 +600,28 @@ finished:
     return ret;
 }
 
+/*
+ * The param is a tm struct in this library tm format
+ * tm_year = 1900-9999
+ * tm_mon = 1-12
+ */
+int get_days_in_month(struct tm *t) {
+    const int short_mon[] = {4, 6, 9, 11};
+    bool is_short = false;
+
+    for(int i = 0; i < 4; i++) {
+        if(t->tm_mon == short_mon[i]) {
+            is_short = true;
+            break;
+        }
+    }
+
+    if(is_short) return 30;
+    if(t->tm_mon == 2)
+        return isleap(t->tm_year) ? 29 : 28;
+    return 31;
+}
+
 int get_week_of_month(struct tm *t) {
     /*
      * ISO 8601 does not specify how to count a week in a month. It however
@@ -528,7 +651,26 @@ int get_week_of_month(struct tm *t) {
     return week + (t->tm_mday + (7 - t->tm_wday)) / 7;
 }
 
-int get_weeks_of_year(int year, int yday, int wday);
+/* returns the number of weeks of the given month in year in struct tm *t */
+int get_weeks_of_month(struct tm *t) {
+    int tm_mday_bcp;
+    int tm_wday_bcp;
+    int week;
+
+    tm_mday_bcp = t->tm_mday;
+    tm_wday_bcp = t->tm_wday;
+
+    t->tm_mday = get_days_in_month(t);
+    t->tm_wday = ((t->tm_mday - tm_mday_bcp) % 7 + tm_wday_bcp) % 7;
+    if(t->tm_wday == 0)
+        t->tm_wday = 7;
+    week = get_week_of_month(t);
+
+    t->tm_mday = tm_mday_bcp;
+    t->tm_wday = tm_wday_bcp;
+    return week;
+}
+
 int get_week_of_year(struct tm *t) {
     /* Week of year number according to ISO 8601 */
     int sun;         /* first Sunday of the year */
@@ -707,6 +849,14 @@ void get_token(const char *input, struct time_token *tok)
     }
 
     for(j = 0; j < 2; j++) {
+        /* scope variable */
+        int neg = 0;
+        if(!(tok->type == YEAR || tok->type == TOD) && input[idx] == '-') {
+            /* Negative value */
+            neg = 1;
+            part[0] = '-';
+            idx++;
+        }
         switch(tok->type) {
             case TOD:
             case YEAR:
@@ -721,7 +871,7 @@ void get_token(const char *input, struct time_token *tok)
             case DOW:
             case WOM:
                 /* No need to check for \0, strtol will do that*/
-                part[0] = input[idx];
+                part[neg] = input[idx];
                 idx++;
                 break;
             case DOM:
@@ -729,7 +879,7 @@ void get_token(const char *input, struct time_token *tok)
             case WOY:
                 for(i = 0; i < 2; i++) {
                     if(isdigit(input[idx])) {
-                        part[i] = input[idx];
+                        part[neg + i] = input[idx];
                         idx++;
                     }
                     else if(i == 0) /* not even a one-digit number */
@@ -739,7 +889,7 @@ void get_token(const char *input, struct time_token *tok)
             case DOY:
                 for(i = 0; i < 3; i++) {
                     if(isdigit(input[idx])) {
-                        part[i] = input[idx];
+                        part[neg + i] = input[idx];
                         idx++;
                     }
                     else if(i == 0) /* zero digits in number */
@@ -756,7 +906,7 @@ void get_token(const char *input, struct time_token *tok)
                 goto error;
             }
             /* if it's not a range */
-            if(input[idx] != '-') {
+            if(input[idx] != '~') {
                 tok->value_high = NORANGE;
                 free(part);
                 tok->pos = idx++;
@@ -786,6 +936,9 @@ error:
     return;
 }
 
+/*
+ * Validates the date stored in t, which is a structure with the usual C date values
+ */
 bool validate_date(struct tm *t) {
     /* 30-days months (mon_num-1 to match struct tm format) */
     const int short_mon[] = {3, 5, 8, 10};
@@ -852,10 +1005,10 @@ int parse_repeat(const char *str, struct repeat *rpt) {
     if(!validate_date(rpt->startdate))
         return -1;
 
-    if(str[read] == '-') {
+    if(str[read] == '~') {
         read++;
         /* date range */
-        /* 17 = first date(8)+ '-' + second date(8)*/
+        /* 17 = first date(8)+ '~' + second date(8)*/
         while(read < 17) {
             if(!isdigit(str[read++]))
                 return -1;
@@ -941,8 +1094,11 @@ errno_t check_range(int val, int low, int high)
         return EINVAL;
     }
 
-    if(low < 0)
-        return EINVAL;
+    if(low == LOW_VAL) {
+        if(high >= 0 && val <= high)
+            return EOK;
+        return ERANGE;
+    }
 
     if (low > high)
         return EINVAL;
diff --git a/src/tests/ipa_hbac-tests.c b/src/tests/ipa_hbac-tests.c
index 9b2247369963eca51c706770951e74772043dfec..9479e99f043f157095fc1ba9027a06617fc5fd96 100644
--- a/src/tests/ipa_hbac-tests.c
+++ b/src/tests/ipa_hbac-tests.c
@@ -839,7 +839,7 @@ START_TEST(ipa_hbac_test_allow_time)
 
     time_t tstamp;
     struct tm *now;
-    char *rule_str, *noallow_rule_str;
+    char *rule_str, *noallow_rule_str, *noallow_range_rule_str;
     char *noallow_rpt_rule_str;
 
     test_ctx = talloc_new(global_talloc_context);
@@ -879,8 +879,14 @@ START_TEST(ipa_hbac_test_allow_time)
                                           (now->tm_year - 1970) % 3 ? 1970 : 1971,
                                           now->tm_mon+1, now->tm_mday);
 
+    noallow_range_rule_str = talloc_asprintf(test_ctx,
+                                             "dayofmonth=%d~%d monthofyear=%d year=%d~%d",
+                                             -31, now->tm_mday == 1 ? now->tm_mday + 1 : now->tm_mday -1,
+                                             now->tm_mon + 1, now->tm_year + 1890, now->tm_year + 1910
+                                            );
+
     rule_str = talloc_asprintf(test_ctx,
-                               "dayofmonth=%d monthofyear=%d year=%d" \
+                               "dayofmonth=%d weekofmonth=-6~-1 monthofyear=%d year=%d" \
                                "repeat=19700101+1d",
                                now->tm_mday, now->tm_mon + 1, now->tm_year + 1900
                               );
@@ -898,7 +904,7 @@ START_TEST(ipa_hbac_test_allow_time)
     fail_if(rules[0]->timerules->timezone == NULL);
 
     rules[0]->timerules->accesstimes = talloc_array(rules[0]->timerules,
-                                       const char *, 4);
+                                       const char *, 5);
     fail_if(rules[0]->timerules->accesstimes == NULL);
 
     rules[0]->timerules->accesstimes[0] = talloc_strdup(rules[0]->timerules->accesstimes,
@@ -910,10 +916,14 @@ START_TEST(ipa_hbac_test_allow_time)
     fail_if(rules[0]->timerules->accesstimes[1] == NULL);
 
     rules[0]->timerules->accesstimes[2] = talloc_strdup(rules[0]->timerules->accesstimes,
-                                                        rule_str);
+                                                        noallow_range_rule_str);
     fail_if(rules[0]->timerules->accesstimes[2] == NULL);
 
-    rules[0]->timerules->accesstimes[3] = NULL;
+    rules[0]->timerules->accesstimes[3] = talloc_strdup(rules[0]->timerules->accesstimes,
+                                                        rule_str);
+    fail_if(rules[0]->timerules->accesstimes[3] == NULL);
+
+    rules[0]->timerules->accesstimes[4] = NULL;
 
     /* Validate the rule */
     is_valid = hbac_rule_is_complete(rules[0], &missing_attrs);
-- 
2.4.3

