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