1/* Shared library add-on to iptables to add TIME matching support. */
2#include <stdio.h>
3#include <netdb.h>
4#include <string.h>
5#include <stdlib.h>
6#include <stddef.h> /* for 'offsetof' */
7#include <getopt.h>
8
9//#include <iptables.h>
10#include <ip6tables.h>
11#include <linux/netfilter_ipv4/ipt_time.h>
12#include <time.h>
13
14static int globaldays;
15
16/* Function which prints out usage message. */
17static void
18help(void)
19{
20	printf(
21"TIME v%s options:\n"
22" [ --timestart value ] [ --timestop value] [ --days listofdays ] [ --datestart value ] [ --datestop value ]\n"
23"          timestart value : HH:MM (default 00:00)\n"
24"          timestop  value : HH:MM (default 23:59)\n"
25"                            Note: daylight savings time changes are not tracked\n"
26"          listofdays value: a list of days to apply\n"
27"                            from Mon,Tue,Wed,Thu,Fri,Sat,Sun\n"
28"                            Coma speparated, no space, case sensitive.\n"
29"                            Defaults to all days.\n"
30"          datestart value : YYYY[:MM[:DD[:hh[:mm[:ss]]]]]\n"
31"                            If any of month, day, hour, minute or second is\n"
32"                            not specified, then defaults to their smallest\n"
33"                            1900 <= YYYY < 2037\n"
34"                               1 <= MM <= 12\n"
35"                               1 <= DD <= 31\n"
36"                               0 <= hh <= 23\n"
37"                               0 <= mm <= 59\n"
38"                               0 <= ss <= 59\n"
39"          datestop  value : YYYY[:MM[:DD[:hh[:mm[:ss]]]]]\n"
40"                            If the whole option is ommited, default to never stop\n"
41"                            If any of month, day, hour, minute or second is\n"
42"                            not specified, then default to their smallest\n",
43IPTABLES_VERSION);
44}
45
46static struct option opts[] = {
47	{ "timestart", 1, 0, '1' },
48	{ "timestop", 1, 0, '2' },
49	{ "days", 1, 0, '3'},
50	{ "datestart", 1, 0, '4' },
51	{ "datestop", 1, 0, '5' },
52	{0}
53};
54
55/* Initialize the match. */
56static void
57init(struct ip6t_entry_match *m, unsigned int *nfcache)
58{
59	struct ipt_time_info *info = (struct ipt_time_info *)m->data;
60	globaldays = 0;
61        /* By default, we match on everyday */
62	info->days_match = 127;
63	/* By default, we match on every hour:min of the day */
64	info->time_start = 0;
65	info->time_stop  = 1439;  /* (23*60+59 = 1439 */
66	/* By default, we don't have any date-begin or date-end boundaries */
67	info->date_start = 0;
68	info->date_stop  = LONG_MAX;
69}
70
71/**
72 * param: part1, a pointer on a string 2 chars maximum long string, that will contain the hours.
73 * param: part2, a pointer on a string 2 chars maximum long string, that will contain the minutes.
74 * param: str_2_parse, the string to parse.
75 * return: 1 if ok, 0 if error.
76 */
77static int
78split_time(char **part1, char **part2, const char *str_2_parse)
79{
80	unsigned short int i,j=0;
81	char *rpart1 = *part1;
82	char *rpart2 = *part2;
83	unsigned char found_column = 0;
84
85	/* Check the length of the string */
86	if (strlen(str_2_parse) > 5)
87		return 0;
88	/* parse the first part until the ':' */
89	for (i=0; i<2; i++)
90	{
91		if (str_2_parse[i] == ':')
92			found_column = 1;
93		else
94			rpart1[i] = str_2_parse[i];
95	}
96	if (!found_column)
97		i++;
98	j=i;
99	/* parse the second part */
100	for (; i<strlen(str_2_parse); i++)
101	{
102		rpart2[i-j] = str_2_parse[i];
103	}
104	/* if we are here, format should be ok. */
105	return 1;
106}
107
108static int
109parse_number(char *str, int num_min, int num_max, int *number)
110{
111	/* if the number starts with 0, replace it with a space else
112	string_to_number() will interpret it as octal !! */
113	if (strlen(str) == 0)
114		return 0;
115
116	if ((str[0] == '0') && (str[1] != '\0'))
117		str[0] = ' ';
118
119	return string_to_number(str, num_min, num_max, number);
120}
121
122static void
123parse_time_string(int *hour, int *minute, const char *time)
124{
125	char *hours;
126	char *minutes;
127	hours = (char *)malloc(3);
128	minutes = (char *)malloc(3);
129	memset(hours, 0, 3);
130	memset(minutes, 0, 3);
131
132	if (split_time((char **)&hours, (char **)&minutes, time) == 1)
133	{
134		*hour = 0;
135		*minute = 0;
136		if ((parse_number((char *)hours, 0, 23, hour) != -1) &&
137		    (parse_number((char *)minutes, 0, 59, minute) != -1))
138		{
139			free(hours);
140			free(minutes);
141			return;
142		}
143	}
144
145	free(hours);
146	free(minutes);
147
148	/* If we are here, there was a problem ..*/
149	exit_error(PARAMETER_PROBLEM,
150		   "invalid time `%s' specified, should be HH:MM format", time);
151}
152
153/* return 1->ok, return 0->error */
154static int
155parse_day(int *days, int from, int to, const char *string)
156{
157	char *dayread;
158	char *days_str[7] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
159	unsigned short int days_of_week[7] = {64, 32, 16, 8, 4, 2, 1};
160	unsigned int i;
161
162	dayread = (char *)malloc(4);
163	bzero(dayread, 4);
164	if ((to-from) != 3) {
165		free(dayread);
166		return 0;
167	}
168	for (i=from; i<to; i++)
169		dayread[i-from] = string[i];
170	for (i=0; i<7; i++)
171		if (strcmp(dayread, days_str[i]) == 0)
172		{
173			*days |= days_of_week[i];
174			free(dayread);
175			return 1;
176		}
177	/* if we are here, we didn't read a valid day */
178	free(dayread);
179	return 0;
180}
181
182static void
183parse_days_string(int *days, const char *daystring)
184{
185	int len;
186	int i=0;
187	char *err = "invalid days `%s' specified, should be Sun,Mon,Tue... format";
188
189	len = strlen(daystring);
190	if (len < 3)
191		exit_error(PARAMETER_PROBLEM, err, daystring);
192	while(i<len)
193	{
194		if (parse_day(days, i, i+3, daystring) == 0)
195			exit_error(PARAMETER_PROBLEM, err, daystring);
196		i += 4;
197	}
198}
199
200static int
201parse_date_field(const char *str_to_parse, int str_to_parse_s, int start_pos,
202                 char *dest, int *next_pos)
203{
204	unsigned char found_value = 0;
205	unsigned char found_column = 0;
206	int i;
207
208	for (i=0; i<2; i++)
209	{
210		if ((i+start_pos) >= str_to_parse_s) /* don't exit boundaries of the string..  */
211			break;
212		if (str_to_parse[i+start_pos] == ':')
213			found_column = 1;
214		else
215		{
216			found_value = 1;
217			dest[i] = str_to_parse[i+start_pos];
218		}
219	}
220	if (found_value == 0)
221		return 0;
222	*next_pos = i + start_pos;
223	if (found_column == 0)
224		++(*next_pos);
225	return 1;
226}
227
228static int
229split_date(char *year, char *month,  char *day,
230           char *hour, char *minute, char *second,
231           const char *str_to_parse)
232{
233        int i;
234        unsigned char found_column = 0;
235	int str_to_parse_s = strlen(str_to_parse);
236
237        /* Check the length of the string */
238        if ((str_to_parse_s > 19) ||  /* YYYY:MM:DD:HH:MM:SS */
239            (str_to_parse_s < 4))     /* YYYY*/
240                return 0;
241
242	/* Clear the buffers */
243        memset(year, 0, 4);
244	memset(month, 0, 2);
245	memset(day, 0, 2);
246	memset(hour, 0, 2);
247	memset(minute, 0, 2);
248	memset(second, 0, 2);
249
250	/* parse the year YYYY */
251	found_column = 0;
252	for (i=0; i<5; i++)
253	{
254		if (i >= str_to_parse_s)
255			break;
256		if (str_to_parse[i] == ':')
257		{
258			found_column = 1;
259			break;
260		}
261		else
262			year[i] = str_to_parse[i];
263	}
264	if (found_column == 1)
265		++i;
266
267	/* parse the month if it exists */
268	if (! parse_date_field(str_to_parse, str_to_parse_s, i, month, &i))
269		return 1;
270
271	if (! parse_date_field(str_to_parse, str_to_parse_s, i, day, &i))
272		return 1;
273
274	if (! parse_date_field(str_to_parse, str_to_parse_s, i, hour, &i))
275		return 1;
276
277	if (! parse_date_field(str_to_parse, str_to_parse_s, i, minute, &i))
278		return 1;
279
280	parse_date_field(str_to_parse, str_to_parse_s, i, second, &i);
281
282        /* if we are here, format should be ok. */
283        return 1;
284}
285
286static time_t
287parse_date_string(const char *str_to_parse)
288{
289	char year[5];
290	char month[3];
291	char day[3];
292	char hour[3];
293	char minute[3];
294	char second[3];
295	struct tm t;
296	time_t temp_time;
297
298	memset(year, 0, 5);
299	memset(month, 0, 3);
300	memset(day, 0, 3);
301	memset(hour, 0, 3);
302	memset(minute, 0, 3);
303	memset(second, 0, 3);
304
305        if (split_date(year, month, day, hour, minute, second, str_to_parse) == 1)
306        {
307		memset((void *)&t, 0, sizeof(struct tm));
308		t.tm_isdst = 0;
309		t.tm_mday = 1;
310		if (!((parse_number(year, 1900, 2037, &(t.tm_year)) == -1) ||
311		      (parse_number(month, 1, 12, &(t.tm_mon)) == -1) ||
312		      (parse_number(day, 1, 31, &(t.tm_mday)) == -1) ||
313		      (parse_number(hour, 0, 9999, &(t.tm_hour)) == -1) ||
314		      (parse_number(minute, 0, 59, &(t.tm_min)) == -1) ||
315		      (parse_number(second, 0, 59, &(t.tm_sec)) == -1)))
316		{
317			t.tm_year -= 1900;
318			--(t.tm_mon);
319			temp_time = mktime(&t);
320			if (temp_time != -1)
321				return temp_time;
322		}
323	}
324	exit_error(PARAMETER_PROBLEM,
325		   "invalid date `%s' specified, should be YYYY[:MM[:DD[:hh[:mm[:ss]]]]] format", str_to_parse);
326}
327
328#define IPT_TIME_START 0x01
329#define IPT_TIME_STOP  0x02
330#define IPT_TIME_DAYS  0x04
331#define IPT_DATE_START 0x08
332#define IPT_DATE_STOP  0x10
333
334/* Function which parses command options; returns true if it
335   ate an option */
336static int
337parse(int c, char **argv, int invert, unsigned int *flags,
338      const struct ip6t_entry *entry,
339      unsigned int *nfcache,
340      struct ip6t_entry_match **match)
341{
342	struct ipt_time_info *timeinfo = (struct ipt_time_info *)(*match)->data;
343	int hours, minutes;
344	time_t temp_date;
345
346	switch (c)
347	{
348		/* timestart */
349	case '1':
350		if (invert)
351			exit_error(PARAMETER_PROBLEM,
352                                   "unexpected '!' with --timestart");
353		if (*flags & IPT_TIME_START)
354                        exit_error(PARAMETER_PROBLEM,
355                                   "Can't specify --timestart twice");
356		parse_time_string(&hours, &minutes, optarg);
357		timeinfo->time_start = (hours * 60) + minutes;
358		*flags |= IPT_TIME_START;
359		break;
360		/* timestop */
361	case '2':
362		if (invert)
363			exit_error(PARAMETER_PROBLEM,
364                                   "unexpected '!' with --timestop");
365		if (*flags & IPT_TIME_STOP)
366                        exit_error(PARAMETER_PROBLEM,
367                                   "Can't specify --timestop twice");
368		parse_time_string(&hours, &minutes, optarg);
369		timeinfo->time_stop = (hours * 60) + minutes;
370		*flags |= IPT_TIME_STOP;
371		break;
372
373		/* days */
374	case '3':
375		if (invert)
376			exit_error(PARAMETER_PROBLEM,
377                                   "unexpected '!' with --days");
378		if (*flags & IPT_TIME_DAYS)
379                        exit_error(PARAMETER_PROBLEM,
380                                   "Can't specify --days twice");
381		parse_days_string(&globaldays, optarg);
382		timeinfo->days_match = globaldays;
383		*flags |= IPT_TIME_DAYS;
384		break;
385
386		/* datestart */
387	case '4':
388		if (invert)
389			exit_error(PARAMETER_PROBLEM,
390                                   "unexpected '!' with --datestart");
391		if (*flags & IPT_DATE_START)
392			exit_error(PARAMETER_PROBLEM,
393                                   "Can't specify --datestart twice");
394		temp_date = parse_date_string(optarg);
395		timeinfo->date_start = temp_date;
396		*flags |= IPT_DATE_START;
397		break;
398
399		/* datestop*/
400	case '5':
401		if (invert)
402			exit_error(PARAMETER_PROBLEM,
403                                   "unexpected '!' with --datestop");
404		if (*flags & IPT_DATE_STOP)
405			exit_error(PARAMETER_PROBLEM,
406                                   "Can't specify --datestop twice");
407		temp_date = parse_date_string(optarg);
408		timeinfo->date_stop = temp_date;
409		*flags |= IPT_DATE_STOP;
410		break;
411	default:
412		return 0;
413	}
414	return 1;
415}
416
417/* Final check */
418static void
419final_check(unsigned int flags)
420{
421	/* Nothing to do */
422}
423
424
425static void
426print_days(int daynum)
427{
428	char *days[7] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
429	unsigned short int days_of_week[7] = {64, 32, 16, 8, 4, 2, 1};
430	unsigned short int i, nbdays=0;
431
432	for (i=0; i<7; i++) {
433		if ((days_of_week[i] & daynum) == days_of_week[i])
434		{
435			if (nbdays>0)
436				printf(",%s", days[i]);
437			else
438				printf("%s", days[i]);
439			++nbdays;
440		}
441	}
442	printf(" ");
443}
444
445static void
446divide_time(int fulltime, int *hours, int *minutes)
447{
448	*hours = fulltime / 60;
449	*minutes = fulltime % 60;
450}
451
452static void
453print_date(time_t date, char *command)
454{
455	struct tm *t;
456
457	/* If it's default value, don't print..*/
458	if (((date == 0) || (date == LONG_MAX)) && (command != NULL))
459		return;
460	t = localtime(&date);
461	if (command != NULL)
462		printf("%s %d:%d:%d:%d:%d:%d ", command, (t->tm_year + 1900), (t->tm_mon + 1),
463			t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);
464        else
465        	printf("%d-%d-%d %d:%d:%d ", (t->tm_year + 1900), (t->tm_mon + 1),
466			t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);
467}
468
469/* Prints out the matchinfo. */
470static void
471print(const struct ip6t_ip6 *ip,
472      const struct ip6t_entry_match *match,
473      int numeric)
474{
475	struct ipt_time_info *time = ((struct ipt_time_info *)match->data);
476	int hour_start, hour_stop, minute_start, minute_stop;
477
478	divide_time(time->time_start, &hour_start, &minute_start);
479	divide_time(time->time_stop, &hour_stop, &minute_stop);
480	printf("TIME ");
481	if (time->time_start != 0)
482		printf("from %d:%d ", hour_start, minute_start);
483	if (time->time_stop != 1439) /* 23*60+59 = 1439 */
484		printf("to %d:%d ", hour_stop, minute_stop);
485	printf("on ");
486	if (time->days_match == 127)
487		printf("all days ");
488	else
489		print_days(time->days_match);
490	if (time->date_start != 0)
491	{
492		printf("starting from ");
493		print_date(time->date_start, NULL);
494	}
495	if (time->date_stop != LONG_MAX)
496	{
497		printf("until date ");
498		print_date(time->date_stop, NULL);
499	}
500}
501
502/* Saves the data in parsable form to stdout. */
503static void
504save(const struct ip6t_ip6 *ip, const struct ip6t_entry_match *match)
505{
506	struct ipt_time_info *time = ((struct ipt_time_info *)match->data);
507	int hour_start, hour_stop, minute_start, minute_stop;
508
509	divide_time(time->time_start, &hour_start, &minute_start);
510	divide_time(time->time_stop, &hour_stop, &minute_stop);
511	if (time->time_start != 0)
512		printf("--timestart %.2d:%.2d ",
513		        hour_start, minute_start);
514
515	if (time->time_stop != 1439) /* 23*60+59 = 1439 */
516		printf("--timestop %.2d:%.2d ",
517		        hour_stop, minute_stop);
518
519	if (time->days_match != 127)
520	{
521		printf("--days ");
522		print_days(time->days_match);
523		printf(" ");
524	}
525	print_date(time->date_start, "--datestart");
526	print_date(time->date_stop, "--datestop");
527}
528
529/* have to use offsetof() instead of IPT_ALIGN(), since kerneltime must not
530 * be compared when user deletes rule with '-D' */
531static
532struct ip6tables_match timestruct = {
533	.next		= NULL,
534	.name		= "time",
535	.version	= IPTABLES_VERSION,
536	.size		= IP6T_ALIGN(sizeof(struct ipt_time_info)),
537	.userspacesize	= offsetof(struct ipt_time_info, kerneltime),
538	.help		= &help,
539	.init		= &init,
540	.parse		= &parse,
541	.final_check	= &final_check,
542	.print		= &print,
543	.save		= &save,
544	.extra_opts	= opts
545};
546
547void _init(void)
548{
549	register_match6(&timestruct);
550}
551