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