2 *****************************************************************************
6 * Author: Damien S. Stuart
8 * Purpose: Fwknop routines for managing ipfw firewall rules.
10 * Copyright 2010 Damien Stuart (dstuart@dstuart.org)
12 * License (GNU Public License):
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.
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.
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
29 *****************************************************************************
31 #include "fwknopd_common.h"
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];
47 get_next_rule_num(void)
51 for(i=0; i < fwc.max_rules; i++)
53 if(fwc.rule_map[i] == RULE_FREE)
54 return(fwc.start_rule_num + i);
61 zero_cmd_buffers(void)
63 memset(cmd_buf, 0x0, CMD_BUFSIZE);
64 memset(err_buf, 0x0, CMD_BUFSIZE);
65 memset(cmd_out, 0x0, STANDARD_CMD_OUT_BUFSIZE);
69 ipfw_set_exists(const fko_srv_options_t *opts,
70 const char *fw_command, const unsigned short set_num)
76 snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPFW_LIST_SET_RULES_ARGS,
81 res = run_extcmd(cmd_buf, cmd_out, STANDARD_CMD_OUT_BUFSIZE, 0);
84 log_msg(LOG_INFO, "ipfw_set_exists() CMD: '%s' (res: %d)",
87 if(!EXTCMD_IS_SUCCESS(res))
90 if(cmd_out[0] == '\0')
96 /* Print all firewall rules currently instantiated by the running fwknopd
100 fw_dump_rules(const fko_srv_options_t *opts)
102 int res, got_err = 0;
104 if (opts->fw_list_all)
106 fprintf(stdout, "Listing all ipfw rules...\n");
111 /* Create the list command for all rules
113 snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPFW_LIST_ALL_RULES_ARGS,
114 opts->fw_config->fw_command
117 res = system(cmd_buf);
120 log_msg(LOG_INFO, "fw_dump_rules() CMD: '%s' (res: %d)",
123 /* Expect full success on this */
124 if(! EXTCMD_IS_SUCCESS(res))
126 log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf);
132 fprintf(stdout, "Listing fwknopd ipfw rules...\n");
137 /* Create the list command for active rules
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
144 printf("\nActive Rules:\n");
145 res = system(cmd_buf);
148 log_msg(LOG_INFO, "fw_dump_rules() CMD: '%s' (res: %d)",
151 /* Expect full success on this */
152 if(! EXTCMD_IS_SUCCESS(res))
154 log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf);
158 /* Create the list command for expired rules
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
165 printf("\nExpired Rules:\n");
166 res = system(cmd_buf);
169 log_msg(LOG_INFO, "fw_dump_rules() CMD: '%s' (res: %d)",
172 /* Expect full success on this */
173 if(! EXTCMD_IS_SUCCESS(res))
175 log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf);
184 fw_config_init(fko_srv_options_t *opts)
187 memset(&fwc, 0x0, sizeof(struct fw_config));
189 /* Set our firewall exe command path (iptables in most cases).
191 strlcpy(fwc.fw_command, opts->config[CONF_FIREWALL_EXE], MAX_PATH_LEN);
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]);
199 /* Let us find it via our opts struct as well.
201 opts->fw_config = &fwc;
207 fw_initialize(const fko_srv_options_t *opts)
210 unsigned short curr_rule;
213 /* For now, we just call fw_cleanup to start with clean slate.
215 res = fw_cleanup(opts);
219 fprintf(stderr, "Fatal: Errors detected during ipfw rules initialization.\n");
223 /* Allocate our rule_map array for tracking active (and expired) rules.
225 fwc.rule_map = calloc(fwc.max_rules, sizeof(char));
227 if(fwc.rule_map == NULL)
229 fprintf(stderr, "Fatal: Memory allocation error in fw_initialize.\n");
233 /* Create a check-state rule if necessary.
235 if(strncasecmp(opts->config[CONF_IPFW_ADD_CHECK_STATE], "Y", 1) == 0)
239 snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPFW_ADD_CHECK_STATE_ARGS,
245 res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, 0);
248 log_msg(LOG_INFO, "fw_initialize() CMD: '%s' (res: %d, err: %s)",
249 cmd_buf, res, err_buf);
251 if(EXTCMD_IS_SUCCESS(res))
253 log_msg(LOG_INFO, "Added check-state rule %u to set %u",
258 fwc.rule_map[0] = RULE_ACTIVE;
261 log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf);
264 /* Make sure our expire set is disabled.
268 snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPFW_DISABLE_SET_ARGS,
273 res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, 0);
276 log_msg(LOG_INFO, "fw_initialize() CMD: '%s' (res: %d, err: %s)",
277 cmd_buf, res, err_buf);
279 if(EXTCMD_IS_SUCCESS(res))
280 log_msg(LOG_INFO, "Set ipfw set %u to disabled.",
283 log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf);
285 /* Now read the expire set in case there are existing
290 snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPFW_LIST_EXP_SET_RULES_ARGS,
291 opts->fw_config->fw_command,
295 res = run_extcmd(cmd_buf, cmd_out, STANDARD_CMD_OUT_BUFSIZE, 0);
298 log_msg(LOG_INFO, "fw_initialize() CMD: '%s' (res: %d)",
301 if(!EXTCMD_IS_SUCCESS(res))
303 log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, cmd_out);
307 if(opts->verbose > 1)
308 log_msg(LOG_INFO, "RULES LIST: %s", cmd_out);
310 /* Find the first "# DISABLED" string (if any).
312 ndx = strstr(cmd_out, "# DISABLED ");
314 /* Assume no disabled rules if we did not see the string.
319 /* Otherwise we walk each line to pull the rule number and
320 * set the appropriate rule map entries.
324 /* Skip over the DISABLED string to the rule num.
330 curr_rule = atoi(ndx);
332 if(curr_rule >= fwc.start_rule_num
333 && curr_rule < fwc.start_rule_num + fwc.max_rules)
335 fwc.rule_map[curr_rule - fwc.start_rule_num] = RULE_EXPIRED;
340 log_msg(LOG_WARNING, "fw_initialize: No rule number found where expected.");
342 /* Find the next "# DISABLED" string (if any).
344 ndx = strstr(ndx, "# DISABLED ");
350 fw_cleanup(const fko_srv_options_t *opts)
352 int res, got_err = 0;
356 if(fwc.active_set_num > 0
357 && ipfw_set_exists(opts, fwc.fw_command, fwc.active_set_num))
359 /* Create the set delete command for active rules
361 snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPFW_DEL_RULE_SET_ARGS,
366 res = system(cmd_buf);
369 log_msg(LOG_INFO, "fw_cleanup() CMD: '%s' (res: %d)",
372 /* Expect full success on this */
373 if(! EXTCMD_IS_SUCCESS(res))
375 log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf);
380 /* --DSS Keep expired rule list so any existing established
384 if(fwc.expire_set_num > 0)
386 /* Create the set delete command for expired rules
388 snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPFW_DEL_RULE_SET_ARGS,
393 //printf("CMD: '%s'\n", cmd_buf);
394 res = system(cmd_buf);
396 /* Expect full success on this */
397 if(! EXTCMD_IS_SUCCESS(res))
399 log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf);
405 /* Free the rule map.
407 if(fwc.rule_map != NULL)
413 /****************************************************************************/
415 /* Rule Processing - Create an access request...
418 process_spa_request(const fko_srv_options_t *opts, const acc_stanza_t *acc, spa_data_t *spadat)
420 unsigned short rule_num;
422 acc_port_list_t *port_list = NULL;
423 acc_port_list_t *ple;
425 unsigned int fst_proto;
426 unsigned int fst_port;
432 /* Parse and expand our access message.
434 expand_acc_port_list(&port_list, spadat->spa_message_remain);
436 /* Start at the top of the proto-port list...
440 /* Remember the first proto/port combo in case we need them
441 * for NAT access requests.
443 fst_proto = ple->proto;
444 fst_port = ple->port;
446 /* Set our expire time value.
449 exp_ts = now + spadat->fw_access_timeout;
451 /* For straight access requests, we currently support multiple proto/port
454 if(spadat->message_type == FKO_ACCESS_MSG
455 || spadat->message_type == FKO_CLIENT_TIMEOUT_ACCESS_MSG)
457 /* Pull the next available rule number.
459 rule_num = get_next_rule_num();
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.
466 log_msg(LOG_WARNING, "Access request rejected: Maximum allowed number of rules has been reached.");
470 /* Create an access command for each proto/port for the source ip.
476 snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPFW_ADD_RULE_ARGS,
486 res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, 0);
489 log_msg(LOG_INFO, "process_spa_request() CMD: '%s' (res: %d, err: %s)",
490 cmd_buf, res, err_buf);
492 if(EXTCMD_IS_SUCCESS(res))
494 log_msg(LOG_INFO, "Added Rule %u for %s, %s expires at %u",
497 spadat->spa_message_remain, exp_ts
500 fwc.rule_map[rule_num - fwc.start_rule_num] = RULE_ACTIVE;
505 /* Reset the next expected expire time for this chain if it
508 if(fwc.next_expire < now || exp_ts < fwc.next_expire)
509 fwc.next_expire = exp_ts;
512 log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf);
520 /* No other SPA request modes are supported yet.
522 if(spadat->message_type == FKO_LOCAL_NAT_ACCESS_MSG
523 || spadat->message_type == FKO_CLIENT_TIMEOUT_LOCAL_NAT_ACCESS_MSG)
525 log_msg(LOG_WARNING, "Local NAT requests are not currently supported.");
527 else if(spadat->message_type == FKO_NAT_ACCESS_MSG
528 || spadat->message_type == FKO_CLIENT_TIMEOUT_NAT_ACCESS_MSG)
530 log_msg(LOG_WARNING, "Forwarding/NAT requests are not currently supported.");
539 /* Iterate over the current rule set and purge expired
543 check_firewall_rules(const fko_srv_options_t *opts)
546 char rule_num_str[6];
547 char *ndx, *rn_start, *rn_end, *tmp_mark;
550 time_t now, rule_exp, min_exp = 0;
551 unsigned short curr_rule;
553 /* Just in case we somehow lose track and fall out-of-whack.
555 if(fwc.active_rules > fwc.max_rules)
556 fwc.active_rules = 0;
558 /* If there are no active rules or we have not yet
559 * reached our expected next expire time, continue.
561 if(fwc.active_rules == 0)
566 if (fwc.next_expire > now)
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.
574 snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPFW_LIST_SET_RULES_ARGS,
575 opts->fw_config->fw_command,
579 res = run_extcmd(cmd_buf, cmd_out, STANDARD_CMD_OUT_BUFSIZE, 0);
582 log_msg(LOG_INFO, "check_firewall_rules() CMD: '%s' (res: %d)",
585 if(!EXTCMD_IS_SUCCESS(res))
587 log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, cmd_out);
591 if(opts->verbose > 1)
592 log_msg(LOG_INFO, "RULES LIST: %s", cmd_out);
594 /* Find the first _exp_ string (if any).
596 ndx = strstr(cmd_out, EXPIRE_COMMENT_PREFIX);
600 /* we did not find an expected rule.
603 "Did not find expire comment in rules list %i.\n", i);
605 if (fwc.active_rules > 0)
611 /* Walk the list and process rules as needed.
613 while (ndx != NULL) {
614 /* Jump forward and extract the timestamp
616 ndx += strlen(EXPIRE_COMMENT_PREFIX);
618 /* remember this spot for when we look for the next
623 strlcpy(exp_str, ndx, 11);
624 rule_exp = (time_t)atoll(exp_str);
628 /* Backtrack and get the rule number and delete it.
631 while(--rn_start > cmd_out)
633 if(*rn_start == '\n')
637 if(*rn_start == '\n')
641 else if(rn_start > cmd_out)
643 /* This should not happen. But if it does, complain,
644 * decrement the active rule value, and go on.
647 "Rule parse error while finding rule line start.");
649 if (fwc.active_rules > 0)
655 rn_end = strchr(rn_start, ' ');
659 /* This should not happen. But if it does, complain,
660 * decrement the active rule value, and go on.
663 "Rule parse error while finding rule number.");
665 if (fwc.active_rules > 0)
671 strlcpy(rule_num_str, rn_start, (rn_end - rn_start)+1);
673 curr_rule = atoi(rule_num_str);
677 /* Move the rule to the expired rules set.
679 snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPFW_MOVE_RULE_ARGS,
680 opts->fw_config->fw_command,
685 res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, 0);
688 log_msg(LOG_INFO, "check_firewall_rules() CMD: '%s' (res: %d, err: %s)",
689 cmd_buf, res, err_buf);
691 if(EXTCMD_IS_SUCCESS(res))
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
697 if (fwc.active_rules > 0)
700 fwc.rule_map[curr_rule - fwc.start_rule_num] = RULE_EXPIRED;
703 log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf);
707 /* Track the minimum future rule expire time.
710 min_exp = (min_exp < rule_exp) ? min_exp : rule_exp;
713 /* Push our tracking index forward beyond (just processed) _exp_
714 * string so we can continue to the next rule in the list.
716 ndx = strstr(tmp_mark, EXPIRE_COMMENT_PREFIX);
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.
722 if(fwc.active_rules < 1)
725 fwc.next_expire = min_exp;
728 /* Iterate over the expired rule set and purge those that no longer have
729 * corresponding dynamic rules.
732 ipfw_purge_expired_rules(const fko_srv_options_t *opts)
738 unsigned short curr_rule;
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.
746 snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPFW_LIST_SET_DYN_RULES_ARGS,
747 opts->fw_config->fw_command,
751 res = run_extcmd(cmd_buf, cmd_out, STANDARD_CMD_OUT_BUFSIZE, 0);
754 log_msg(LOG_INFO, "ipfw_purge_expired_rules() CMD: '%s' (res: %d)",
757 if(!EXTCMD_IS_SUCCESS(res))
759 log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, cmd_out);
763 /* We may not have any dynamic rules at all - someone might not have
764 * initiated a connection (for example)
766 if (cmd_out[0] != '\0')
768 co_end = cmd_out + strlen(cmd_out);
770 if(opts->verbose > 1)
771 log_msg(LOG_INFO, "EXP RULES LIST: %s", cmd_out);
773 /* Find the "## Dynamic rules" string.
775 ndx = strcasestr(cmd_out, "## Dynamic rules");
780 "Unexpected error: did not find 'Dynamic rules' string in list output."
785 /* Jump to the next newline char.
787 ndx = strchr(ndx, '\n');
792 "Unexpected error: did not find 'Dynamic rules' line terminating newline."
797 /* Walk the list of dynamic rules (if any).
803 while(!isdigit(*ndx) && ndx < co_end)
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.
815 curr_rule = atoi(ndx);
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;
822 ndx = strchr(ndx, '\n');
826 /* Now, walk the rule map and remove any still marked as expired.
828 for(i=0; i<fwc.max_rules; i++)
830 /* If it is TMP_MARKED, set it back to EXPIRED and move on.
832 if(fwc.rule_map[i] == RULE_TMP_MARKED)
834 fwc.rule_map[i] = RULE_EXPIRED;
838 /* If it is not expired, move on.
840 if(fwc.rule_map[i] != RULE_EXPIRED)
843 /* This rule is ready to go away.
847 curr_rule = fwc.start_rule_num + i;
849 snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPFW_DEL_RULE_ARGS,
850 opts->fw_config->fw_command,
855 res = run_extcmd(cmd_buf, cmd_out, STANDARD_CMD_OUT_BUFSIZE, 0);
858 log_msg(LOG_INFO, "ipfw_purge_expired_rules() CMD: '%s' (res: %d)",
861 if(!EXTCMD_IS_SUCCESS(res))
863 log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, cmd_out);
867 log_msg(LOG_INFO, "Purged rule %u from set %u", curr_rule, fwc.expire_set_num);
869 fwc.rule_map[curr_rule - fwc.start_rule_num] = RULE_FREE;
875 #endif /* FIREWALL_IPFW */