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