6c16c29f0c1db400aecf34d02a2469a6e3ca99a0
[openwrt.git] / target / linux / linux-2.4 / patches / generic / 611-netfilter_condition.patch
1 diff -ruN linux-2.4.30-old/Documentation/Configure.help linux-2.4.30-new/Documentation/Configure.help
2 --- linux-2.4.30-old/Documentation/Configure.help 2005-11-13 21:52:27.000000000 +0100
3 +++ linux-2.4.30-new/Documentation/Configure.help 2005-11-13 22:20:15.000000000 +0100
4 @@ -2979,6 +2979,14 @@
5 If you want to compile it as a module, say M here and read
6 <file:Documentation/modules.txt>. If unsure, say `N'.
7
8 +Condition variable match support
9 +CONFIG_IP_NF_MATCH_CONDITION
10 + This option allows you to match firewall rules against condition
11 + variables stored in the /proc/net/ipt_condition directory.
12 +
13 + If you want to compile it as a module, say M here and read
14 + Documentation/modules.txt. If unsure, say `N'.
15 +
16 conntrack match support
17 CONFIG_IP_NF_MATCH_CONNTRACK
18 This is a general conntrack match module, a superset of the state match.
19 @@ -3354,6 +3362,14 @@
20 If you want to compile it as a module, say M here and read
21 <file:Documentation/modules.txt>. If unsure, say `N'.
22
23 +Condition variable match support
24 +CONFIG_IP6_NF_MATCH_CONDITION
25 + This option allows you to match firewall rules against condition
26 + variables stored in the /proc/net/ipt_condition directory.
27 +
28 + If you want to compile it as a module, say M here and read
29 + Documentation/modules.txt. If unsure, say `N'.
30 +
31 Multiple port match support
32 CONFIG_IP6_NF_MATCH_MULTIPORT
33 Multiport matching allows you to match TCP or UDP packets based on
34 diff -ruN linux-2.4.30-old/include/linux/netfilter_ipv4/ipt_condition.h linux-2.4.30-new/include/linux/netfilter_ipv4/ipt_condition.h
35 --- linux-2.4.30-old/include/linux/netfilter_ipv4/ipt_condition.h 1970-01-01 01:00:00.000000000 +0100
36 +++ linux-2.4.30-new/include/linux/netfilter_ipv4/ipt_condition.h 2005-11-13 22:20:14.000000000 +0100
37 @@ -0,0 +1,11 @@
38 +#ifndef __IPT_CONDITION_MATCH__
39 +#define __IPT_CONDITION_MATCH__
40 +
41 +#define CONDITION_NAME_LEN 32
42 +
43 +struct condition_info {
44 + char name[CONDITION_NAME_LEN];
45 + int invert;
46 +};
47 +
48 +#endif
49 diff -ruN linux-2.4.30-old/include/linux/netfilter_ipv6/ip6t_condition.h linux-2.4.30-new/include/linux/netfilter_ipv6/ip6t_condition.h
50 --- linux-2.4.30-old/include/linux/netfilter_ipv6/ip6t_condition.h 1970-01-01 01:00:00.000000000 +0100
51 +++ linux-2.4.30-new/include/linux/netfilter_ipv6/ip6t_condition.h 2005-11-13 22:20:14.000000000 +0100
52 @@ -0,0 +1,11 @@
53 +#ifndef __IP6T_CONDITION_MATCH__
54 +#define __IP6T_CONDITION_MATCH__
55 +
56 +#define CONDITION6_NAME_LEN 32
57 +
58 +struct condition6_info {
59 + char name[CONDITION6_NAME_LEN];
60 + int invert;
61 +};
62 +
63 +#endif
64 diff -ruN linux-2.4.30-old/net/ipv4/netfilter/Config.in linux-2.4.30-new/net/ipv4/netfilter/Config.in
65 --- linux-2.4.30-old/net/ipv4/netfilter/Config.in 2005-11-13 21:52:27.000000000 +0100
66 +++ linux-2.4.30-new/net/ipv4/netfilter/Config.in 2005-11-13 22:20:15.000000000 +0100
67 @@ -43,6 +43,7 @@
68 dep_tristate ' netfilter MARK match support' CONFIG_IP_NF_MATCH_MARK $CONFIG_IP_NF_IPTABLES
69 dep_tristate ' Multiple port match support' CONFIG_IP_NF_MATCH_MULTIPORT $CONFIG_IP_NF_IPTABLES
70 dep_tristate ' TOS match support' CONFIG_IP_NF_MATCH_TOS $CONFIG_IP_NF_IPTABLES
71 + dep_tristate ' condition match support' CONFIG_IP_NF_MATCH_CONDITION $CONFIG_IP_NF_IPTABLES
72 dep_tristate ' recent match support' CONFIG_IP_NF_MATCH_RECENT $CONFIG_IP_NF_IPTABLES
73 dep_tristate ' ECN match support' CONFIG_IP_NF_MATCH_ECN $CONFIG_IP_NF_IPTABLES
74 dep_tristate ' peer to peer traffic match support' CONFIG_IP_NF_MATCH_IPP2P $CONFIG_IP_NF_IPTABLES
75 diff -ruN linux-2.4.30-old/net/ipv4/netfilter/Makefile linux-2.4.30-new/net/ipv4/netfilter/Makefile
76 --- linux-2.4.30-old/net/ipv4/netfilter/Makefile 2005-11-13 21:52:27.000000000 +0100
77 +++ linux-2.4.30-new/net/ipv4/netfilter/Makefile 2005-11-13 22:20:15.000000000 +0100
78 @@ -94,6 +94,7 @@
79 obj-$(CONFIG_IP_NF_MATCH_MULTIPORT) += ipt_multiport.o
80 obj-$(CONFIG_IP_NF_MATCH_OWNER) += ipt_owner.o
81 obj-$(CONFIG_IP_NF_MATCH_TOS) += ipt_tos.o
82 +obj-$(CONFIG_IP_NF_MATCH_CONDITION) += ipt_condition.o
83
84 obj-$(CONFIG_IP_NF_MATCH_RECENT) += ipt_recent.o
85
86 diff -ruN linux-2.4.30-old/net/ipv4/netfilter/ipt_condition.c linux-2.4.30-new/net/ipv4/netfilter/ipt_condition.c
87 --- linux-2.4.30-old/net/ipv4/netfilter/ipt_condition.c 1970-01-01 01:00:00.000000000 +0100
88 +++ linux-2.4.30-new/net/ipv4/netfilter/ipt_condition.c 2005-11-13 22:20:14.000000000 +0100
89 @@ -0,0 +1,256 @@
90 +/*-------------------------------------------*\
91 +| Netfilter Condition Module |
92 +| |
93 +| Description: This module allows firewall |
94 +| rules to match using condition variables |
95 +| stored in /proc files. |
96 +| |
97 +| Author: Stephane Ouellette 2002-10-22 |
98 +| <ouellettes@videotron.ca> |
99 +| |
100 +| History: |
101 +| 2003-02-10 Second version with improved |
102 +| locking and simplified code. |
103 +| |
104 +| This software is distributed under the |
105 +| terms of the GNU GPL. |
106 +\*-------------------------------------------*/
107 +
108 +#include<linux/module.h>
109 +#include<linux/proc_fs.h>
110 +#include<linux/spinlock.h>
111 +#include<linux/string.h>
112 +#include<asm/atomic.h>
113 +#include<linux/netfilter_ipv4/ip_tables.h>
114 +#include<linux/netfilter_ipv4/ipt_condition.h>
115 +
116 +
117 +#ifndef CONFIG_PROC_FS
118 +#error "Proc file system support is required for this module"
119 +#endif
120 +
121 +
122 +MODULE_AUTHOR("Stephane Ouellette <ouellettes@videotron.ca>");
123 +MODULE_DESCRIPTION("Allows rules to match against condition variables");
124 +MODULE_LICENSE("GPL");
125 +
126 +
127 +struct condition_variable {
128 + struct condition_variable *next;
129 + struct proc_dir_entry *status_proc;
130 + atomic_t refcount;
131 + int enabled; /* TRUE == 1, FALSE == 0 */
132 +};
133 +
134 +
135 +static rwlock_t list_lock;
136 +static struct condition_variable *head = NULL;
137 +static struct proc_dir_entry *proc_net_condition = NULL;
138 +
139 +
140 +static int
141 +ipt_condition_read_info(char *buffer, char **start, off_t offset,
142 + int length, int *eof, void *data)
143 +{
144 + struct condition_variable *var =
145 + (struct condition_variable *) data;
146 +
147 + if (offset == 0) {
148 + *start = buffer;
149 + buffer[0] = (var->enabled) ? '1' : '0';
150 + buffer[1] = '\n';
151 + return 2;
152 + }
153 +
154 + *eof = 1;
155 + return 0;
156 +}
157 +
158 +
159 +static int
160 +ipt_condition_write_info(struct file *file, const char *buffer,
161 + unsigned long length, void *data)
162 +{
163 + struct condition_variable *var =
164 + (struct condition_variable *) data;
165 +
166 + if (length) {
167 + /* Match only on the first character */
168 + switch (buffer[0]) {
169 + case '0':
170 + var->enabled = 0;
171 + break;
172 + case '1':
173 + var->enabled = 1;
174 + }
175 + }
176 +
177 + return (int) length;
178 +}
179 +
180 +
181 +static int
182 +match(const struct sk_buff *skb, const struct net_device *in,
183 + const struct net_device *out, const void *matchinfo, int offset,
184 + const void *hdr, u_int16_t datalen, int *hotdrop)
185 +{
186 + const struct condition_info *info =
187 + (const struct condition_info *) matchinfo;
188 + struct condition_variable *var;
189 + int condition_status = 0;
190 +
191 + read_lock(&list_lock);
192 +
193 + for (var = head; var; var = var->next) {
194 + if (strcmp(info->name, var->status_proc->name) == 0) {
195 + condition_status = var->enabled;
196 + break;
197 + }
198 + }
199 +
200 + read_unlock(&list_lock);
201 +
202 + return condition_status ^ info->invert;
203 +}
204 +
205 +
206 +
207 +static int
208 +checkentry(const char *tablename, const struct ipt_ip *ip,
209 + void *matchinfo, unsigned int matchsize, unsigned int hook_mask)
210 +{
211 + struct condition_info *info = (struct condition_info *) matchinfo;
212 + struct condition_variable *var, *newvar;
213 +
214 + if (matchsize != IPT_ALIGN(sizeof(struct condition_info)))
215 + return 0;
216 +
217 + /* The first step is to check if the condition variable already exists. */
218 + /* Here, a read lock is sufficient because we won't change the list */
219 + read_lock(&list_lock);
220 +
221 + for (var = head; var; var = var->next) {
222 + if (strcmp(info->name, var->status_proc->name) == 0) {
223 + atomic_inc(&var->refcount);
224 + read_unlock(&list_lock);
225 + return 1;
226 + }
227 + }
228 +
229 + read_unlock(&list_lock);
230 +
231 + /* At this point, we need to allocate a new condition variable */
232 + newvar = kmalloc(sizeof(struct condition_variable), GFP_KERNEL);
233 +
234 + if (!newvar)
235 + return -ENOMEM;
236 +
237 + /* Create the condition variable's proc file entry */
238 + newvar->status_proc = create_proc_entry(info->name, 0644, proc_net_condition);
239 +
240 + if (!newvar->status_proc) {
241 + /*
242 + * There are two possibilities:
243 + * 1- Another condition variable with the same name has been created, which is valid.
244 + * 2- There was a memory allocation error.
245 + */
246 + kfree(newvar);
247 + read_lock(&list_lock);
248 +
249 + for (var = head; var; var = var->next) {
250 + if (strcmp(info->name, var->status_proc->name) == 0) {
251 + atomic_inc(&var->refcount);
252 + read_unlock(&list_lock);
253 + return 1;
254 + }
255 + }
256 +
257 + read_unlock(&list_lock);
258 + return -ENOMEM;
259 + }
260 +
261 + atomic_set(&newvar->refcount, 1);
262 + newvar->enabled = 0;
263 + newvar->status_proc->owner = THIS_MODULE;
264 + newvar->status_proc->data = newvar;
265 + wmb();
266 + newvar->status_proc->read_proc = ipt_condition_read_info;
267 + newvar->status_proc->write_proc = ipt_condition_write_info;
268 +
269 + write_lock(&list_lock);
270 +
271 + newvar->next = head;
272 + head = newvar;
273 +
274 + write_unlock(&list_lock);
275 +
276 + return 1;
277 +}
278 +
279 +
280 +static void
281 +destroy(void *matchinfo, unsigned int matchsize)
282 +{
283 + struct condition_info *info = (struct condition_info *) matchinfo;
284 + struct condition_variable *var, *prev = NULL;
285 +
286 + if (matchsize != IPT_ALIGN(sizeof(struct condition_info)))
287 + return;
288 +
289 + write_lock(&list_lock);
290 +
291 + for (var = head; var && strcmp(info->name, var->status_proc->name);
292 + prev = var, var = var->next);
293 +
294 + if (var && atomic_dec_and_test(&var->refcount)) {
295 + if (prev)
296 + prev->next = var->next;
297 + else
298 + head = var->next;
299 +
300 + write_unlock(&list_lock);
301 + remove_proc_entry(var->status_proc->name, proc_net_condition);
302 + kfree(var);
303 + } else
304 + write_unlock(&list_lock);
305 +}
306 +
307 +
308 +static struct ipt_match condition_match = {
309 + .name = "condition",
310 + .match = &match,
311 + .checkentry = &checkentry,
312 + .destroy = &destroy,
313 + .me = THIS_MODULE
314 +};
315 +
316 +
317 +static int __init
318 +init(void)
319 +{
320 + int errorcode;
321 +
322 + rwlock_init(&list_lock);
323 + proc_net_condition = proc_mkdir("ipt_condition", proc_net);
324 +
325 + if (proc_net_condition) {
326 + errorcode = ipt_register_match(&condition_match);
327 +
328 + if (errorcode)
329 + remove_proc_entry("ipt_condition", proc_net);
330 + } else
331 + errorcode = -EACCES;
332 +
333 + return errorcode;
334 +}
335 +
336 +
337 +static void __exit
338 +fini(void)
339 +{
340 + ipt_unregister_match(&condition_match);
341 + remove_proc_entry("ipt_condition", proc_net);
342 +}
343 +
344 +module_init(init);
345 +module_exit(fini);
346 diff -ruN linux-2.4.30-old/net/ipv6/netfilter/Config.in linux-2.4.30-new/net/ipv6/netfilter/Config.in
347 --- linux-2.4.30-old/net/ipv6/netfilter/Config.in 2003-06-13 16:51:39.000000000 +0200
348 +++ linux-2.4.30-new/net/ipv6/netfilter/Config.in 2005-11-13 22:20:15.000000000 +0100
349 @@ -17,6 +17,7 @@
350 if [ "$CONFIG_IP6_NF_IPTABLES" != "n" ]; then
351 # The simple matches.
352 dep_tristate ' limit match support' CONFIG_IP6_NF_MATCH_LIMIT $CONFIG_IP6_NF_IPTABLES
353 + dep_tristate ' condition match support' CONFIG_IP6_NF_MATCH_CONDITION $CONFIG_IP6_NF_IPTABLES
354 dep_tristate ' MAC address match support' CONFIG_IP6_NF_MATCH_MAC $CONFIG_IP6_NF_IPTABLES
355 if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then
356 dep_tristate ' Routing header match support (EXPERIMENTAL)' CONFIG_IP6_NF_MATCH_RT $CONFIG_IP6_NF_IPTABLES
357 diff -ruN linux-2.4.30-old/net/ipv6/netfilter/Makefile linux-2.4.30-new/net/ipv6/netfilter/Makefile
358 --- linux-2.4.30-old/net/ipv6/netfilter/Makefile 2003-06-13 16:51:39.000000000 +0200
359 +++ linux-2.4.30-new/net/ipv6/netfilter/Makefile 2005-11-13 22:20:15.000000000 +0100
360 @@ -14,6 +14,7 @@
361 # Link order matters here.
362 obj-$(CONFIG_IP6_NF_IPTABLES) += ip6_tables.o
363 obj-$(CONFIG_IP6_NF_MATCH_LIMIT) += ip6t_limit.o
364 +obj-$(CONFIG_IP6_NF_MATCH_CONDITION) += ip6t_condition.o
365 obj-$(CONFIG_IP6_NF_MATCH_MARK) += ip6t_mark.o
366 obj-$(CONFIG_IP6_NF_MATCH_LENGTH) += ip6t_length.o
367 obj-$(CONFIG_IP6_NF_MATCH_MAC) += ip6t_mac.o
368 diff -ruN linux-2.4.30-old/net/ipv6/netfilter/ip6t_condition.c linux-2.4.30-new/net/ipv6/netfilter/ip6t_condition.c
369 --- linux-2.4.30-old/net/ipv6/netfilter/ip6t_condition.c 1970-01-01 01:00:00.000000000 +0100
370 +++ linux-2.4.30-new/net/ipv6/netfilter/ip6t_condition.c 2005-11-13 22:20:14.000000000 +0100
371 @@ -0,0 +1,254 @@
372 +/*-------------------------------------------*\
373 +| Netfilter Condition Module for IPv6 |
374 +| |
375 +| Description: This module allows firewall |
376 +| rules to match using condition variables |
377 +| stored in /proc files. |
378 +| |
379 +| Author: Stephane Ouellette 2003-02-10 |
380 +| <ouellettes@videotron.ca> |
381 +| |
382 +| This software is distributed under the |
383 +| terms of the GNU GPL. |
384 +\*-------------------------------------------*/
385 +
386 +#include<linux/module.h>
387 +#include<linux/proc_fs.h>
388 +#include<linux/spinlock.h>
389 +#include<linux/string.h>
390 +#include<asm/atomic.h>
391 +#include<linux/netfilter_ipv6/ip6_tables.h>
392 +#include<linux/netfilter_ipv6/ip6t_condition.h>
393 +
394 +
395 +#ifndef CONFIG_PROC_FS
396 +#error "Proc file system support is required for this module"
397 +#endif
398 +
399 +
400 +MODULE_AUTHOR("Stephane Ouellette <ouellettes@videotron.ca>");
401 +MODULE_DESCRIPTION("Allows rules to match against condition variables");
402 +MODULE_LICENSE("GPL");
403 +
404 +
405 +struct condition_variable {
406 + struct condition_variable *next;
407 + struct proc_dir_entry *status_proc;
408 + atomic_t refcount;
409 + int enabled; /* TRUE == 1, FALSE == 0 */
410 +};
411 +
412 +
413 +static rwlock_t list_lock;
414 +static struct condition_variable *head = NULL;
415 +static struct proc_dir_entry *proc_net_condition = NULL;
416 +
417 +
418 +static int
419 +ipt_condition_read_info(char *buffer, char **start, off_t offset,
420 + int length, int *eof, void *data)
421 +{
422 + struct condition_variable *var =
423 + (struct condition_variable *) data;
424 +
425 + if (offset == 0) {
426 + *start = buffer;
427 + buffer[0] = (var->enabled) ? '1' : '0';
428 + buffer[1] = '\n';
429 + return 2;
430 + }
431 +
432 + *eof = 1;
433 + return 0;
434 +}
435 +
436 +
437 +static int
438 +ipt_condition_write_info(struct file *file, const char *buffer,
439 + unsigned long length, void *data)
440 +{
441 + struct condition_variable *var =
442 + (struct condition_variable *) data;
443 +
444 + if (length) {
445 + /* Match only on the first character */
446 + switch (buffer[0]) {
447 + case '0':
448 + var->enabled = 0;
449 + break;
450 + case '1':
451 + var->enabled = 1;
452 + }
453 + }
454 +
455 + return (int) length;
456 +}
457 +
458 +
459 +static int
460 +match(const struct sk_buff *skb, const struct net_device *in,
461 + const struct net_device *out, const void *matchinfo, int offset,
462 + const void *hdr, u_int16_t datalen, int *hotdrop)
463 +{
464 + const struct condition6_info *info =
465 + (const struct condition6_info *) matchinfo;
466 + struct condition_variable *var;
467 + int condition_status = 0;
468 +
469 + read_lock(&list_lock);
470 +
471 + for (var = head; var; var = var->next) {
472 + if (strcmp(info->name, var->status_proc->name) == 0) {
473 + condition_status = var->enabled;
474 + break;
475 + }
476 + }
477 +
478 + read_unlock(&list_lock);
479 +
480 + return condition_status ^ info->invert;
481 +}
482 +
483 +
484 +
485 +static int
486 +checkentry(const char *tablename, const struct ip6t_ip6 *ip,
487 + void *matchinfo, unsigned int matchsize, unsigned int hook_mask)
488 +{
489 + struct condition6_info *info =
490 + (struct condition6_info *) matchinfo;
491 + struct condition_variable *var, *newvar;
492 +
493 + if (matchsize != IP6T_ALIGN(sizeof(struct condition6_info)))
494 + return 0;
495 +
496 + /* The first step is to check if the condition variable already exists. */
497 + /* Here, a read lock is sufficient because we won't change the list */
498 + read_lock(&list_lock);
499 +
500 + for (var = head; var; var = var->next) {
501 + if (strcmp(info->name, var->status_proc->name) == 0) {
502 + atomic_inc(&var->refcount);
503 + read_unlock(&list_lock);
504 + return 1;
505 + }
506 + }
507 +
508 + read_unlock(&list_lock);
509 +
510 + /* At this point, we need to allocate a new condition variable */
511 + newvar = kmalloc(sizeof(struct condition_variable), GFP_KERNEL);
512 +
513 + if (!newvar)
514 + return -ENOMEM;
515 +
516 + /* Create the condition variable's proc file entry */
517 + newvar->status_proc = create_proc_entry(info->name, 0644, proc_net_condition);
518 +
519 + if (!newvar->status_proc) {
520 + /*
521 + * There are two possibilities:
522 + * 1- Another condition variable with the same name has been created, which is valid.
523 + * 2- There was a memory allocation error.
524 + */
525 + kfree(newvar);
526 + read_lock(&list_lock);
527 +
528 + for (var = head; var; var = var->next) {
529 + if (strcmp(info->name, var->status_proc->name) == 0) {
530 + atomic_inc(&var->refcount);
531 + read_unlock(&list_lock);
532 + return 1;
533 + }
534 + }
535 +
536 + read_unlock(&list_lock);
537 + return -ENOMEM;
538 + }
539 +
540 + atomic_set(&newvar->refcount, 1);
541 + newvar->enabled = 0;
542 + newvar->status_proc->owner = THIS_MODULE;
543 + newvar->status_proc->data = newvar;
544 + wmb();
545 + newvar->status_proc->read_proc = ipt_condition_read_info;
546 + newvar->status_proc->write_proc = ipt_condition_write_info;
547 +
548 + write_lock(&list_lock);
549 +
550 + newvar->next = head;
551 + head = newvar;
552 +
553 + write_unlock(&list_lock);
554 +
555 + return 1;
556 +}
557 +
558 +
559 +static void
560 +destroy(void *matchinfo, unsigned int matchsize)
561 +{
562 + struct condition6_info *info =
563 + (struct condition6_info *) matchinfo;
564 + struct condition_variable *var, *prev = NULL;
565 +
566 + if (matchsize != IP6T_ALIGN(sizeof(struct condition6_info)))
567 + return;
568 +
569 + write_lock(&list_lock);
570 +
571 + for (var = head; var && strcmp(info->name, var->status_proc->name);
572 + prev = var, var = var->next);
573 +
574 + if (var && atomic_dec_and_test(&var->refcount)) {
575 + if (prev)
576 + prev->next = var->next;
577 + else
578 + head = var->next;
579 +
580 + write_unlock(&list_lock);
581 + remove_proc_entry(var->status_proc->name, proc_net_condition);
582 + kfree(var);
583 + } else
584 + write_unlock(&list_lock);
585 +}
586 +
587 +
588 +static struct ip6t_match condition_match = {
589 + .name = "condition",
590 + .match = &match,
591 + .checkentry = &checkentry,
592 + .destroy = &destroy,
593 + .me = THIS_MODULE
594 +};
595 +
596 +
597 +static int __init
598 +init(void)
599 +{
600 + int errorcode;
601 +
602 + rwlock_init(&list_lock);
603 + proc_net_condition = proc_mkdir("ip6t_condition", proc_net);
604 +
605 + if (proc_net_condition) {
606 + errorcode = ipt_register_match(&condition_match);
607 +
608 + if (errorcode)
609 + remove_proc_entry("ip6t_condition", proc_net);
610 + } else
611 + errorcode = -EACCES;
612 +
613 + return errorcode;
614 +}
615 +
616 +
617 +static void __exit
618 +fini(void)
619 +{
620 + ipt_unregister_match(&condition_match);
621 + remove_proc_entry("ip6t_condition", proc_net);
622 +}
623 +
624 +module_init(init);
625 +module_exit(fini);
This page took 0.063084 seconds and 3 git commands to generate.