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"
22 #define DEBUGP(x, args...) fprintf(stderr, x, ## args)
24 #define DEBUGP(x, args...)
27 /* no need to link with iptables.o */
28 const char *program_name;
29 const char *program_version;
31 #ifndef IPTABLES_MULTI
33 void exit_error(enum exittype status, const char *msg, ...)
38 fprintf(stderr, "%s v%s: ", program_name, program_version);
39 vfprintf(stderr, msg, args);
41 fprintf(stderr, "\n");
42 /* On error paths, make sure that we don't leak memory */
47 static void print_usage(const char *name, const char *version)
48 __attribute__ ((noreturn));
50 static int verbose = 0;
51 /* Whether to combine actions of sequential rules with identical conditions */
52 static int combine = 0;
53 /* Keeping track of external matches and targets. */
54 static struct option options[] = {
55 {"verbose", 0, NULL, 'v'},
56 {"combine", 0, NULL, 'c'},
57 {"help", 0, NULL, 'h'},
62 print_usage(const char *name, const char *version)
64 fprintf(stderr, "Usage: %s [-c] [-v] [-h]\n"
66 " [ --verbose ]\n" " [ --help ]\n", name);
72 parse_counters(char *string, struct ipt_counters *ctr)
74 u_int64_t *pcnt, *bcnt;
80 (string, "[%llu:%llu]",
81 (unsigned long long *)pcnt,
82 (unsigned long long *)bcnt) == 2);
87 /* global new argv and argc */
88 static char *newargv[255];
89 static unsigned int newargc = 0;
91 static char *oldargv[255];
92 static unsigned int oldargc = 0;
94 /* arg meta data, were they quoted, frinstance */
95 static int newargvattr[255];
97 #define IPT_CHAIN_MAXNAMELEN IPT_TABLE_MAXNAMELEN
98 static char closeActionTag[IPT_TABLE_MAXNAMELEN + 1];
99 static char closeRuleTag[IPT_TABLE_MAXNAMELEN + 1];
100 static char curTable[IPT_TABLE_MAXNAMELEN + 1];
101 static char curChain[IPT_CHAIN_MAXNAMELEN + 1];
106 struct ipt_counters count;
110 #define maxChains 10240 /* max chains per table */
111 static struct chain chains[maxChains];
112 static int nextChain = 0;
114 /* funCtion adding one argument to newargv, updating newargc
115 * returns true if argument added, false otherwise */
117 add_argv(char *what, int quoted)
119 DEBUGP("add_argv: %d %s\n", newargc, what);
120 if (what && ((newargc + 1) < sizeof(newargv) / sizeof(char *))) {
121 newargv[newargc] = strdup(what);
122 newargvattr[newargc] = quoted;
134 for (i = 0; i < newargc; i++) {
140 for (i = 0; i < oldargc; i++) {
147 /* save parsed rule for comparison with next rule
148 to perform action agregation on duplicate conditions */
154 for (i = 0; i < oldargc; i++)
158 for (i = 0; i < oldargc; i++) {
159 oldargv[i] = newargv[i];
164 /* like puts but with xml encoding */
166 xmlEncode(char *text)
168 while (text && *text) {
169 if ((unsigned char) (*text) >= 127)
170 printf("&#%d;", (unsigned char) (*text));
171 else if (*text == '&')
173 else if (*text == '<')
175 else if (*text == '>')
177 else if (*text == '"')
185 /* Output text as a comment, avoiding a double hyphen */
187 xmlCommentEscape(char *comment)
191 while (comment && *comment) {
192 if (*comment == '-') {
200 /* strip trailing newline */
201 if (*comment == '\n' && *(comment + 1) == 0);
209 xmlComment(char *comment)
212 xmlCommentEscape(comment);
217 xmlAttrS(char *name, char *value)
219 printf("%s=\"", name);
225 xmlAttrI(char *name, long long int num)
227 printf("%s=\"%lld\" ", name, num);
233 if (curChain[0] == 0)
236 if (closeActionTag[0])
237 printf("%s\n", closeActionTag);
238 closeActionTag[0] = 0;
240 printf("%s\n", closeRuleTag);
243 printf(" </chain>\n");
249 openChain(char *chain, char *policy, struct ipt_counters *ctr, char close)
253 strncpy(curChain, chain, IPT_CHAIN_MAXNAMELEN);
254 curChain[IPT_CHAIN_MAXNAMELEN] = '\0';
257 xmlAttrS("name", curChain);
258 if (strcmp(policy, "-") != 0)
259 xmlAttrS("policy", policy);
260 xmlAttrI("packet-count", (unsigned long long) ctr->pcnt);
261 xmlAttrI("byte-count", (unsigned long long) ctr->bcnt);
270 existsChain(char *chain)
272 /* open a saved chain */
275 if (0 == strcmp(curChain, chain))
277 for (c = 0; c < nextChain; c++)
278 if (chains[c].chain && strcmp(chains[c].chain, chain) == 0)
284 needChain(char *chain)
286 /* open a saved chain */
289 if (0 == strcmp(curChain, chain))
292 for (c = 0; c < nextChain; c++)
293 if (chains[c].chain && strcmp(chains[c].chain, chain) == 0) {
294 openChain(chains[c].chain, chains[c].policy,
295 &(chains[c].count), '\0');
296 /* And, mark it as done so we don't create
297 an empty chain at table-end time */
298 chains[c].created = 1;
303 saveChain(char *chain, char *policy, struct ipt_counters *ctr)
305 if (nextChain >= maxChains) {
306 exit_error(PARAMETER_PROBLEM,
307 "%s: line %u chain name invalid\n",
311 chains[nextChain].chain = strdup(chain);
312 chains[nextChain].policy = strdup(policy);
313 chains[nextChain].count = *ctr;
314 chains[nextChain].created = 0;
323 for (c = 0; c < nextChain; c++)
324 if (!chains[c].created) {
325 openChain(chains[c].chain, chains[c].policy,
326 &(chains[c].count), '/');
327 free(chains[c].chain);
328 free(chains[c].policy);
339 printf(" </table>\n");
344 openTable(char *table)
348 strncpy(curTable, table, IPT_TABLE_MAXNAMELEN);
349 curTable[IPT_TABLE_MAXNAMELEN] = '\0';
352 xmlAttrS("name", curTable);
356 // is char* -j --jump -g or --goto
361 && (strcmp((arg), "-j") == 0 || strcmp((arg), "--jump") == 0
362 || strcmp((arg), "-g") == 0
363 || strcmp((arg), "--goto") == 0));
366 // is it a terminating target like -j ACCEPT, etc
367 // (or I guess -j SNAT in nat table, but we don't check for that yet
369 isTerminatingTarget(char *arg)
372 && (strcmp((arg), "ACCEPT") == 0
373 || strcmp((arg), "DROP") == 0
374 || strcmp((arg), "QUEUE") == 0
375 || strcmp((arg), "RETURN") == 0));
378 // part=-1 means do conditions, part=1 means do rules, part=0 means do both
380 do_rule_part(char *leveltag1, char *leveltag2, int part, int argc,
381 char *argv[], int argvattr[])
383 int arg = 1; // ignore leading -A
384 char invert_next = 0;
385 char *thisChain = NULL;
386 char *spacer = ""; // space when needed to assemble arguments
392 #define CLOSE_LEVEL(LEVEL) \
394 if (level ## LEVEL) printf("</%s>\n", \
395 (leveltag ## LEVEL)?(leveltag ## LEVEL):(level ## LEVEL)); \
396 level ## LEVEL=NULL;\
399 #define OPEN_LEVEL(LEVEL,TAG) \
402 if (leveltag ## LEVEL) {\
403 printf("%s<%s ", (leveli ## LEVEL), \
404 (leveltag ## LEVEL));\
405 xmlAttrS("type", (TAG)); \
406 } else printf("%s<%s ", (leveli ## LEVEL), (level ## LEVEL)); \
409 thisChain = argv[arg++];
411 if (part == 1) { /* skip */
412 /* use argvattr to tell which arguments were quoted
413 to avoid comparing quoted arguments, like comments, to -j, */
414 while (arg < argc && (argvattr[arg] || !isTarget(argv[arg])))
418 /* Before we start, if the first arg is -[^-] and not -m or -j or -g
419 then start a dummy <match> tag for old style built-in matches.
420 We would do this in any case, but no need if it would be empty */
421 if (arg < argc && argv[arg][0] == '-' && !isTarget(argv[arg])
422 && strcmp(argv[arg], "-m") != 0) {
423 OPEN_LEVEL(1, "match");
427 // If ! is followed by -* then apply to that else output as data
428 // Stop, if we need to
429 if (part == -1 && !argvattr[arg] && (isTarget(argv[arg]))) {
431 } else if (!argvattr[arg] && strcmp(argv[arg], "!") == 0) {
432 if ((arg + 1) < argc && argv[arg + 1][0] == '-')
435 printf("%s%s", spacer, argv[arg]);
437 } else if (!argvattr[arg] && isTarget(argv[arg])
438 && existsChain(argv[arg + 1])
439 && (2 + arg >= argc)) {
440 if (!((1 + arg) < argc))
441 // no args to -j, -m or -g, ignore & finish loop
445 printf("%s", leveli1);
449 if (strcmp(argv[arg], "-g") == 0
450 || strcmp(argv[arg], "--goto") == 0) {
451 /* goto user chain */
452 OPEN_LEVEL(1, "goto");
455 OPEN_LEVEL(2, argv[arg]);
459 /* call user chain */
460 OPEN_LEVEL(1, "call");
463 OPEN_LEVEL(2, argv[arg]);
467 } else if (!argvattr[arg]
468 && (isTarget(argv[arg])
469 || strcmp(argv[arg], "-m") == 0
470 || strcmp(argv[arg], "--module") == 0)) {
471 if (!((1 + arg) < argc))
472 // no args to -j, -m or -g, ignore & finish loop
476 printf("%s", leveli1);
481 OPEN_LEVEL(1, (argv[arg]));
482 // Optimize case, can we close this tag already?
483 if ((arg + 1) >= argc || (!argvattr[arg + 1]
484 && (isTarget(argv[arg + 1])
485 || strcmp(argv[arg + 1],
487 || strcmp(argv[arg + 1],
495 } else if (!argvattr[arg] && argv[arg][0] == '-') {
500 while (*tag == '-' && *tag)
506 printf(" invert=\"1\"");
509 // Optimize case, can we close this tag already?
510 if (!((arg + 1) < argc)
511 || (argv[arg + 1][0] == '-' /* NOT QUOTED */ )) {
517 } else { // regular data
518 char *spaces = strchr(argv[arg], ' ');
519 printf("%s", spacer);
520 if (spaces || argvattr[arg])
522 // if argv[arg] contains a space, enclose in quotes
523 xmlEncode(argv[arg]);
524 if (spaces || argvattr[arg])
532 printf("%s", leveli1);
541 /* compare arguments up to -j or -g for match.
542 NOTE: We don't want to combine actions if there were no criteria
543 in each rule, or rules didn't have an action
544 NOTE: Depends on arguments being in some kind of "normal" order which
545 is the case when processing the ACTUAL output of actual iptables-save
546 rather than a file merely in a compatable format */
548 unsigned int old = 0;
549 unsigned int new = 0;
553 while (new < newargc && old < oldargc) {
554 if (isTarget(oldargv[old]) && isTarget(newargv[new])) {
555 /* if oldarg was a terminating action then it makes no sense
556 * to combine further actions into the same xml */
557 if (((strcmp((oldargv[old]), "-j") == 0
558 || strcmp((oldargv[old]), "--jump") == 0)
560 && isTerminatingTarget(oldargv[old+1]) )
561 || strcmp((oldargv[old]), "-g") == 0
562 || strcmp((oldargv[old]), "--goto") == 0 ) {
563 /* Previous rule had terminating action */
570 // break when old!=new
571 if (strcmp(oldargv[old], newargv[new]) != 0) {
579 // We won't match unless both rules had a target.
580 // This means we don't combine target-less rules, which is good
585 /* has a nice parsed rule starting with -A */
587 do_rule(char *pcnt, char *bcnt, int argc, char *argv[], int argvattr[])
589 /* are these conditions the same as the previous rule?
590 * If so, skip arg straight to -j or -g */
591 if (combine && argc > 2 && !isTarget(argv[2]) && compareRules()) {
592 xmlComment("Combine action from next rule");
595 if (closeActionTag[0]) {
596 printf("%s\n", closeActionTag);
597 closeActionTag[0] = 0;
599 if (closeRuleTag[0]) {
600 printf("%s\n", closeRuleTag);
605 //xmlAttrS("table",curTable); // not needed in full mode
606 //xmlAttrS("chain",argv[1]); // not needed in full mode
608 xmlAttrS("packet-count", pcnt);
610 xmlAttrS("byte-count", bcnt);
613 strncpy(closeRuleTag, " </rule>\n", IPT_TABLE_MAXNAMELEN);
614 closeRuleTag[IPT_TABLE_MAXNAMELEN] = '\0';
616 /* no point in writing out condition if there isn't one */
617 if (argc >= 3 && !isTarget(argv[2])) {
618 printf(" <conditions>\n");
619 do_rule_part(NULL, NULL, -1, argc, argv, argvattr);
620 printf(" </conditions>\n");
623 /* Write out the action */
624 //do_rule_part("action","arg",1,argc,argv,argvattr);
625 if (!closeActionTag[0]) {
626 printf(" <actions>\n");
627 strncpy(closeActionTag, " </actions>\n",
628 IPT_TABLE_MAXNAMELEN);
629 closeActionTag[IPT_TABLE_MAXNAMELEN] = '\0';
631 do_rule_part(NULL, NULL, 1, argc, argv, argvattr);
635 #ifdef IPTABLES_MULTI
637 iptables_xml_main(int argc, char *argv[])
640 main(int argc, char *argv[])
647 program_name = "iptables-xml";
648 program_version = XTABLES_VERSION;
651 while ((c = getopt_long(argc, argv, "cvh", options, NULL)) != -1) {
657 printf("xptables-xml\n");
661 print_usage("iptables-xml", XTABLES_VERSION);
666 if (optind == argc - 1) {
667 in = fopen(argv[optind], "r");
669 fprintf(stderr, "Can't open %s: %s", argv[optind],
673 } else if (optind < argc) {
674 fprintf(stderr, "Unknown arguments found on commandline");
679 printf("<iptables-rules version=\"1.0\">\n");
681 /* Grab standard input. */
682 while (fgets(buffer, sizeof(buffer), in)) {
687 if (buffer[0] == '\n')
689 else if (buffer[0] == '#') {
695 printf("<!-- line %d ", line);
696 xmlCommentEscape(buffer);
700 if ((strcmp(buffer, "COMMIT\n") == 0) && (curTable[0])) {
701 DEBUGP("Calling commit\n");
704 } else if ((buffer[0] == '*')) {
708 table = strtok(buffer + 1, " \t\n");
709 DEBUGP("line %u, table '%s'\n", line, table);
711 exit_error(PARAMETER_PROBLEM,
712 "%s: line %u table name invalid\n",
719 } else if ((buffer[0] == ':') && (curTable[0])) {
721 char *policy, *chain;
722 struct ipt_counters count;
725 chain = strtok(buffer + 1, " \t\n");
726 DEBUGP("line %u, chain '%s'\n", line, chain);
728 exit_error(PARAMETER_PROBLEM,
729 "%s: line %u chain name invalid\n",
734 DEBUGP("Creating new chain '%s'\n", chain);
736 policy = strtok(NULL, " \t\n");
737 DEBUGP("line %u, policy '%s'\n", line, policy);
739 exit_error(PARAMETER_PROBLEM,
740 "%s: line %u policy invalid\n",
745 ctrs = strtok(NULL, " \t\n");
746 parse_counters(ctrs, &count);
747 saveChain(chain, policy, &count);
750 } else if (curTable[0]) {
759 char *param_start, *curchar;
760 int quote_open, quoted;
762 /* reset the newargv */
765 if (buffer[0] == '[') {
766 /* we have counters in our input */
767 ptr = strchr(buffer, ']');
769 exit_error(PARAMETER_PROBLEM,
770 "Bad line %u: need ]\n",
773 pcnt = strtok(buffer + 1, ":");
775 exit_error(PARAMETER_PROBLEM,
776 "Bad line %u: need :\n",
779 bcnt = strtok(NULL, "]");
781 exit_error(PARAMETER_PROBLEM,
782 "Bad line %u: need ]\n",
785 /* start command parsing after counter */
786 parsestart = ptr + 1;
788 /* start command parsing at start of line */
793 /* This is a 'real' parser crafted in artist mode
794 * not hacker mode. If the author can live with that
795 * then so can everyone else */
798 /* We need to know which args were quoted so we
799 can preserve quote */
801 param_start = parsestart;
803 for (curchar = parsestart; *curchar; curchar++) {
804 if (*curchar == '"') {
805 /* quote_open cannot be true if there
806 * was no previous character. Thus,
807 * curchar-1 has to be within bounds */
809 *(curchar - 1) != '\\') {
819 || *curchar == '\t' || *curchar == '\n') {
820 char param_buffer[1024];
821 int param_len = curchar - param_start;
832 /* end of one parameter */
833 strncpy(param_buffer, param_start,
835 *(param_buffer + param_len) = '\0';
837 /* check if table name specified */
838 if (!strncmp(param_buffer, "-t", 3)
839 || !strncmp(param_buffer,
841 exit_error(PARAMETER_PROBLEM,
842 "Line %u seems to have a "
843 "-t table option.\n",
848 add_argv(param_buffer, quoted);
851 strcmp(newargv[newargc - 2], "-A"))
852 chain = newargv[newargc - 1];
854 param_start += param_len + 1;
856 /* regular character, skip */
860 DEBUGP("calling do_command(%u, argv, &%s, handle):\n",
863 for (a = 0; a < newargc; a++)
864 DEBUGP("argv[%u]: %s\n", a, newargv[a]);
866 needChain(chain);// Should we explicitly look for -A
867 do_rule(pcnt, bcnt, newargc, newargv, newargvattr);
873 fprintf(stderr, "%s: line %u failed\n",
879 fprintf(stderr, "%s: COMMIT expected at line %u\n",
880 program_name, line + 1);
884 printf("</iptables-rules>\n");