fwknop/server/fw_util_firewalld.c

1951 lines
59 KiB
C

/**
* \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, "%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, " tcp ");
else if(proto == IPPROTO_UDP)
snprintf(proto_search, CMD_BUFSIZE, " udp ");
else if(proto == IPPROTO_ICMP)
snprintf(proto_search, CMD_BUFSIZE, " icmp ");
else
snprintf(proto_search, CMD_BUFSIZE, " %u ", proto);
snprintf(port_search, CMD_BUFSIZE, "dpt:%u ", port);
snprintf(nat_port_search, CMD_BUFSIZE, ":%u", nat_port);
snprintf(target_search, CMD_BUFSIZE, " %s ", fwc->target);
if (srcip != NULL)
snprintf(srcip_search, CMD_BUFSIZE, " %s ", srcip);
if (dstip != NULL)
snprintf(dstip_search, CMD_BUFSIZE, " %s ", dstip);
if (natip != NULL)
snprintf(natip_search, CMD_BUFSIZE, " to:%s", natip);
snprintf(exp_ts_search, CMD_BUFSIZE, "%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:<ip>:<port>' 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, "%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, "%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, "%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, "%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, "%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, "%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, "%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, "%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, "%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, 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, "%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, " %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, "%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, "%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, "%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, "%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, "%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, "%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, "%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, "%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, "%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, "%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, "%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, "%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, "%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, "%s -I %s %s",
opts->fw_config->fw_command, fw_chain, fw_rule);
} else {
snprintf(cmd_buf, CMD_BUFSIZE, "%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, 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, 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, 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, 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, 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, " ");
/* 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,
"--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,
"--to-source %s", opts->config[CONF_SNAT_TRANSLATE_IP]);
}
memset(rule_buf, 0, CMD_BUFSIZE);
snprintf(rule_buf, CMD_BUFSIZE, 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,
"--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,
"--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,
"--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,
"--to-ports %i", fst_port);
}
memset(rule_buf, 0, CMD_BUFSIZE);
snprintf(rule_buf, CMD_BUFSIZE, 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, "%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, "%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***/