/** * \file server/fw_util_firewalld.c * * \brief Fwknop routines for managing firewalld firewall rules. */ /* Fwknop is developed primarily by the people listed in the file 'AUTHORS'. * Copyright (C) 2009-2015 fwknop developers and contributors. For a full * list of contributors, see the file 'CREDITS'. * * License (GNU General Public License): * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA * ***************************************************************************** */ #include "fwknopd_common.h" #ifdef FIREWALL_FIREWALLD #include "fw_util.h" #include "utils.h" #include "log_msg.h" #include "extcmd.h" #include "access.h" static struct fw_config fwc; static char cmd_buf[CMD_BUFSIZE]; static char err_buf[CMD_BUFSIZE]; static char cmd_out[STANDARD_CMD_OUT_BUFSIZE]; /* assume 'firewall-cmd --direct --passthrough ipv4 -C' is offered * (see firewd_chk_support()). */ static int have_firewd_chk_support = 1; static void zero_cmd_buffers(void) { memset(cmd_buf, 0x0, CMD_BUFSIZE); memset(err_buf, 0x0, CMD_BUFSIZE); memset(cmd_out, 0x0, STANDARD_CMD_OUT_BUFSIZE); } static int pid_status = 0; static int rule_exists_no_chk_support(const fko_srv_options_t * const opts, const struct fw_chain * const fwc, const unsigned int proto, const char * const srcip, const char * const dstip, const unsigned int port, const char * const natip, const unsigned int nat_port, const unsigned int exp_ts) { int rule_exists=0; char fw_line_buf[CMD_BUFSIZE] = {0}; char target_search[CMD_BUFSIZE] = {0}; char proto_search[CMD_BUFSIZE] = {0}; char srcip_search[CMD_BUFSIZE] = {0}; char dstip_search[CMD_BUFSIZE] = {0}; char natip_search[CMD_BUFSIZE] = {0}; char port_search[CMD_BUFSIZE] = {0}; char nat_port_search[CMD_BUFSIZE] = {0}; char exp_ts_search[CMD_BUFSIZE] = {0}; char *ndx = NULL; snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " FIREWD_LIST_RULES_ARGS, opts->fw_config->fw_command, fwc->table, fwc->to_chain ); #if CODE_COVERAGE /* If we're maximizing code coverage, then exercise the run_extcmd_write() * function which is normally only used for the PF firewall. This is to * maximize code coverage in conjunction with the test suite, and is never * compiled in for a production release of fwknop. */ if(run_extcmd_write("/bin/grep -v test", "/bin/echo test", &pid_status, opts) == 0) log_msg(LOG_WARNING, "[ignore] Code coverage: Executed command"); #endif if(proto == IPPROTO_TCP) snprintf(proto_search, CMD_BUFSIZE-1, " tcp "); else if(proto == IPPROTO_UDP) snprintf(proto_search, CMD_BUFSIZE-1, " udp "); else if(proto == IPPROTO_ICMP) snprintf(proto_search, CMD_BUFSIZE-1, " icmp "); else snprintf(proto_search, CMD_BUFSIZE-1, " %u ", proto); snprintf(port_search, CMD_BUFSIZE-1, "dpt:%u ", port); snprintf(nat_port_search, CMD_BUFSIZE-1, ":%u", nat_port); snprintf(target_search, CMD_BUFSIZE-1, " %s ", fwc->target); if (srcip != NULL) snprintf(srcip_search, CMD_BUFSIZE-1, " %s ", srcip); if (dstip != NULL) snprintf(dstip_search, CMD_BUFSIZE-1, " %s ", dstip); if (natip != NULL) snprintf(natip_search, CMD_BUFSIZE-1, " to:%s", natip); snprintf(exp_ts_search, CMD_BUFSIZE-1, "%u ", exp_ts); /* search for each of the substrings - the rule expiration time is the * primary search method */ if(search_extcmd_getline(cmd_buf, fw_line_buf, CMD_BUFSIZE, NO_TIMEOUT, exp_ts_search, &pid_status, opts)) { chop_newline(fw_line_buf); /* we have an iptables policy rule that matches the * expiration time, so make sure this rule matches the * other fields too. If not, then it is for different * access requested by a separate SPA packet. */ if(((proto == ANY_PROTO) ? 1 : (strstr(fw_line_buf, proto_search) != NULL)) && ((srcip == NULL) ? 1 : (strstr(fw_line_buf, srcip_search) != NULL)) && ((dstip == NULL) ? 1 : (strstr(fw_line_buf, dstip_search) != NULL)) && ((natip == NULL) ? 1 : (strstr(fw_line_buf, natip_search) != NULL)) && (strstr(fw_line_buf, target_search) != NULL) && ((port == ANY_PORT) ? 1 : (strstr(fw_line_buf, port_search) != NULL))) { rule_exists = 1; } } /* If there is a nat port, we have to qualify it as part * of the 'to::' portion of the rule (at the end) */ if(rule_exists && nat_port != NAT_ANY_PORT) { ndx = strstr(fw_line_buf, " to:"); /* Make sure there isn't a duplicate " to:" string (i.e. if someone * was trying to be tricky with the iptables comment match). */ if(ndx != NULL && (strstr((ndx+strlen(" to:")), " to:") == NULL)) { ndx = strstr((ndx+strlen(" to:")), nat_port_search); if (ndx == NULL) { rule_exists = 0; } else if((*(ndx+strlen(nat_port_search)) != '\0') && (*(ndx+strlen(nat_port_search)) != ' ')) { rule_exists = 0; } } else { rule_exists = 0; } } if(rule_exists) log_msg(LOG_DEBUG, "rule_exists_no_chk_support() %s %u -> %s expires: %u rule already exists", proto_search, port, srcip, exp_ts); else log_msg(LOG_DEBUG, "rule_exists_no_chk_support() %s %u -> %s expires: %u rule does not exist", proto_search, port, srcip, exp_ts); return(rule_exists); } static int rule_exists_chk_support(const fko_srv_options_t * const opts, const char * const chain, const char * const rule) { int rule_exists = 0; int res = 0; zero_cmd_buffers(); snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " FIREWD_CHK_RULE_ARGS, opts->fw_config->fw_command, chain, rule); res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, WANT_STDERR, NO_TIMEOUT, &pid_status, opts); chop_newline(err_buf); log_msg(LOG_DEBUG, "rule_exists_chk_support() CMD: '%s' (res: %d, err: %s)", cmd_buf, res, err_buf); if(strncmp(err_buf, "success", strlen("success")) == 0) { rule_exists = 1; log_msg(LOG_DEBUG, "rule_exists_chk_support() Rule : '%s' in %s already exists", rule, chain); } else { log_msg(LOG_DEBUG, "rule_exists_chk_support() Rule : '%s' in %s does not exist", rule, chain); } return(rule_exists); } static int rule_exists(const fko_srv_options_t * const opts, const struct fw_chain * const fwc, const char * const rule, const unsigned int proto, const char * const srcip, const char * const dstip, const unsigned int port, const char * const nat_ip, const unsigned int nat_port, const unsigned int exp_ts) { int rule_exists = 0; if(have_firewd_chk_support == 1) rule_exists = rule_exists_chk_support(opts, fwc->to_chain, rule); else rule_exists = rule_exists_no_chk_support(opts, fwc, proto, srcip, (opts->fw_config->use_destination ? dstip : NULL), port, nat_ip, nat_port, exp_ts); if(rule_exists == 1) log_msg(LOG_DEBUG, "rule_exists() Rule : '%s' in %s already exists", rule, fwc->to_chain); else log_msg(LOG_DEBUG, "rule_exists() Rule : '%s' in %s does not exist", rule, fwc->to_chain); return(rule_exists); } static void firewd_chk_support(const fko_srv_options_t * const opts) { int res = 1; struct fw_chain *in_chain = &(opts->fw_config->chain[FIREWD_INPUT_ACCESS]); zero_cmd_buffers(); /* Add a harmless rule to the firewalld INPUT chain and see if firewalld * supports '-C' to check for it. Set "have_firewd_chk_support" accordingly, * delete the rule, and return. */ snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " FIREWD_TMP_CHK_RULE_ARGS, opts->fw_config->fw_command, in_chain->table, in_chain->from_chain, 1, /* first rule */ in_chain->target ); res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, WANT_STDERR, NO_TIMEOUT, &pid_status, opts); chop_newline(err_buf); log_msg(LOG_DEBUG, "firewd_chk_support() CMD: '%s' (res: %d, err: %s)", cmd_buf, res, err_buf); zero_cmd_buffers(); /* Now see if '-C' works */ snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " FIREWD_TMP_VERIFY_CHK_ARGS, opts->fw_config->fw_command, in_chain->table, in_chain->from_chain, in_chain->target ); res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, WANT_STDERR, NO_TIMEOUT, &pid_status, opts); chop_newline(err_buf); log_msg(LOG_DEBUG, "firewd_chk_support() CMD: '%s' (res: %d, err: %s)", cmd_buf, res, err_buf); if(strncmp(err_buf, "success", strlen("success")) == 0) { log_msg(LOG_DEBUG, "firewd_chk_support() -C supported"); have_firewd_chk_support = 1; } else { log_msg(LOG_DEBUG, "firewd_chk_support() -C not supported"); have_firewd_chk_support = 0; } /* Delete the tmp rule */ zero_cmd_buffers(); snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " FIREWD_DEL_RULE_ARGS, opts->fw_config->fw_command, in_chain->table, in_chain->from_chain, 1 ); run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, WANT_STDERR, NO_TIMEOUT, &pid_status, opts); return; } static int comment_match_exists(const fko_srv_options_t * const opts) { int res = 1; char *ndx = NULL; struct fw_chain *in_chain = &(opts->fw_config->chain[FIREWD_INPUT_ACCESS]); zero_cmd_buffers(); /* Add a harmless rule to the firewalld INPUT chain that uses the comment * match and make sure it exists. If not, return zero. Otherwise, delete * the rule and return true. */ snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " FIREWD_TMP_COMMENT_ARGS, opts->fw_config->fw_command, in_chain->table, in_chain->from_chain, 1, /* first rule */ in_chain->target ); res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, WANT_STDERR, NO_TIMEOUT, &pid_status, opts); chop_newline(err_buf); if((!EXTCMD_IS_SUCCESS(res)) || (pid_status != 0)) { log_msg(LOG_ERR, "comment_match_exists() Error %i from cmd:'%s': %s", res, cmd_buf, cmd_out); return 0; /* Errored out*/ } log_msg(LOG_DEBUG, "comment_match_exists() CMD: '%s' (res: %d, err: %s)", cmd_buf, res, err_buf); zero_cmd_buffers(); snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " FIREWD_LIST_RULES_ARGS, opts->fw_config->fw_command, in_chain->table, in_chain->from_chain ); res = run_extcmd(cmd_buf, cmd_out, STANDARD_CMD_OUT_BUFSIZE, WANT_STDERR, NO_TIMEOUT, &pid_status, opts); chop_newline(cmd_out); if(!EXTCMD_IS_SUCCESS(res)) log_msg(LOG_ERR, "comment_match_exists() Error %i from cmd:'%s': %s", res, cmd_buf, cmd_out); ndx = strstr(cmd_out, TMP_COMMENT); if(ndx == NULL) res = 0; /* did not find the tmp comment */ else res = 1; if(res == 1) { /* Delete the tmp comment rule */ zero_cmd_buffers(); snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " FIREWD_DEL_RULE_ARGS, opts->fw_config->fw_command, in_chain->table, in_chain->from_chain, 1 ); run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, WANT_STDERR, NO_TIMEOUT, &pid_status, opts); } return res; } static int add_jump_rule(const fko_srv_options_t * const opts, const int chain_num) { int res = 0, rv = 0; zero_cmd_buffers(); snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " FIREWD_ADD_JUMP_RULE_ARGS, fwc.fw_command, fwc.chain[chain_num].table, fwc.chain[chain_num].from_chain, fwc.chain[chain_num].jump_rule_pos, fwc.chain[chain_num].to_chain ); res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, WANT_STDERR, NO_TIMEOUT, &pid_status, opts); log_msg(LOG_DEBUG, "add_jump_rule() CMD: '%s' (res: %d, err: %s)", cmd_buf, res, err_buf); if(EXTCMD_IS_SUCCESS(res)) { log_msg(LOG_INFO, "Added jump rule from chain: %s to chain: %s", fwc.chain[chain_num].from_chain, fwc.chain[chain_num].to_chain); rv = 1; } else log_msg(LOG_ERR, "add_jump_rule() Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); return rv; } static int chain_exists(const fko_srv_options_t * const opts, const int chain_num) { int res = 0, rv = 0; zero_cmd_buffers(); snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " FIREWD_CHAIN_EXISTS_ARGS, fwc.fw_command, fwc.chain[chain_num].table, fwc.chain[chain_num].to_chain ); res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, WANT_STDERR, NO_TIMEOUT, &pid_status, opts); chop_newline(err_buf); log_msg(LOG_DEBUG, "chain_exists() CMD: '%s' (res: %d, err: %s)", cmd_buf, res, err_buf); if(strstr(err_buf, FIREWD_CMD_FAIL_STR) == NULL) { log_msg(LOG_DEBUG, "'%s' table '%s' chain exists", fwc.chain[chain_num].table, fwc.chain[chain_num].to_chain); rv = 1; } else log_msg(LOG_DEBUG, "chain_exists() Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); return rv; } static int jump_rule_exists_chk_support(const fko_srv_options_t * const opts, const int chain_num) { int exists = 0; char rule_buf[CMD_BUFSIZE] = {0}; snprintf(rule_buf, CMD_BUFSIZE-1, FIREWD_CHK_JUMP_RULE_ARGS, fwc.chain[chain_num].table, fwc.chain[chain_num].to_chain ); if(rule_exists_chk_support(opts, fwc.chain[chain_num].from_chain, rule_buf) == 1) { log_msg(LOG_DEBUG, "jump_rule_exists_chk_support() jump rule found"); exists = 1; } else log_msg(LOG_DEBUG, "jump_rule_exists_chk_support() jump rule not found"); return exists; } static int jump_rule_exists_no_chk_support(const fko_srv_options_t * const opts, const int chain_num) { int exists = 0; char chain_search[CMD_BUFSIZE] = {0}; snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " FIREWD_LIST_RULES_ARGS, fwc.fw_command, fwc.chain[chain_num].table, fwc.chain[chain_num].from_chain ); /* include spaces on either side as produced by 'firewalld -L' output */ snprintf(chain_search, CMD_BUFSIZE-1, " %s ", fwc.chain[chain_num].to_chain); if(search_extcmd(cmd_buf, WANT_STDERR, NO_TIMEOUT, chain_search, &pid_status, opts) > 0) exists = 1; if(exists) log_msg(LOG_DEBUG, "jump_rule_exists_no_chk_support() jump rule found"); else log_msg(LOG_DEBUG, "jump_rule_exists_no_chk_support() jump rule not found"); return(exists); } static int jump_rule_exists(const fko_srv_options_t * const opts, const int chain_num) { int exists = 0; if(have_firewd_chk_support == 1) exists = jump_rule_exists_chk_support(opts, chain_num); else exists = jump_rule_exists_no_chk_support(opts, chain_num); return exists; } /* Print all firewall rules currently instantiated by the running fwknopd * daemon to stdout. */ int fw_dump_rules(const fko_srv_options_t * const opts) { int i; int res, got_err = 0; struct fw_chain *ch = opts->fw_config->chain; if (opts->fw_list_all == 1) { fprintf(stdout, "Listing all firewalld rules in applicable tables...\n"); fflush(stdout); for(i=0; i < NUM_FWKNOP_ACCESS_TYPES; i++) { if(fwc.chain[i].target[0] == '\0') continue; zero_cmd_buffers(); /* Create the list command */ snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " FIREWD_LIST_ALL_RULES_ARGS, opts->fw_config->fw_command, ch[i].table ); res = run_extcmd(cmd_buf, NULL, 0, NO_STDERR, NO_TIMEOUT, &pid_status, opts); log_msg(LOG_DEBUG, "fw_dump_rules() CMD: '%s' (res: %d)", cmd_buf, res); /* Expect full success on this */ if(! EXTCMD_IS_SUCCESS(res)) { log_msg(LOG_ERR, "fw_dump_rules() Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); got_err++; } } } else { fprintf(stdout, "Listing rules in fwknopd firewalld chains...\n"); fflush(stdout); for(i=0; i < NUM_FWKNOP_ACCESS_TYPES; i++) { if(fwc.chain[i].target[0] == '\0') continue; zero_cmd_buffers(); /* Create the list command */ snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " FIREWD_LIST_RULES_ARGS, opts->fw_config->fw_command, ch[i].table, ch[i].to_chain ); fprintf(stdout, "\n"); fflush(stdout); res = run_extcmd(cmd_buf, NULL, 0, NO_STDERR, NO_TIMEOUT, &pid_status, opts); log_msg(LOG_DEBUG, "fw_dump_rules() CMD: '%s' (res: %d)", cmd_buf, res); /* Expect full success on this */ if(! EXTCMD_IS_SUCCESS(res)) { log_msg(LOG_ERR, "fw_dump_rules() Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); got_err++; } } } return(got_err); } /* Quietly flush and delete all fwknop custom chains. */ static void delete_all_chains(const fko_srv_options_t * const opts) { int i, res, cmd_ctr = 0; for(i=0; i < NUM_FWKNOP_ACCESS_TYPES; i++) { if(fwc.chain[i].target[0] == '\0') continue; /* First look for a jump rule to this chain and remove it if it * is there. */ cmd_ctr = 0; while(cmd_ctr < CMD_LOOP_TRIES && (jump_rule_exists(opts, i) == 1)) { zero_cmd_buffers(); snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " FIREWD_DEL_JUMP_RULE_ARGS, fwc.fw_command, fwc.chain[i].table, fwc.chain[i].from_chain, fwc.chain[i].to_chain ); res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, WANT_STDERR, NO_TIMEOUT, &pid_status, opts); chop_newline(err_buf); log_msg(LOG_DEBUG, "delete_all_chains() CMD: '%s' (res: %d, err: %s)", cmd_buf, res, err_buf); /* Expect full success on this */ if(! EXTCMD_IS_SUCCESS(res)) log_msg(LOG_ERR, "delete_all_chains() Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); cmd_ctr++; } zero_cmd_buffers(); /* Now flush and remove the chain. */ snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " FIREWD_FLUSH_CHAIN_ARGS, fwc.fw_command, fwc.chain[i].table, fwc.chain[i].to_chain ); res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, WANT_STDERR, NO_TIMEOUT, &pid_status, opts); chop_newline(err_buf); log_msg(LOG_DEBUG, "delete_all_chains() CMD: '%s' (res: %d, err: %s)", cmd_buf, res, err_buf); /* Expect full success on this */ if(! EXTCMD_IS_SUCCESS(res)) log_msg(LOG_ERR, "delete_all_chains() Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); zero_cmd_buffers(); snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " FIREWD_DEL_CHAIN_ARGS, fwc.fw_command, fwc.chain[i].table, fwc.chain[i].to_chain ); res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, WANT_STDERR, NO_TIMEOUT, &pid_status, opts); chop_newline(err_buf); log_msg(LOG_DEBUG, "delete_all_chains() CMD: '%s' (res: %d, err: %s)", cmd_buf, res, err_buf); /* Expect full success on this */ if(! EXTCMD_IS_SUCCESS(res)) log_msg(LOG_ERR, "delete_all_chains() Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); #if USE_LIBNETFILTER_QUEUE if(opts->enable_nfq_capture) { zero_cmd_buffers(); /* Delete the rule to direct traffic to the NFQ chain. */ snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " FIREWD_DEL_RULE_ARGS, fwc.fw_command, opts->config[CONF_NFQ_TABLE], "INPUT", 1 ); res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, WANT_STDERR, NO_TIMEOUT, &pid_status, opts); if (opts->verbose) log_msg(LOG_INFO, "delete_all_chains() CMD: '%s' (res: %d, err: %s)", cmd_buf, res, err_buf); /* Expect full success on this */ if(! EXTCMD_IS_SUCCESS(res)) log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); zero_cmd_buffers(); /* Flush the NFQ chain */ snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " FIREWD_FLUSH_CHAIN_ARGS, fwc.fw_command, opts->config[CONF_NFQ_TABLE], opts->config[CONF_NFQ_CHAIN] ); res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, WANT_STDERR, NO_TIMEOUT, &pid_status, opts); if (opts->verbose) log_msg(LOG_INFO, "delete_all_chains() CMD: '%s' (res: %d, err: %s)", cmd_buf, res, err_buf); /* Expect full success on this */ if(! EXTCMD_IS_SUCCESS(res)) log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); zero_cmd_buffers(); /* Delete the NF_QUEUE chains and rules */ snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " FIREWD_DEL_CHAIN_ARGS, fwc.fw_command, opts->config[CONF_NFQ_TABLE], opts->config[CONF_NFQ_CHAIN] ); res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, WANT_STDERR, NO_TIMEOUT, &pid_status, opts); if (opts->verbose) log_msg(LOG_INFO, "delete_all_chains() CMD: '%s' (res: %d, err: %s)", cmd_buf, res, err_buf); /* Expect full success on this */ if(! EXTCMD_IS_SUCCESS(res)) log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); } #endif } return; } static int create_chain(const fko_srv_options_t * const opts, const int chain_num) { int res = 0, rv = 0; zero_cmd_buffers(); /* Create the custom chain. */ snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " FIREWD_NEW_CHAIN_ARGS, fwc.fw_command, fwc.chain[chain_num].table, fwc.chain[chain_num].to_chain ); res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, WANT_STDERR, NO_TIMEOUT, &pid_status, opts); chop_newline(err_buf); log_msg(LOG_DEBUG, "create_chain() CMD: '%s' (res: %d, err: %s)", cmd_buf, res, err_buf); /* Expect full success on this */ if(EXTCMD_IS_SUCCESS(res)) rv = 1; else log_msg(LOG_ERR, "create_chain() Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); return rv; } static int mk_chain(const fko_srv_options_t * const opts, const int chain_num) { int err = 0; /* Make sure the required chain and jump rule exist */ if(! chain_exists(opts, chain_num)) if(! create_chain(opts, chain_num)) err++; if (! jump_rule_exists(opts, chain_num)) if(! add_jump_rule(opts, chain_num)) err++; return err; } /* Create the fwknop custom chains (at least those that are configured). */ static int create_fw_chains(const fko_srv_options_t * const opts) { int i, got_err = 0; #if USE_LIBNETFILTER_QUEUE int res = 0; #endif for(i=0; i < NUM_FWKNOP_ACCESS_TYPES; i++) { if(fwc.chain[i].target[0] == '\0') continue; got_err += mk_chain(opts, i); } #if USE_LIBNETFILTER_QUEUE if(opts->enable_nfq_capture) { zero_cmd_buffers(); /* Create the NF_QUEUE chains and rules */ snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " FIREWD_NEW_CHAIN_ARGS, fwc.fw_command, opts->config[CONF_NFQ_TABLE], opts->config[CONF_NFQ_CHAIN] ); res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, WANT_STDERR, NO_TIMEOUT, &pid_status, opts); if (opts->verbose) log_msg(LOG_INFO, "create_fw_chains() CMD: '%s' (res: %d, err: %s)", cmd_buf, res, err_buf); /* Expect full success on this */ if(! EXTCMD_IS_SUCCESS(res)) { log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); got_err++; } zero_cmd_buffers(); /* Create the rule to direct traffic to the NFQ chain. */ snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " FIREWD_ADD_JUMP_RULE_ARGS, fwc.fw_command, opts->config[CONF_NFQ_TABLE], "INPUT", 1, opts->config[CONF_NFQ_CHAIN] ); res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, WANT_STDERR, NO_TIMEOUT, &pid_status, opts); if (opts->verbose) log_msg(LOG_INFO, "create_fw_chains() CMD: '%s' (res: %d, err: %s)", cmd_buf, res, err_buf); /* Expect full success on this */ if(! EXTCMD_IS_SUCCESS(res)) { log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); got_err++; } zero_cmd_buffers(); /* Create the rule to direct SPA packets to the queue. * If an interface is specified use the "_WITH_IF" version * of the command. */ if(strlen(opts->config[CONF_NFQ_INTERFACE]) > 0) { snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " FIREWD_NFQ_ADD_ARGS_WITH_IF, fwc.fw_command, opts->config[CONF_NFQ_TABLE], opts->config[CONF_NFQ_CHAIN], opts->config[CONF_NFQ_INTERFACE], opts->config[CONF_NFQ_PORT], opts->config[CONF_NFQ_QUEUE_NUMBER] ); } else { snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " FIREWD_NFQ_ADD_ARGS, fwc.fw_command, opts->config[CONF_NFQ_TABLE], opts->config[CONF_NFQ_CHAIN], opts->config[CONF_NFQ_PORT], opts->config[CONF_NFQ_QUEUE_NUMBER] ); } res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, WANT_STDERR, NO_TIMEOUT, &pid_status, opts); if (opts->verbose) log_msg(LOG_INFO, "create_fw_chains() CMD: '%s' (res: %d, err: %s)", cmd_buf, res, err_buf); /* Expect full success on this */ if(! EXTCMD_IS_SUCCESS(res)) { log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); got_err++; } } #endif return(got_err); } static int set_fw_chain_conf(const int type, const char * const conf_str) { int i, j, is_err; char tbuf[MAX_LINE_LEN] = {0}; const char *ndx = conf_str; char *chain_fields[FW_NUM_CHAIN_FIELDS]; struct fw_chain *chain = &(fwc.chain[type]); if(conf_str == NULL) { log_msg(LOG_ERR, "[*] NULL conf_str"); return 0; } chain->type = type; if(ndx != NULL) chain_fields[0] = tbuf; i = 0; j = 1; while(*ndx != '\0') { if(*ndx != ' ') { if(*ndx == ',') { tbuf[i] = '\0'; chain_fields[j++] = &(tbuf[++i]); } else tbuf[i++] = *ndx; } if(*ndx != '\0' && *ndx != ' ' && *ndx != ',' && *ndx != '_' && isalnum(*ndx) == 0) { log_msg(LOG_ERR, "[*] Custom chain config parse error: " "invalid character '%c' for chain type %i, " "line: %s", *ndx, type, conf_str); return 0; } ndx++; } /* Sanity check - j should be the number of chain fields * (excluding the type). */ if(j != FW_NUM_CHAIN_FIELDS) { log_msg(LOG_ERR, "[*] Custom chain config parse error: " "wrong number of fields for chain type %i, " "line: %s", type, conf_str); return 0; } /* Pull and set Target */ strlcpy(chain->target, chain_fields[0], sizeof(chain->target)); /* Pull and set Table */ strlcpy(chain->table, chain_fields[1], sizeof(chain->table)); /* Pull and set From_chain */ strlcpy(chain->from_chain, chain_fields[2], sizeof(chain->from_chain)); /* Pull and set Jump_rule_position */ chain->jump_rule_pos = strtol_wrapper(chain_fields[3], 0, RCHK_MAX_FIREWD_RULE_NUM, NO_EXIT_UPON_ERR, &is_err); if(is_err != FKO_SUCCESS) { log_msg(LOG_ERR, "[*] invalid jump rule position in Line: %s", conf_str); return 0; } /* Pull and set To_chain */ strlcpy(chain->to_chain, chain_fields[4], sizeof(chain->to_chain)); /* Pull and set to_chain rule position */ chain->rule_pos = strtol_wrapper(chain_fields[5], 0, RCHK_MAX_FIREWD_RULE_NUM, NO_EXIT_UPON_ERR, &is_err); if(is_err != FKO_SUCCESS) { log_msg(LOG_ERR, "[*] invalid to_chain rule position in Line: %s", conf_str); return 0; } return 1; } int fw_config_init(fko_srv_options_t * const opts) { memset(&fwc, 0x0, sizeof(struct fw_config)); /* Set our firewall exe command path (firewall-cmd or iptables in most cases). */ #if FIREWALL_FIREWALLD char cmd_passthru[512]; snprintf(cmd_passthru, sizeof cmd_passthru, "%s %s ", opts->config[CONF_FIREWALL_EXE], FIREWD_CMD_PREFIX); strlcpy(fwc.fw_command, cmd_passthru, sizeof(fwc.fw_command)); #else strlcpy(fwc.fw_command, opts->config[CONF_FIREWALL_EXE], sizeof(fwc.fw_command)); #endif #if HAVE_LIBFIU fiu_return_on("fw_config_init", 0); #endif /* Pull the fwknop chain config info and setup our internal * config struct. The FIREWD_INPUT is the only one that is * required. The rest are optional. */ if(set_fw_chain_conf(FIREWD_INPUT_ACCESS, opts->config[CONF_FIREWD_INPUT_ACCESS]) != 1) return 0; /* The FWKNOP_OUTPUT_ACCESS requires ENABLE_FIREWD_OUTPUT_ACCESS == Y */ if(strncasecmp(opts->config[CONF_ENABLE_FIREWD_OUTPUT], "Y", 1)==0) if(set_fw_chain_conf(FIREWD_OUTPUT_ACCESS, opts->config[CONF_FIREWD_OUTPUT_ACCESS]) != 1) return 0; /* The remaining access chains require ENABLE_FIREWD_FORWARDING * or ENABLE_FIREWD_LOCAL_NAT */ if(strncasecmp(opts->config[CONF_ENABLE_FIREWD_FORWARDING], "Y", 1)==0 || strncasecmp(opts->config[CONF_ENABLE_FIREWD_LOCAL_NAT], "Y", 1)==0) { if(set_fw_chain_conf(FIREWD_FORWARD_ACCESS, opts->config[CONF_FIREWD_FORWARD_ACCESS]) != 1) return 0; if(set_fw_chain_conf(FIREWD_DNAT_ACCESS, opts->config[CONF_FIREWD_DNAT_ACCESS]) != 1) return 0; /* Requires ENABLE_FIREWD_SNAT = Y */ if(strncasecmp(opts->config[CONF_ENABLE_FIREWD_SNAT], "Y", 1)==0) { /* Support both SNAT and MASQUERADE - this will be controlled * via the access.conf configuration for individual rules */ if(set_fw_chain_conf(FIREWD_MASQUERADE_ACCESS, opts->config[CONF_FIREWD_MASQUERADE_ACCESS]) != 1) return 0; if(set_fw_chain_conf(FIREWD_SNAT_ACCESS, opts->config[CONF_FIREWD_SNAT_ACCESS]) != 1) return 0; } } if(strncasecmp(opts->config[CONF_ENABLE_DESTINATION_RULE], "Y", 1)==0) { fwc.use_destination = 1; } /* Let us find it via our opts struct as well. */ opts->fw_config = &fwc; return 1; } int fw_initialize(const fko_srv_options_t * const opts) { int res = 1; /* See if firewalld offers the '-C' argument (older versions don't). If not, * then switch to parsing firewalld -L output to find rules. */ if(opts->firewd_disable_check_support) have_firewd_chk_support = 0; else firewd_chk_support(opts); /* Flush the chains (just in case) so we can start fresh. */ if(strncasecmp(opts->config[CONF_FLUSH_FIREWD_AT_INIT], "Y", 1) == 0) delete_all_chains(opts); /* Now create any configured chains. */ if(create_fw_chains(opts) != 0) { log_msg(LOG_WARNING, "fw_initialize() Warning: Errors detected during fwknop custom chain creation"); res = 0; } /* Make sure that the 'comment' match is available */ if(strncasecmp(opts->config[CONF_ENABLE_FIREWD_COMMENT_CHECK], "Y", 1) == 0) { if(comment_match_exists(opts) == 1) { log_msg(LOG_INFO, "firewalld 'comment' match is available"); } else { log_msg(LOG_WARNING, "Warning: Could not use the 'comment' match"); res = 0; } } return(res); } int fw_cleanup(const fko_srv_options_t * const opts) { if(strncasecmp(opts->config[CONF_FLUSH_FIREWD_AT_EXIT], "N", 1) == 0 && opts->fw_flush == 0) return(0); delete_all_chains(opts); return(0); } static int create_rule(const fko_srv_options_t * const opts, const char * const fw_chain, const char * const fw_rule) { int res = 0; zero_cmd_buffers(); if (strncasecmp(opts->config[CONF_ENABLE_RULE_PREPEND], "Y", 1) == 0) { snprintf(cmd_buf, CMD_BUFSIZE-1, "%s -I %s %s", opts->fw_config->fw_command, fw_chain, fw_rule); } else { snprintf(cmd_buf, CMD_BUFSIZE-1, "%s -A %s %s", opts->fw_config->fw_command, fw_chain, fw_rule); } res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, WANT_STDERR, NO_TIMEOUT, &pid_status, opts); chop_newline(err_buf); log_msg(LOG_DEBUG, "create_rule() CMD: '%s' (res: %d, err: %s)", cmd_buf, res, err_buf); if(EXTCMD_IS_SUCCESS(res)) { log_msg(LOG_DEBUG, "create_rule() Rule: '%s' added to %s", fw_rule, fw_chain); res = 1; } else log_msg(LOG_ERR, "create_rule() Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); return res; } static void firewd_rule(const fko_srv_options_t * const opts, const char * const complete_rule_buf, const char * const fw_rule_macro, const char * const srcip, const char * const dstip, const unsigned int proto, const unsigned int port, const char * const nat_ip, const unsigned int nat_port, struct fw_chain * const chain, const unsigned int exp_ts, const time_t now, const char * const msg, const char * const access_msg) { char rule_buf[CMD_BUFSIZE] = {0}; if(complete_rule_buf != NULL && complete_rule_buf[0] != 0x0) { strlcpy(rule_buf, complete_rule_buf, CMD_BUFSIZE-1); } else { memset(rule_buf, 0, CMD_BUFSIZE); snprintf(rule_buf, CMD_BUFSIZE-1, fw_rule_macro, chain->table, proto, srcip, dstip, port, exp_ts, chain->target ); } /* Check to make sure that the chain and jump rule exists */ mk_chain(opts, chain->type); if(rule_exists(opts, chain, rule_buf, proto, srcip, dstip, port, nat_ip, nat_port, exp_ts) == 0) { if(create_rule(opts, chain->to_chain, rule_buf)) { log_msg(LOG_INFO, "Added %s rule to %s for %s -> %s %s, expires at %u", msg, chain->to_chain, srcip, (dstip == NULL) ? FIREWD_ANY_IP : dstip, access_msg, exp_ts ); chain->active_rules++; /* Reset the next expected expire time for this chain if it * is warranted. */ if(chain->next_expire < now || exp_ts < chain->next_expire) chain->next_expire = exp_ts; } } return; } static void forward_access_rule(const fko_srv_options_t * const opts, const acc_stanza_t * const acc, struct fw_chain * const fwd_chain, const char * const nat_ip, const unsigned int nat_port, const unsigned int fst_proto, const unsigned int fst_port, spa_data_t * const spadat, const unsigned int exp_ts, const time_t now) { char rule_buf[CMD_BUFSIZE] = {0}; log_msg(LOG_DEBUG, "forward_access_rule() forward_all: %d, nat_ip: %s, nat_port: %d", acc->forward_all, nat_ip, nat_port); memset(rule_buf, 0, CMD_BUFSIZE); if(acc->forward_all) { snprintf(rule_buf, CMD_BUFSIZE-1, FIREWD_FWD_ALL_RULE_ARGS, fwd_chain->table, spadat->use_src_ip, exp_ts, fwd_chain->target ); /* Make a global ACCEPT rule for all ports/protocols */ firewd_rule(opts, rule_buf, NULL, spadat->use_src_ip, NULL, ANY_PROTO, ANY_PORT, NULL, NAT_ANY_PORT, fwd_chain, exp_ts, now, "FORWARD ALL", "*/*"); } else { /* Make the FORWARD access rule */ snprintf(rule_buf, CMD_BUFSIZE-1, FIREWD_FWD_RULE_ARGS, fwd_chain->table, fst_proto, spadat->use_src_ip, nat_port, exp_ts, fwd_chain->target ); firewd_rule(opts, rule_buf, NULL, spadat->use_src_ip, NULL, fst_proto, nat_port, NULL, NAT_ANY_PORT, fwd_chain, exp_ts, now, "FORWARD", spadat->spa_message_remain); } return; } static void dnat_rule(const fko_srv_options_t * const opts, const acc_stanza_t * const acc, struct fw_chain * const dnat_chain, const char * const nat_ip, const unsigned int nat_port, const unsigned int fst_proto, const unsigned int fst_port, spa_data_t * const spadat, const unsigned int exp_ts, const time_t now) { char rule_buf[CMD_BUFSIZE] = {0}; log_msg(LOG_DEBUG, "dnat_rule() forward_all: %d, nat_ip: %s, nat_port: %d", acc->forward_all, nat_ip, nat_port); if(acc->forward_all) { memset(rule_buf, 0, CMD_BUFSIZE); snprintf(rule_buf, CMD_BUFSIZE-1, FIREWD_DNAT_ALL_RULE_ARGS, dnat_chain->table, spadat->use_src_ip, (fwc.use_destination ? spadat->pkt_destination_ip : FIREWD_ANY_IP), exp_ts, dnat_chain->target, nat_ip ); /* Make a global DNAT rule for all ports/protocols */ firewd_rule(opts, rule_buf, NULL, spadat->use_src_ip, NULL, ANY_PROTO, ANY_PORT, NULL, NAT_ANY_PORT, dnat_chain, exp_ts, now, "DNAT ALL", "*/*"); } else { memset(rule_buf, 0, CMD_BUFSIZE); snprintf(rule_buf, CMD_BUFSIZE-1, FIREWD_DNAT_RULE_ARGS, dnat_chain->table, fst_proto, spadat->use_src_ip, (fwc.use_destination ? spadat->pkt_destination_ip : FIREWD_ANY_IP), fst_port, exp_ts, dnat_chain->target, nat_ip, nat_port ); firewd_rule(opts, rule_buf, NULL, spadat->use_src_ip, (fwc.use_destination ? spadat->pkt_destination_ip : FIREWD_ANY_IP), fst_proto, fst_port, nat_ip, nat_port, dnat_chain, exp_ts, now, "DNAT", spadat->spa_message_remain); } return; } static void snat_rule(const fko_srv_options_t * const opts, const acc_stanza_t * const acc, const char * const nat_ip, const unsigned int nat_port, const unsigned int fst_proto, const unsigned int fst_port, spa_data_t * const spadat, const unsigned int exp_ts, const time_t now) { char rule_buf[CMD_BUFSIZE] = {0}; char snat_target[SNAT_TARGET_BUFSIZE] = {0}; struct fw_chain *snat_chain = NULL; log_msg(LOG_DEBUG, "snat_rule() forward_all: %d, nat_ip: %s, nat_port: %d, force_snat: %d, force_snat_ip: %s, force_masq: %d", acc->forward_all, nat_ip, nat_port, acc->force_snat, (acc->force_snat_ip == NULL) ? "(NONE)" : acc->force_snat_ip, acc->force_masquerade); if(acc->forward_all) { /* Default to MASQUERADE */ snat_chain = &(opts->fw_config->chain[FIREWD_MASQUERADE_ACCESS]); snprintf(snat_target, SNAT_TARGET_BUFSIZE-1, " "); /* Add SNAT or MASQUERADE rules. */ if(acc->force_snat && acc->force_snat_ip != NULL && is_valid_ipv4_addr(acc->force_snat_ip, strlen(acc->force_snat_ip))) { /* Using static SNAT */ snat_chain = &(opts->fw_config->chain[FIREWD_SNAT_ACCESS]); snprintf(snat_target, SNAT_TARGET_BUFSIZE-1, "--to-source %s", acc->force_snat_ip); } else if((opts->config[CONF_SNAT_TRANSLATE_IP] != NULL) && is_valid_ipv4_addr(opts->config[CONF_SNAT_TRANSLATE_IP], strlen(opts->config[CONF_SNAT_TRANSLATE_IP]))) { /* Using static SNAT */ snat_chain = &(opts->fw_config->chain[FIREWD_SNAT_ACCESS]); snprintf(snat_target, SNAT_TARGET_BUFSIZE-1, "--to-source %s", opts->config[CONF_SNAT_TRANSLATE_IP]); } memset(rule_buf, 0, CMD_BUFSIZE); snprintf(rule_buf, CMD_BUFSIZE-1, FIREWD_SNAT_ALL_RULE_ARGS, snat_chain->table, spadat->use_src_ip, exp_ts, snat_chain->target, snat_target ); firewd_rule(opts, rule_buf, NULL, spadat->use_src_ip, NULL, ANY_PROTO, ANY_PORT, NULL, NAT_ANY_PORT, snat_chain, exp_ts, now, "SNAT ALL", "*/*"); } else { /* Add SNAT or MASQUERADE rules. */ if(acc->force_snat && acc->force_snat_ip != NULL && is_valid_ipv4_addr(acc->force_snat_ip, strlen(acc->force_snat_ip))) { /* Using static SNAT */ snat_chain = &(opts->fw_config->chain[FIREWD_SNAT_ACCESS]); snprintf(snat_target, SNAT_TARGET_BUFSIZE-1, "--to-source %s", acc->force_snat_ip); } else if(acc->force_snat && acc->force_masquerade) { /* Using MASQUERADE */ snat_chain = &(opts->fw_config->chain[FIREWD_MASQUERADE_ACCESS]); snprintf(snat_target, SNAT_TARGET_BUFSIZE-1, "--to-ports %i", fst_port); } else if((opts->config[CONF_SNAT_TRANSLATE_IP] != NULL) && is_valid_ipv4_addr(opts->config[CONF_SNAT_TRANSLATE_IP], strlen(opts->config[CONF_SNAT_TRANSLATE_IP]))) { /* Using static SNAT */ snat_chain = &(opts->fw_config->chain[FIREWD_SNAT_ACCESS]); snprintf(snat_target, SNAT_TARGET_BUFSIZE-1, "--to-source %s", opts->config[CONF_SNAT_TRANSLATE_IP]); } else { /* Using MASQUERADE */ snat_chain = &(opts->fw_config->chain[FIREWD_MASQUERADE_ACCESS]); snprintf(snat_target, SNAT_TARGET_BUFSIZE-1, "--to-ports %i", fst_port); } memset(rule_buf, 0, CMD_BUFSIZE); snprintf(rule_buf, CMD_BUFSIZE-1, FIREWD_SNAT_RULE_ARGS, snat_chain->table, fst_proto, nat_ip, nat_port, exp_ts, snat_chain->target, snat_target ); firewd_rule(opts, rule_buf, NULL, spadat->use_src_ip, NULL, fst_proto, nat_port, nat_ip, nat_port, snat_chain, exp_ts, now, "SNAT", spadat->spa_message_remain); } return; } /****************************************************************************/ /* Rule Processing - Create an access request... */ int process_spa_request(const fko_srv_options_t * const opts, const acc_stanza_t * const acc, spa_data_t * const spadat) { char nat_ip[MAX_IPV4_STR_LEN] = {0}; char nat_dst[MAX_HOSTNAME_LEN] = {0}; unsigned int nat_port = 0; unsigned int fst_proto; unsigned int fst_port; struct fw_chain * const in_chain = &(opts->fw_config->chain[FIREWD_INPUT_ACCESS]); struct fw_chain * const out_chain = &(opts->fw_config->chain[FIREWD_OUTPUT_ACCESS]); struct fw_chain * const fwd_chain = &(opts->fw_config->chain[FIREWD_FORWARD_ACCESS]); struct fw_chain * const dnat_chain = &(opts->fw_config->chain[FIREWD_DNAT_ACCESS]); acc_port_list_t *port_list = NULL; acc_port_list_t *ple = NULL; char *ndx = NULL; int res = 0, is_err; int str_len; time_t now; unsigned int exp_ts; /* Parse and expand our access message. */ if(expand_acc_port_list(&port_list, spadat->spa_message_remain) != 1) { /* technically we would already have exited with an error if there were * any memory allocation errors (see the add_port_list() function), but * for completeness... */ free_acc_port_list(port_list); return res; } /* Start at the top of the proto-port list... */ ple = port_list; /* Remember the first proto/port combo in case we need them * for NAT access requests. */ fst_proto = ple->proto; fst_port = ple->port; /* Set our expire time value. */ time(&now); exp_ts = now + spadat->fw_access_timeout; /* deal with SPA packets that themselves request a NAT operation */ if(spadat->message_type == FKO_LOCAL_NAT_ACCESS_MSG || spadat->message_type == FKO_CLIENT_TIMEOUT_LOCAL_NAT_ACCESS_MSG || spadat->message_type == FKO_NAT_ACCESS_MSG || spadat->message_type == FKO_CLIENT_TIMEOUT_NAT_ACCESS_MSG || acc->force_nat) { if(acc->force_nat) { strlcpy(nat_ip, acc->force_nat_ip, sizeof(nat_ip)); nat_port = acc->force_nat_port; } else { ndx = strchr(spadat->nat_access, ','); str_len = strcspn(spadat->nat_access, ","); if((ndx != NULL) && (str_len <= MAX_HOSTNAME_LEN)) { strlcpy(nat_dst, spadat->nat_access, str_len+1); if(! is_valid_ipv4_addr(nat_dst, str_len)) { if(strncasecmp(opts->config[CONF_ENABLE_NAT_DNS], "Y", 1) == 0) { if (!is_valid_hostname(nat_dst, str_len)) { log_msg(LOG_INFO, "Invalid Hostname in NAT SPA message"); free_acc_port_list(port_list); return res; } if (ipv4_resolve(nat_dst, nat_ip) == 0) { log_msg(LOG_INFO, "Resolved NAT IP in SPA message"); } else { log_msg(LOG_INFO, "Unable to resolve Hostname in NAT SPA message"); free_acc_port_list(port_list); return res; } } else { log_msg(LOG_INFO, "Received Hostname in NAT SPA message, but hostname is disabled."); free_acc_port_list(port_list); return res; } } else { strlcpy(nat_ip, nat_dst, MAX_IPV4_STR_LEN); } nat_port = strtol_wrapper(ndx+1, 0, MAX_PORT, NO_EXIT_UPON_ERR, &is_err); if(is_err != FKO_SUCCESS) { log_msg(LOG_INFO, "Invalid NAT port in SPA message"); free_acc_port_list(port_list); res = is_err; return res; } } else { log_msg(LOG_INFO, "Invalid NAT IP in SPA message"); free_acc_port_list(port_list); return res; } } if(spadat->message_type == FKO_LOCAL_NAT_ACCESS_MSG || spadat->message_type == FKO_CLIENT_TIMEOUT_LOCAL_NAT_ACCESS_MSG) { firewd_rule(opts, NULL, FIREWD_RULE_ARGS, spadat->use_src_ip, (fwc.use_destination ? spadat->pkt_destination_ip : FIREWD_ANY_IP), fst_proto, nat_port, nat_ip, nat_port, in_chain, exp_ts, now, "local NAT", spadat->spa_message_remain); } else if(strlen(fwd_chain->to_chain)) { /* FORWARD access rule */ forward_access_rule(opts, acc, fwd_chain, nat_ip, nat_port, fst_proto, fst_port, spadat, exp_ts, now); } /* DNAT rule */ if(strlen(dnat_chain->to_chain) && !acc->disable_dnat) dnat_rule(opts, acc, dnat_chain, nat_ip, nat_port, fst_proto, fst_port, spadat, exp_ts, now); /* SNAT rule */ if(acc->force_snat || strncasecmp(opts->config[CONF_ENABLE_FIREWD_SNAT], "Y", 1) == 0) snat_rule(opts, acc, nat_ip, nat_port, fst_proto, fst_port, spadat, exp_ts, now); } else /* Non-NAT request - this is the typical case. */ { /* Create an access command for each proto/port for the source ip. */ while(ple != NULL) { firewd_rule(opts, NULL, FIREWD_RULE_ARGS, spadat->use_src_ip, (fwc.use_destination ? spadat->pkt_destination_ip : FIREWD_ANY_IP), ple->proto, ple->port, NULL, NAT_ANY_PORT, in_chain, exp_ts, now, "access", spadat->spa_message_remain); /* We need to make a corresponding OUTPUT rule if out_chain target * is not NULL. */ if(strlen(out_chain->to_chain)) { firewd_rule(opts, NULL, FIREWD_OUT_RULE_ARGS, spadat->use_src_ip, (fwc.use_destination ? spadat->pkt_destination_ip : FIREWD_ANY_IP), ple->proto, ple->port, NULL, NAT_ANY_PORT, out_chain, exp_ts, now, "OUTPUT", spadat->spa_message_remain); } ple = ple->next; } } /* Done with the port list for access rules. */ free_acc_port_list(port_list); return(res); } static void rm_expired_rules(const fko_srv_options_t * const opts, const char * const fw_output_buf, char *ndx, struct fw_chain *ch, int cpos, time_t now) { char exp_str[12] = {0}; char rule_num_str[6] = {0}; char *rn_start, *rn_end, *tmp_mark; int res, is_err, rn_offset=0, rule_num; time_t rule_exp, min_exp = 0; /* walk the list and process rules as needed. */ while (ndx != NULL) { /* Jump forward and extract the timestamp */ ndx += strlen(EXPIRE_COMMENT_PREFIX); /* remember this spot for when we look for the next * rule. */ tmp_mark = ndx; strlcpy(exp_str, ndx, sizeof(exp_str)); if (strchr(exp_str, '*') != NULL) strchr(exp_str, '*')[0] = '\0'; chop_spaces(exp_str); if(!is_digits(exp_str)) { /* go to the next rule if it exists */ ndx = strstr(tmp_mark, EXPIRE_COMMENT_PREFIX); continue; } rule_exp = (time_t)atoll(exp_str); if(rule_exp <= now) { /* Backtrack and get the rule number and delete it. */ rn_start = ndx; while(--rn_start > fw_output_buf) { if(*rn_start == '\n') break; } if(*rn_start != '\n') { /* This should not happen. But if it does, complain, * decrement the active rule value, and go on. */ log_msg(LOG_ERR, "Rule parse error while finding rule line start in chain %i", cpos); if (ch[cpos].active_rules > 0) ch[cpos].active_rules--; break; } rn_start++; rn_end = strchr(rn_start, ' '); if(rn_end == NULL) { /* This should not happen. But if it does, complain, * decrement the active rule value, and go on. */ log_msg(LOG_ERR, "Rule parse error while finding rule number in chain %i", cpos); if (ch[cpos].active_rules > 0) ch[cpos].active_rules--; break; } strlcpy(rule_num_str, rn_start, (rn_end - rn_start)+1); rule_num = strtol_wrapper(rule_num_str, rn_offset, RCHK_MAX_FIREWD_RULE_NUM, NO_EXIT_UPON_ERR, &is_err); if(is_err != FKO_SUCCESS) { log_msg(LOG_ERR, "Rule parse error while finding rule number in chain %i", cpos); if (ch[cpos].active_rules > 0) ch[cpos].active_rules--; break; } zero_cmd_buffers(); snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " FIREWD_DEL_RULE_ARGS, opts->fw_config->fw_command, ch[cpos].table, ch[cpos].to_chain, rule_num - rn_offset /* account for position of previously deleted rule with rn_offset */ ); res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, WANT_STDERR, NO_TIMEOUT, &pid_status, opts); chop_newline(err_buf); log_msg(LOG_DEBUG, "check_firewall_rules() CMD: '%s' (res: %d, err: %s)", cmd_buf, res, err_buf); if(EXTCMD_IS_SUCCESS(res)) { log_msg(LOG_INFO, "Removed rule %s from %s with expire time of %u", rule_num_str, ch[cpos].to_chain, rule_exp ); rn_offset++; if (ch[cpos].active_rules > 0) ch[cpos].active_rules--; } else log_msg(LOG_ERR, "rm_expired_rules() Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); } else { /* Track the minimum future rule expire time. */ min_exp = (min_exp < rule_exp) ? min_exp : rule_exp; } /* Push our tracking index forward beyond (just processed) _exp_ * string so we can continue to the next rule in the list. */ ndx = strstr(tmp_mark, EXPIRE_COMMENT_PREFIX); } /* Set the next pending expire time accordingly. 0 if there are no * more rules, or whatever the next expected (min_exp) time will be. */ if(ch[cpos].active_rules < 1) ch[cpos].next_expire = 0; else if(min_exp) ch[cpos].next_expire = min_exp; return; } /* Iterate over the configure firewall access chains and purge expired * firewall rules. */ void check_firewall_rules(const fko_srv_options_t * const opts, const int chk_rm_all) { char *ndx; char fw_output_buf[STANDARD_CMD_OUT_BUFSIZE] = {0}; int i, res; time_t now; struct fw_chain *ch = opts->fw_config->chain; time(&now); /* Iterate over each chain and look for active rules to delete. */ for(i=0; i < NUM_FWKNOP_ACCESS_TYPES; i++) { /* If there are no active rules or we have not yet * reached our expected next expire time, continue. */ if(!chk_rm_all && (ch[i].active_rules == 0 || ch[i].next_expire > now)) continue; if(ch[i].table[0] == '\0' || ch[i].to_chain[i] == '\0') continue; zero_cmd_buffers(); memset(fw_output_buf, 0x0, STANDARD_CMD_OUT_BUFSIZE); /* Get the current list of rules for this chain and delete * any that have expired. Note that chk_rm_all puts us in * garbage collection mode, and allows any rules that have * been manually added (potentially by a program separate * from fwknopd) to take advantage of fwknopd's timeout * mechanism. */ snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " FIREWD_LIST_RULES_ARGS, opts->fw_config->fw_command, ch[i].table, ch[i].to_chain ); res = run_extcmd(cmd_buf, fw_output_buf, STANDARD_CMD_OUT_BUFSIZE, WANT_STDERR, NO_TIMEOUT, &pid_status, opts); chop_newline(fw_output_buf); log_msg(LOG_DEBUG, "check_firewall_rules() CMD: '%s' (res: %d, fw_output_buf: %s)", cmd_buf, res, fw_output_buf); if(!EXTCMD_IS_SUCCESS(res)) { log_msg(LOG_ERR, "check_firewall_rules() Error %i from cmd:'%s': %s", res, cmd_buf, fw_output_buf); continue; } log_msg(LOG_DEBUG, "RES=%i, CMD_BUF: %s\nRULES LIST: %s", res, cmd_buf, fw_output_buf); ndx = strstr(fw_output_buf, EXPIRE_COMMENT_PREFIX); if(ndx == NULL) { /* we did not find a candidate rule to expire */ log_msg(LOG_DEBUG, "Did not find expire comment in rules list %i", i); if (ch[i].active_rules > 0) ch[i].active_rules--; continue; } rm_expired_rules(opts, fw_output_buf, ndx, ch, i, now); } return; } int validate_firewd_chain_conf(const char * const chain_str) { int j, rv = 1; const char *ndx = chain_str; j = 1; while(*ndx != '\0') { if(*ndx == ',') j++; if(*ndx != '\0' && *ndx != ' ' && *ndx != ',' && *ndx != '_' && isalnum(*ndx) == 0) { rv = 0; break; } ndx++; } /* Sanity check - j should be the number of chain fields * (excluding the type). */ if(j != FW_NUM_CHAIN_FIELDS) rv = 0; return rv; } #endif /* FIREWALL_FIREWALLD */ /***EOF***/