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