1 /* Code to convert iptables-save format to xml format,
2 * (C) 2006 Ufo Mechanic <azez@ufomechanic.net>
3 * based on iptables-restore (C) 2000-2002 by Harald Welte <laforge@gnumonks.org>
4 * based on previous code from Rusty Russell <rusty@linuxcare.com.au>
5 *
6 * This code is distributed under the terms of GNU GPL v2
7 */
8 #include "config.h"
9 #include <getopt.h>
10 #include <errno.h>
11 #include <string.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <stdarg.h>
15 #include "iptables.h"
16 #include "libiptc/libiptc.h"
17 #include "xtables-multi.h"
18 #include <xtables.h>
19 #include "xshared.h"
20
21 struct xtables_globals iptables_xml_globals = {
22 .option_offset = 0,
23 .program_version = PACKAGE_VERSION,
24 .program_name = "iptables-xml",
25 };
26 #define prog_name iptables_xml_globals.program_name
27 #define prog_vers iptables_xml_globals.program_version
28
29 static void print_usage(const char *name, const char *version)
30 __attribute__ ((noreturn));
31
32 static int verbose;
33 /* Whether to combine actions of sequential rules with identical conditions */
34 static int combine;
35 /* Keeping track of external matches and targets. */
36 static const struct option options[] = {
37 {"verbose", 0, NULL, 'v'},
38 {"combine", 0, NULL, 'c'},
39 {"help", 0, NULL, 'h'},
40 { .name = NULL }
41 };
42
43 static void
print_usage(const char * name,const char * version)44 print_usage(const char *name, const char *version)
45 {
46 fprintf(stderr, "Usage: %s [-c] [-v] [-h]\n"
47 " [--combine ]\n"
48 " [ --verbose ]\n" " [ --help ]\n", name);
49
50 exit(1);
51 }
52
53 #define XT_CHAIN_MAXNAMELEN XT_TABLE_MAXNAMELEN
54 static char closeActionTag[XT_TABLE_MAXNAMELEN + 1];
55 static char closeRuleTag[XT_TABLE_MAXNAMELEN + 1];
56 static char curTable[XT_TABLE_MAXNAMELEN + 1];
57 static char curChain[XT_CHAIN_MAXNAMELEN + 1];
58
59 struct chain {
60 char *chain;
61 char *policy;
62 struct xt_counters count;
63 int created;
64 };
65
66 #define maxChains 10240 /* max chains per table */
67 static struct chain chains[maxChains];
68 static int nextChain;
69
70 /* like puts but with xml encoding */
71 static void
xmlEncode(char * text)72 xmlEncode(char *text)
73 {
74 while (text && *text) {
75 if ((unsigned char) (*text) >= 127)
76 printf("&#%d;", (unsigned char) (*text));
77 else if (*text == '&')
78 printf("&");
79 else if (*text == '<')
80 printf("<");
81 else if (*text == '>')
82 printf(">");
83 else if (*text == '"')
84 printf(""");
85 else
86 putchar(*text);
87 text++;
88 }
89 }
90
91 /* Output text as a comment, avoiding a double hyphen */
92 static void
xmlCommentEscape(char * comment)93 xmlCommentEscape(char *comment)
94 {
95 int h_count = 0;
96
97 while (comment && *comment) {
98 if (*comment == '-') {
99 h_count++;
100 if (h_count >= 2) {
101 h_count = 0;
102 putchar(' ');
103 }
104 putchar('*');
105 }
106 /* strip trailing newline */
107 if (*comment == '\n' && *(comment + 1) == 0);
108 else
109 putchar(*comment);
110 comment++;
111 }
112 }
113
114 static void
xmlComment(char * comment)115 xmlComment(char *comment)
116 {
117 printf("<!-- ");
118 xmlCommentEscape(comment);
119 printf(" -->\n");
120 }
121
122 static void
xmlAttrS(char * name,char * value)123 xmlAttrS(char *name, char *value)
124 {
125 printf("%s=\"", name);
126 xmlEncode(value);
127 printf("\" ");
128 }
129
130 static void
xmlAttrI(char * name,long long int num)131 xmlAttrI(char *name, long long int num)
132 {
133 printf("%s=\"%lld\" ", name, num);
134 }
135
136 static void
closeChain(void)137 closeChain(void)
138 {
139 if (curChain[0] == 0)
140 return;
141
142 if (closeActionTag[0])
143 printf("%s\n", closeActionTag);
144 closeActionTag[0] = 0;
145 if (closeRuleTag[0])
146 printf("%s\n", closeRuleTag);
147 closeRuleTag[0] = 0;
148 if (curChain[0])
149 printf(" </chain>\n");
150 curChain[0] = 0;
151 //lastRule[0]=0;
152 }
153
154 static void
openChain(char * chain,char * policy,struct xt_counters * ctr,char close)155 openChain(char *chain, char *policy, struct xt_counters *ctr, char close)
156 {
157 closeChain();
158
159 strncpy(curChain, chain, XT_CHAIN_MAXNAMELEN);
160 curChain[XT_CHAIN_MAXNAMELEN] = '\0';
161
162 printf(" <chain ");
163 xmlAttrS("name", curChain);
164 if (strcmp(policy, "-") != 0)
165 xmlAttrS("policy", policy);
166 xmlAttrI("packet-count", (unsigned long long) ctr->pcnt);
167 xmlAttrI("byte-count", (unsigned long long) ctr->bcnt);
168 if (close) {
169 printf("%c", close);
170 curChain[0] = 0;
171 }
172 printf(">\n");
173 }
174
175 static int
existsChain(char * chain)176 existsChain(char *chain)
177 {
178 /* open a saved chain */
179 int c = 0;
180
181 if (0 == strcmp(curChain, chain))
182 return 1;
183 for (c = 0; c < nextChain; c++)
184 if (chains[c].chain && strcmp(chains[c].chain, chain) == 0)
185 return 1;
186 return 0;
187 }
188
189 static void
needChain(char * chain)190 needChain(char *chain)
191 {
192 /* open a saved chain */
193 int c = 0;
194
195 if (0 == strcmp(curChain, chain))
196 return;
197
198 for (c = 0; c < nextChain; c++)
199 if (chains[c].chain && strcmp(chains[c].chain, chain) == 0) {
200 openChain(chains[c].chain, chains[c].policy,
201 &(chains[c].count), '\0');
202 /* And, mark it as done so we don't create
203 an empty chain at table-end time */
204 chains[c].created = 1;
205 }
206 }
207
208 static void
saveChain(char * chain,char * policy,struct xt_counters * ctr)209 saveChain(char *chain, char *policy, struct xt_counters *ctr)
210 {
211 if (nextChain >= maxChains)
212 xtables_error(PARAMETER_PROBLEM,
213 "%s: line %u chain name invalid\n",
214 prog_name, line);
215
216 chains[nextChain].chain = strdup(chain);
217 chains[nextChain].policy = strdup(policy);
218 chains[nextChain].count = *ctr;
219 chains[nextChain].created = 0;
220 nextChain++;
221 }
222
223 static void
finishChains(void)224 finishChains(void)
225 {
226 int c;
227
228 for (c = 0; c < nextChain; c++)
229 if (!chains[c].created) {
230 openChain(chains[c].chain, chains[c].policy,
231 &(chains[c].count), '/');
232 free(chains[c].chain);
233 free(chains[c].policy);
234 }
235 nextChain = 0;
236 }
237
238 static void
closeTable(void)239 closeTable(void)
240 {
241 closeChain();
242 finishChains();
243 if (curTable[0])
244 printf(" </table>\n");
245 curTable[0] = 0;
246 }
247
248 static void
openTable(char * table)249 openTable(char *table)
250 {
251 closeTable();
252
253 strncpy(curTable, table, XT_TABLE_MAXNAMELEN);
254 curTable[XT_TABLE_MAXNAMELEN] = '\0';
255
256 printf(" <table ");
257 xmlAttrS("name", curTable);
258 printf(">\n");
259 }
260
261 // is char* -j --jump -g or --goto
262 static int
isTarget(char * arg)263 isTarget(char *arg)
264 {
265 return ((arg)
266 && (strcmp((arg), "-j") == 0 || strcmp((arg), "--jump") == 0
267 || strcmp((arg), "-g") == 0
268 || strcmp((arg), "--goto") == 0));
269 }
270
271 // is it a terminating target like -j ACCEPT, etc
272 // (or I guess -j SNAT in nat table, but we don't check for that yet
273 static int
isTerminatingTarget(char * arg)274 isTerminatingTarget(char *arg)
275 {
276 return ((arg)
277 && (strcmp((arg), "ACCEPT") == 0
278 || strcmp((arg), "DROP") == 0
279 || strcmp((arg), "QUEUE") == 0
280 || strcmp((arg), "RETURN") == 0));
281 }
282
283 // part=-1 means do conditions, part=1 means do rules, part=0 means do both
284 static void
do_rule_part(char * leveltag1,char * leveltag2,int part,int argc,char * argv[],int argvattr[])285 do_rule_part(char *leveltag1, char *leveltag2, int part, int argc,
286 char *argv[], int argvattr[])
287 {
288 int i;
289 int arg = 2; // ignore leading -A <chain>
290 char invert_next = 0;
291 char *spacer = ""; // space when needed to assemble arguments
292 char *level1 = NULL;
293 char *level2 = NULL;
294 char *leveli1 = " ";
295 char *leveli2 = " ";
296
297 #define CLOSE_LEVEL(LEVEL) \
298 do { \
299 if (level ## LEVEL) printf("</%s>\n", \
300 (leveltag ## LEVEL)?(leveltag ## LEVEL):(level ## LEVEL)); \
301 level ## LEVEL=NULL;\
302 } while(0)
303
304 #define OPEN_LEVEL(LEVEL,TAG) \
305 do {\
306 level ## LEVEL=TAG;\
307 if (leveltag ## LEVEL) {\
308 printf("%s<%s ", (leveli ## LEVEL), \
309 (leveltag ## LEVEL));\
310 xmlAttrS("type", (TAG)); \
311 } else printf("%s<%s ", (leveli ## LEVEL), (level ## LEVEL)); \
312 } while(0)
313
314 if (part == 1) { /* skip */
315 /* use argvattr to tell which arguments were quoted
316 to avoid comparing quoted arguments, like comments, to -j, */
317 while (arg < argc && (argvattr[arg] || !isTarget(argv[arg])))
318 arg++;
319 }
320
321 /* Before we start, if the first arg is -[^-] and not -m or -j or -g
322 * then start a dummy <match> tag for old style built-in matches.
323 * We would do this in any case, but no need if it would be empty.
324 * In the case of negation, we need to look at arg+1
325 */
326 if (arg < argc && strcmp(argv[arg], "!") == 0)
327 i = arg + 1;
328 else
329 i = arg;
330 if (i < argc && argv[i][0] == '-' && !isTarget(argv[i])
331 && strcmp(argv[i], "-m") != 0) {
332 OPEN_LEVEL(1, "match");
333 printf(">\n");
334 }
335 while (arg < argc) {
336 // If ! is followed by -* then apply to that else output as data
337 // Stop, if we need to
338 if (part == -1 && !argvattr[arg] && (isTarget(argv[arg]))) {
339 break;
340 } else if (!argvattr[arg] && strcmp(argv[arg], "!") == 0) {
341 if ((arg + 1) < argc && argv[arg + 1][0] == '-')
342 invert_next = '!';
343 else
344 printf("%s%s", spacer, argv[arg]);
345 spacer = " ";
346 } else if (!argvattr[arg] && isTarget(argv[arg]) &&
347 (arg + 1 < argc) &&
348 existsChain(argv[arg + 1])) {
349 CLOSE_LEVEL(2);
350 if (level1)
351 printf("%s", leveli1);
352 CLOSE_LEVEL(1);
353 spacer = "";
354 invert_next = 0;
355 if (strcmp(argv[arg], "-g") == 0
356 || strcmp(argv[arg], "--goto") == 0) {
357 /* goto user chain */
358 OPEN_LEVEL(1, "goto");
359 printf(">\n");
360 arg++;
361 OPEN_LEVEL(2, argv[arg]);
362 printf("/>\n");
363 level2 = NULL;
364 } else {
365 /* call user chain */
366 OPEN_LEVEL(1, "call");
367 printf(">\n");
368 arg++;
369 OPEN_LEVEL(2, argv[arg]);
370 printf("/>\n");
371 level2 = NULL;
372 }
373 } else if (!argvattr[arg]
374 && (isTarget(argv[arg])
375 || strcmp(argv[arg], "-m") == 0
376 || strcmp(argv[arg], "--module") == 0)) {
377 if (!((1 + arg) < argc))
378 // no args to -j, -m or -g, ignore & finish loop
379 break;
380 CLOSE_LEVEL(2);
381 if (level1)
382 printf("%s", leveli1);
383 CLOSE_LEVEL(1);
384 spacer = "";
385 invert_next = 0;
386 arg++;
387 OPEN_LEVEL(1, (argv[arg]));
388 // Optimize case, can we close this tag already?
389 if ((arg + 1) >= argc || (!argvattr[arg + 1]
390 && (isTarget(argv[arg + 1])
391 || strcmp(argv[arg + 1],
392 "-m") == 0
393 || strcmp(argv[arg + 1],
394 "--module") ==
395 0))) {
396 printf(" />\n");
397 level1 = NULL;
398 } else {
399 printf(">\n");
400 }
401 } else if (!argvattr[arg] && argv[arg][0] == '-') {
402 char *tag;
403 CLOSE_LEVEL(2);
404 // Skip past any -
405 tag = argv[arg];
406 while (*tag == '-' && *tag)
407 tag++;
408
409 spacer = "";
410 OPEN_LEVEL(2, tag);
411 if (invert_next)
412 printf(" invert=\"1\"");
413 invert_next = 0;
414
415 // Optimize case, can we close this tag already?
416 if (!((arg + 1) < argc)
417 || (argv[arg + 1][0] == '-' /* NOT QUOTED */ )) {
418 printf(" />\n");
419 level2 = NULL;
420 } else {
421 printf(">");
422 }
423 } else { // regular data
424 char *spaces = strchr(argv[arg], ' ');
425 printf("%s", spacer);
426 if (spaces || argvattr[arg])
427 printf(""");
428 // if argv[arg] contains a space, enclose in quotes
429 xmlEncode(argv[arg]);
430 if (spaces || argvattr[arg])
431 printf(""");
432 spacer = " ";
433 }
434 arg++;
435 }
436 CLOSE_LEVEL(2);
437 if (level1)
438 printf("%s", leveli1);
439 CLOSE_LEVEL(1);
440 }
441
442 static int
compareRules(int newargc,char * newargv[],int oldargc,char * oldargv[])443 compareRules(int newargc, char *newargv[], int oldargc, char *oldargv[])
444 {
445 /* Compare arguments up to -j or -g for match.
446 * NOTE: We don't want to combine actions if there were no criteria
447 * in each rule, or rules didn't have an action.
448 * NOTE: Depends on arguments being in some kind of "normal" order which
449 * is the case when processing the ACTUAL output of actual iptables-save
450 * rather than a file merely in a compatible format.
451 */
452
453 unsigned int old = 0;
454 unsigned int new = 0;
455
456 int compare = 0;
457
458 while (new < newargc && old < oldargc) {
459 if (isTarget(oldargv[old]) && isTarget(newargv[new])) {
460 /* if oldarg was a terminating action then it makes no sense
461 * to combine further actions into the same xml */
462 if (((strcmp((oldargv[old]), "-j") == 0
463 || strcmp((oldargv[old]), "--jump") == 0)
464 && old+1 < oldargc
465 && isTerminatingTarget(oldargv[old+1]) )
466 || strcmp((oldargv[old]), "-g") == 0
467 || strcmp((oldargv[old]), "--goto") == 0 ) {
468 /* Previous rule had terminating action */
469 compare = 0;
470 } else {
471 compare = 1;
472 }
473 break;
474 }
475 // break when old!=new
476 if (strcmp(oldargv[old], newargv[new]) != 0) {
477 compare = 0;
478 break;
479 }
480
481 old++;
482 new++;
483 }
484 // We won't match unless both rules had a target.
485 // This means we don't combine target-less rules, which is good
486
487 return compare == 1;
488 }
489
490 /* has a nice parsed rule starting with -A */
491 static void
do_rule(char * pcnt,char * bcnt,int argc,char * argv[],int argvattr[],int oldargc,char * oldargv[])492 do_rule(char *pcnt, char *bcnt, int argc, char *argv[], int argvattr[],
493 int oldargc, char *oldargv[])
494 {
495 /* are these conditions the same as the previous rule?
496 * If so, skip arg straight to -j or -g */
497 if (combine && argc > 2 && !isTarget(argv[2]) &&
498 compareRules(argc, argv, oldargc, oldargv)) {
499 xmlComment("Combine action from next rule");
500 } else {
501
502 if (closeActionTag[0]) {
503 printf("%s\n", closeActionTag);
504 closeActionTag[0] = 0;
505 }
506 if (closeRuleTag[0]) {
507 printf("%s\n", closeRuleTag);
508 closeRuleTag[0] = 0;
509 }
510
511 printf(" <rule ");
512 //xmlAttrS("table",curTable); // not needed in full mode
513 //xmlAttrS("chain",argv[1]); // not needed in full mode
514 if (pcnt)
515 xmlAttrS("packet-count", pcnt);
516 if (bcnt)
517 xmlAttrS("byte-count", bcnt);
518 printf(">\n");
519
520 strncpy(closeRuleTag, " </rule>\n", XT_TABLE_MAXNAMELEN);
521 closeRuleTag[XT_TABLE_MAXNAMELEN] = '\0';
522
523 /* no point in writing out condition if there isn't one */
524 if (argc >= 3 && !isTarget(argv[2])) {
525 printf(" <conditions>\n");
526 do_rule_part(NULL, NULL, -1, argc, argv, argvattr);
527 printf(" </conditions>\n");
528 }
529 }
530 /* Write out the action */
531 //do_rule_part("action","arg",1,argc,argv,argvattr);
532 if (!closeActionTag[0]) {
533 printf(" <actions>\n");
534 strncpy(closeActionTag, " </actions>\n",
535 XT_TABLE_MAXNAMELEN);
536 closeActionTag[XT_TABLE_MAXNAMELEN] = '\0';
537 }
538 do_rule_part(NULL, NULL, 1, argc, argv, argvattr);
539 }
540
541 int
iptables_xml_main(int argc,char * argv[])542 iptables_xml_main(int argc, char *argv[])
543 {
544 struct argv_store last_rule = {}, cur_rule = {};
545 char buffer[10240];
546 int c;
547 FILE *in;
548
549 line = 0;
550
551 xtables_set_params(&iptables_xml_globals);
552 while ((c = getopt_long(argc, argv, "cvh", options, NULL)) != -1) {
553 switch (c) {
554 case 'c':
555 combine = 1;
556 break;
557 case 'v':
558 printf("xptables-xml\n");
559 verbose = 1;
560 break;
561 case 'h':
562 print_usage("iptables-xml", PACKAGE_VERSION);
563 break;
564 }
565 }
566
567 if (optind == argc - 1) {
568 in = fopen(argv[optind], "re");
569 if (!in) {
570 fprintf(stderr, "Can't open %s: %s", argv[optind],
571 strerror(errno));
572 exit(1);
573 }
574 } else if (optind < argc) {
575 fprintf(stderr, "Unknown arguments found on commandline");
576 exit(1);
577 } else
578 in = stdin;
579
580 printf("<iptables-rules version=\"1.0\">\n");
581
582 /* Grab standard input. */
583 while (fgets(buffer, sizeof(buffer), in)) {
584 int ret = 0;
585
586 line++;
587
588 if (buffer[0] == '\n')
589 continue;
590 else if (buffer[0] == '#') {
591 xmlComment(buffer);
592 continue;
593 }
594
595 if (verbose) {
596 printf("<!-- line %d ", line);
597 xmlCommentEscape(buffer);
598 printf(" -->\n");
599 }
600
601 if ((strcmp(buffer, "COMMIT\n") == 0) && (curTable[0])) {
602 DEBUGP("Calling commit\n");
603 closeTable();
604 ret = 1;
605 } else if ((buffer[0] == '*')) {
606 /* New table */
607 char *table;
608
609 table = strtok(buffer + 1, " \t\n");
610 DEBUGP("line %u, table '%s'\n", line, table);
611 if (!table)
612 xtables_error(PARAMETER_PROBLEM,
613 "%s: line %u table name invalid\n",
614 prog_name, line);
615
616 openTable(table);
617
618 ret = 1;
619 } else if ((buffer[0] == ':') && (curTable[0])) {
620 /* New chain. */
621 char *policy, *chain;
622 struct xt_counters count;
623 char *ctrs;
624
625 chain = strtok(buffer + 1, " \t\n");
626 DEBUGP("line %u, chain '%s'\n", line, chain);
627 if (!chain)
628 xtables_error(PARAMETER_PROBLEM,
629 "%s: line %u chain name invalid\n",
630 prog_name, line);
631
632 DEBUGP("Creating new chain '%s'\n", chain);
633
634 policy = strtok(NULL, " \t\n");
635 DEBUGP("line %u, policy '%s'\n", line, policy);
636 if (!policy)
637 xtables_error(PARAMETER_PROBLEM,
638 "%s: line %u policy invalid\n",
639 prog_name, line);
640
641 ctrs = strtok(NULL, " \t\n");
642 parse_counters(ctrs, &count);
643 saveChain(chain, policy, &count);
644
645 ret = 1;
646 } else if (curTable[0]) {
647 unsigned int a;
648 char *pcnt = NULL;
649 char *bcnt = NULL;
650 char *parsestart = buffer;
651 char *chain = NULL;
652
653 tokenize_rule_counters(&parsestart, &pcnt, &bcnt, line);
654 add_param_to_argv(&cur_rule, parsestart, line);
655
656 DEBUGP("calling do_command4(%u, argv, &%s, handle):\n",
657 cur_rule.argc, curTable);
658 debug_print_argv(&cur_rule);
659
660 for (a = 1; a < cur_rule.argc; a++) {
661 if (strcmp(cur_rule.argv[a - 1], "-A"))
662 continue;
663 chain = cur_rule.argv[a];
664 break;
665 }
666 if (!chain) {
667 fprintf(stderr, "%s: line %u failed - no chain found\n",
668 prog_name, line);
669 exit(1);
670 }
671 needChain(chain);// Should we explicitly look for -A
672 do_rule(pcnt, bcnt, cur_rule.argc, cur_rule.argv,
673 cur_rule.argvattr, last_rule.argc, last_rule.argv);
674
675 save_argv(&last_rule, &cur_rule);
676 ret = 1;
677 }
678 if (!ret) {
679 fprintf(stderr, "%s: line %u failed\n",
680 prog_name, line);
681 exit(1);
682 }
683 }
684 if (curTable[0]) {
685 fprintf(stderr, "%s: COMMIT expected at line %u\n",
686 prog_name, line + 1);
687 exit(1);
688 }
689
690 fclose(in);
691 printf("</iptables-rules>\n");
692 free_argv(&last_rule);
693
694 return 0;
695 }
696