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("&amp;");
79 		else if (*text == '<')
80 			printf("&lt;");
81 		else if (*text == '>')
82 			printf("&gt;");
83 		else if (*text == '"')
84 			printf("&quot;");
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("&quot;");
428 			// if argv[arg] contains a space, enclose in quotes
429 			xmlEncode(argv[arg]);
430 			if (spaces || argvattr[arg])
431 				printf("&quot;");
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