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