(Fernando Arnaboldi, IOActive) Found and fixed several DoS/code execution vulns for...
[fwknop.git] / server / fw_util_iptables.c
1 /*
2  *****************************************************************************
3  *
4  * File:    fw_util_iptables.c
5  *
6  * Author:  Damien S. Stuart
7  *
8  * Purpose: Fwknop routines for managing iptables firewall rules.
9  *
10  * Copyright 2010 Damien Stuart (dstuart@dstuart.org)
11  *
12  *  License (GNU Public License):
13  *
14  *  This program is free software; you can redistribute it and/or
15  *  modify it under the terms of the GNU General Public License
16  *  as published by the Free Software Foundation; either version 2
17  *  of the License, or (at your option) any later version.
18  *
19  *  This program is distributed in the hope that it will be useful,
20  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
21  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  *  GNU General Public License for more details.
23  *
24  *  You should have received a copy of the GNU General Public License
25  *  along with this program; if not, write to the Free Software
26  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
27  *  USA
28  *
29  *****************************************************************************
30 */
31
32 #include "fwknopd_common.h"
33
34 #ifdef FIREWALL_IPTABLES
35
36 #include "fw_util.h"
37 #include "utils.h"
38 #include "log_msg.h"
39 #include "extcmd.h"
40 #include "access.h"
41
42 static struct fw_config fwc;
43 static char   cmd_buf[CMD_BUFSIZE];
44 static char   err_buf[CMD_BUFSIZE];
45 static char   cmd_out[STANDARD_CMD_OUT_BUFSIZE];
46
47 static void
48 zero_cmd_buffers(void)
49 {
50     memset(cmd_buf, 0x0, CMD_BUFSIZE);
51     memset(err_buf, 0x0, CMD_BUFSIZE);
52     memset(cmd_out, 0x0, STANDARD_CMD_OUT_BUFSIZE);
53 }
54
55 static int
56 comment_match_exists(const fko_srv_options_t *opts)
57 {
58     int               res = 1;
59     char             *ndx = NULL;
60     struct fw_chain  *in_chain  = &(opts->fw_config->chain[IPT_INPUT_ACCESS]);
61
62     zero_cmd_buffers();
63
64     /* Add a harmless rule to the iptables OUTPUT chain that uses the comment
65      * match and make sure it exists.  If not, return zero.  Otherwise, delete
66      * the rule and return true.
67     */
68     snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPT_TMP_COMMENT_ARGS,
69         opts->fw_config->fw_command,
70         in_chain->table,
71         in_chain->to_chain,
72         1,   /* first rule */
73         in_chain->target
74     );
75
76     res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, 0);
77
78     if (opts->verbose)
79         log_msg(LOG_INFO, "comment_match_exists() CMD: '%s' (res: %d, err: %s)",
80                 cmd_buf, res, err_buf);
81
82     zero_cmd_buffers();
83
84     snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPT_LIST_RULES_ARGS,
85         opts->fw_config->fw_command,
86         in_chain->table,
87         in_chain->to_chain
88     );
89
90     res = run_extcmd(cmd_buf, cmd_out, STANDARD_CMD_OUT_BUFSIZE, 0);
91
92     if(!EXTCMD_IS_SUCCESS(res))
93         log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, cmd_out);
94
95     ndx = strstr(cmd_out, TMP_COMMENT);
96     if(ndx == NULL)
97         res = 0;  /* did not find the tmp comment */
98     else
99         res = 1;
100
101     if(res == 1)
102     {
103         /* Delete the tmp comment rule
104         */
105         zero_cmd_buffers();
106
107         snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPT_DEL_RULE_ARGS,
108             opts->fw_config->fw_command,
109             in_chain->table,
110             in_chain->to_chain,
111             1
112         );
113         run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, 0);
114     }
115
116     return res;
117 }
118
119 static int
120 add_jump_rule(const fko_srv_options_t *opts, const int chain_num)
121 {
122     int res = 0;
123
124     zero_cmd_buffers();
125
126     snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPT_ADD_JUMP_RULE_ARGS,
127         fwc.fw_command,
128         fwc.chain[chain_num].table,
129         fwc.chain[chain_num].from_chain,
130         fwc.chain[chain_num].jump_rule_pos,
131         fwc.chain[chain_num].to_chain
132     );
133
134     res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, 0);
135
136     if (opts->verbose)
137         log_msg(LOG_INFO, "add_jump_rule() CMD: '%s' (res: %d, err: %s)",
138             cmd_buf, res, err_buf);
139
140     if(EXTCMD_IS_SUCCESS(res))
141         log_msg(LOG_INFO, "Added jump rule from chain: %s to chain: %s",
142             fwc.chain[chain_num].from_chain,
143             fwc.chain[chain_num].to_chain);
144     else
145         log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf);
146
147     return res;
148 }
149
150 static int
151 jump_rule_exists(const int chain_num)
152 {
153     int     num, pos = 0;
154     char    cmd_buf[CMD_BUFSIZE] = {0};
155     char    target[CMD_BUFSIZE] = {0};
156     char    line_buf[CMD_BUFSIZE] = {0};
157     FILE   *ipt;
158
159     sprintf(cmd_buf, "%s " IPT_LIST_RULES_ARGS,
160         fwc.fw_command,
161         fwc.chain[chain_num].table,
162         fwc.chain[chain_num].from_chain
163     );
164
165     ipt = popen(cmd_buf, "r");
166
167     if(ipt == NULL)
168     {
169         log_msg(LOG_ERR,
170             "Got error %i trying to get rules list.\n", errno);
171         return(-1);
172     }
173
174     while((fgets(line_buf, CMD_BUFSIZE-1, ipt)) != NULL)
175     {
176         /* Get past comments and empty lines (note: we only look at the
177          * first character).
178         */
179         if(IS_EMPTY_LINE(line_buf[0]))
180             continue;
181
182         if(sscanf(line_buf, "%i %s ", &num, target) == 2)
183         {
184             if(strcmp(target, fwc.chain[chain_num].to_chain) == 0)
185             {
186                 pos = num;
187                 break;
188             }
189         }
190     }
191
192     pclose(ipt);
193
194     return(pos);
195 }
196
197 /* Print all firewall rules currently instantiated by the running fwknopd
198  * daemon to stdout.
199 */
200 int
201 fw_dump_rules(const fko_srv_options_t *opts)
202 {
203     int     i;
204     int     res, got_err = 0;
205
206     struct fw_chain *ch = opts->fw_config->chain;
207
208     if (opts->fw_list_all == 1)
209     {
210         fprintf(stdout, "Listing all iptables rules in applicable tables...\n");
211         fflush(stdout);
212
213         for(i=0; i<(NUM_FWKNOP_ACCESS_TYPES); i++)
214         {
215
216             if(fwc.chain[i].target[0] == '\0')
217                 continue;
218
219             zero_cmd_buffers();
220
221             /* Create the list command
222             */
223             snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPT_LIST_ALL_RULES_ARGS,
224                 opts->fw_config->fw_command,
225                 ch[i].table
226             );
227
228             res = system(cmd_buf);
229
230             if (opts->verbose)
231                 log_msg(LOG_INFO, "fw_dump_rules() CMD: '%s' (res: %d)",
232                     cmd_buf, res);
233
234             /* Expect full success on this */
235             if(! EXTCMD_IS_SUCCESS(res))
236             {
237                 log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); 
238                 got_err++;
239             }
240         }
241     }
242     else
243     {
244         fprintf(stdout, "Listing rules in fwknopd iptables chains...\n");
245         fflush(stdout);
246
247         for(i=0; i<(NUM_FWKNOP_ACCESS_TYPES); i++)
248         {
249
250             if(fwc.chain[i].target[0] == '\0')
251                 continue;
252
253             zero_cmd_buffers();
254
255             /* Create the list command
256             */
257             snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPT_LIST_RULES_ARGS,
258                 opts->fw_config->fw_command,
259                 ch[i].table,
260                 ch[i].to_chain
261             );
262
263             res = system(cmd_buf);
264
265             if (opts->verbose)
266                 log_msg(LOG_INFO, "fw_dump_rules() CMD: '%s' (res: %d)",
267                     cmd_buf, res);
268
269             /* Expect full success on this */
270             if(! EXTCMD_IS_SUCCESS(res))
271             {
272                 log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf);
273                 got_err++;
274             }
275         }
276     }
277
278     return(got_err);
279 }
280
281 /* Quietly flush and delete all fwknop custom chains.
282 */
283 static void
284 delete_all_chains(const fko_srv_options_t *opts)
285 {
286     int     i, res;
287     int     jump_rule_num;
288
289     for(i=0; i<(NUM_FWKNOP_ACCESS_TYPES); i++)
290     {
291         if(fwc.chain[i].target[0] == '\0')
292             continue;
293
294         /* First look for a jump rule to this chain and remove it if it
295          * is there.
296         */
297         if((jump_rule_num = jump_rule_exists(i)) > 0)
298         {
299             zero_cmd_buffers();
300
301             snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPT_DEL_RULE_ARGS,
302                 fwc.fw_command,
303                 fwc.chain[i].table,
304                 fwc.chain[i].from_chain,
305                 jump_rule_num
306             );
307
308             res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, 0);
309
310             if (opts->verbose)
311                 log_msg(LOG_INFO, "delete_all_chains() CMD: '%s' (res: %d, err: %s)",
312                     cmd_buf, res, err_buf);
313
314             /* Expect full success on this */
315             if(! EXTCMD_IS_SUCCESS(res))
316                 log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf);
317         }
318
319         zero_cmd_buffers();
320
321         /* Now flush and remove the chain.
322         */
323         snprintf(cmd_buf, CMD_BUFSIZE-1,
324             "(%s " IPT_FLUSH_CHAIN_ARGS "; %s " IPT_DEL_CHAIN_ARGS ")", // > /dev/null 2>&1",
325             fwc.fw_command,
326             fwc.chain[i].table,
327             fwc.chain[i].to_chain,
328             fwc.fw_command,
329             fwc.chain[i].table,
330             fwc.chain[i].to_chain
331         );
332
333         res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, 0);
334
335         if (opts->verbose)
336             log_msg(LOG_INFO, "delete_all_chains() CMD: '%s' (res: %d, err: %s)",
337                 cmd_buf, res, err_buf);
338
339         /* Expect full success on this */
340         if(! EXTCMD_IS_SUCCESS(res))
341             log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf);
342     }
343 }
344
345 /* Create the fwknop custom chains (at least those that are configured).
346 */
347 static int
348 create_fw_chains(const fko_srv_options_t *opts)
349 {
350     int     i;
351     int     res, got_err = 0;
352
353     for(i=0; i<(NUM_FWKNOP_ACCESS_TYPES); i++)
354     {
355         if(fwc.chain[i].target[0] == '\0')
356             continue;
357
358         zero_cmd_buffers();
359
360         /* Create the custom chain.
361         */
362         snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPT_NEW_CHAIN_ARGS,
363             fwc.fw_command,
364             fwc.chain[i].table,
365             fwc.chain[i].to_chain
366         );
367
368         res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, 0);
369
370         if (opts->verbose)
371             log_msg(LOG_INFO, "create_fw_chains() CMD: '%s' (res: %d, err: %s)",
372                 cmd_buf, res, err_buf);
373
374         /* Expect full success on this */
375         if(! EXTCMD_IS_SUCCESS(res))
376         {
377             log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf);
378             got_err++;
379         }
380
381         /* Then create the jump rule to that chain.
382         */
383         res = add_jump_rule(opts, i);
384
385         /* Expect full success on this */
386         if(! EXTCMD_IS_SUCCESS(res))
387             got_err++;
388     }
389
390     return(got_err);
391 }
392
393
394 static void
395 set_fw_chain_conf(const int type, char *conf_str)
396 {
397     int i, j;
398     char tbuf[1024]     = {0};
399     char *ndx           = conf_str;
400
401     char *chain_fields[FW_NUM_CHAIN_FIELDS];
402
403     struct fw_chain *chain = &(fwc.chain[type]);
404
405     chain->type = type;
406
407     if(ndx != NULL)
408         chain_fields[0] = tbuf;
409
410     i = 0;
411     j = 1;
412     while(*ndx != '\0')
413     {
414         if(*ndx != ' ')
415         {
416             if(*ndx == ',')
417             {
418                 tbuf[i] = '\0';
419                 chain_fields[j++] = &(tbuf[++i]);
420             }
421             else
422                 tbuf[i++] = *ndx;
423         }
424         ndx++;
425     }
426
427     /* Sanity check - j should be the number of chain fields
428      * (excluding the type).
429     */
430     if(j != FW_NUM_CHAIN_FIELDS)
431     {
432         fprintf(stderr, "[*] Custom Chain config parse error.\n"
433             "Wrong number of fields for chain type %i\n"
434             "Line: %s\n", type, conf_str);
435         exit(EXIT_FAILURE);
436     }
437
438     /* Pull and set Target */
439     strlcpy(chain->target, chain_fields[0], MAX_TARGET_NAME_LEN);
440
441     /* Pull and set Table */
442     strlcpy(chain->table, chain_fields[1], MAX_TABLE_NAME_LEN);
443
444     /* Pull and set From_chain */
445     strlcpy(chain->from_chain, chain_fields[2], MAX_CHAIN_NAME_LEN);
446
447     /* Pull and set Jump_rule_position */
448     chain->jump_rule_pos = atoi(chain_fields[3]);
449
450     /* Pull and set To_chain */
451     strlcpy(chain->to_chain, chain_fields[4], MAX_CHAIN_NAME_LEN);
452
453     /* Pull and set Jump_rule_position */
454     chain->rule_pos = atoi(chain_fields[5]);
455
456 }
457
458 void
459 fw_config_init(fko_srv_options_t *opts)
460 {
461
462     memset(&fwc, 0x0, sizeof(struct fw_config));
463
464     /* Set our firewall exe command path (iptables in most cases).
465     */
466     strlcpy(fwc.fw_command, opts->config[CONF_FIREWALL_EXE], MAX_PATH_LEN);
467
468     /* Pull the fwknop chain config info and setup our internal
469      * config struct.  The IPT_INPUT is the only one that is
470      * required. The rest are optional.
471     */
472     set_fw_chain_conf(IPT_INPUT_ACCESS, opts->config[CONF_IPT_INPUT_ACCESS]);
473
474     /* The FWKNOP_OUTPUT_ACCESS requires ENABLE_IPT_OUTPUT_ACCESS be Y
475     */
476     if(strncasecmp(opts->config[CONF_ENABLE_IPT_OUTPUT], "Y", 1)==0)
477         set_fw_chain_conf(IPT_OUTPUT_ACCESS, opts->config[CONF_IPT_OUTPUT_ACCESS]);
478
479     /* The remaining access chains require ENABLE_IPT_FORWARDING = Y
480     */
481     if(strncasecmp(opts->config[CONF_ENABLE_IPT_FORWARDING], "Y", 1)==0)
482     {
483
484         set_fw_chain_conf(IPT_FORWARD_ACCESS, opts->config[CONF_IPT_FORWARD_ACCESS]);
485         set_fw_chain_conf(IPT_DNAT_ACCESS, opts->config[CONF_IPT_DNAT_ACCESS]);
486
487         /* SNAT (whichever mode) requires ENABLE_IPT_SNAT = Y
488         */
489         if(strncasecmp(opts->config[CONF_ENABLE_IPT_SNAT], "Y", 1)==0)
490         {
491             /* If an SNAT_TRANSLATE_IP is specified use the SNAT_ACCESS mode.
492              * Otherwise, use MASQUERADE_ACCESS.
493              *
494              * XXX: --DSS: Not sure if using the TRANSLATE_IP parameter as
495              *             the determining factor is the best why to handle
496              *             this.
497              *
498             */
499             if(opts->config[CONF_SNAT_TRANSLATE_IP] != NULL
500               && strncasecmp(opts->config[CONF_SNAT_TRANSLATE_IP], "__CHANGEME__", 10)!=0)
501                 set_fw_chain_conf(IPT_SNAT_ACCESS, opts->config[CONF_IPT_SNAT_ACCESS]);
502             else
503                 set_fw_chain_conf(IPT_MASQUERADE_ACCESS, opts->config[CONF_IPT_MASQUERADE_ACCESS]);
504         }
505     }
506
507     /* Let us find it via our opts struct as well.
508     */
509     opts->fw_config = &fwc;
510
511     return;
512 }
513
514 void
515 fw_initialize(const fko_srv_options_t *opts)
516 {
517     int res;
518
519     /* Flush the chains (just in case) so we can start fresh.
520     */
521     if(strncasecmp(opts->config[CONF_FLUSH_IPT_AT_INIT], "Y", 1) == 0)
522         delete_all_chains(opts);
523
524     /* Now create any configured chains.
525     */
526     res = create_fw_chains(opts);
527
528     if(res != 0)
529     {
530         fprintf(stderr, "Warning: Errors detected during fwknop custom chain creation.\n");
531         exit(EXIT_FAILURE);
532     }
533
534     /* Make sure that the 'comment' match is available
535     */
536     if((strncasecmp(opts->config[CONF_ENABLE_IPT_COMMENT_CHECK], "Y", 1) == 0)
537             && (comment_match_exists(opts) != 1))
538     {
539         fprintf(stderr, "Warning: Could not use the 'comment' match.\n");
540         exit(EXIT_FAILURE);
541     }
542 }
543
544 int
545 fw_cleanup(const fko_srv_options_t *opts)
546 {
547     if(strncasecmp(opts->config[CONF_FLUSH_IPT_AT_EXIT], "N", 1) == 0)
548         return(0);
549
550     delete_all_chains(opts);
551     return(0);
552 }
553
554 /****************************************************************************/
555
556 /* Rule Processing - Create an access request...
557 */
558 int
559 process_spa_request(const fko_srv_options_t *opts, const acc_stanza_t *acc, spa_data_t *spadat)
560 {
561     char             nat_ip[MAX_IPV4_STR_LEN] = {0};
562     char             snat_target[SNAT_TARGET_BUFSIZE] = {0};
563     char            *ndx;
564
565     unsigned int     nat_port = 0;
566
567     acc_port_list_t *port_list = NULL;
568     acc_port_list_t *ple;
569
570     unsigned int    fst_proto;
571     unsigned int    fst_port;
572
573     struct fw_chain *in_chain   = &(opts->fw_config->chain[IPT_INPUT_ACCESS]);
574     struct fw_chain *out_chain  = &(opts->fw_config->chain[IPT_OUTPUT_ACCESS]);
575     struct fw_chain *fwd_chain  = &(opts->fw_config->chain[IPT_FORWARD_ACCESS]);
576     struct fw_chain *dnat_chain = &(opts->fw_config->chain[IPT_DNAT_ACCESS]);
577     struct fw_chain *snat_chain; /* We assign this later (if we need to). */
578
579     int             res = 0;
580     time_t          now;
581     unsigned int    exp_ts;
582
583     /* Parse and expand our access message.
584     */
585     if(expand_acc_port_list(&port_list, spadat->spa_message_remain) != 1)
586         return res;
587
588     /* Start at the top of the proto-port list...
589     */
590     ple = port_list;
591
592     /* Remember the first proto/port combo in case we need them
593      * for NAT access requests.
594     */
595     fst_proto = ple->proto;
596     fst_port  = ple->port;
597
598     /* Set our expire time value.
599     */
600     time(&now);
601     exp_ts = now + spadat->fw_access_timeout;
602
603     /* For straight access requests, we currently support multiple proto/port
604      * request.
605     */
606     if((spadat->message_type == FKO_ACCESS_MSG
607       || spadat->message_type == FKO_CLIENT_TIMEOUT_ACCESS_MSG) && !acc->force_nat)
608     {
609
610         /* Check to make sure that the jump rules exist for each
611          * required chain
612         */
613         if(jump_rule_exists(IPT_INPUT_ACCESS) == 0)
614             add_jump_rule(opts, IPT_INPUT_ACCESS);
615
616         if(out_chain->to_chain != NULL && strlen(out_chain->to_chain))
617             if(jump_rule_exists(IPT_OUTPUT_ACCESS) == 0)
618                 add_jump_rule(opts, IPT_OUTPUT_ACCESS);
619
620         /* Create an access command for each proto/port for the source ip.
621         */
622         while(ple != NULL)
623         {
624             zero_cmd_buffers();
625
626             snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPT_ADD_RULE_ARGS,
627                 opts->fw_config->fw_command,
628                 in_chain->table,
629                 in_chain->to_chain,
630                 ple->proto,
631                 spadat->use_src_ip,
632                 ple->port,
633                 exp_ts,
634                 in_chain->target
635             );
636
637             res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, 0);
638
639             if (opts->verbose)
640                 log_msg(LOG_INFO, "process_spa_request() CMD: '%s' (res: %d, err: %s)",
641                     cmd_buf, res, err_buf);
642
643             if(EXTCMD_IS_SUCCESS(res))
644             {
645                 log_msg(LOG_INFO, "Added Rule to %s for %s, %s expires at %u",
646                     in_chain->to_chain, spadat->use_src_ip,
647                     spadat->spa_message_remain, exp_ts
648                 );
649
650                 in_chain->active_rules++;
651
652                 /* Reset the next expected expire time for this chain if it
653                 * is warranted.
654                 */
655                 if(in_chain->next_expire < now || exp_ts < in_chain->next_expire)
656                     in_chain->next_expire = exp_ts;
657             }
658             else
659                 log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf);
660
661             /* If we have to make an corresponding OUTPUT rule if out_chain target
662             * is not NULL.
663             */
664             if(out_chain->to_chain != NULL && strlen(out_chain->to_chain))
665             {
666                 zero_cmd_buffers();
667
668                 snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPT_ADD_OUT_RULE_ARGS,
669                     opts->fw_config->fw_command,
670                     out_chain->table,
671                     out_chain->to_chain,
672                     ple->proto,
673                     spadat->use_src_ip,
674                     ple->port,
675                     exp_ts,
676                     out_chain->target
677                 );
678
679                 res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, 0);
680
681                 if (opts->verbose)
682                     log_msg(LOG_INFO, "process_spa_request() CMD: '%s' (res: %d, err: %s)",
683                         cmd_buf, res, err_buf);
684
685                 if(EXTCMD_IS_SUCCESS(res))
686                 {
687                     log_msg(LOG_INFO, "Added OUTPUT Rule to %s for %s, %s expires at %u",
688                         out_chain->to_chain, spadat->use_src_ip,
689                         spadat->spa_message_remain, exp_ts
690                     );
691
692                     out_chain->active_rules++;
693
694                     /* Reset the next expected expire time for this chain if it
695                     * is warranted.
696                     */
697                     if(out_chain->next_expire < now || exp_ts < out_chain->next_expire)
698                         out_chain->next_expire = exp_ts;
699                 }
700                 else
701                     log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); 
702
703             }
704
705             ple = ple->next;
706         }
707     }
708     /* NAT requests... */
709     else if(spadat->message_type == FKO_LOCAL_NAT_ACCESS_MSG
710       || spadat->message_type == FKO_CLIENT_TIMEOUT_LOCAL_NAT_ACCESS_MSG
711       || spadat->message_type == FKO_NAT_ACCESS_MSG
712       || spadat->message_type == FKO_CLIENT_TIMEOUT_NAT_ACCESS_MSG
713       || acc->force_nat)
714     {
715         /* Parse out the NAT IP and Port components.
716         */
717         if(acc->force_nat)
718         {
719             strlcpy(nat_ip, acc->force_nat_ip, MAX_IPV4_STR_LEN);
720             nat_port = acc->force_nat_port;
721         }
722         else
723         {
724             ndx = strchr(spadat->nat_access, ',');
725             if(ndx != NULL)
726             {
727                 strlcpy(nat_ip, spadat->nat_access, (ndx-spadat->nat_access)+1);
728                 nat_port = atoi(ndx+1);
729             }
730         }
731
732         if(spadat->message_type == FKO_LOCAL_NAT_ACCESS_MSG)
733         {
734             /* Need to add an ACCEPT rule into the INPUT chain
735             */
736             zero_cmd_buffers();
737
738             snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPT_ADD_RULE_ARGS,
739                 opts->fw_config->fw_command,
740                 in_chain->table,
741                 in_chain->to_chain,
742                 fst_proto,
743                 spadat->use_src_ip,
744                 nat_port,
745                 exp_ts,
746                 in_chain->target
747             );
748
749             res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, 0);
750
751             if (opts->verbose)
752                 log_msg(LOG_INFO, "process_spa_request() CMD: '%s' (res: %d, err: %s)",
753                     cmd_buf, res, err_buf);
754
755             if(EXTCMD_IS_SUCCESS(res))
756             {
757                 log_msg(LOG_INFO, "Added Rule to %s for %s, %s expires at %u",
758                     in_chain->to_chain, spadat->use_src_ip,
759                     spadat->spa_message_remain, exp_ts
760                 );
761
762                 in_chain->active_rules++;
763
764                 /* Reset the next expected expire time for this chain if it
765                 * is warranted.
766                 */
767                 if(in_chain->next_expire < now || exp_ts < in_chain->next_expire)
768                     in_chain->next_expire = exp_ts;
769             }
770             else
771                 log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf);
772
773         }
774         else if(fwd_chain->to_chain != NULL && strlen(fwd_chain->to_chain))
775         {
776             /* Make our FORWARD and NAT rules, and make sure the
777              * required jump rule exists
778             */
779             if (jump_rule_exists(IPT_FORWARD_ACCESS) == 0)
780                 add_jump_rule(opts, IPT_FORWARD_ACCESS);
781
782             zero_cmd_buffers();
783
784             snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPT_ADD_FWD_RULE_ARGS,
785                 opts->fw_config->fw_command,
786                 fwd_chain->table,
787                 fwd_chain->to_chain,
788                 fst_proto,
789                 spadat->use_src_ip,
790                 nat_ip,
791                 nat_port,
792                 exp_ts,
793                 fwd_chain->target
794             );
795
796             res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, 0);
797
798             if (opts->verbose)
799                 log_msg(LOG_INFO, "process_spa_request() CMD: '%s' (res: %d, err: %s)",
800                     cmd_buf, res, err_buf);
801
802             if(EXTCMD_IS_SUCCESS(res))
803             {
804                 log_msg(LOG_INFO, "Added FORWARD Rule to %s for %s, %s expires at %u",
805                     fwd_chain->to_chain, spadat->use_src_ip,
806                     spadat->spa_message_remain, exp_ts
807                 );
808
809                 fwd_chain->active_rules++;
810
811                 /* Reset the next expected expire time for this chain if it
812                 * is warranted.
813                 */
814                 if(fwd_chain->next_expire < now || exp_ts < fwd_chain->next_expire)
815                     fwd_chain->next_expire = exp_ts;
816             }
817             else
818                 log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); 
819         }
820
821         if(dnat_chain->to_chain != NULL && strlen(dnat_chain->to_chain))
822         {
823
824             /* Make sure the required jump rule exists
825             */
826             if (jump_rule_exists(IPT_DNAT_ACCESS) == 0)
827                 add_jump_rule(opts, IPT_DNAT_ACCESS);
828
829             zero_cmd_buffers();
830
831             snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPT_ADD_DNAT_RULE_ARGS,
832                 opts->fw_config->fw_command,
833                 dnat_chain->table,
834                 dnat_chain->to_chain,
835                 fst_proto,
836                 spadat->use_src_ip,
837                 fst_port,
838                 exp_ts,
839                 dnat_chain->target,
840                 nat_ip,
841                 nat_port
842             );
843
844             res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, 0);
845
846             if (opts->verbose)
847                 log_msg(LOG_INFO, "process_spa_request() CMD: '%s' (res: %d, err: %s)",
848                     cmd_buf, res, err_buf);
849
850             if(EXTCMD_IS_SUCCESS(res))
851             {
852                 log_msg(LOG_INFO, "Added DNAT Rule to %s for %s, %s expires at %u",
853                     dnat_chain->to_chain, spadat->use_src_ip,
854                     spadat->spa_message_remain, exp_ts
855                 );
856
857                 dnat_chain->active_rules++;
858
859                 /* Reset the next expected expire time for this chain if it
860                 * is warranted.
861                 */
862                 if(dnat_chain->next_expire < now || exp_ts < dnat_chain->next_expire)
863                     dnat_chain->next_expire = exp_ts;
864             }
865             else
866                 log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); 
867         }
868
869         /* If SNAT (or MASQUERADE) is wanted, then we add those rules here as well.
870         */
871         if(strncasecmp(opts->config[CONF_ENABLE_IPT_SNAT], "Y", 1) == 0)
872         {
873             zero_cmd_buffers();
874
875             /* Setup some parameter depending on whether we are using SNAT
876              * or MASQUERADE.
877             */
878             if(strncasecmp(opts->config[CONF_SNAT_TRANSLATE_IP], "__CHANGEME__", 10)!=0)
879             {
880                 /* Using static SNAT */
881                 snat_chain = &(opts->fw_config->chain[IPT_SNAT_ACCESS]);
882                 snprintf(snat_target, SNAT_TARGET_BUFSIZE-1,
883                     "--to-source %s:%i", opts->config[CONF_SNAT_TRANSLATE_IP],
884                     fst_port);
885             }
886             else
887             {
888                 /* Using MASQUERADE */
889                 snat_chain = &(opts->fw_config->chain[IPT_MASQUERADE_ACCESS]);
890                 snprintf(snat_target, SNAT_TARGET_BUFSIZE-1,
891                     "--to-ports %i", fst_port);
892             }
893
894             snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPT_ADD_SNAT_RULE_ARGS,
895                 opts->fw_config->fw_command,
896                 snat_chain->table,
897                 snat_chain->to_chain,
898                 fst_proto,
899                 nat_ip,
900                 nat_port,
901                 exp_ts,
902                 snat_chain->target,
903                 snat_target
904             );
905
906             res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, 0);
907
908             if (opts->verbose)
909                 log_msg(LOG_INFO, "process_spa_request() CMD: '%s' (res: %d, err: %s)",
910                     cmd_buf, res, err_buf);
911
912             if(EXTCMD_IS_SUCCESS(res))
913             {
914                 log_msg(LOG_INFO, "Added Source NAT Rule to %s for %s, %s expires at %u",
915                     snat_chain->to_chain, spadat->use_src_ip,
916                     spadat->spa_message_remain, exp_ts
917                 );
918
919                 snat_chain->active_rules++;
920
921                 /* Reset the next expected expire time for this chain if it
922                 * is warranted.
923                 */
924             if(snat_chain->next_expire < now || exp_ts < snat_chain->next_expire)
925                     snat_chain->next_expire = exp_ts;
926             }
927             else
928                 log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf);
929         }
930     }
931
932     /* Done with the port list for access rules.
933     */
934     free_acc_port_list(port_list);
935
936     return(res);
937 }
938
939 /* Iterate over the configure firewall access chains and purge expired
940  * firewall rules.
941 */
942 void
943 check_firewall_rules(const fko_srv_options_t *opts)
944 {
945     char             exp_str[12];
946     char             rule_num_str[6];
947     char            *ndx, *rn_start, *rn_end, *tmp_mark;
948
949     int             i, res, rn_offset;
950     time_t          now, rule_exp, min_exp = 0;
951
952     struct fw_chain *ch = opts->fw_config->chain;
953
954     time(&now);
955
956     /* Iterate over each chain and look for active rules to delete.
957     */
958     for(i = 0; i < NUM_FWKNOP_ACCESS_TYPES; i++)
959     {
960         /* If there are no active rules or we have not yet
961          * reached our expected next expire time, continue.
962         */
963         if(ch[i].active_rules == 0 || ch[i].next_expire > now)
964             continue;
965
966         zero_cmd_buffers();
967
968         rn_offset = 0;
969
970         /* There should be a rule to delete.  Get the current list of
971          * rules for this chain and delete the ones that are expired.
972         */
973         snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPT_LIST_RULES_ARGS,
974             opts->fw_config->fw_command,
975             ch[i].table,
976             ch[i].to_chain
977         );
978
979         res = run_extcmd(cmd_buf, cmd_out, STANDARD_CMD_OUT_BUFSIZE, 0);
980
981         if (opts->verbose)
982             log_msg(LOG_INFO, "check_firewall_rules() CMD: '%s' (res: %d, err: %s)",
983                 cmd_buf, res, err_buf);
984
985         if(!EXTCMD_IS_SUCCESS(res))
986         {
987             log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, cmd_out);
988             continue;
989         }
990
991         if(opts->verbose > 1)
992             log_msg(LOG_INFO, "RES=%i, CMD_BUF: %s\nRULES LIST: %s", res, cmd_buf, cmd_out);
993
994         ndx = strstr(cmd_out, EXPIRE_COMMENT_PREFIX);
995         if(ndx == NULL)
996         {
997             /* we did not find an expected rule.
998             */
999             log_msg(LOG_ERR,
1000                 "Did not find expire comment in rules list %i.\n", i);
1001
1002             if (ch[i].active_rules > 0)
1003                 ch[i].active_rules--;
1004
1005             continue;
1006         }
1007
1008         /* walk the list and process rules as needed.
1009         */
1010         while (ndx != NULL) {
1011             /* Jump forward and extract the timestamp
1012             */
1013             ndx += strlen(EXPIRE_COMMENT_PREFIX);
1014
1015             /* remember this spot for when we look for the next
1016              * rule.
1017             */
1018             tmp_mark = ndx;
1019
1020             strlcpy(exp_str, ndx, 11);
1021             rule_exp = (time_t)atoll(exp_str);
1022
1023             if(rule_exp <= now)
1024             {
1025                 /* Backtrack and get the rule number and delete it.
1026                 */
1027                 rn_start = ndx;
1028                 while(--rn_start > cmd_out)
1029                 {
1030                     if(*rn_start == '\n')
1031                         break;
1032                 }
1033
1034                 if(*rn_start != '\n')
1035                 {
1036                     /* This should not happen. But if it does, complain,
1037                      * decrement the active rule value, and go on.
1038                     */
1039                     log_msg(LOG_ERR,
1040                         "Rule parse error while finding rule line start in chain %i", i);
1041
1042                     if (ch[i].active_rules > 0)
1043                         ch[i].active_rules--;
1044
1045                     break;
1046                 }
1047                 rn_start++;
1048
1049                 rn_end = strchr(rn_start, ' ');
1050                 if(rn_end == NULL)
1051                 {
1052                     /* This should not happen. But if it does, complain,
1053                      * decrement the active rule value, and go on.
1054                     */
1055                     log_msg(LOG_ERR,
1056                         "Rule parse error while finding rule number in chain %i", i);
1057
1058                     if (ch[i].active_rules > 0)
1059                         ch[i].active_rules--;
1060
1061                     break;
1062                 }
1063
1064                 strlcpy(rule_num_str, rn_start, (rn_end - rn_start)+1);
1065
1066                 zero_cmd_buffers();
1067
1068                 snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPT_DEL_RULE_ARGS,
1069                     opts->fw_config->fw_command,
1070                     ch[i].table,
1071                     ch[i].to_chain,
1072                     atoi(rule_num_str) - rn_offset
1073                 );
1074
1075
1076                 res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, 0);
1077
1078                 if (opts->verbose)
1079                     log_msg(LOG_INFO, "check_firewall_rules() CMD: '%s' (res: %d, err: %s)",
1080                         cmd_buf, res, err_buf);
1081
1082                 if(EXTCMD_IS_SUCCESS(res))
1083                 {
1084                     log_msg(LOG_INFO, "Removed rule %s from %s with expire time of %u.",
1085                         rule_num_str, ch[i].to_chain, rule_exp
1086                     );
1087
1088                     rn_offset++;
1089
1090                     if (ch[i].active_rules > 0)
1091                         ch[i].active_rules--;
1092                 }
1093                 else
1094                     log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); 
1095
1096             }
1097             else
1098             {
1099                 /* Track the minimum future rule expire time.
1100                 */
1101                 if(rule_exp > now)
1102                     min_exp = (min_exp < rule_exp) ? min_exp : rule_exp;
1103             }
1104
1105             /* Push our tracking index forward beyond (just processed) _exp_
1106              * string so we can continue to the next rule in the list.
1107             */
1108             ndx = strstr(tmp_mark, EXPIRE_COMMENT_PREFIX);
1109         }
1110
1111         /* Set the next pending expire time accordingly. 0 if there are no
1112          * more rules, or whatever the next expected (min_exp) time will be.
1113         */
1114         if(ch[i].active_rules < 1)
1115             ch[i].next_expire = 0;
1116         else if(min_exp)
1117             ch[i].next_expire = min_exp;
1118     }
1119 }
1120
1121 #endif /* FIREWALL_IPTABLES */
1122
1123 /***EOF***/