Added tweaks to ipfw command for Mac OS X
[fwknop.git] / server / fw_util_ipfw.c
1 /*
2  *****************************************************************************
3  *
4  * File:    fw_util_ipfw.c
5  *
6  * Author:  Damien S. Stuart
7  *
8  * Purpose: Fwknop routines for managing ipfw 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 #include "fwknopd_common.h"
32
33 #if FIREWALL_IPFW
34
35 #include "fw_util.h"
36 #include "utils.h"
37 #include "log_msg.h"
38 #include "extcmd.h"
39 #include "access.h"
40
41 static struct fw_config fwc;
42 static char   cmd_buf[CMD_BUFSIZE];
43 static char   err_buf[CMD_BUFSIZE];
44 static char   cmd_out[STANDARD_CMD_OUT_BUFSIZE];
45
46 static unsigned short
47 get_next_rule_num(void)
48 {
49     unsigned short i;
50
51     for(i=0; i < fwc.max_rules; i++)
52     {
53         if(fwc.rule_map[i] == RULE_FREE)
54             return(fwc.start_rule_num + i);
55     }
56
57     return(0);
58 }
59
60 static void
61 zero_cmd_buffers(void)
62 {
63     memset(cmd_buf, 0x0, CMD_BUFSIZE);
64     memset(err_buf, 0x0, CMD_BUFSIZE);
65     memset(cmd_out, 0x0, STANDARD_CMD_OUT_BUFSIZE);
66 }
67
68 static int
69 ipfw_set_exists(const fko_srv_options_t *opts,
70     const char *fw_command, const unsigned short set_num)
71 {
72     int res = 0;
73
74     zero_cmd_buffers();
75
76     snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPFW_LIST_SET_RULES_ARGS,
77         fw_command,
78         set_num
79     );
80
81     res = run_extcmd(cmd_buf, cmd_out, STANDARD_CMD_OUT_BUFSIZE, 0);
82
83     if (opts->verbose)
84         log_msg(LOG_INFO, "ipfw_set_exists() CMD: '%s' (res: %d)",
85             cmd_buf, res);
86
87     if(!EXTCMD_IS_SUCCESS(res))
88         return(0);
89
90     if(cmd_out[0] == '\0')
91         return(0);
92
93     return(1);
94 }
95
96 /* Print all firewall rules currently instantiated by the running fwknopd
97  * daemon to stdout.
98 */
99 int
100 fw_dump_rules(const fko_srv_options_t *opts)
101 {
102     int     res, got_err = 0;
103
104     if (opts->fw_list_all)
105     {
106         fprintf(stdout, "Listing all ipfw rules...\n");
107         fflush(stdout);
108
109         zero_cmd_buffers();
110
111         /* Create the list command for all rules
112         */
113         snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPFW_LIST_ALL_RULES_ARGS,
114             opts->fw_config->fw_command
115         );
116
117         res = system(cmd_buf);
118
119         if (opts->verbose)
120             log_msg(LOG_INFO, "fw_dump_rules() CMD: '%s' (res: %d)",
121                 cmd_buf, res);
122
123         /* Expect full success on this */
124         if(! EXTCMD_IS_SUCCESS(res))
125         {
126             log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf);
127             got_err++;
128         }
129     }
130     else
131     {
132         fprintf(stdout, "Listing fwknopd ipfw rules...\n");
133         fflush(stdout);
134
135         zero_cmd_buffers();
136
137         /* Create the list command for active rules
138         */
139         snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPFW_LIST_RULES_ARGS,
140             opts->fw_config->fw_command,
141             opts->fw_config->active_set_num
142         );
143
144         printf("\nActive Rules:\n");
145         res = system(cmd_buf);
146
147         if (opts->verbose)
148             log_msg(LOG_INFO, "fw_dump_rules() CMD: '%s' (res: %d)",
149                 cmd_buf, res);
150
151         /* Expect full success on this */
152         if(! EXTCMD_IS_SUCCESS(res))
153         {
154             log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf);
155             got_err++;
156         }
157
158         /* Create the list command for expired rules
159         */
160         snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPFW_LIST_RULES_ARGS,
161             opts->fw_config->fw_command,
162             opts->fw_config->expire_set_num
163         );
164
165         printf("\nExpired Rules:\n");
166         res = system(cmd_buf);
167
168         if (opts->verbose)
169             log_msg(LOG_INFO, "fw_dump_rules() CMD: '%s' (res: %d)",
170                 cmd_buf, res);
171
172         /* Expect full success on this */
173         if(! EXTCMD_IS_SUCCESS(res))
174         {
175             log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf);
176             got_err++;
177         }
178     }
179
180     return(got_err);
181 }
182
183 void
184 fw_config_init(fko_srv_options_t *opts)
185 {
186
187     memset(&fwc, 0x0, sizeof(struct fw_config));
188
189     /* Set our firewall exe command path (iptables in most cases).
190     */
191     strlcpy(fwc.fw_command, opts->config[CONF_FIREWALL_EXE], MAX_PATH_LEN);
192
193     fwc.start_rule_num = atoi(opts->config[CONF_IPFW_START_RULE_NUM]);
194     fwc.max_rules      = atoi(opts->config[CONF_IPFW_MAX_RULES]);
195     fwc.active_set_num = atoi(opts->config[CONF_IPFW_ACTIVE_SET_NUM]);
196     fwc.expire_set_num = atoi(opts->config[CONF_IPFW_EXPIRE_SET_NUM]);
197     fwc.purge_interval = atoi(opts->config[CONF_IPFW_EXPIRE_PURGE_INTERVAL]);
198
199     /* Let us find it via our opts struct as well.
200     */
201     opts->fw_config = &fwc;
202
203     return;
204 }
205
206 void
207 fw_initialize(const fko_srv_options_t *opts)
208 {
209     int             res = 0;
210     unsigned short  curr_rule;
211     char           *ndx;
212
213     /* For now, we just call fw_cleanup to start with clean slate.
214     */
215     res = fw_cleanup(opts);
216
217     if(res != 0)
218     {
219         fprintf(stderr, "Fatal: Errors detected during ipfw rules initialization.\n");
220         exit(EXIT_FAILURE);
221     }
222
223     /* Allocate our rule_map array for tracking active (and expired) rules.
224     */
225     fwc.rule_map = calloc(fwc.max_rules, sizeof(char));
226
227     if(fwc.rule_map == NULL)
228     {
229         fprintf(stderr, "Fatal: Memory allocation error in fw_initialize.\n");
230         exit(EXIT_FAILURE);
231     }
232
233     /* Create a check-state rule if necessary.
234     */
235     if(strncasecmp(opts->config[CONF_IPFW_ADD_CHECK_STATE], "Y", 1) == 0)
236     {
237         zero_cmd_buffers();
238
239         snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPFW_ADD_CHECK_STATE_ARGS,
240             fwc.fw_command,
241             fwc.start_rule_num,
242             fwc.active_set_num
243         );
244
245         res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, 0);
246
247         if (opts->verbose)
248             log_msg(LOG_INFO, "fw_initialize() CMD: '%s' (res: %d, err: %s)",
249                 cmd_buf, res, err_buf);
250
251         if(EXTCMD_IS_SUCCESS(res))
252         {
253             log_msg(LOG_INFO, "Added check-state rule %u to set %u",
254                 fwc.start_rule_num,
255                 fwc.active_set_num
256             );
257
258             fwc.rule_map[0] = RULE_ACTIVE;
259         }
260         else
261             log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); 
262     }
263
264     /* Make sure our expire set is disabled.
265     */
266     zero_cmd_buffers();
267
268     snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPFW_DISABLE_SET_ARGS,
269         fwc.fw_command,
270         fwc.expire_set_num
271     );
272
273     res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, 0);
274
275     if (opts->verbose)
276         log_msg(LOG_INFO, "fw_initialize() CMD: '%s' (res: %d, err: %s)",
277             cmd_buf, res, err_buf);
278
279     if(EXTCMD_IS_SUCCESS(res))
280         log_msg(LOG_INFO, "Set ipfw set %u to disabled.",
281             fwc.expire_set_num);
282     else
283         log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf);
284
285     /* Now read the expire set in case there are existing
286      * rules to track.
287     */
288     zero_cmd_buffers();
289
290     snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPFW_LIST_EXP_SET_RULES_ARGS,
291         opts->fw_config->fw_command,
292         fwc.expire_set_num
293     );
294
295     res = run_extcmd(cmd_buf, cmd_out, STANDARD_CMD_OUT_BUFSIZE, 0);
296
297     if (opts->verbose)
298         log_msg(LOG_INFO, "fw_initialize() CMD: '%s' (res: %d)",
299             cmd_buf, res);
300
301     if(!EXTCMD_IS_SUCCESS(res))
302     {
303         log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, cmd_out);
304         return;
305     }
306
307     if(opts->verbose > 1)
308         log_msg(LOG_INFO, "RULES LIST: %s", cmd_out);
309
310     /* Find the first "# DISABLED" string (if any).
311     */
312     ndx = strstr(cmd_out, "# DISABLED ");
313
314     /* Assume no disabled rules if we did not see the string.
315     */
316     if(ndx == NULL)
317         return;
318
319     /* Otherwise we walk each line to pull the rule number and
320      * set the appropriate rule map entries.
321     */
322     while(ndx != NULL)
323     {
324         /* Skip over the DISABLED string to the rule num.
325         */
326         ndx += 11;
327
328         if(isdigit(*ndx))
329         {
330             curr_rule = atoi(ndx);
331
332             if(curr_rule >= fwc.start_rule_num
333               && curr_rule < fwc.start_rule_num + fwc.max_rules)
334             {
335                 fwc.rule_map[curr_rule - fwc.start_rule_num] = RULE_EXPIRED;
336                 fwc.total_rules++;
337             }
338         }
339         else
340             log_msg(LOG_WARNING, "fw_initialize: No rule number found where expected.");
341
342         /* Find the next "# DISABLED" string (if any).
343         */
344         ndx = strstr(ndx, "# DISABLED ");
345     }
346 }
347
348
349 int
350 fw_cleanup(const fko_srv_options_t *opts)
351 {
352     int     res, got_err = 0;
353
354     zero_cmd_buffers();
355
356     if(fwc.active_set_num > 0
357         && ipfw_set_exists(opts, fwc.fw_command, fwc.active_set_num))
358     {
359         /* Create the set delete command for active rules
360         */
361         snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPFW_DEL_RULE_SET_ARGS,
362             fwc.fw_command,
363             fwc.active_set_num
364         );
365
366         res = system(cmd_buf);
367
368         if (opts->verbose)
369             log_msg(LOG_INFO, "fw_cleanup() CMD: '%s' (res: %d)",
370                 cmd_buf, res);
371
372         /* Expect full success on this */
373         if(! EXTCMD_IS_SUCCESS(res))
374         {
375             log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); 
376             got_err++;
377         }
378     }
379
380 /* --DSS Keep expired rule list so any existing established
381          are not lost */
382 #if 0
383
384     if(fwc.expire_set_num > 0)
385     {
386         /* Create the set delete command for expired rules
387         */
388         snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPFW_DEL_RULE_SET_ARGS,
389             fwc.fw_command,
390             fwc.expire_set_num
391         );
392    
393         //printf("CMD: '%s'\n", cmd_buf);
394         res = system(cmd_buf);
395
396         /* Expect full success on this */
397         if(! EXTCMD_IS_SUCCESS(res))
398         {
399             log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); 
400             got_err++;
401         }
402     }
403 #endif
404
405     /* Free the rule map.
406     */
407     if(fwc.rule_map != NULL)
408         free(fwc.rule_map);
409
410     return(got_err);
411 }
412
413 /****************************************************************************/
414
415 /* Rule Processing - Create an access request...
416 */
417 int
418 process_spa_request(const fko_srv_options_t *opts, const acc_stanza_t *acc, spa_data_t *spadat)
419 {
420     unsigned short   rule_num;
421
422     acc_port_list_t *port_list = NULL;
423     acc_port_list_t *ple;
424
425     unsigned int    fst_proto;
426     unsigned int    fst_port;
427
428     int             res = 0;
429     time_t          now;
430     unsigned int    exp_ts;
431
432     /* Parse and expand our access message.
433     */
434     expand_acc_port_list(&port_list, spadat->spa_message_remain);
435
436     /* Start at the top of the proto-port list...
437     */
438     ple = port_list;
439
440     /* Remember the first proto/port combo in case we need them
441      * for NAT access requests.
442     */
443     fst_proto = ple->proto;
444     fst_port  = ple->port;
445
446     /* Set our expire time value.
447     */
448     time(&now);
449     exp_ts = now + spadat->fw_access_timeout;
450
451     /* For straight access requests, we currently support multiple proto/port
452      * request.
453     */
454     if(spadat->message_type == FKO_ACCESS_MSG
455       || spadat->message_type == FKO_CLIENT_TIMEOUT_ACCESS_MSG)
456     {
457         /* Pull the next available rule number.
458         */
459         rule_num = get_next_rule_num();
460
461         /* If rule_num comes back as 0, we aready have the maximum number
462          * of active rules allowed so we reject and bail here.
463         */
464         if(rule_num == 0)
465         {
466             log_msg(LOG_WARNING, "Access request rejected: Maximum allowed number of rules has been reached.");
467             return(-1);
468         }
469
470         /* Create an access command for each proto/port for the source ip.
471         */
472         while(ple != NULL)
473         {
474             zero_cmd_buffers();
475
476             snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPFW_ADD_RULE_ARGS,
477                 fwc.fw_command,
478                 rule_num,
479                 fwc.active_set_num,
480                 ple->proto,
481                 spadat->use_src_ip,
482                 ple->port,
483                 exp_ts
484             );
485
486             res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, 0);
487
488             if (opts->verbose)
489                 log_msg(LOG_INFO, "process_spa_request() CMD: '%s' (res: %d, err: %s)",
490                     cmd_buf, res, err_buf);
491
492             if(EXTCMD_IS_SUCCESS(res))
493             {
494                 log_msg(LOG_INFO, "Added Rule %u for %s, %s expires at %u",
495                     rule_num,
496                     spadat->use_src_ip,
497                     spadat->spa_message_remain, exp_ts
498                 );
499
500                 fwc.rule_map[rule_num - fwc.start_rule_num] = RULE_ACTIVE;
501
502                 fwc.active_rules++;
503                 fwc.total_rules++;
504
505                 /* Reset the next expected expire time for this chain if it
506                  * is warranted.
507                 */
508                 if(fwc.next_expire < now || exp_ts < fwc.next_expire)
509                     fwc.next_expire = exp_ts;
510             }
511             else
512                 log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); 
513
514             ple = ple->next;
515         }
516
517     }
518     else
519     {
520         /* No other SPA request modes are supported yet.
521         */
522         if(spadat->message_type == FKO_LOCAL_NAT_ACCESS_MSG
523           || spadat->message_type == FKO_CLIENT_TIMEOUT_LOCAL_NAT_ACCESS_MSG)
524         {
525             log_msg(LOG_WARNING, "Local NAT requests are not currently supported.");
526         }
527         else if(spadat->message_type == FKO_NAT_ACCESS_MSG
528           || spadat->message_type == FKO_CLIENT_TIMEOUT_NAT_ACCESS_MSG)
529         {
530             log_msg(LOG_WARNING, "Forwarding/NAT requests are not currently supported.");
531         }
532
533         return(-1);
534     }
535
536     return(res);
537 }
538
539 /* Iterate over the current rule set and purge expired
540  * firewall rules.
541 */
542 void
543 check_firewall_rules(const fko_srv_options_t *opts)
544 {
545     char            exp_str[12];
546     char            rule_num_str[6];
547     char           *ndx, *rn_start, *rn_end, *tmp_mark;
548
549     int             i=0, res=0;
550     time_t          now, rule_exp, min_exp = 0;
551     unsigned short  curr_rule;
552
553     /* Just in case we somehow lose track and fall out-of-whack.
554     */
555     if(fwc.active_rules > fwc.max_rules)
556         fwc.active_rules = 0;
557
558     /* If there are no active rules or we have not yet
559      * reached our expected next expire time, continue.
560     */
561     if(fwc.active_rules == 0)
562         return;
563
564     time(&now);
565
566     if (fwc.next_expire > now)
567         return;
568
569     zero_cmd_buffers();
570
571     /* There should be a rule to delete.  Get the current list of
572      * rules for this chain and delete the ones that are expired.
573     */
574     snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPFW_LIST_SET_RULES_ARGS,
575         opts->fw_config->fw_command,
576         fwc.active_set_num
577     );
578
579     res = run_extcmd(cmd_buf, cmd_out, STANDARD_CMD_OUT_BUFSIZE, 0);
580
581     if (opts->verbose)
582         log_msg(LOG_INFO, "check_firewall_rules() CMD: '%s' (res: %d)",
583             cmd_buf, res);
584
585     if(!EXTCMD_IS_SUCCESS(res))
586     {
587         log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, cmd_out);
588         return;
589     }
590
591     if(opts->verbose > 1)
592         log_msg(LOG_INFO, "RULES LIST: %s", cmd_out);
593
594     /* Find the first _exp_ string (if any).
595     */
596     ndx = strstr(cmd_out, EXPIRE_COMMENT_PREFIX);
597
598     if(ndx == NULL)
599     {
600         /* we did not find an expected rule.
601         */
602         log_msg(LOG_ERR,
603             "Did not find expire comment in rules list %i.\n", i);
604
605         if (fwc.active_rules > 0)
606             fwc.active_rules--;
607
608         return;
609     }
610
611     /* Walk the list and process rules as needed.
612     */
613     while (ndx != NULL) {
614         /* Jump forward and extract the timestamp
615         */
616         ndx += strlen(EXPIRE_COMMENT_PREFIX);
617
618         /* remember this spot for when we look for the next
619          * rule.
620         */
621         tmp_mark = ndx;
622
623         strlcpy(exp_str, ndx, 11);
624         rule_exp = (time_t)atoll(exp_str);
625
626         if(rule_exp <= now)
627         {
628             /* Backtrack and get the rule number and delete it.
629             */
630             rn_start = ndx;
631             while(--rn_start > cmd_out)
632             {
633                 if(*rn_start == '\n')
634                     break;
635             }
636
637             if(*rn_start == '\n')
638             {
639                 rn_start++;
640             }
641             else if(rn_start > cmd_out)
642             {
643                 /* This should not happen. But if it does, complain,
644                  * decrement the active rule value, and go on.
645                 */
646                 log_msg(LOG_ERR,
647                     "Rule parse error while finding rule line start.");
648
649                 if (fwc.active_rules > 0)
650                     fwc.active_rules--;
651
652                 break;
653             }
654
655             rn_end = strchr(rn_start, ' ');
656
657             if(rn_end == NULL)
658             {
659                 /* This should not happen. But if it does, complain,
660                  * decrement the active rule value, and go on.
661                 */
662                 log_msg(LOG_ERR,
663                     "Rule parse error while finding rule number.");
664
665                 if (fwc.active_rules > 0)
666                     fwc.active_rules--;
667
668                 break;
669             }
670
671             strlcpy(rule_num_str, rn_start, (rn_end - rn_start)+1);
672
673             curr_rule = atoi(rule_num_str);
674
675             zero_cmd_buffers();
676
677             /* Move the rule to the expired rules set.
678             */
679             snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPFW_MOVE_RULE_ARGS,
680                 opts->fw_config->fw_command,
681                 curr_rule,
682                 fwc.expire_set_num
683             );
684
685             res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, 0);
686
687             if (opts->verbose)
688                 log_msg(LOG_INFO, "check_firewall_rules() CMD: '%s' (res: %d, err: %s)",
689                     cmd_buf, res, err_buf);
690
691             if(EXTCMD_IS_SUCCESS(res))
692             {
693                 log_msg(LOG_INFO, "Moved rule %s with expire time of %u to set %u.",
694                     rule_num_str, rule_exp, fwc.expire_set_num
695                 );
696
697                 if (fwc.active_rules > 0)
698                     fwc.active_rules--;
699
700                 fwc.rule_map[curr_rule - fwc.start_rule_num] = RULE_EXPIRED;
701             }
702             else
703                 log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf);
704         }
705         else
706         {
707             /* Track the minimum future rule expire time.
708             */
709             if(rule_exp > now)
710                 min_exp = (min_exp < rule_exp) ? min_exp : rule_exp;
711         }
712
713         /* Push our tracking index forward beyond (just processed) _exp_
714          * string so we can continue to the next rule in the list.
715         */
716         ndx = strstr(tmp_mark, EXPIRE_COMMENT_PREFIX);
717     }
718
719     /* Set the next pending expire time accordingly. 0 if there are no
720      * more rules, or whatever the next expected (min_exp) time will be.
721     */
722     if(fwc.active_rules < 1)
723         fwc.next_expire = 0;
724     else if(min_exp)
725         fwc.next_expire = min_exp;
726 }
727
728 /* Iterate over the expired rule set and purge those that no longer have
729  * corresponding dynamic rules.
730 */
731 void
732 ipfw_purge_expired_rules(const fko_srv_options_t *opts)
733 {
734     char           *ndx, *co_end;
735
736     int             i, res;
737
738     unsigned short  curr_rule;
739
740     /* First, we get the current active dynamic rules for the expired rule
741      * set. Then we compare it to the expired rules in the rule_map. Any
742      * rules in the map that do not have a dynamic rule, can be deleted.
743     */
744     zero_cmd_buffers();
745
746     snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPFW_LIST_SET_DYN_RULES_ARGS,
747         opts->fw_config->fw_command,
748         fwc.expire_set_num
749     );
750
751     res = run_extcmd(cmd_buf, cmd_out, STANDARD_CMD_OUT_BUFSIZE, 0);
752
753     if (opts->verbose)
754         log_msg(LOG_INFO, "ipfw_purge_expired_rules() CMD: '%s' (res: %d)",
755             cmd_buf, res);
756
757     if(!EXTCMD_IS_SUCCESS(res))
758     {
759         log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, cmd_out);
760         return;
761     }
762
763     /* We may not have any dynamic rules at all - someone might not have
764      * initiated a connection (for example)
765     */
766     if (cmd_out[0] != '\0')
767     {
768         co_end = cmd_out + strlen(cmd_out);
769
770         if(opts->verbose > 1)
771             log_msg(LOG_INFO, "EXP RULES LIST: %s", cmd_out);
772
773         /* Find the "## Dynamic rules" string.
774         */
775         ndx = strcasestr(cmd_out, "## Dynamic rules");
776
777         if(ndx == NULL)
778         {
779             log_msg(LOG_ERR,
780                 "Unexpected error: did not find 'Dynamic rules' string in list output."
781             );
782             return;
783         }
784
785         /* Jump to the next newline char.
786         */
787         ndx = strchr(ndx, '\n');
788
789         if(ndx == NULL)
790         {
791             log_msg(LOG_ERR,
792                 "Unexpected error: did not find 'Dynamic rules' line terminating newline."
793             );
794             return;
795         }
796
797         /* Walk the list of dynamic rules (if any).
798         */
799         while(ndx != NULL)
800         {
801             ndx++;
802
803             while(!isdigit(*ndx) && ndx < co_end)
804                 ndx++;
805
806             if(ndx >= co_end)
807                 break;
808
809             /* If we are at a digit, assume it is a rule number, extract it,
810              * and if it falls in the correct range, mark it (so it is not
811              * removed in the next step.
812             */
813             if(isdigit(*ndx))
814             {
815                 curr_rule = atoi(ndx);
816
817                 if(curr_rule >= fwc.start_rule_num
818                   && curr_rule < fwc.start_rule_num + fwc.max_rules)
819                     fwc.rule_map[curr_rule - fwc.start_rule_num] = RULE_TMP_MARKED;
820             }
821
822             ndx = strchr(ndx, '\n');
823         }
824     }
825
826     /* Now, walk the rule map and remove any still marked as expired.
827     */
828     for(i=0; i<fwc.max_rules; i++)
829     {
830         /* If it is TMP_MARKED, set it back to EXPIRED and move on.
831         */
832         if(fwc.rule_map[i] == RULE_TMP_MARKED)
833         {
834             fwc.rule_map[i] = RULE_EXPIRED;
835             continue;
836         }
837
838         /* If it is not expired, move on.
839         */
840         if(fwc.rule_map[i] != RULE_EXPIRED)
841             continue;
842
843         /* This rule is ready to go away.
844         */
845         zero_cmd_buffers();
846
847         curr_rule = fwc.start_rule_num + i;
848
849         snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPFW_DEL_RULE_ARGS,
850             opts->fw_config->fw_command,
851 #ifndef __APPLE__
852             fwc.expire_set_num,
853 #endif
854             curr_rule
855         );
856
857         res = run_extcmd(cmd_buf, cmd_out, STANDARD_CMD_OUT_BUFSIZE, 0);
858
859         if (opts->verbose)
860             log_msg(LOG_INFO, "ipfw_purge_expired_rules() CMD: '%s' (res: %d)",
861                 cmd_buf, res);
862
863         if(!EXTCMD_IS_SUCCESS(res))
864         {
865             log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, cmd_out);
866             continue;
867         }
868
869         log_msg(LOG_INFO, "Purged rule %u from set %u", curr_rule, fwc.expire_set_num);
870
871         fwc.rule_map[curr_rule - fwc.start_rule_num] = RULE_FREE;
872
873         fwc.total_rules--;
874     }
875 }
876
877 #endif /* FIREWALL_IPFW */
878
879 /***EOF***/