/* Code to convert iptables-save format to xml format, * (C) 2006 Ufo Mechanic <azez@ufomechanic.net> * based on iptables-restor (C) 2000-2002 by Harald Welte <laforge@gnumonks.org> * based on previous code from Rusty Russell <rusty@linuxcare.com.au> * * This code is distributed under the terms of GNU GPL v2 * * $Id: iptables-xml.c,v 1.4 2006/11/09 12:02:17 azez Exp $ */ #include <getopt.h> #include <sys/errno.h> #include <string.h> #include <stdio.h> #include <stdlib.h> #include <stdarg.h> #include "iptables.h" #include "libiptc/libiptc.h" #ifdef DEBUG #define DEBUGP(x, args...) fprintf(stderr, x, ## args) #else #define DEBUGP(x, args...) #endif /* no need to link with iptables.o */ const char *program_name; const char *program_version; #ifndef IPTABLES_MULTI int line = 0; void exit_error(enum exittype status, char *msg, ...) { va_list args; va_start(args, msg); fprintf(stderr, "%s v%s: ", program_name, program_version); vfprintf(stderr, msg, args); va_end(args); fprintf(stderr, "\n"); /* On error paths, make sure that we don't leak memory */ exit(status); } #endif static void print_usage(const char *name, const char *version) __attribute__ ((noreturn)); static int verbose = 0; /* Whether to combine actions of sequential rules with identical conditions */ static int combine = 0; /* Keeping track of external matches and targets. */ static struct option options[] = { {"verbose", 0, 0, 'v'}, {"combine", 0, 0, 'c'}, {"help", 0, 0, 'h'}, {0} }; static void print_usage(const char *name, const char *version) { fprintf(stderr, "Usage: %s [-c] [-v] [-h]\n" " [--combine ]\n" " [ --verbose ]\n" " [ --help ]\n", name); exit(1); } static int parse_counters(char *string, struct ipt_counters *ctr) { if (string != NULL) return (sscanf (string, "[%llu:%llu]", (unsigned long long *) &ctr->pcnt, (unsigned long long *) &ctr->bcnt) == 2); else return (0 == 2); } /* global new argv and argc */ static char *newargv[255]; static int newargc = 0; static char *oldargv[255]; static int oldargc = 0; /* arg meta data, were they quoted, frinstance */ static int newargvattr[255]; #define IPT_CHAIN_MAXNAMELEN IPT_TABLE_MAXNAMELEN char closeActionTag[IPT_TABLE_MAXNAMELEN + 1]; char closeRuleTag[IPT_TABLE_MAXNAMELEN + 1]; char curTable[IPT_TABLE_MAXNAMELEN + 1]; char curChain[IPT_CHAIN_MAXNAMELEN + 1]; typedef struct chain { char *chain; char *policy; struct ipt_counters count; int created; } chain; #define maxChains 10240 /* max chains per table */ static chain chains[maxChains]; static int nextChain = 0; /* funCtion adding one argument to newargv, updating newargc * returns true if argument added, false otherwise */ static int add_argv(char *what, int quoted) { DEBUGP("add_argv: %d %s\n", newargc, what); if (what && ((newargc + 1) < sizeof(newargv) / sizeof(char *))) { newargv[newargc] = strdup(what); newargvattr[newargc] = quoted; newargc++; return 1; } else return 0; } static void free_argv(void) { int i; for (i = 0; i < newargc; i++) { free(newargv[i]); newargv[i] = NULL; } newargc = 0; for (i = 0; i < oldargc; i++) { free(oldargv[i]); oldargv[i] = NULL; } oldargc = 0; } /* save parsed rule for comparison with next rule to perform action agregation on duplicate conditions */ static void save_argv(void) { int i; for (i = 0; i < oldargc; i++) free(oldargv[i]); oldargc = newargc; newargc = 0; for (i = 0; i < oldargc; i++) { oldargv[i] = newargv[i]; newargv[i] = NULL; } } /* like puts but with xml encoding */ static void xmlEncode(char *text) { while (text && *text) { if ((unsigned char) (*text) >= 127) printf("&#%d;", (unsigned char) (*text)); else if (*text == '&') printf("&"); else if (*text == '<') printf("<"); else if (*text == '>') printf(">"); else if (*text == '"') printf("""); else putchar(*text); text++; } } /* Output text as a comment, avoiding a double hyphen */ static void xmlCommentEscape(char *comment) { int h_count = 0; while (comment && *comment) { if (*comment == '-') { h_count++; if (h_count >= 2) { h_count = 0; putchar(' '); } putchar('*'); } /* strip trailing newline */ if (*comment == '\n' && *(comment + 1) == 0); else putchar(*comment); comment++; } } static void xmlComment(char *comment) { printf("<!-- "); xmlCommentEscape(comment); printf(" -->\n"); } static void xmlAttrS(char *name, char *value) { printf("%s=\"", name); xmlEncode(value); printf("\" "); } static void xmlAttrI(char *name, long long int num) { printf("%s=\"%lld\" ", name, num); } static void closeChain() { if (curChain[0] == 0) return; if (closeActionTag[0]) printf("%s\n", closeActionTag); closeActionTag[0] = 0; if (closeRuleTag[0]) printf("%s\n", closeRuleTag); closeRuleTag[0] = 0; if (curChain[0]) printf(" </chain>\n"); curChain[0] = 0; //lastRule[0]=0; } static void openChain(char *chain, char *policy, struct ipt_counters *ctr, char close) { closeChain(); strncpy(curChain, chain, IPT_CHAIN_MAXNAMELEN); curChain[IPT_CHAIN_MAXNAMELEN] = '\0'; printf(" <chain "); xmlAttrS("name", curChain); if (strcmp(policy, "-") != 0) xmlAttrS("policy", policy); xmlAttrI("packet-count", (unsigned long long) ctr->pcnt); xmlAttrI("byte-count", (unsigned long long) ctr->bcnt); if (close) { printf("%c", close); curChain[0] = 0; } printf(">\n"); } static int existsChain(char *chain) { /* open a saved chain */ int c = 0; if (0 == strcmp(curChain, chain)) return 1; for (c = 0; c < nextChain; c++) if (chains[c].chain && strcmp(chains[c].chain, chain) == 0) return 1; return 0; } static void needChain(char *chain) { /* open a saved chain */ int c = 0; if (0 == strcmp(curChain, chain)) return; for (c = 0; c < nextChain; c++) if (chains[c].chain && strcmp(chains[c].chain, chain) == 0) { openChain(chains[c].chain, chains[c].policy, &(chains[c].count), '\0'); /* And, mark it as done so we don't create an empty chain at table-end time */ chains[c].created = 1; } } static void saveChain(char *chain, char *policy, struct ipt_counters *ctr) { if (nextChain >= maxChains) { exit_error(PARAMETER_PROBLEM, "%s: line %u chain name invalid\n", program_name, line); exit(1); }; chains[nextChain].chain = strdup(chain); chains[nextChain].policy = strdup(policy); chains[nextChain].count = *ctr; chains[nextChain].created = 0; nextChain++; } static void finishChains() { int c; for (c = 0; c < nextChain; c++) if (!chains[c].created) { openChain(chains[c].chain, chains[c].policy, &(chains[c].count), '/'); free(chains[c].chain); free(chains[c].policy); } nextChain = 0; } static void closeTable() { closeChain(); finishChains(); if (curTable[0]) printf(" </table>\n"); curTable[0] = 0; } static void openTable(char *table) { closeTable(); strncpy(curTable, table, IPT_TABLE_MAXNAMELEN); curTable[IPT_TABLE_MAXNAMELEN] = '\0'; printf(" <table "); xmlAttrS("name", curTable); printf(">\n"); } // is char* -j --jump -g or --goto static int isTarget(char *arg) { return ((arg) && (strcmp((arg), "-j") == 0 || strcmp((arg), "--jump") == 0 || strcmp((arg), "-g") == 0 || strcmp((arg), "--goto") == 0)); } // part=-1 means do conditions, part=1 means do rules, part=0 means do both static void do_rule_part(char *leveltag1, char *leveltag2, int part, int argc, char *argv[], int argvattr[]) { int arg = 1; // ignore leading -A char invert_next = 0; char *thisChain = NULL; char *spacer = ""; // space when needed to assemble arguments char *level1 = NULL; char *level2 = NULL; char *leveli1 = " "; char *leveli2 = " "; #define CLOSE_LEVEL(LEVEL) \ do { \ if (level ## LEVEL) printf("</%s>\n", \ (leveltag ## LEVEL)?(leveltag ## LEVEL):(level ## LEVEL)); \ level ## LEVEL=NULL;\ } while(0) #define OPEN_LEVEL(LEVEL,TAG) \ do {\ level ## LEVEL=TAG;\ if (leveltag ## LEVEL) {\ printf("%s<%s ", (leveli ## LEVEL), \ (leveltag ## LEVEL));\ xmlAttrS("type", (TAG)); \ } else printf("%s<%s ", (leveli ## LEVEL), (level ## LEVEL)); \ } while(0) thisChain = argv[arg++]; if (part == 1) { /* skip */ /* use argvattr to tell which arguments were quoted to avoid comparing quoted arguments, like comments, to -j, */ while (arg < argc && (argvattr[arg] || !isTarget(argv[arg]))) arg++; } /* Before we start, if the first arg is -[^-] and not -m or -j or -g then start a dummy <match> tag for old style built-in matches. We would do this in any case, but no need if it would be empty */ if (arg < argc && argv[arg][0] == '-' && !isTarget(argv[arg]) && strcmp(argv[arg], "-m") != 0) { OPEN_LEVEL(1, "match"); printf(">\n"); } while (arg < argc) { // If ! is followed by -* then apply to that else output as data // Stop, if we need to if (part == -1 && !argvattr[arg] && (isTarget(argv[arg]))) { break; } else if (!argvattr[arg] && strcmp(argv[arg], "!") == 0) { if ((arg + 1) < argc && argv[arg + 1][0] == '-') invert_next = '!'; else printf("%s%s", spacer, argv[arg]); spacer = " "; } else if (!argvattr[arg] && isTarget(argv[arg]) && existsChain(argv[arg + 1]) && (2 + arg >= argc)) { if (!((1 + arg) < argc)) // no args to -j, -m or -g, ignore & finish loop break; CLOSE_LEVEL(2); if (level1) printf("%s", leveli1); CLOSE_LEVEL(1); spacer = ""; invert_next = 0; if (strcmp(argv[arg], "-g") == 0 || strcmp(argv[arg], "--goto") == 0) { /* goto user chain */ OPEN_LEVEL(1, "goto"); printf(">\n"); arg++; OPEN_LEVEL(2, argv[arg]); printf("/>\n"); level2 = NULL; } else { /* call user chain */ OPEN_LEVEL(1, "call"); printf(">\n"); arg++; OPEN_LEVEL(2, argv[arg]); printf("/>\n"); level2 = NULL; } } else if (!argvattr[arg] && (isTarget(argv[arg]) || strcmp(argv[arg], "-m") == 0 || strcmp(argv[arg], "--module") == 0)) { if (!((1 + arg) < argc)) // no args to -j, -m or -g, ignore & finish loop break; CLOSE_LEVEL(2); if (level1) printf("%s", leveli1); CLOSE_LEVEL(1); spacer = ""; invert_next = 0; arg++; OPEN_LEVEL(1, (argv[arg])); // Optimize case, can we close this tag already? if ((arg + 1) >= argc || (!argvattr[arg + 1] && (isTarget(argv[arg + 1]) || strcmp(argv[arg + 1], "-m") == 0 || strcmp(argv[arg + 1], "--module") == 0))) { printf(" />\n"); level1 = NULL; } else { printf(">\n"); } } else if (!argvattr[arg] && argv[arg][0] == '-') { char *tag; CLOSE_LEVEL(2); // Skip past any - tag = argv[arg]; while (*tag == '-' && *tag) tag++; spacer = ""; OPEN_LEVEL(2, tag); if (invert_next) printf(" invert=\"1\""); invert_next = 0; // Optimize case, can we close this tag already? if (!((arg + 1) < argc) || (argv[arg + 1][0] == '-' /* NOT QUOTED */ )) { printf(" />\n"); level2 = NULL; } else { printf(">"); } } else { // regular data char *spaces = strchr(argv[arg], ' '); printf("%s", spacer); if (spaces || argvattr[arg]) printf("""); // if argv[arg] contains a space, enclose in quotes xmlEncode(argv[arg]); if (spaces || argvattr[arg]) printf("""); spacer = " "; } arg++; } CLOSE_LEVEL(2); if (level1) printf("%s", leveli1); CLOSE_LEVEL(1); return; } static int compareRules() { /* compare arguments up to -j or -g for match. NOTE: We don't want to combine actions if there were no criteria in each rule, or rules didn't have an action NOTE: Depends on arguments being in some kind of "normal" order which is the case when processing the ACTUAL output of actual iptables-save rather than a file merely in a compatable format */ int old = 0; int new = 0; int compare = 0; while (new < newargc && old < oldargc) { if (isTarget(oldargv[old]) && isTarget(newargv[new])) { compare = 1; break; } // break when old!=new if (strcmp(oldargv[old], newargv[new]) != 0) { compare = 0; break; } old++; new++; } // We won't match unless both rules had a target. // This means we don't combine target-less rules, which is good return compare == 1; } /* has a nice parsed rule starting with -A */ static void do_rule(char *pcnt, char *bcnt, int argc, char *argv[], int argvattr[]) { /* are these conditions the same as the previous rule? * If so, skip arg straight to -j or -g */ if (combine && argc > 2 && !isTarget(argv[2]) && compareRules()) { xmlComment("Combine action from next rule"); } else { if (closeActionTag[0]) { printf("%s\n", closeActionTag); closeActionTag[0] = 0; } if (closeRuleTag[0]) { printf("%s\n", closeRuleTag); closeRuleTag[0] = 0; } printf(" <rule "); //xmlAttrS("table",curTable); // not needed in full mode //xmlAttrS("chain",argv[1]); // not needed in full mode if (pcnt) xmlAttrS("packet-count", pcnt); if (bcnt) xmlAttrS("byte-count", bcnt); printf(">\n"); strncpy(closeRuleTag, " </rule>\n", IPT_TABLE_MAXNAMELEN); closeRuleTag[IPT_TABLE_MAXNAMELEN] = '\0'; /* no point in writing out condition if there isn't one */ if (argc >= 3 && !isTarget(argv[2])) { printf(" <conditions>\n"); do_rule_part(NULL, NULL, -1, argc, argv, argvattr); printf(" </conditions>\n"); } } /* Write out the action */ //do_rule_part("action","arg",1,argc,argv,argvattr); if (!closeActionTag[0]) { printf(" <actions>\n"); strncpy(closeActionTag, " </actions>\n", IPT_TABLE_MAXNAMELEN); closeActionTag[IPT_TABLE_MAXNAMELEN] = '\0'; } do_rule_part(NULL, NULL, 1, argc, argv, argvattr); } #ifdef IPTABLES_MULTI int iptables_xml_main(int argc, char *argv[]) #else int main(int argc, char *argv[]) #endif { char buffer[10240]; int c; FILE *in; program_name = "iptables-xml"; program_version = IPTABLES_VERSION; line = 0; while ((c = getopt_long(argc, argv, "cvh", options, NULL)) != -1) { switch (c) { case 'c': combine = 1; break; case 'v': printf("xptables-xml\n"); verbose = 1; break; case 'h': print_usage("iptables-xml", IPTABLES_VERSION); break; } } if (optind == argc - 1) { in = fopen(argv[optind], "r"); if (!in) { fprintf(stderr, "Can't open %s: %s", argv[optind], strerror(errno)); exit(1); } } else if (optind < argc) { fprintf(stderr, "Unknown arguments found on commandline"); exit(1); } else in = stdin; printf("<iptables-rules version=\"1.0\">\n"); /* Grab standard input. */ while (fgets(buffer, sizeof(buffer), in)) { int ret = 0; line++; if (buffer[0] == '\n') continue; else if (buffer[0] == '#') { xmlComment(buffer); continue; } if (verbose) { printf("<!-- line %d ", line); xmlCommentEscape(buffer); printf(" -->\n"); } if ((strcmp(buffer, "COMMIT\n") == 0) && (curTable[0])) { DEBUGP("Calling commit\n"); closeTable(); ret = 1; } else if ((buffer[0] == '*')) { /* New table */ char *table; table = strtok(buffer + 1, " \t\n"); DEBUGP("line %u, table '%s'\n", line, table); if (!table) { exit_error(PARAMETER_PROBLEM, "%s: line %u table name invalid\n", program_name, line); exit(1); } openTable(table); ret = 1; } else if ((buffer[0] == ':') && (curTable[0])) { /* New chain. */ char *policy, *chain; struct ipt_counters count; char *ctrs; chain = strtok(buffer + 1, " \t\n"); DEBUGP("line %u, chain '%s'\n", line, chain); if (!chain) { exit_error(PARAMETER_PROBLEM, "%s: line %u chain name invalid\n", program_name, line); exit(1); } DEBUGP("Creating new chain '%s'\n", chain); policy = strtok(NULL, " \t\n"); DEBUGP("line %u, policy '%s'\n", line, policy); if (!policy) { exit_error(PARAMETER_PROBLEM, "%s: line %u policy invalid\n", program_name, line); exit(1); } ctrs = strtok(NULL, " \t\n"); parse_counters(ctrs, &count); saveChain(chain, policy, &count); ret = 1; } else if (curTable[0]) { int a; char *ptr = buffer; char *pcnt = NULL; char *bcnt = NULL; char *parsestart; char *chain = NULL; /* the parser */ char *param_start, *curchar; int quote_open, quoted; /* reset the newargv */ newargc = 0; if (buffer[0] == '[') { /* we have counters in our input */ ptr = strchr(buffer, ']'); if (!ptr) exit_error(PARAMETER_PROBLEM, "Bad line %u: need ]\n", line); pcnt = strtok(buffer + 1, ":"); if (!pcnt) exit_error(PARAMETER_PROBLEM, "Bad line %u: need :\n", line); bcnt = strtok(NULL, "]"); if (!bcnt) exit_error(PARAMETER_PROBLEM, "Bad line %u: need ]\n", line); /* start command parsing after counter */ parsestart = ptr + 1; } else { /* start command parsing at start of line */ parsestart = buffer; } /* This is a 'real' parser crafted in artist mode * not hacker mode. If the author can live with that * then so can everyone else */ quote_open = 0; /* We need to know which args were quoted so we can preserve quote */ quoted = 0; param_start = parsestart; for (curchar = parsestart; *curchar; curchar++) { if (*curchar == '"') { /* quote_open cannot be true if there * was no previous character. Thus, * curchar-1 has to be within bounds */ if (quote_open && *(curchar - 1) != '\\') { quote_open = 0; *curchar = ' '; } else { quote_open = 1; quoted = 1; param_start++; } } if (*curchar == ' ' || *curchar == '\t' || *curchar == '\n') { char param_buffer[1024]; int param_len = curchar - param_start; if (quote_open) continue; if (!param_len) { /* two spaces? */ param_start++; continue; } /* end of one parameter */ strncpy(param_buffer, param_start, param_len); *(param_buffer + param_len) = '\0'; /* check if table name specified */ if (!strncmp(param_buffer, "-t", 3) || !strncmp(param_buffer, "--table", 8)) { exit_error(PARAMETER_PROBLEM, "Line %u seems to have a " "-t table option.\n", line); exit(1); } add_argv(param_buffer, quoted); if (newargc >= 2 && 0 == strcmp(newargv[newargc - 2], "-A")) chain = newargv[newargc - 1]; quoted = 0; param_start += param_len + 1; } else { /* regular character, skip */ } } DEBUGP("calling do_command(%u, argv, &%s, handle):\n", newargc, curTable); for (a = 0; a < newargc; a++) DEBUGP("argv[%u]: %s\n", a, newargv[a]); needChain(chain);// Should we explicitly look for -A do_rule(pcnt, bcnt, newargc, newargv, newargvattr); save_argv(); ret = 1; } if (!ret) { fprintf(stderr, "%s: line %u failed\n", program_name, line); exit(1); } } if (curTable[0]) { fprintf(stderr, "%s: COMMIT expected at line %u\n", program_name, line + 1); exit(1); } printf("</iptables-rules>\n"); free_argv(); return 0; }