1 /* Code to convert iptables-save format to xml format,
2 * (C) 2006 Ufo Mechanic <azez@ufomechanic.net>
3 * based on iptables-restor (C) 2000-2002 by Harald Welte <laforge@gnumonks.org>
4 * based on previous code from Rusty Russell <rusty@linuxcare.com.au>
6 * This code is distributed under the terms of GNU GPL v2
8 * $Id: iptables-xml.c,v 1.4 2006/11/09 12:02:17 azez Exp $
12 #include <sys/errno.h>
18 #include "libiptc/libiptc.h"
19 #include "iptables-multi.h"
23 #define DEBUGP(x, args...) fprintf(stderr, x, ## args)
25 #define DEBUGP(x, args...)
28 #ifndef IPTABLES_MULTI
32 struct xtables_globals iptables_xml_globals
= {
34 .program_version
= IPTABLES_VERSION
,
35 .program_name
= "iptables-xml",
37 #define prog_name iptables_xml_globals.program_name
38 #define prog_vers iptables_xml_globals.program_version
40 static void print_usage(const char *name
, const char *version
)
41 __attribute__ ((noreturn
));
43 static int verbose
= 0;
44 /* Whether to combine actions of sequential rules with identical conditions */
45 static int combine
= 0;
46 /* Keeping track of external matches and targets. */
47 static struct option options
[] = {
48 {"verbose", 0, NULL
, 'v'},
49 {"combine", 0, NULL
, 'c'},
50 {"help", 0, NULL
, 'h'},
55 print_usage(const char *name
, const char *version
)
57 fprintf(stderr
, "Usage: %s [-c] [-v] [-h]\n"
59 " [ --verbose ]\n" " [ --help ]\n", name
);
65 parse_counters(char *string
, struct ipt_counters
*ctr
)
67 u_int64_t
*pcnt
, *bcnt
;
73 (string
, "[%llu:%llu]",
74 (unsigned long long *)pcnt
,
75 (unsigned long long *)bcnt
) == 2);
80 /* global new argv and argc */
81 static char *newargv
[255];
82 static unsigned int newargc
= 0;
84 static char *oldargv
[255];
85 static unsigned int oldargc
= 0;
87 /* arg meta data, were they quoted, frinstance */
88 static int newargvattr
[255];
90 #define IPT_CHAIN_MAXNAMELEN IPT_TABLE_MAXNAMELEN
91 static char closeActionTag
[IPT_TABLE_MAXNAMELEN
+ 1];
92 static char closeRuleTag
[IPT_TABLE_MAXNAMELEN
+ 1];
93 static char curTable
[IPT_TABLE_MAXNAMELEN
+ 1];
94 static char curChain
[IPT_CHAIN_MAXNAMELEN
+ 1];
99 struct ipt_counters count
;
103 #define maxChains 10240 /* max chains per table */
104 static struct chain chains
[maxChains
];
105 static int nextChain
= 0;
107 /* funCtion adding one argument to newargv, updating newargc
108 * returns true if argument added, false otherwise */
110 add_argv(char *what
, int quoted
)
112 DEBUGP("add_argv: %d %s\n", newargc
, what
);
113 if (what
&& newargc
+ 1 < ARRAY_SIZE(newargv
)) {
114 newargv
[newargc
] = strdup(what
);
115 newargvattr
[newargc
] = quoted
;
127 for (i
= 0; i
< newargc
; i
++) {
133 for (i
= 0; i
< oldargc
; i
++) {
140 /* save parsed rule for comparison with next rule
141 to perform action agregation on duplicate conditions */
147 for (i
= 0; i
< oldargc
; i
++)
151 for (i
= 0; i
< oldargc
; i
++) {
152 oldargv
[i
] = newargv
[i
];
157 /* like puts but with xml encoding */
159 xmlEncode(char *text
)
161 while (text
&& *text
) {
162 if ((unsigned char) (*text
) >= 127)
163 printf("&#%d;", (unsigned char) (*text
));
164 else if (*text
== '&')
166 else if (*text
== '<')
168 else if (*text
== '>')
170 else if (*text
== '"')
178 /* Output text as a comment, avoiding a double hyphen */
180 xmlCommentEscape(char *comment
)
184 while (comment
&& *comment
) {
185 if (*comment
== '-') {
193 /* strip trailing newline */
194 if (*comment
== '\n' && *(comment
+ 1) == 0);
202 xmlComment(char *comment
)
205 xmlCommentEscape(comment
);
210 xmlAttrS(char *name
, char *value
)
212 printf("%s=\"", name
);
218 xmlAttrI(char *name
, long long int num
)
220 printf("%s=\"%lld\" ", name
, num
);
226 if (curChain
[0] == 0)
229 if (closeActionTag
[0])
230 printf("%s\n", closeActionTag
);
231 closeActionTag
[0] = 0;
233 printf("%s\n", closeRuleTag
);
236 printf(" </chain>\n");
242 openChain(char *chain
, char *policy
, struct ipt_counters
*ctr
, char close
)
246 strncpy(curChain
, chain
, IPT_CHAIN_MAXNAMELEN
);
247 curChain
[IPT_CHAIN_MAXNAMELEN
] = '\0';
250 xmlAttrS("name", curChain
);
251 if (strcmp(policy
, "-") != 0)
252 xmlAttrS("policy", policy
);
253 xmlAttrI("packet-count", (unsigned long long) ctr
->pcnt
);
254 xmlAttrI("byte-count", (unsigned long long) ctr
->bcnt
);
263 existsChain(char *chain
)
265 /* open a saved chain */
268 if (0 == strcmp(curChain
, chain
))
270 for (c
= 0; c
< nextChain
; c
++)
271 if (chains
[c
].chain
&& strcmp(chains
[c
].chain
, chain
) == 0)
277 needChain(char *chain
)
279 /* open a saved chain */
282 if (0 == strcmp(curChain
, chain
))
285 for (c
= 0; c
< nextChain
; c
++)
286 if (chains
[c
].chain
&& strcmp(chains
[c
].chain
, chain
) == 0) {
287 openChain(chains
[c
].chain
, chains
[c
].policy
,
288 &(chains
[c
].count
), '\0');
289 /* And, mark it as done so we don't create
290 an empty chain at table-end time */
291 chains
[c
].created
= 1;
296 saveChain(char *chain
, char *policy
, struct ipt_counters
*ctr
)
298 if (nextChain
>= maxChains
) {
299 xtables_error(PARAMETER_PROBLEM
,
300 "%s: line %u chain name invalid\n",
304 chains
[nextChain
].chain
= strdup(chain
);
305 chains
[nextChain
].policy
= strdup(policy
);
306 chains
[nextChain
].count
= *ctr
;
307 chains
[nextChain
].created
= 0;
316 for (c
= 0; c
< nextChain
; c
++)
317 if (!chains
[c
].created
) {
318 openChain(chains
[c
].chain
, chains
[c
].policy
,
319 &(chains
[c
].count
), '/');
320 free(chains
[c
].chain
);
321 free(chains
[c
].policy
);
332 printf(" </table>\n");
337 openTable(char *table
)
341 strncpy(curTable
, table
, IPT_TABLE_MAXNAMELEN
);
342 curTable
[IPT_TABLE_MAXNAMELEN
] = '\0';
345 xmlAttrS("name", curTable
);
349 // is char* -j --jump -g or --goto
354 && (strcmp((arg
), "-j") == 0 || strcmp((arg
), "--jump") == 0
355 || strcmp((arg
), "-g") == 0
356 || strcmp((arg
), "--goto") == 0));
359 // is it a terminating target like -j ACCEPT, etc
360 // (or I guess -j SNAT in nat table, but we don't check for that yet
362 isTerminatingTarget(char *arg
)
365 && (strcmp((arg
), "ACCEPT") == 0
366 || strcmp((arg
), "DROP") == 0
367 || strcmp((arg
), "QUEUE") == 0
368 || strcmp((arg
), "RETURN") == 0));
371 // part=-1 means do conditions, part=1 means do rules, part=0 means do both
373 do_rule_part(char *leveltag1
, char *leveltag2
, int part
, int argc
,
374 char *argv
[], int argvattr
[])
376 int arg
= 1; // ignore leading -A
377 char invert_next
= 0;
378 char *thisChain
= NULL
;
379 char *spacer
= ""; // space when needed to assemble arguments
385 #define CLOSE_LEVEL(LEVEL) \
387 if (level ## LEVEL) printf("</%s>\n", \
388 (leveltag ## LEVEL)?(leveltag ## LEVEL):(level ## LEVEL)); \
389 level ## LEVEL=NULL;\
392 #define OPEN_LEVEL(LEVEL,TAG) \
395 if (leveltag ## LEVEL) {\
396 printf("%s<%s ", (leveli ## LEVEL), \
397 (leveltag ## LEVEL));\
398 xmlAttrS("type", (TAG)); \
399 } else printf("%s<%s ", (leveli ## LEVEL), (level ## LEVEL)); \
402 thisChain
= argv
[arg
++];
404 if (part
== 1) { /* skip */
405 /* use argvattr to tell which arguments were quoted
406 to avoid comparing quoted arguments, like comments, to -j, */
407 while (arg
< argc
&& (argvattr
[arg
] || !isTarget(argv
[arg
])))
411 /* Before we start, if the first arg is -[^-] and not -m or -j or -g
412 then start a dummy <match> tag for old style built-in matches.
413 We would do this in any case, but no need if it would be empty */
414 if (arg
< argc
&& argv
[arg
][0] == '-' && !isTarget(argv
[arg
])
415 && strcmp(argv
[arg
], "-m") != 0) {
416 OPEN_LEVEL(1, "match");
420 // If ! is followed by -* then apply to that else output as data
421 // Stop, if we need to
422 if (part
== -1 && !argvattr
[arg
] && (isTarget(argv
[arg
]))) {
424 } else if (!argvattr
[arg
] && strcmp(argv
[arg
], "!") == 0) {
425 if ((arg
+ 1) < argc
&& argv
[arg
+ 1][0] == '-')
428 printf("%s%s", spacer
, argv
[arg
]);
430 } else if (!argvattr
[arg
] && isTarget(argv
[arg
])
431 && existsChain(argv
[arg
+ 1])
432 && (2 + arg
>= argc
)) {
433 if (!((1 + arg
) < argc
))
434 // no args to -j, -m or -g, ignore & finish loop
438 printf("%s", leveli1
);
442 if (strcmp(argv
[arg
], "-g") == 0
443 || strcmp(argv
[arg
], "--goto") == 0) {
444 /* goto user chain */
445 OPEN_LEVEL(1, "goto");
448 OPEN_LEVEL(2, argv
[arg
]);
452 /* call user chain */
453 OPEN_LEVEL(1, "call");
456 OPEN_LEVEL(2, argv
[arg
]);
460 } else if (!argvattr
[arg
]
461 && (isTarget(argv
[arg
])
462 || strcmp(argv
[arg
], "-m") == 0
463 || strcmp(argv
[arg
], "--module") == 0)) {
464 if (!((1 + arg
) < argc
))
465 // no args to -j, -m or -g, ignore & finish loop
469 printf("%s", leveli1
);
474 OPEN_LEVEL(1, (argv
[arg
]));
475 // Optimize case, can we close this tag already?
476 if ((arg
+ 1) >= argc
|| (!argvattr
[arg
+ 1]
477 && (isTarget(argv
[arg
+ 1])
478 || strcmp(argv
[arg
+ 1],
480 || strcmp(argv
[arg
+ 1],
488 } else if (!argvattr
[arg
] && argv
[arg
][0] == '-') {
493 while (*tag
== '-' && *tag
)
499 printf(" invert=\"1\"");
502 // Optimize case, can we close this tag already?
503 if (!((arg
+ 1) < argc
)
504 || (argv
[arg
+ 1][0] == '-' /* NOT QUOTED */ )) {
510 } else { // regular data
511 char *spaces
= strchr(argv
[arg
], ' ');
512 printf("%s", spacer
);
513 if (spaces
|| argvattr
[arg
])
515 // if argv[arg] contains a space, enclose in quotes
516 xmlEncode(argv
[arg
]);
517 if (spaces
|| argvattr
[arg
])
525 printf("%s", leveli1
);
532 /* compare arguments up to -j or -g for match.
533 NOTE: We don't want to combine actions if there were no criteria
534 in each rule, or rules didn't have an action
535 NOTE: Depends on arguments being in some kind of "normal" order which
536 is the case when processing the ACTUAL output of actual iptables-save
537 rather than a file merely in a compatable format */
539 unsigned int old
= 0;
540 unsigned int new = 0;
544 while (new < newargc
&& old
< oldargc
) {
545 if (isTarget(oldargv
[old
]) && isTarget(newargv
[new])) {
546 /* if oldarg was a terminating action then it makes no sense
547 * to combine further actions into the same xml */
548 if (((strcmp((oldargv
[old
]), "-j") == 0
549 || strcmp((oldargv
[old
]), "--jump") == 0)
551 && isTerminatingTarget(oldargv
[old
+1]) )
552 || strcmp((oldargv
[old
]), "-g") == 0
553 || strcmp((oldargv
[old
]), "--goto") == 0 ) {
554 /* Previous rule had terminating action */
561 // break when old!=new
562 if (strcmp(oldargv
[old
], newargv
[new]) != 0) {
570 // We won't match unless both rules had a target.
571 // This means we don't combine target-less rules, which is good
576 /* has a nice parsed rule starting with -A */
578 do_rule(char *pcnt
, char *bcnt
, int argc
, char *argv
[], int argvattr
[])
580 /* are these conditions the same as the previous rule?
581 * If so, skip arg straight to -j or -g */
582 if (combine
&& argc
> 2 && !isTarget(argv
[2]) && compareRules()) {
583 xmlComment("Combine action from next rule");
586 if (closeActionTag
[0]) {
587 printf("%s\n", closeActionTag
);
588 closeActionTag
[0] = 0;
590 if (closeRuleTag
[0]) {
591 printf("%s\n", closeRuleTag
);
596 //xmlAttrS("table",curTable); // not needed in full mode
597 //xmlAttrS("chain",argv[1]); // not needed in full mode
599 xmlAttrS("packet-count", pcnt
);
601 xmlAttrS("byte-count", bcnt
);
604 strncpy(closeRuleTag
, " </rule>\n", IPT_TABLE_MAXNAMELEN
);
605 closeRuleTag
[IPT_TABLE_MAXNAMELEN
] = '\0';
607 /* no point in writing out condition if there isn't one */
608 if (argc
>= 3 && !isTarget(argv
[2])) {
609 printf(" <conditions>\n");
610 do_rule_part(NULL
, NULL
, -1, argc
, argv
, argvattr
);
611 printf(" </conditions>\n");
614 /* Write out the action */
615 //do_rule_part("action","arg",1,argc,argv,argvattr);
616 if (!closeActionTag
[0]) {
617 printf(" <actions>\n");
618 strncpy(closeActionTag
, " </actions>\n",
619 IPT_TABLE_MAXNAMELEN
);
620 closeActionTag
[IPT_TABLE_MAXNAMELEN
] = '\0';
622 do_rule_part(NULL
, NULL
, 1, argc
, argv
, argvattr
);
625 #ifdef IPTABLES_MULTI
627 iptables_xml_main(int argc
, char *argv
[])
630 main(int argc
, char *argv
[])
639 xtables_set_params(&iptables_xml_globals
);
640 while ((c
= getopt_long(argc
, argv
, "cvh", options
, NULL
)) != -1) {
646 printf("xptables-xml\n");
650 print_usage("iptables-xml", IPTABLES_VERSION
);
655 if (optind
== argc
- 1) {
656 in
= fopen(argv
[optind
], "r");
658 fprintf(stderr
, "Can't open %s: %s", argv
[optind
],
662 } else if (optind
< argc
) {
663 fprintf(stderr
, "Unknown arguments found on commandline");
668 printf("<iptables-rules version=\"1.0\">\n");
670 /* Grab standard input. */
671 while (fgets(buffer
, sizeof(buffer
), in
)) {
676 if (buffer
[0] == '\n')
678 else if (buffer
[0] == '#') {
684 printf("<!-- line %d ", line
);
685 xmlCommentEscape(buffer
);
689 if ((strcmp(buffer
, "COMMIT\n") == 0) && (curTable
[0])) {
690 DEBUGP("Calling commit\n");
693 } else if ((buffer
[0] == '*')) {
697 table
= strtok(buffer
+ 1, " \t\n");
698 DEBUGP("line %u, table '%s'\n", line
, table
);
700 xtables_error(PARAMETER_PROBLEM
,
701 "%s: line %u table name invalid\n",
708 } else if ((buffer
[0] == ':') && (curTable
[0])) {
710 char *policy
, *chain
;
711 struct ipt_counters count
;
714 chain
= strtok(buffer
+ 1, " \t\n");
715 DEBUGP("line %u, chain '%s'\n", line
, chain
);
717 xtables_error(PARAMETER_PROBLEM
,
718 "%s: line %u chain name invalid\n",
723 DEBUGP("Creating new chain '%s'\n", chain
);
725 policy
= strtok(NULL
, " \t\n");
726 DEBUGP("line %u, policy '%s'\n", line
, policy
);
728 xtables_error(PARAMETER_PROBLEM
,
729 "%s: line %u policy invalid\n",
734 ctrs
= strtok(NULL
, " \t\n");
735 parse_counters(ctrs
, &count
);
736 saveChain(chain
, policy
, &count
);
739 } else if (curTable
[0]) {
748 char *param_start
, *curchar
;
749 int quote_open
, quoted
;
751 /* reset the newargv */
754 if (buffer
[0] == '[') {
755 /* we have counters in our input */
756 ptr
= strchr(buffer
, ']');
758 xtables_error(PARAMETER_PROBLEM
,
759 "Bad line %u: need ]\n",
762 pcnt
= strtok(buffer
+ 1, ":");
764 xtables_error(PARAMETER_PROBLEM
,
765 "Bad line %u: need :\n",
768 bcnt
= strtok(NULL
, "]");
770 xtables_error(PARAMETER_PROBLEM
,
771 "Bad line %u: need ]\n",
774 /* start command parsing after counter */
775 parsestart
= ptr
+ 1;
777 /* start command parsing at start of line */
782 /* This is a 'real' parser crafted in artist mode
783 * not hacker mode. If the author can live with that
784 * then so can everyone else */
787 /* We need to know which args were quoted so we
788 can preserve quote */
790 param_start
= parsestart
;
792 for (curchar
= parsestart
; *curchar
; curchar
++) {
793 if (*curchar
== '"') {
794 /* quote_open cannot be true if there
795 * was no previous character. Thus,
796 * curchar-1 has to be within bounds */
798 *(curchar
- 1) != '\\') {
808 || *curchar
== '\t' || *curchar
== '\n') {
809 char param_buffer
[1024];
810 int param_len
= curchar
- param_start
;
821 /* end of one parameter */
822 strncpy(param_buffer
, param_start
,
824 *(param_buffer
+ param_len
) = '\0';
826 /* check if table name specified */
827 if (!strncmp(param_buffer
, "-t", 3)
828 || !strncmp(param_buffer
,
830 xtables_error(PARAMETER_PROBLEM
,
831 "Line %u seems to have a "
832 "-t table option.\n",
837 add_argv(param_buffer
, quoted
);
840 strcmp(newargv
[newargc
- 2], "-A"))
841 chain
= newargv
[newargc
- 1];
843 param_start
+= param_len
+ 1;
845 /* regular character, skip */
849 DEBUGP("calling do_command(%u, argv, &%s, handle):\n",
852 for (a
= 0; a
< newargc
; a
++)
853 DEBUGP("argv[%u]: %s\n", a
, newargv
[a
]);
855 needChain(chain
);// Should we explicitly look for -A
856 do_rule(pcnt
, bcnt
, newargc
, newargv
, newargvattr
);
862 fprintf(stderr
, "%s: line %u failed\n",
868 fprintf(stderr
, "%s: COMMIT expected at line %u\n",
869 prog_name
, line
+ 1);
873 printf("</iptables-rules>\n");