libxt_time.c revision db50b83bc3cd634beb71f38978ad7d035c88ff11
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 <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
20enum {
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
35static const char *const week_days[] = {
36	NULL, "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun",
37};
38
39static 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
57static 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
73static 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
88static 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
162static 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
192static 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
210static 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
227static 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
258static 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
298static 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
321static 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
349static 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
364static 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
373static 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
408static 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
434static 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
448void _init(void)
449{
450	xtables_register_match(&time_match);
451}
452