1 /* 2 * libxt_time - iptables part for xt_time 3 * Copyright CC Computer Consultants GmbH, 2007 4 * Contact: <jengelh (at) computergmbh.de> 5 * 6 * libxt_time.c is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation; either version 2 or 3 of the License. 9 * 10 * Based on libipt_time.c. 11 */ 12 #include <stdio.h> 13 #include <string.h> 14 #include <stdlib.h> 15 #include <time.h> 16 #include <linux/types.h> 17 #include <linux/netfilter/xt_time.h> 18 #include <xtables.h> 19 20 enum { 21 O_DATE_START = 0, 22 O_DATE_STOP, 23 O_TIME_START, 24 O_TIME_STOP, 25 O_MONTHDAYS, 26 O_WEEKDAYS, 27 O_LOCAL_TZ, 28 O_UTC, 29 O_KERNEL_TZ, 30 F_LOCAL_TZ = 1 << O_LOCAL_TZ, 31 F_UTC = 1 << O_UTC, 32 F_KERNEL_TZ = 1 << O_KERNEL_TZ, 33 }; 34 35 static const char *const week_days[] = { 36 NULL, "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun", 37 }; 38 39 static const struct xt_option_entry time_opts[] = { 40 {.name = "datestart", .id = O_DATE_START, .type = XTTYPE_STRING}, 41 {.name = "datestop", .id = O_DATE_STOP, .type = XTTYPE_STRING}, 42 {.name = "timestart", .id = O_TIME_START, .type = XTTYPE_STRING}, 43 {.name = "timestop", .id = O_TIME_STOP, .type = XTTYPE_STRING}, 44 {.name = "weekdays", .id = O_WEEKDAYS, .type = XTTYPE_STRING, 45 .flags = XTOPT_INVERT}, 46 {.name = "monthdays", .id = O_MONTHDAYS, .type = XTTYPE_STRING, 47 .flags = XTOPT_INVERT}, 48 {.name = "localtz", .id = O_LOCAL_TZ, .type = XTTYPE_NONE, 49 .excl = F_UTC}, 50 {.name = "utc", .id = O_UTC, .type = XTTYPE_NONE, 51 .excl = F_LOCAL_TZ | F_KERNEL_TZ}, 52 {.name = "kerneltz", .id = O_KERNEL_TZ, .type = XTTYPE_NONE, 53 .excl = F_UTC}, 54 XTOPT_TABLEEND, 55 }; 56 57 static void time_help(void) 58 { 59 printf( 60 "time match options:\n" 61 " --datestart time Start and stop time, to be given in ISO 8601\n" 62 " --datestop time (YYYY[-MM[-DD[Thh[:mm[:ss]]]]])\n" 63 " --timestart time Start and stop daytime (hh:mm[:ss])\n" 64 " --timestop time (between 00:00:00 and 23:59:59)\n" 65 "[!] --monthdays value List of days on which to match, separated by comma\n" 66 " (Possible days: 1 to 31; defaults to all)\n" 67 "[!] --weekdays value List of weekdays on which to match, sep. by comma\n" 68 " (Possible days: Mon,Tue,Wed,Thu,Fri,Sat,Sun or 1 to 7\n" 69 " Defaults to all weekdays.)\n" 70 " --kerneltz Work with the kernel timezone instead of UTC\n"); 71 } 72 73 static void time_init(struct xt_entry_match *m) 74 { 75 struct xt_time_info *info = (void *)m->data; 76 77 /* By default, we match on every day, every daytime */ 78 info->monthdays_match = XT_TIME_ALL_MONTHDAYS; 79 info->weekdays_match = XT_TIME_ALL_WEEKDAYS; 80 info->daytime_start = XT_TIME_MIN_DAYTIME; 81 info->daytime_stop = XT_TIME_MAX_DAYTIME; 82 83 /* ...and have no date-begin or date-end boundary */ 84 info->date_start = 0; 85 info->date_stop = INT_MAX; 86 } 87 88 static time_t time_parse_date(const char *s, bool end) 89 { 90 unsigned int month = 1, day = 1, hour = 0, minute = 0, second = 0; 91 unsigned int year = end ? 2038 : 1970; 92 const char *os = s; 93 struct tm tm; 94 time_t ret; 95 char *e; 96 97 year = strtoul(s, &e, 10); 98 if ((*e != '-' && *e != '\0') || year < 1970 || year > 2038) 99 goto out; 100 if (*e == '\0') 101 goto eval; 102 103 s = e + 1; 104 month = strtoul(s, &e, 10); 105 if ((*e != '-' && *e != '\0') || month > 12) 106 goto out; 107 if (*e == '\0') 108 goto eval; 109 110 s = e + 1; 111 day = strtoul(s, &e, 10); 112 if ((*e != 'T' && *e != '\0') || day > 31) 113 goto out; 114 if (*e == '\0') 115 goto eval; 116 117 s = e + 1; 118 hour = strtoul(s, &e, 10); 119 if ((*e != ':' && *e != '\0') || hour > 23) 120 goto out; 121 if (*e == '\0') 122 goto eval; 123 124 s = e + 1; 125 minute = strtoul(s, &e, 10); 126 if ((*e != ':' && *e != '\0') || minute > 59) 127 goto out; 128 if (*e == '\0') 129 goto eval; 130 131 s = e + 1; 132 second = strtoul(s, &e, 10); 133 if (*e != '\0' || second > 59) 134 goto out; 135 136 eval: 137 tm.tm_year = year - 1900; 138 tm.tm_mon = month - 1; 139 tm.tm_mday = day; 140 tm.tm_hour = hour; 141 tm.tm_min = minute; 142 tm.tm_sec = second; 143 tm.tm_isdst = 0; 144 /* 145 * Offsetting, if any, is done by xt_time.ko, 146 * so we have to disable it here in userspace. 147 */ 148 setenv("TZ", "UTC", true); 149 tzset(); 150 ret = mktime(&tm); 151 if (ret >= 0) 152 return ret; 153 perror("mktime"); 154 xtables_error(OTHER_PROBLEM, "mktime returned an error"); 155 156 out: 157 xtables_error(PARAMETER_PROBLEM, "Invalid date \"%s\" specified. Should " 158 "be YYYY[-MM[-DD[Thh[:mm[:ss]]]]]", os); 159 return -1; 160 } 161 162 static unsigned int time_parse_minutes(const char *s) 163 { 164 unsigned int hour, minute, second = 0; 165 char *e; 166 167 hour = strtoul(s, &e, 10); 168 if (*e != ':' || hour > 23) 169 goto out; 170 171 s = e + 1; 172 minute = strtoul(s, &e, 10); 173 if ((*e != ':' && *e != '\0') || minute > 59) 174 goto out; 175 if (*e == '\0') 176 goto eval; 177 178 s = e + 1; 179 second = strtoul(s, &e, 10); 180 if (*e != '\0' || second > 59) 181 goto out; 182 183 eval: 184 return 60 * 60 * hour + 60 * minute + second; 185 186 out: 187 xtables_error(PARAMETER_PROBLEM, "invalid time \"%s\" specified, " 188 "should be hh:mm[:ss] format and within the boundaries", s); 189 return -1; 190 } 191 192 static const char *my_strseg(char *buf, unsigned int buflen, 193 const char **arg, char delim) 194 { 195 const char *sep; 196 197 if (*arg == NULL || **arg == '\0') 198 return NULL; 199 sep = strchr(*arg, delim); 200 if (sep == NULL) { 201 snprintf(buf, buflen, "%s", *arg); 202 *arg = NULL; 203 return buf; 204 } 205 snprintf(buf, buflen, "%.*s", (unsigned int)(sep - *arg), *arg); 206 *arg = sep + 1; 207 return buf; 208 } 209 210 static uint32_t time_parse_monthdays(const char *arg) 211 { 212 char day[3], *err = NULL; 213 uint32_t ret = 0; 214 unsigned int i; 215 216 while (my_strseg(day, sizeof(day), &arg, ',') != NULL) { 217 i = strtoul(day, &err, 0); 218 if ((*err != ',' && *err != '\0') || i > 31) 219 xtables_error(PARAMETER_PROBLEM, 220 "%s is not a valid day for --monthdays", day); 221 ret |= 1 << i; 222 } 223 224 return ret; 225 } 226 227 static unsigned int time_parse_weekdays(const char *arg) 228 { 229 char day[4], *err = NULL; 230 unsigned int i, ret = 0; 231 bool valid; 232 233 while (my_strseg(day, sizeof(day), &arg, ',') != NULL) { 234 i = strtoul(day, &err, 0); 235 if (*err == '\0') { 236 if (i == 0) 237 xtables_error(PARAMETER_PROBLEM, 238 "No, the week does NOT begin with Sunday."); 239 ret |= 1 << i; 240 continue; 241 } 242 243 valid = false; 244 for (i = 1; i < ARRAY_SIZE(week_days); ++i) 245 if (strncmp(day, week_days[i], 2) == 0) { 246 ret |= 1 << i; 247 valid = true; 248 } 249 250 if (!valid) 251 xtables_error(PARAMETER_PROBLEM, 252 "%s is not a valid day specifier", day); 253 } 254 255 return ret; 256 } 257 258 static void time_parse(struct xt_option_call *cb) 259 { 260 struct xt_time_info *info = cb->data; 261 262 xtables_option_parse(cb); 263 switch (cb->entry->id) { 264 case O_DATE_START: 265 info->date_start = time_parse_date(cb->arg, false); 266 break; 267 case O_DATE_STOP: 268 info->date_stop = time_parse_date(cb->arg, true); 269 break; 270 case O_TIME_START: 271 info->daytime_start = time_parse_minutes(cb->arg); 272 break; 273 case O_TIME_STOP: 274 info->daytime_stop = time_parse_minutes(cb->arg); 275 break; 276 case O_LOCAL_TZ: 277 fprintf(stderr, "WARNING: --localtz is being replaced by " 278 "--kerneltz, since \"local\" is ambiguous. Note the " 279 "kernel timezone has caveats - " 280 "see manpage for details.\n"); 281 /* fallthrough */ 282 case O_KERNEL_TZ: 283 info->flags |= XT_TIME_LOCAL_TZ; 284 break; 285 case O_MONTHDAYS: 286 info->monthdays_match = time_parse_monthdays(cb->arg); 287 if (cb->invert) 288 info->monthdays_match ^= XT_TIME_ALL_MONTHDAYS; 289 break; 290 case O_WEEKDAYS: 291 info->weekdays_match = time_parse_weekdays(cb->arg); 292 if (cb->invert) 293 info->weekdays_match ^= XT_TIME_ALL_WEEKDAYS; 294 break; 295 } 296 } 297 298 static void time_print_date(time_t date, const char *command) 299 { 300 struct tm *t; 301 302 /* If it is the default value, do not print it. */ 303 if (date == 0 || date == LONG_MAX) 304 return; 305 306 t = gmtime(&date); 307 if (command != NULL) 308 /* 309 * Need a contiguous string (no whitespaces), hence using 310 * the ISO 8601 "T" variant. 311 */ 312 printf(" %s %04u-%02u-%02uT%02u:%02u:%02u", 313 command, t->tm_year + 1900, t->tm_mon + 1, 314 t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec); 315 else 316 printf(" %04u-%02u-%02u %02u:%02u:%02u", 317 t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, 318 t->tm_hour, t->tm_min, t->tm_sec); 319 } 320 321 static void time_print_monthdays(uint32_t mask, bool human_readable) 322 { 323 unsigned int i, nbdays = 0; 324 325 printf(" "); 326 for (i = 1; i <= 31; ++i) 327 if (mask & (1 << i)) { 328 if (nbdays++ > 0) 329 printf(","); 330 printf("%u", i); 331 if (human_readable) 332 switch (i % 10) { 333 case 1: 334 printf("st"); 335 break; 336 case 2: 337 printf("nd"); 338 break; 339 case 3: 340 printf("rd"); 341 break; 342 default: 343 printf("th"); 344 break; 345 } 346 } 347 } 348 349 static void time_print_weekdays(unsigned int mask) 350 { 351 unsigned int i, nbdays = 0; 352 353 printf(" "); 354 for (i = 1; i <= 7; ++i) 355 if (mask & (1 << i)) { 356 if (nbdays > 0) 357 printf(",%s", week_days[i]); 358 else 359 printf("%s", week_days[i]); 360 ++nbdays; 361 } 362 } 363 364 static inline void divide_time(unsigned int fulltime, unsigned int *hours, 365 unsigned int *minutes, unsigned int *seconds) 366 { 367 *seconds = fulltime % 60; 368 fulltime /= 60; 369 *minutes = fulltime % 60; 370 *hours = fulltime / 60; 371 } 372 373 static void time_print(const void *ip, const struct xt_entry_match *match, 374 int numeric) 375 { 376 const struct xt_time_info *info = (const void *)match->data; 377 unsigned int h, m, s; 378 379 printf(" TIME"); 380 381 if (info->daytime_start != XT_TIME_MIN_DAYTIME || 382 info->daytime_stop != XT_TIME_MAX_DAYTIME) { 383 divide_time(info->daytime_start, &h, &m, &s); 384 printf(" from %02u:%02u:%02u", h, m, s); 385 divide_time(info->daytime_stop, &h, &m, &s); 386 printf(" to %02u:%02u:%02u", h, m, s); 387 } 388 if (info->weekdays_match != XT_TIME_ALL_WEEKDAYS) { 389 printf(" on"); 390 time_print_weekdays(info->weekdays_match); 391 } 392 if (info->monthdays_match != XT_TIME_ALL_MONTHDAYS) { 393 printf(" on"); 394 time_print_monthdays(info->monthdays_match, true); 395 } 396 if (info->date_start != 0) { 397 printf(" starting from"); 398 time_print_date(info->date_start, NULL); 399 } 400 if (info->date_stop != INT_MAX) { 401 printf(" until date"); 402 time_print_date(info->date_stop, NULL); 403 } 404 if (!(info->flags & XT_TIME_LOCAL_TZ)) 405 printf(" UTC"); 406 } 407 408 static void time_save(const void *ip, const struct xt_entry_match *match) 409 { 410 const struct xt_time_info *info = (const void *)match->data; 411 unsigned int h, m, s; 412 413 if (info->daytime_start != XT_TIME_MIN_DAYTIME || 414 info->daytime_stop != XT_TIME_MAX_DAYTIME) { 415 divide_time(info->daytime_start, &h, &m, &s); 416 printf(" --timestart %02u:%02u:%02u", h, m, s); 417 divide_time(info->daytime_stop, &h, &m, &s); 418 printf(" --timestop %02u:%02u:%02u", h, m, s); 419 } 420 if (info->monthdays_match != XT_TIME_ALL_MONTHDAYS) { 421 printf(" --monthdays"); 422 time_print_monthdays(info->monthdays_match, false); 423 } 424 if (info->weekdays_match != XT_TIME_ALL_WEEKDAYS) { 425 printf(" --weekdays"); 426 time_print_weekdays(info->weekdays_match); 427 } 428 time_print_date(info->date_start, "--datestart"); 429 time_print_date(info->date_stop, "--datestop"); 430 if (info->flags & XT_TIME_LOCAL_TZ) 431 printf(" --kerneltz"); 432 } 433 434 static struct xtables_match time_match = { 435 .name = "time", 436 .family = NFPROTO_UNSPEC, 437 .version = XTABLES_VERSION, 438 .size = XT_ALIGN(sizeof(struct xt_time_info)), 439 .userspacesize = XT_ALIGN(sizeof(struct xt_time_info)), 440 .help = time_help, 441 .init = time_init, 442 .print = time_print, 443 .save = time_save, 444 .x6_parse = time_parse, 445 .x6_options = time_opts, 446 }; 447 448 void _init(void) 449 { 450 xtables_register_match(&time_match); 451 } 452