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"
21
#define DEBUGP(x, args...) fprintf(stderr, x, ## args)
23
#define DEBUGP(x, args...)
26
/* no need to link with iptables.o */
27
const char *program_name;
28
const char *program_version;
30
#ifndef IPTABLES_MULTI
32
void exit_error(enum exittype status, const char *msg, ...)
37
fprintf(stderr, "%s v%s: ", program_name, program_version);
38
vfprintf(stderr, msg, args);
40
fprintf(stderr, "\n");
41
/* On error paths, make sure that we don't leak memory */
46
static void print_usage(const char *name, const char *version)
47
__attribute__ ((noreturn));
49
static int verbose = 0;
50
/* Whether to combine actions of sequential rules with identical conditions */
51
static int combine = 0;
52
/* Keeping track of external matches and targets. */
53
static struct option options[] = {
54
{"verbose", 0, 0, 'v'},
55
{"combine", 0, 0, 'c'},
61
print_usage(const char *name, const char *version)
63
fprintf(stderr, "Usage: %s [-c] [-v] [-h]\n"
65
" [ --verbose ]\n" " [ --help ]\n", name);
71
parse_counters(char *string, struct ipt_counters *ctr)
73
u_int64_t *pcnt, *bcnt;
79
(string, "[%llu:%llu]",
80
(unsigned long long *)pcnt,
81
(unsigned long long *)bcnt) == 2);
86
/* global new argv and argc */
87
static char *newargv[255];
88
static int newargc = 0;
90
static char *oldargv[255];
91
static int oldargc = 0;
93
/* arg meta data, were they quoted, frinstance */
94
static int newargvattr[255];
96
#define IPT_CHAIN_MAXNAMELEN IPT_TABLE_MAXNAMELEN
97
char closeActionTag[IPT_TABLE_MAXNAMELEN + 1];
98
char closeRuleTag[IPT_TABLE_MAXNAMELEN + 1];
99
char curTable[IPT_TABLE_MAXNAMELEN + 1];
100
char curChain[IPT_CHAIN_MAXNAMELEN + 1];
106
struct ipt_counters count;
110
#define maxChains 10240 /* max chains per table */
111
static 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 */
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 = IPTABLES_VERSION;
651
while ((c = getopt_long(argc, argv, "cvh", options, NULL)) != -1) {
657
printf("xptables-xml\n");
661
print_usage("iptables-xml", IPTABLES_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");