static const char rcsid[] = "$Id: time.c,v 1.28 2004/04/30 17:00:58 will Exp $"; #include "super.h" #define ANYDAY 7 /* Maximum length of a time field in super.tab. */ #define MAX_T_FIELDLEN 80 struct weekDay { char name[20]; char abbr[20]; }; #ifdef HAVE_LOCALE_H /* We will pick up weekday names dynamically; make the first name a * null string so we know that initialization has yet to be done. */ static struct weekDay weekday[7] = { { "", ""}, }; #else static struct weekDay weekday[7] = { { "sunday", "sun", }, { "monday", "mon", }, { "tuesday", "tue", }, { "wednesday", "wed", }, { "thursday", "thu", }, { "friday", "fri", }, { "saturday", "sat" } }; #endif char * dayname(n) int n; /* day number: 0..6 = sun..sat; 7 = any */ { /* Returns ptr to day of week */ static char *anyday="*"; #ifdef HAVE_LOCALE_H if (*(weekday[0].name) == '\0') readtime_init(); #endif if (n < 0 || n > 6) return anyday; else return weekday[n].name; } int daynum(t) int t; /* A Unix time */ { /* Returns day of week, with 0 = sunday. This routine takes the input * time as given -- it doesn't assume GMT or any such thing -- and * returns the day number: you should use localtime() or gmtime() if * you have them available. */ int jd = (int) (2440587.5 + ((double) t) / 86400.); int i = (jd+1) / 7; return jd - 7 * i + 1; } int InsertTimeList(str, wdlist, tl, timetype, invert) char *str; /* Contains one of the time types accepted by readtimerange(), * and extended to allow brace-expansion */ char **wdlist; /* list-expanded form of str */ TimeList *tl; /* Insert time list elements at tl->next */ char *timetype; /* "per-cmd" or "global": a string to use in info msgs */ int invert; /* Inverts the test */ { /* Checks if time is in the range specified by the time string str. * Returns * -1 on syntax error, malloc error, or similar; * 0 otherwise. * Side effect: sets the matches.time field. */ int match; int iwd; char *tok; TimeList *new; for (iwd=0, match=0; (tok=wdlist[iwd]); iwd++) { new = (TimeList *) malloc(sizeof(TimeList)); if (!new) return Error(0, 0, "%t\n\tFailed to malloc space for time entry\n"); new->next = tl->next; new->te.invert = invert; /* interpret pat */ if (readtimerange(tok, &new->te.begin, &new->te.end, &new->te.day) == -1) return -1; if (debug) (void) fprintf(stderr, "\tInsert %s time pattern: %stime~%s (min=%d-%d day=%d [dayname=%s])\n", timetype, invert ? "!" : "", tok, new->te.begin, new->te.end, new->te.day, dayname(new->te.day)); tl->next = new; } return 0; } void matchtime(our, tl) OurTime *our; /* A time entry to match. Only the "min" and "day" fields * will be used, so it's ok if the "start" field doesn't * match them. */ TimeList *tl; /* A list of times to match against; 1st used is tl->next */ { /* The time list created by the InsertTimeList function * is in reverse order, so we only need to find the first * entry in the list that is a match (+ or -) and stop there. * But if there are no matches, we scan all entries. * Side effects: * If an entry is matched, matches.time is set to 0 or 1, * according as the entry is/is not inverted. Otherwise unmodified. * If an entry is non-inverted, matches.alltime_invert is set to 0, * otherwise unmodified. Note that this will include all entries * only if there was no match, because scanning stops with a match. */ /* The caller should interpret the returned matches as * follows. * Case 1: Our time 12:30 * time~8-17 !time~12-13 time~23-24 * a. time~8-17 matches, user allowed. * b. time~12-13 matches, ! means user disallowed. * c. time~23-24 doesn't match, doesn't change allow/disallow. * Therefore user disallowed. * Case 2: Our time 12:30 * time~8-17 !time~12-13 !time~23-24 * a. time~8-17 matches, user allowed. * b. time~12-13 matches, ! means user disallowed. * c. time~23-24 doesn't match, doesn't change allow/disallow. * Therefore user disallowed. * Case 3: Our time 12:30 * !time~23-24 * a. time~23-24 doesn't match, doesn't change allow/disallow. * b. Thus fall back on default. Default is * i) allow if _all_ statements are inverted; * ii) else disallow. * Therefore user is allowed. * Case 4: Our time 17:30 * time~8-17 !time~12-13 !time~23-24 * a. time~8-17 doesn't match, doesn't change allow/disallow. * b. time~12-13 doesn't match, doesn't change allow/disallow. * c. time~23-24 doesn't match, doesn't change allow/disallow. * d. Thus fall back on default. Default is * i) allow if _all_ statements are inverted; * ii) else disallow. * Therefore user is disallowed. */ int match; for (tl=tl->next, match=0; tl && !match; tl=tl->next) { if (!tl->te.invert) matches.allinverted = 0; if (our->min >= tl->te.begin && our->min <= tl->te.end && (tl->te.day == ANYDAY || our->day == tl->te.day)) { /* matches */ match = 1; matches.time = tl->te.invert ? 0 : 1; if (debug || it_came_from_cmdline) { (void) fprintf(stderr, "\t%s: %stime~%d:%.2d-%d:%.2d/%s\n", tl->te.invert ? "Permission denied" : "Permission allowed", tl->te.invert ? "!" : "", tl->te.begin/60, tl->te.begin%60, tl->te.end/60, tl->te.end%60, dayname(tl->te.day)); } } else { /* Our time is not in the range of this time entry. */ if (debug || it_came_from_cmdline) { (void) fprintf(stderr, "\tNot applicable: %stime~%d:%.2d-%d:%.2d/%s\n", tl->te.invert ? "!" : "", tl->te.begin/60, tl->te.begin%60, tl->te.end/60, tl->te.end%60, dayname(tl->te.day)); } } } } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Frees all elements in a TimeList, except the one it's given. * The "next" field of that element is set NULL. */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ void free_TimeList(tl) TimeList *tl; { TimeList *tlp; if (!tl || !tl->next) return; tlp = tl->next; tl->next = NULL; for (tl=tl->next ; tl; tl = tlp) { tlp = tl->next; free(tl); } } #ifdef HAVE_LOCALE_H /* The following function should be invoked each time the locale * is changed (via lang=xxx). */ void readtime_init() { struct tm ti; int i; /* We know the following to be a Sunday */ ti.tm_sec = 0; ti.tm_min = 0; ti.tm_hour = 12; ti.tm_mday = 4; ti.tm_wday = 0; ti.tm_mon = 0; ti.tm_year = 70; /* Figure out the days of the week in the current locale */ if (debug) fprintf(stderr, "\tInitializing days of week. Full names and official abbr's:\n\t"); for (i = 0; i < 7; i++) { strftime(weekday[i].name, sizeof(weekday[0].name)-1, "%A", &ti); strtolower(weekday[i].name); strftime(weekday[i].abbr, sizeof(weekday[0].abbr)-1, "%a", &ti); strtolower(weekday[i].abbr); if (debug) { fprintf(stderr, "%s (%s) ", weekday[i].name, weekday[i].abbr); if (i == 3) fputs("\n\t", stderr); } ti.tm_mday++; ti.tm_wday++; } if (debug) fputs("\n", stderr); } #endif int readtimerange(str, t1, t2, d) char *str; short *t1; /* Returned with 1st time, units=minutes, range = 0..1439 */ short *t2; /* Returned with 2nd time, units=minutes, range = 0..1439 */ char *d; /* Returned with day number (0=Sunday) or ANYDAY */ { /* str is a string containing one of the following patterns: * * "" (implies all days, all hours) * [/]dayname (implies all hours) * hh[:mm]-hh[:mm]/dayname * hh[:mm]-hh[:mm] (implies all days) * xhh[:mm]/dayname (x is one of <, >, <=, >= ) * xhh[:mm] (implies all days; x is as above) * * The valid daynames are (case-insensitive) either an official abbreviated * day name in the current locale; a 3-or-more character abbreviation * of the full weekday; or "*", meaning any day. * For convenience, the upper time can be 24:00, but it is converted * to 11:59. * Returns: -1 on invalid time pattern; 0 otherwise. */ char *s, *p; int hh1, mm1; int hh2, mm2; int i, l; int has_relop; #ifdef HAVE_ENUM enum { LT, LE, GT, GE } relop; #else /* No enums! */ #define LT 0 #define LE 1 #define GT 2 #define GE 3 int relop; #endif if (strlen(str) > MAX_T_FIELDLEN) { return Error(0, 0, "%t\n\tInvalid time range; first %d chars are: <%*.*s>\n", MAX_T_FIELDLEN, MAX_T_FIELDLEN, str); } #ifdef HAVE_LOCALE_H if (*(weekday[0].name) == '\0') readtime_init(); #endif s = str; has_relop = 0; if (*s == '<' || *s == '>') { /* Must be xhh[:mm][/dayname] */ has_relop = 1; switch (*s++) { case '<': relop = (*s == '=') ? (s++, LE) : LT; break; case '>': relop = (*s == '=') ? (s++, GE) : GT; break; default: return Error(0, 0, "%t\n\tInvalid time range <%s>\n", str); } if (!isdigit(*s)) return Error(0, 0, "%t\n\tInvalid time range <%s> (invalid hour part)\n", str); hh1 = strtol(s, &p, 10); if (p == s || hh1 > 24 || hh1 < 0) return Error(0, 0, "%t\n\tInvalid time range <%s> (invalid hour part)\n", str); s = p; if (*s != ':') { /* minutes not given */ mm1 = 0; } else { mm1 = strtol(++s, &p, 10); if (p == s || mm1 > 59 || mm1 < 0 || (mm1 > 0 && hh1 == 24)) return Error(0, 0, "%t\n\tInvalid time range <%s> (invalid minute part)\n", str); } s = p; switch (relop) { case LT: *t1 = 0; *t2 = hh1*60 + mm1 - 1; break; case LE: *t1 = 0; *t2 = hh1*60 + mm1; break; case GT: *t1 = hh1*60 + mm1 + 1; *t2 = 24*60 - 1; break; case GE: *t1 = hh1*60 + mm1; *t2 = 24*60 - 1; break; default: return Error(0, 0, "%t\n\tInternal time range error -- relop is %d\n", relop); } } else if (isdigit(*str)) { /* It must begin hh:mm-hh:mm */ s = str; hh1 = strtol(s, &p, 10); if (p == s || hh1 > 24 || hh1 < 0) return Error(0, 0, "%t\n\tInvalid time range <%s> (invalid first hr)\n", str); s = p; if (*s != ':') { mm1 = 0; } else { mm1 = strtol(++s, &p, 10); if (p == s || mm1 > 59 || mm1 < 0 || (mm1 > 0 && hh1 == 24)) return Error(0, 0, "%t\n\tInvalid time range <%s> (invalid first min)\n", str); } *t1 = hh1*60 + mm1; s = p; if (*s++ != '-') return Error(0, 0, "%t\n\tInvalid time range <%s> (expected '-' in hh:mm-hh:mm)\n", str); hh2 = strtol(s, &p, 10); if (p == s || hh2 > 24 || hh2 < 0) return Error(0, 0, "%t\n\tInvalid time range <%s> (invalid second hr)\n", str); s = p; if (*s != ':') { mm2 = 0; } else { mm2 = strtol(++s, &p, 10); if (p == s || mm2 > 59 || mm2 < 0 || (mm2 > 0 && hh2 == 24)) return Error(0, 0, "%t\n\tInvalid time range <%s> (invalid second min)\n", str); } s = p; if (*s != '\0' && *s != '/') return Error(0, 0, "%t\n\tInvalid time range <%s> (invalid char after time range)\n", str); *t2 = hh2*60 + mm2; } else { /* We've seen no time part; must be dayname only; time is all hrs */ s = str; *t1 = 0; *t2 = 24 * 60; } if (*s == '/') /* Skip field separator, if present */ s++; if (*s == '\0' || strcmp(s, "*") == 0) { *d = ANYDAY; /* all days */ } else { /* It must be a weekday, expressed in the current locale. * Allow just dayname or /dayname. */ char buf[MAX_T_FIELDLEN+1]; strcpy(buf, s); strtolower(buf); for (i=0; i<7; i++) { #if 0 fprintf(stderr, "HERE: s=<%s> buf=<%s> day=<%s> abbr=<%s>\n", s, buf, weekday[i].name, weekday[i].abbr); #endif if (strlen(buf) < 3) { ; /* not accepted -- must be at least three chars */ } else if (strncmp(weekday[i].name, buf, (l=strlen(buf))) == 0) { *d = i; break; } else if (strcmp(weekday[i].abbr, buf) == 0) { *d = i; break; } } if (i >= 7) return Error(0, 0, "%t\n\tInvalid time range <%s> (invalid day field)\n", str); } /* Convert 24:00 to 23:59 */ if (*t1 == 24*60) (*t1)--; if (*t2 == 24*60) (*t2)--; if (*t1 < 0 || *t2 < 0 || *t1 > 24*60-1 || *t2 > 24*60-1) return Error(0, 0, "%t\n\tInvalid time range <%s>\n", str); return 0; } int readtime(str, t, d) char *str; short *t; /* Returned with time, units=minutes, range = 0..1439 */ char *d; /* Returned with day number (0=Sunday) */ { /* str must be like: * * hh[:mm]/dayname * * The valid daynames are (case-insensitive) either an official abbreviated * day name in the current locale; a 3-or-more character abbreviation * of the full weekday; or "*", meaning any day. * Returns: -1 on invalid time pattern; 0 otherwise. */ char buf[MAX_T_FIELDLEN+1]; int hh, mm; int i, l; #ifdef HAVE_LOCALE_H if (*(weekday[0].name) == '\0') readtime_init(); #endif if (strlen(str) > sizeof(buf)-1) return Error(0, 0, "%t\n\tInvalid time <%s>\n", str); if (sscanf(str, "%d:%d/%s", &hh, &mm, buf) != 3) { mm = 0; if ( (sscanf(str, "%d:/%s", &hh, buf) != 2) && (sscanf(str, "%d/%s", &hh, buf) != 2) ) { return Error(0, 0, "%t\n\tInvalid time <%s>; format must be hh[:mm]/dayname\n", str); } } if (hh > 23 || hh < 0) return Error(0, 0, "%t\n\tInvalid time <%s> (invalid hr)\n", str); if (mm > 59 || mm < 0) return Error(0, 0, "%t\n\tInvalid time <%s> (invalid min)\n", str); *t = hh*60 + mm; /* The dayname must be a weekday, expressed in the current locale. */ strtolower(buf); for (i=0; i<7; i++) { if (strlen(buf) < 3) { ; /* not accepted -- must be at least three chars */ } else if (strncmp(weekday[i].name, buf, (l=strlen(buf))) == 0) { *d = i; break; } else if (strcmp(weekday[i].abbr, buf) == 0) { *d = i; break; } } if (i >= 7) return Error(0, 0, "%t\n\tInvalid time <%s> (invalid dayname)\n", str); return 0; }