static int chain_exists(const fko_srv_options_t * const opts, const int chain_num) { int res = 0; zero_cmd_buffers(); snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPT_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(EXTCMD_IS_SUCCESS(res)) log_msg(LOG_DEBUG, "'%s' table '%s' chain exists", fwc.chain[chain_num].table, fwc.chain[chain_num].to_chain); else log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); return res; }
static int add_jump_rule(const fko_srv_options_t *opts, const int chain_num) { int res = 0; zero_cmd_buffers(); snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPT_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, 0); if (opts->verbose) log_msg(LOG_INFO, "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); else log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); return res; }
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 " IPT_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(EXTCMD_IS_SUCCESS(res) && strlen(err_buf)) { log_msg(LOG_DEBUG, "rule_exists_chk_support() Rule : '%s' in %s does not exist", rule, chain); } else { rule_exists = 1; log_msg(LOG_DEBUG, "rule_exists_chk_support() Rule : '%s' in %s already exists", rule, chain); } return(rule_exists); }
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 " IPT_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, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); return rv; }
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(); 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, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); return res; }
static int ipfw_set_exists(const fko_srv_options_t *opts, const char *fw_command, const unsigned short set_num) { int res = 0; zero_cmd_buffers(); snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPFW_LIST_SET_RULES_ARGS, fw_command, set_num ); res = run_extcmd(cmd_buf, cmd_out, STANDARD_CMD_OUT_BUFSIZE, 0); if (opts->verbose) log_msg(LOG_INFO, "ipfw_set_exists() CMD: '%s' (res: %d)", cmd_buf, res); if(!EXTCMD_IS_SUCCESS(res)) return(0); if(cmd_out[0] == '\0') return(0); return(1); }
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 " IPT_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, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); return rv; }
/* Check to see if the fwknop anchor is linked into the main policy. If not, * any rules added/deleted by fwknopd will have no effect on real traffic. */ static int anchor_active(const fko_srv_options_t *opts) { int res = 0; char anchor_search_str[MAX_PF_ANCHOR_SEARCH_LEN] = {0}; /* Build our anchor search string */ snprintf(anchor_search_str, MAX_PF_ANCHOR_SEARCH_LEN-1, "%s\n", opts->fw_config->anchor); zero_cmd_buffers(); snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " PF_ANCHOR_CHECK_ARGS, opts->fw_config->fw_command ); res = run_extcmd(cmd_buf, cmd_out, STANDARD_CMD_OUT_BUFSIZE, 0); if(!EXTCMD_IS_SUCCESS(res)) { log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, cmd_out); return 0; } /* Check to see if the anchor exists and is linked into the main policy */ if(strstr(cmd_out, anchor_search_str) == NULL) return 0; return 1; }
/* Create the fwknop custom chains (at least those that are configured). */ static int create_fw_chains(const fko_srv_options_t *opts) { int i; int res, got_err = 0; for(i=0; i<(NUM_FWKNOP_ACCESS_TYPES); i++) { if(fwc.chain[i].target[0] == '\0') continue; zero_cmd_buffers(); /* Create the custom chain. */ snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPT_NEW_CHAIN_ARGS, fwc.fw_command, fwc.chain[i].table, fwc.chain[i].to_chain ); res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, 0); 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++; } /* Then create the jump rule to that chain. */ res = add_jump_rule(opts, i); /* Expect full success on this */ if(! EXTCMD_IS_SUCCESS(res)) got_err++; } return(got_err); }
static void delete_all_anchor_rules(const fko_srv_options_t *opts) { int res = 0; zero_cmd_buffers(); snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " PF_DEL_ALL_ANCHOR_RULES, fwc.fw_command, fwc.anchor ); res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, 0); /* 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); return; }
static int ipfw_set_exists(const char *fw_command, const unsigned short set_num) { int res = 0; zero_cmd_buffers(); snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPFW_LIST_SET_RULES_ARGS, fw_command, set_num ); res = run_extcmd(cmd_buf, cmd_out, STANDARD_CMD_OUT_BUFSIZE, 0); if(!EXTCMD_IS_SUCCESS(res)) return(0); if(cmd_out[0] == '\0') return(0); return(1); }
/* Process the SPA packet data */ void incoming_spa(fko_srv_options_t *opts) { /* Always a good idea to initialize ctx to null if it will be used * repeatedly (especially when using fko_new_with_data()). */ fko_ctx_t ctx = NULL; char *spa_ip_demark, *gpg_id, *raw_digest = NULL; time_t now_ts; int res, status, ts_diff, enc_type, stanza_num=0; int added_replay_digest = 0, pkt_data_len=0; int is_err, cmd_exec_success = 0, attempted_decrypt = 0; int conf_pkt_age = 0; char dump_buf[CTX_DUMP_BUFSIZE]; spa_pkt_info_t *spa_pkt = &(opts->spa_pkt); /* This will hold our pertinent SPA data. */ spa_data_t spadat; /* Loop through all access stanzas looking for a match */ acc_stanza_t *acc = opts->acc_stanzas; acc_string_list_t *gpg_id_ndx; unsigned char is_gpg_match = 0; inet_ntop(AF_INET, &(spa_pkt->packet_src_ip), spadat.pkt_source_ip, sizeof(spadat.pkt_source_ip)); /* At this point, we want to validate and (if needed) preprocess the * SPA data and/or to be reasonably sure we have a SPA packet (i.e * try to eliminate obvious non-spa packets). */ pkt_data_len = spa_pkt->packet_data_len; res = preprocess_spa_data(opts, spadat.pkt_source_ip); if(res != FKO_SUCCESS) { log_msg(LOG_DEBUG, "[%s] preprocess_spa_data() returned error %i: '%s' for incoming packet.", spadat.pkt_source_ip, res, get_errstr(res)); return; } if(opts->foreground == 1 && opts->verbose > 2) { printf("[+] candidate SPA packet payload:\n"); hex_dump(spa_pkt->packet_data, pkt_data_len); } if(strncasecmp(opts->config[CONF_ENABLE_SPA_PACKET_AGING], "Y", 1) == 0) { conf_pkt_age = strtol_wrapper(opts->config[CONF_MAX_SPA_PACKET_AGE], 0, RCHK_MAX_SPA_PACKET_AGE, NO_EXIT_UPON_ERR, &is_err); if(is_err != FKO_SUCCESS) { log_msg(LOG_ERR, "[*] [%s] invalid MAX_SPA_PACKET_AGE", spadat.pkt_source_ip); return; } } if (is_src_match(opts->acc_stanzas, ntohl(spa_pkt->packet_src_ip))) { if(strncasecmp(opts->config[CONF_ENABLE_DIGEST_PERSISTENCE], "Y", 1) == 0) { /* Check for a replay attack */ res = get_raw_digest(&raw_digest, (char *)spa_pkt->packet_data); if(res != FKO_SUCCESS) { if (raw_digest != NULL) free(raw_digest); return; } if (raw_digest == NULL) return; if (is_replay(opts, raw_digest) != SPA_MSG_SUCCESS) { free(raw_digest); return; } } } else { log_msg(LOG_WARNING, "No access data found for source IP: %s", spadat.pkt_source_ip ); return; } /* Now that we know there is a matching access.conf stanza and the * incoming SPA packet is not a replay, see if we should grant any * access */ while(acc) { res = FKO_SUCCESS; cmd_exec_success = 0; attempted_decrypt = 0; stanza_num++; /* Start access loop with a clean FKO context */ if(ctx != NULL) { if(fko_destroy(ctx) == FKO_ERROR_ZERO_OUT_DATA) log_msg(LOG_WARNING, "[%s] (stanza #%d) fko_destroy() could not zero out sensitive data buffer.", spadat.pkt_source_ip, stanza_num, fko_errstr(res) ); ctx = NULL; } /* Check for a match for the SPA source IP and the access stanza */ if(! compare_addr_list(acc->source_list, ntohl(spa_pkt->packet_src_ip))) { acc = acc->next; continue; } log_msg(LOG_INFO, "(stanza #%d) SPA Packet from IP: %s received with access source match", stanza_num, spadat.pkt_source_ip); log_msg(LOG_DEBUG, "SPA Packet: '%s'", spa_pkt->packet_data); /* Make sure this access stanza has not expired */ if(acc->access_expire_time > 0) { if(acc->expired) { acc = acc->next; continue; } else { if(time(NULL) > acc->access_expire_time) { log_msg(LOG_INFO, "[%s] (stanza #%d) Access stanza has expired", spadat.pkt_source_ip, stanza_num); acc->expired = 1; acc = acc->next; continue; } } } /* Get encryption type and try its decoding routine first (if the key * for that type is set) */ enc_type = fko_encryption_type((char *)spa_pkt->packet_data); if(acc->use_rijndael) { if (acc->key == NULL) { log_msg(LOG_ERR, "[%s] (stanza #%d) No KEY for RIJNDAEL encrypted messages", spadat.pkt_source_ip, stanza_num ); acc = acc->next; continue; } /* Command mode messages may be quite long */ if(acc->enable_cmd_exec || enc_type == FKO_ENCRYPTION_RIJNDAEL) { res = fko_new_with_data(&ctx, (char *)spa_pkt->packet_data, acc->key, acc->key_len, acc->encryption_mode, acc->hmac_key, acc->hmac_key_len, acc->hmac_type); attempted_decrypt = 1; if(res == FKO_SUCCESS) cmd_exec_success = 1; } } if(acc->use_gpg && enc_type == FKO_ENCRYPTION_GPG && cmd_exec_success == 0) { /* For GPG we create the new context without decrypting on the fly * so we can set some GPG parameters first. */ if(acc->gpg_decrypt_pw != NULL || acc->gpg_allow_no_pw) { res = fko_new_with_data(&ctx, (char *)spa_pkt->packet_data, NULL, 0, FKO_ENC_MODE_ASYMMETRIC, acc->hmac_key, acc->hmac_key_len, acc->hmac_type); if(res != FKO_SUCCESS) { log_msg(LOG_WARNING, "[%s] (stanza #%d) Error creating fko context (before decryption): %s", spadat.pkt_source_ip, stanza_num, fko_errstr(res) ); acc = acc->next; continue; } /* Set whatever GPG parameters we have. */ if(acc->gpg_exe != NULL) { res = fko_set_gpg_exe(ctx, acc->gpg_exe); if(res != FKO_SUCCESS) { log_msg(LOG_WARNING, "[%s] (stanza #%d) Error setting GPG path %s: %s", spadat.pkt_source_ip, stanza_num, acc->gpg_exe, fko_errstr(res) ); acc = acc->next; continue; } } if(acc->gpg_home_dir != NULL) { res = fko_set_gpg_home_dir(ctx, acc->gpg_home_dir); if(res != FKO_SUCCESS) { log_msg(LOG_WARNING, "[%s] (stanza #%d) Error setting GPG keyring path to %s: %s", spadat.pkt_source_ip, stanza_num, acc->gpg_home_dir, fko_errstr(res) ); acc = acc->next; continue; } } if(acc->gpg_decrypt_id != NULL) fko_set_gpg_recipient(ctx, acc->gpg_decrypt_id); /* If GPG_REQUIRE_SIG is set for this acc stanza, then set * the FKO context accordingly and check the other GPG Sig- * related parameters. This also applies when REMOTE_ID is * set. */ if(acc->gpg_require_sig) { fko_set_gpg_signature_verify(ctx, 1); /* Set whether or not to ignore signature verification errors. */ fko_set_gpg_ignore_verify_error(ctx, acc->gpg_ignore_sig_error); } else { fko_set_gpg_signature_verify(ctx, 0); fko_set_gpg_ignore_verify_error(ctx, 1); } /* Now decrypt the data. */ res = fko_decrypt_spa_data(ctx, acc->gpg_decrypt_pw, 0); attempted_decrypt = 1; } } if(attempted_decrypt == 0) { log_msg(LOG_ERR, "(stanza #%d) No stanza encryption mode match for encryption type: %i.", stanza_num, enc_type); acc = acc->next; continue; } /* Do we have a valid FKO context? Did the SPA decrypt properly? */ if(res != FKO_SUCCESS) { log_msg(LOG_WARNING, "[%s] (stanza #%d) Error creating fko context: %s", spadat.pkt_source_ip, stanza_num, fko_errstr(res)); if(IS_GPG_ERROR(res)) log_msg(LOG_WARNING, "[%s] (stanza #%d) - GPG ERROR: %s", spadat.pkt_source_ip, stanza_num, fko_gpg_errstr(ctx)); acc = acc->next; continue; } /* Add this SPA packet into the replay detection cache */ if (added_replay_digest == 0 && strncasecmp(opts->config[CONF_ENABLE_DIGEST_PERSISTENCE], "Y", 1) == 0) { res = add_replay(opts, raw_digest); if (res != SPA_MSG_SUCCESS) { log_msg(LOG_WARNING, "[%s] (stanza #%d) Could not add digest to replay cache", spadat.pkt_source_ip, stanza_num); acc = acc->next; continue; } added_replay_digest = 1; } /* At this point, we assume the SPA data is valid. Now we need to see * if it meets our access criteria. */ log_msg(LOG_DEBUG, "[%s] (stanza #%d) SPA Decode (res=%i):", spadat.pkt_source_ip, stanza_num, res); res = dump_ctx_to_buffer(ctx, dump_buf, sizeof(dump_buf)); if (res == FKO_SUCCESS) log_msg(LOG_DEBUG, "%s", dump_buf); else log_msg(LOG_WARNING, "Unable to dump FKO context: %s", fko_errstr(res)); /* First, if this is a GPG message, and GPG_REMOTE_ID list is not empty, * then we need to make sure this incoming message is signer ID matches * an entry in the list. */ if(enc_type == FKO_ENCRYPTION_GPG && acc->gpg_require_sig) { res = fko_get_gpg_signature_id(ctx, &gpg_id); if(res != FKO_SUCCESS) { log_msg(LOG_WARNING, "[%s] (stanza #%d) Error pulling the GPG signature ID from the context: %s", spadat.pkt_source_ip, stanza_num, fko_gpg_errstr(ctx)); acc = acc->next; continue; } log_msg(LOG_INFO, "[%s] (stanza #%d) Incoming SPA data signed by '%s'.", spadat.pkt_source_ip, stanza_num, gpg_id); if(acc->gpg_remote_id != NULL) { is_gpg_match = 0; for(gpg_id_ndx = acc->gpg_remote_id_list; gpg_id_ndx != NULL; gpg_id_ndx=gpg_id_ndx->next) { res = fko_gpg_signature_id_match(ctx, gpg_id_ndx->str, &is_gpg_match); if(res != FKO_SUCCESS) { log_msg(LOG_WARNING, "[%s] (stanza #%d) Error in GPG siganture comparision: %s", spadat.pkt_source_ip, stanza_num, fko_gpg_errstr(ctx)); acc = acc->next; continue; } if(is_gpg_match) break; } if(! is_gpg_match) { log_msg(LOG_WARNING, "[%s] (stanza #%d) Incoming SPA packet signed by ID: %s, but that ID is not the GPG_REMOTE_ID list.", spadat.pkt_source_ip, stanza_num, gpg_id); acc = acc->next; continue; } } } /* Populate our spa data struct for future reference. */ res = get_spa_data_fields(ctx, &spadat); /* Figure out what our timeout will be. If it is specified in the SPA * data, then use that. If not, try the FW_ACCESS_TIMEOUT from the * access.conf file (if there is one). Otherwise use the default. */ if(spadat.client_timeout > 0) spadat.fw_access_timeout = spadat.client_timeout; else if(acc->fw_access_timeout > 0) spadat.fw_access_timeout = acc->fw_access_timeout; else spadat.fw_access_timeout = DEF_FW_ACCESS_TIMEOUT; if(res != FKO_SUCCESS) { log_msg(LOG_ERR, "[%s] (stanza #%d) Unexpected error pulling SPA data from the context: %s", spadat.pkt_source_ip, stanza_num, fko_errstr(res)); acc = acc->next; continue; } /* Check packet age if so configured. */ if(strncasecmp(opts->config[CONF_ENABLE_SPA_PACKET_AGING], "Y", 1) == 0) { time(&now_ts); ts_diff = abs(now_ts - spadat.timestamp); if(ts_diff > conf_pkt_age) { log_msg(LOG_WARNING, "[%s] (stanza #%d) SPA data time difference is too great (%i seconds).", spadat.pkt_source_ip, stanza_num, ts_diff); acc = acc->next; continue; } } /* At this point, we have enough to check the embedded (or packet source) * IP address against the defined access rights. We start by splitting * the spa msg source IP from the remainder of the message. */ spa_ip_demark = strchr(spadat.spa_message, ','); if(spa_ip_demark == NULL) { log_msg(LOG_WARNING, "[%s] (stanza #%d) Error parsing SPA message string: %s", spadat.pkt_source_ip, stanza_num, fko_errstr(res)); acc = acc->next; continue; } if((spa_ip_demark-spadat.spa_message) < MIN_IPV4_STR_LEN-1 || (spa_ip_demark-spadat.spa_message) > MAX_IPV4_STR_LEN) { log_msg(LOG_WARNING, "[%s] (stanza #%d) Invalid source IP in SPA message, ignoring SPA packet", spadat.pkt_source_ip, stanza_num, fko_errstr(res)); if(ctx != NULL) { if(fko_destroy(ctx) == FKO_ERROR_ZERO_OUT_DATA) log_msg(LOG_WARNING, "[%s] (stanza #%d) fko_destroy() could not zero out sensitive data buffer.", spadat.pkt_source_ip, stanza_num, fko_errstr(res) ); ctx = NULL; } break; } strlcpy(spadat.spa_message_src_ip, spadat.spa_message, (spa_ip_demark-spadat.spa_message)+1); if(! is_valid_ipv4_addr(spadat.spa_message_src_ip)) { log_msg(LOG_WARNING, "[%s] (stanza #%d) Invalid source IP in SPA message, ignoring SPA packet", spadat.pkt_source_ip, stanza_num, fko_errstr(res)); if(ctx != NULL) { if(fko_destroy(ctx) == FKO_ERROR_ZERO_OUT_DATA) log_msg(LOG_WARNING, "[%s] (stanza #%d) fko_destroy() could not zero out sensitive data buffer.", spadat.pkt_source_ip, stanza_num, fko_errstr(res) ); ctx = NULL; } break; } strlcpy(spadat.spa_message_remain, spa_ip_demark+1, MAX_DECRYPTED_SPA_LEN); /* If use source IP was requested (embedded IP of 0.0.0.0), make sure it * is allowed. */ if(strcmp(spadat.spa_message_src_ip, "0.0.0.0") == 0) { if(acc->require_source_address) { log_msg(LOG_WARNING, "[%s] (stanza #%d) Got 0.0.0.0 when valid source IP was required.", spadat.pkt_source_ip, stanza_num ); acc = acc->next; continue; } spadat.use_src_ip = spadat.pkt_source_ip; } else spadat.use_src_ip = spadat.spa_message_src_ip; /* If REQUIRE_USERNAME is set, make sure the username in this SPA data * matches. */ if(acc->require_username != NULL) { if(strcmp(spadat.username, acc->require_username) != 0) { log_msg(LOG_WARNING, "[%s] (stanza #%d) Username in SPA data (%s) does not match required username: %s", spadat.pkt_source_ip, stanza_num, spadat.username, acc->require_username ); acc = acc->next; continue; } } /* Take action based on SPA message type. */ 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) { #if FIREWALL_IPTABLES if(strncasecmp(opts->config[CONF_ENABLE_IPT_FORWARDING], "Y", 1)!=0) { log_msg(LOG_WARNING, "(stanza #%d) SPA packet from %s requested NAT access, but is not enabled", stanza_num, spadat.pkt_source_ip ); acc = acc->next; continue; } #else log_msg(LOG_WARNING, "(stanza #%d) SPA packet from %s requested unsupported NAT access", stanza_num, spadat.pkt_source_ip ); acc = acc->next; continue; #endif } /* Command messages. */ if(spadat.message_type == FKO_COMMAND_MSG) { if(!acc->enable_cmd_exec) { log_msg(LOG_WARNING, "[%s] (stanza #%d) SPA Command message are not allowed in the current configuration.", spadat.pkt_source_ip, stanza_num ); acc = acc->next; continue; } else { log_msg(LOG_INFO, "[%s] (stanza #%d) Processing SPA Command message: command='%s'.", spadat.pkt_source_ip, stanza_num, spadat.spa_message_remain ); /* Do we need to become another user? If so, we call * run_extcmd_as and pass the cmd_exec_uid. */ if(acc->cmd_exec_user != NULL && strncasecmp(acc->cmd_exec_user, "root", 4) != 0) { log_msg(LOG_INFO, "[%s] (stanza #%d) Setting effective user to %s (UID=%i) before running command.", spadat.pkt_source_ip, stanza_num, acc->cmd_exec_user, acc->cmd_exec_uid); res = run_extcmd_as(acc->cmd_exec_uid, spadat.spa_message_remain, NULL, 0, 0); } else /* Just run it as we are (root that is). */ res = run_extcmd(spadat.spa_message_remain, NULL, 0, 5); /* --DSS XXX: I have found that the status (and res for that * matter) have been unreliable indicators of the * actual exit status of some commands. Not sure * why yet. For now, we will take what we get. */ status = WEXITSTATUS(res); if(opts->verbose > 1) log_msg(LOG_WARNING, "[%s] (stanza #%d) CMD_EXEC: command returned %i", spadat.pkt_source_ip, stanza_num, status); if(status != 0) res = SPA_MSG_COMMAND_ERROR; if(ctx != NULL) { if(fko_destroy(ctx) == FKO_ERROR_ZERO_OUT_DATA) log_msg(LOG_WARNING, "[%s] (stanza #%d) fko_destroy() could not zero out sensitive data buffer.", spadat.pkt_source_ip, stanza_num, fko_errstr(res) ); ctx = NULL; } /* we processed the command on a matching access stanza, so we * don't look for anything else to do with this SPA packet */ break; } } /* From this point forward, we have some kind of access message. So * we first see if access is allowed by checking access against * restrict_ports and open_ports. * * --DSS TODO: We should add BLACKLIST support here as well. */ if(! acc_check_port_access(acc, spadat.spa_message_remain)) { log_msg(LOG_WARNING, "[%s] (stanza #%d) One or more requested protocol/ports was denied per access.conf.", spadat.pkt_source_ip, stanza_num ); acc = acc->next; continue; } /* At this point, we process the SPA request and break out of the * access stanza loop (first valid access stanza stops us looking * for others). */ process_spa_request(opts, acc, &spadat); if(ctx != NULL) { if(fko_destroy(ctx) == FKO_ERROR_ZERO_OUT_DATA) log_msg(LOG_WARNING, "[%s] (stanza #%d) fko_destroy() could not zero out sensitive data buffer.", spadat.pkt_source_ip, stanza_num ); ctx = NULL; } break; } if (raw_digest != NULL) free(raw_digest); if(ctx != NULL) { if(fko_destroy(ctx) == FKO_ERROR_ZERO_OUT_DATA) log_msg(LOG_WARNING, "[%s] fko_destroy() could not zero out sensitive data buffer.", spadat.pkt_source_ip ); ctx = NULL; } return; }
/* Iterate over the expired rule set and purge those that no longer have * corresponding dynamic rules. */ void ipfw_purge_expired_rules(const fko_srv_options_t *opts) { char *ndx, *co_end; int i, res; unsigned short curr_rule; /* First, we get the current active dynamic rules for the expired rule * set. Then we compare it to the expired rules in the rule_map. Any * rules in the map that do not have a dynamic rule, can be deleted. */ zero_cmd_buffers(); snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPFW_LIST_SET_DYN_RULES_ARGS, opts->fw_config->fw_command, fwc.expire_set_num ); res = run_extcmd(cmd_buf, cmd_out, STANDARD_CMD_OUT_BUFSIZE, 0); if (opts->verbose) log_msg(LOG_INFO, "ipfw_purge_expired_rules() CMD: '%s' (res: %d)", cmd_buf, res); if(!EXTCMD_IS_SUCCESS(res)) { log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, cmd_out); return; } /* We may not have any dynamic rules at all - someone might not have * initiated a connection (for example) */ if (cmd_out[0] != '\0') { co_end = cmd_out + strlen(cmd_out); if(opts->verbose > 1) log_msg(LOG_INFO, "EXP RULES LIST: %s", cmd_out); /* Find the "## Dynamic rules" string. */ ndx = strcasestr(cmd_out, "## Dynamic rules"); if(ndx == NULL) { log_msg(LOG_ERR, "Unexpected error: did not find 'Dynamic rules' string in list output." ); return; } /* Jump to the next newline char. */ ndx = strchr(ndx, '\n'); if(ndx == NULL) { log_msg(LOG_ERR, "Unexpected error: did not find 'Dynamic rules' line terminating newline." ); return; } /* Walk the list of dynamic rules (if any). */ while(ndx != NULL) { ndx++; while(!isdigit(*ndx) && ndx < co_end) ndx++; if(ndx >= co_end) break; /* If we are at a digit, assume it is a rule number, extract it, * and if it falls in the correct range, mark it (so it is not * removed in the next step. */ if(isdigit(*ndx)) { curr_rule = atoi(ndx); if(curr_rule >= fwc.start_rule_num && curr_rule < fwc.start_rule_num + fwc.max_rules) fwc.rule_map[curr_rule - fwc.start_rule_num] = RULE_TMP_MARKED; } ndx = strchr(ndx, '\n'); } } /* Now, walk the rule map and remove any still marked as expired. */ for(i=0; i<fwc.max_rules; i++) { /* If it is TMP_MARKED, set it back to EXPIRED and move on. */ if(fwc.rule_map[i] == RULE_TMP_MARKED) { fwc.rule_map[i] = RULE_EXPIRED; continue; } /* If it is not expired, move on. */ if(fwc.rule_map[i] != RULE_EXPIRED) continue; /* This rule is ready to go away. */ zero_cmd_buffers(); curr_rule = fwc.start_rule_num + i; snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPFW_DEL_RULE_ARGS, opts->fw_config->fw_command, #ifndef __APPLE__ fwc.expire_set_num, #endif curr_rule ); res = run_extcmd(cmd_buf, cmd_out, STANDARD_CMD_OUT_BUFSIZE, 0); if (opts->verbose) log_msg(LOG_INFO, "ipfw_purge_expired_rules() CMD: '%s' (res: %d)", cmd_buf, res); if(!EXTCMD_IS_SUCCESS(res)) { log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, cmd_out); continue; } log_msg(LOG_INFO, "Purged rule %u from set %u", curr_rule, fwc.expire_set_num); fwc.rule_map[curr_rule - fwc.start_rule_num] = RULE_FREE; fwc.total_rules--; } }
/* Iterate over the current rule set and purge expired * firewall rules. */ void check_firewall_rules(const fko_srv_options_t *opts) { char exp_str[12]; char rule_num_str[6]; char *ndx, *rn_start, *rn_end, *tmp_mark; int i=0, res=0; time_t now, rule_exp, min_exp = 0; unsigned short curr_rule; /* Just in case we somehow lose track and fall out-of-whack. */ if(fwc.active_rules > fwc.max_rules) fwc.active_rules = 0; /* If there are no active rules or we have not yet * reached our expected next expire time, continue. */ if(fwc.active_rules == 0) return; time(&now); if (fwc.next_expire > now) return; zero_cmd_buffers(); /* There should be a rule to delete. Get the current list of * rules for this chain and delete the ones that are expired. */ snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPFW_LIST_SET_RULES_ARGS, opts->fw_config->fw_command, fwc.active_set_num ); res = run_extcmd(cmd_buf, cmd_out, STANDARD_CMD_OUT_BUFSIZE, 0); if (opts->verbose) log_msg(LOG_INFO, "check_firewall_rules() CMD: '%s' (res: %d)", cmd_buf, res); if(!EXTCMD_IS_SUCCESS(res)) { log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, cmd_out); return; } if(opts->verbose > 1) log_msg(LOG_INFO, "RULES LIST: %s", cmd_out); /* Find the first _exp_ string (if any). */ ndx = strstr(cmd_out, EXPIRE_COMMENT_PREFIX); if(ndx == NULL) { /* we did not find an expected rule. */ log_msg(LOG_ERR, "Did not find expire comment in rules list %i.\n", i); if (fwc.active_rules > 0) fwc.active_rules--; return; } /* 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, 11); 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 > cmd_out) { if(*rn_start == '\n') break; } if(*rn_start == '\n') { rn_start++; } else if(rn_start > cmd_out) { /* 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."); if (fwc.active_rules > 0) fwc.active_rules--; break; } 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."); if (fwc.active_rules > 0) fwc.active_rules--; break; } strlcpy(rule_num_str, rn_start, (rn_end - rn_start)+1); curr_rule = atoi(rule_num_str); zero_cmd_buffers(); /* Move the rule to the expired rules set. */ snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPFW_MOVE_RULE_ARGS, opts->fw_config->fw_command, curr_rule, fwc.expire_set_num ); res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, 0); if (opts->verbose) log_msg(LOG_INFO, "check_firewall_rules() CMD: '%s' (res: %d, err: %s)", cmd_buf, res, err_buf); if(EXTCMD_IS_SUCCESS(res)) { log_msg(LOG_INFO, "Moved rule %s with expire time of %u to set %u.", rule_num_str, rule_exp, fwc.expire_set_num ); if (fwc.active_rules > 0) fwc.active_rules--; fwc.rule_map[curr_rule - fwc.start_rule_num] = RULE_EXPIRED; } else log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); } else { /* Track the minimum future rule expire time. */ if(rule_exp > now) 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(fwc.active_rules < 1) fwc.next_expire = 0; else if(min_exp) fwc.next_expire = min_exp; }
void fw_initialize(const fko_srv_options_t *opts) { int res = 0; unsigned short curr_rule; char *ndx; /* For now, we just call fw_cleanup to start with clean slate. */ if(strncasecmp(opts->config[CONF_FLUSH_IPFW_AT_INIT], "Y", 1) == 0) res = fw_cleanup(opts); if(res != 0) { fprintf(stderr, "Fatal: Errors detected during ipfw rules initialization.\n"); exit(EXIT_FAILURE); } /* Allocate our rule_map array for tracking active (and expired) rules. */ fwc.rule_map = calloc(fwc.max_rules, sizeof(char)); if(fwc.rule_map == NULL) { fprintf(stderr, "Fatal: Memory allocation error in fw_initialize.\n"); exit(EXIT_FAILURE); } /* Create a check-state rule if necessary. */ if(strncasecmp(opts->config[CONF_IPFW_ADD_CHECK_STATE], "Y", 1) == 0) { zero_cmd_buffers(); snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPFW_ADD_CHECK_STATE_ARGS, fwc.fw_command, fwc.start_rule_num, fwc.active_set_num ); res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, 0); if (opts->verbose) log_msg(LOG_INFO, "fw_initialize() CMD: '%s' (res: %d, err: %s)", cmd_buf, res, err_buf); if(EXTCMD_IS_SUCCESS(res)) { log_msg(LOG_INFO, "Added check-state rule %u to set %u", fwc.start_rule_num, fwc.active_set_num ); fwc.rule_map[0] = RULE_ACTIVE; } else log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); } if(fwc.expire_set_num > 0 && (strncasecmp(opts->config[CONF_FLUSH_IPFW_AT_INIT], "Y", 1) == 0)) { /* Make sure our expire set is disabled. */ zero_cmd_buffers(); snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPFW_DISABLE_SET_ARGS, fwc.fw_command, fwc.expire_set_num ); res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, 0); if (opts->verbose) log_msg(LOG_INFO, "fw_initialize() CMD: '%s' (res: %d, err: %s)", cmd_buf, res, err_buf); if(EXTCMD_IS_SUCCESS(res)) log_msg(LOG_INFO, "Set ipfw expire set %u to disabled.", fwc.expire_set_num); else log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); } /* Now read the expire set in case there are existing * rules to track. */ zero_cmd_buffers(); snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPFW_LIST_EXP_SET_RULES_ARGS, opts->fw_config->fw_command, fwc.expire_set_num ); res = run_extcmd(cmd_buf, cmd_out, STANDARD_CMD_OUT_BUFSIZE, 0); if (opts->verbose) log_msg(LOG_INFO, "fw_initialize() CMD: '%s' (res: %d)", cmd_buf, res); if(!EXTCMD_IS_SUCCESS(res)) { log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, cmd_out); return; } if(opts->verbose > 1) log_msg(LOG_INFO, "RULES LIST: %s", cmd_out); /* Find the first "# DISABLED" string (if any). */ ndx = strstr(cmd_out, "# DISABLED "); /* Assume no disabled rules if we did not see the string. */ if(ndx == NULL) return; /* Otherwise we walk each line to pull the rule number and * set the appropriate rule map entries. */ while(ndx != NULL) { /* Skip over the DISABLED string to the rule num. */ ndx += 11; if(isdigit(*ndx)) { curr_rule = atoi(ndx); if(curr_rule >= fwc.start_rule_num && curr_rule < fwc.start_rule_num + fwc.max_rules) { fwc.rule_map[curr_rule - fwc.start_rule_num] = RULE_EXPIRED; fwc.total_rules++; } } else log_msg(LOG_WARNING, "fw_initialize: No rule number found where expected."); /* Find the next "# DISABLED" string (if any). */ ndx = strstr(ndx, "# DISABLED "); } }
/* Rule Processing - Create an access request... */ int process_spa_request(const fko_srv_options_t *opts, const acc_stanza_t *acc, spa_data_t *spadat) { unsigned short rule_num; acc_port_list_t *port_list = NULL; acc_port_list_t *ple; unsigned int fst_proto; unsigned int fst_port; int res = 0; time_t now; unsigned int exp_ts; /* Parse and expand our access message. */ expand_acc_port_list(&port_list, spadat->spa_message_remain); /* 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; /* For straight access requests, we currently support multiple proto/port * request. */ if(spadat->message_type == FKO_ACCESS_MSG || spadat->message_type == FKO_CLIENT_TIMEOUT_ACCESS_MSG) { /* Pull the next available rule number. */ rule_num = get_next_rule_num(); /* If rule_num comes back as 0, we aready have the maximum number * of active rules allowed so we reject and bail here. */ if(rule_num == 0) { log_msg(LOG_WARNING, "Access request rejected: Maximum allowed number of rules has been reached."); return(-1); } /* Create an access command for each proto/port for the source ip. */ while(ple != NULL) { zero_cmd_buffers(); snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPFW_ADD_RULE_ARGS, fwc.fw_command, rule_num, fwc.active_set_num, ple->proto, spadat->use_src_ip, ple->port, exp_ts ); res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, 0); if (opts->verbose) log_msg(LOG_INFO, "process_spa_request() CMD: '%s' (res: %d, err: %s)", cmd_buf, res, err_buf); if(EXTCMD_IS_SUCCESS(res)) { log_msg(LOG_INFO, "Added Rule %u for %s, %s expires at %u", rule_num, spadat->use_src_ip, spadat->spa_message_remain, exp_ts ); fwc.rule_map[rule_num - fwc.start_rule_num] = RULE_ACTIVE; fwc.active_rules++; fwc.total_rules++; /* Reset the next expected expire time for this chain if it * is warranted. */ if(fwc.next_expire < now || exp_ts < fwc.next_expire) fwc.next_expire = exp_ts; } else log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); ple = ple->next; } } else { /* No other SPA request modes are supported yet. */ if(spadat->message_type == FKO_LOCAL_NAT_ACCESS_MSG || spadat->message_type == FKO_CLIENT_TIMEOUT_LOCAL_NAT_ACCESS_MSG) { log_msg(LOG_WARNING, "Local NAT requests are not currently supported."); } else if(spadat->message_type == FKO_NAT_ACCESS_MSG || spadat->message_type == FKO_CLIENT_TIMEOUT_NAT_ACCESS_MSG) { log_msg(LOG_WARNING, "Forwarding/NAT requests are not currently supported."); } return(-1); } return(res); }
/* Iterate over the configure firewall access chains and purge expired * firewall rules. */ void check_firewall_rules(const fko_srv_options_t * const opts) { char exp_str[12] = {0}; char rule_num_str[6] = {0}; char *ndx, *rn_start, *rn_end, *tmp_mark; int i, res, rn_offset, rule_num, is_err; time_t now, rule_exp, min_exp = 0; 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(ch[i].active_rules == 0 || ch[i].next_expire > now) continue; zero_cmd_buffers(); rn_offset = 0; /* There should be a rule to delete. Get the current list of * rules for this chain and delete the ones that are expired. */ snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPT_LIST_RULES_ARGS, opts->fw_config->fw_command, ch[i].table, ch[i].to_chain ); res = run_extcmd(cmd_buf, cmd_out, STANDARD_CMD_OUT_BUFSIZE, WANT_STDERR, NO_TIMEOUT, &pid_status, opts); chop_newline(cmd_out); log_msg(LOG_DEBUG, "check_firewall_rules() CMD: '%s' (res: %d, cmd_out: %s)", cmd_buf, res, cmd_out); if(!EXTCMD_IS_SUCCESS(res)) { log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, cmd_out); continue; } log_msg(LOG_DEBUG, "RES=%i, CMD_BUF: %s\nRULES LIST: %s", res, cmd_buf, cmd_out); ndx = strstr(cmd_out, EXPIRE_COMMENT_PREFIX); if(ndx == NULL) { /* we did not find an expected rule. */ log_msg(LOG_ERR, "Did not find expire comment in rules list %i", i); if (ch[i].active_rules > 0) ch[i].active_rules--; continue; } /* 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)); 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 > cmd_out) { 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", i); if (ch[i].active_rules > 0) ch[i].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", i); if (ch[i].active_rules > 0) ch[i].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_IPT_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", i); if (ch[i].active_rules > 0) ch[i].active_rules--; break; } zero_cmd_buffers(); snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPT_DEL_RULE_ARGS, opts->fw_config->fw_command, ch[i].table, ch[i].to_chain, rule_num - 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[i].to_chain, rule_exp ); rn_offset++; if (ch[i].active_rules > 0) ch[i].active_rules--; } else log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); } else { /* Track the minimum future rule expire time. */ if(rule_exp > now) 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[i].active_rules < 1) ch[i].next_expire = 0; else if(min_exp) ch[i].next_expire = min_exp; } }
/* Process the SPA packet data */ int incoming_spa(fko_srv_options_t *opts) { /* Always a good idea to initialize ctx to null if it will be used * repeatedly (especially when using fko_new_with_data(). */ fko_ctx_t ctx = NULL; char *spa_ip_demark, *gpg_id; time_t now_ts; int res, status, ts_diff, enc_type; spa_pkt_info_t *spa_pkt = &(opts->spa_pkt); /* This will hold our pertinent SPA data. */ spa_data_t spadat; /* Get the access.conf data for the stanza that matches this incoming * source IP address. */ acc_stanza_t *acc = acc_check_source(opts, spa_pkt->packet_src_ip); inet_ntop(AF_INET, &(spa_pkt->packet_src_ip), spadat.pkt_source_ip, sizeof(spadat.pkt_source_ip)); /* At this point, we want to validate and (if needed) preprocess the * SPA data and/or to be reasonably sure we have a SPA packet (i.e * try to eliminate obvious non-spa packets). */ res = preprocess_spa_data(opts, spadat.pkt_source_ip); if(res != FKO_SUCCESS) return(SPA_MSG_NOT_SPA_DATA); log_msg(LOG_INFO, "SPA Packet from IP: %s received.", spadat.pkt_source_ip); if(acc == NULL) { log_msg(LOG_WARNING, "No access data found for source IP: %s", spadat.pkt_source_ip ); return(SPA_MSG_ACCESS_DENIED); } if(opts->verbose > 1) log_msg(LOG_INFO, "SPA Packet: '%s'\n", spa_pkt->packet_data); /* Get encryption type and try its decoding routine first (if the key * for that type is set) */ enc_type = fko_encryption_type((char *)spa_pkt->packet_data); if(enc_type == FKO_ENCRYPTION_RIJNDAEL) { if(acc->key != NULL) res = fko_new_with_data(&ctx, (char *)spa_pkt->packet_data, acc->key); else { log_msg(LOG_ERR, "No KEY for RIJNDAEL encrypted messages"); return(SPA_MSG_FKO_CTX_ERROR); } } else if(enc_type == FKO_ENCRYPTION_GPG) { /* For GPG we create the new context without decrypting on the fly * so we can set some GPG parameters first. */ if(acc->gpg_decrypt_pw != NULL) { res = fko_new_with_data(&ctx, (char *)spa_pkt->packet_data, NULL); if(res != FKO_SUCCESS) { log_msg(LOG_WARNING, "Error creating fko context (before decryption): %s", fko_errstr(res) ); return(SPA_MSG_FKO_CTX_ERROR); } /* Set whatever GPG parameters we have. */ if(acc->gpg_home_dir != NULL) res = fko_set_gpg_home_dir(ctx, acc->gpg_home_dir); if(res != FKO_SUCCESS) { log_msg(LOG_WARNING, "Error setting GPG keyring path to %s: %s", acc->gpg_home_dir, fko_errstr(res) ); return(SPA_MSG_FKO_CTX_ERROR); } if(acc->gpg_decrypt_id != NULL) fko_set_gpg_recipient(ctx, acc->gpg_decrypt_id); /* If GPG_REQUIRE_SIG is set for this acc stanza, then set * the FKO context accordingly and check the other GPG Sig- * related parameters. This also applies when REMOTE_ID is * set. */ if(acc->gpg_require_sig) { fko_set_gpg_signature_verify(ctx, 1); /* Set whether or not to ignore signature verification errors. */ fko_set_gpg_ignore_verify_error(ctx, acc->gpg_ignore_sig_error); } else { fko_set_gpg_signature_verify(ctx, 0); fko_set_gpg_ignore_verify_error(ctx, 1); } /* Now decrypt the data. */ res = fko_decrypt_spa_data(ctx, acc->gpg_decrypt_pw); } else { log_msg(LOG_ERR, "No GPG_DECRYPT_PW for GPG encrypted messages"); return(SPA_MSG_FKO_CTX_ERROR); } } else { log_msg(LOG_ERR, "Unable to determing encryption type. Got type=%i.", enc_type); return(SPA_MSG_FKO_CTX_ERROR); } /* Do we have a valid FKO context? */ if(res != FKO_SUCCESS) { log_msg(LOG_WARNING, "Error creating fko context: %s", fko_errstr(res)); if(IS_GPG_ERROR(res)) log_msg(LOG_WARNING, " - GPG ERROR: %s", fko_gpg_errstr(ctx)); goto clean_and_bail; } /* At this point, we assume the SPA data is valid. Now we need to see * if it meets our access criteria. */ if(opts->verbose > 2) log_msg(LOG_INFO, "SPA Decode (res=%i):\n%s", res, dump_ctx(ctx)); /* First, if this is a GPG message, and GPG_REMOTE_ID list is not empty, * then we need to make sure this incoming message is signer ID matches * an entry in the list. */ if(enc_type == FKO_ENCRYPTION_GPG && acc->gpg_require_sig) { res = fko_get_gpg_signature_id(ctx, &gpg_id); if(res != FKO_SUCCESS) { log_msg(LOG_WARNING, "Error pulling the GPG signature ID from the context: %s", fko_gpg_errstr(ctx)); goto clean_and_bail; } if(opts->verbose) log_msg(LOG_INFO, "Incoming SPA data signed by '%s'.", gpg_id); if(acc->gpg_remote_id != NULL && !acc_check_gpg_remote_id(acc, gpg_id)) { log_msg(LOG_WARNING, "Incoming SPA packet signed by ID: %s, but that ID is not the GPG_REMOTE_ID list.", gpg_id); goto clean_and_bail; } } /* Check for replays if so configured. */ if(strncasecmp(opts->config[CONF_ENABLE_DIGEST_PERSISTENCE], "Y", 1) == 0) { res = replay_check(opts, ctx); if(res != 0) /* non-zero means we have seen this packet before. */ goto clean_and_bail; } /* Populate our spa data struct for future reference. */ res = get_spa_data_fields(ctx, &spadat); /* Figure out what our timeout will be. If it is specified in the SPA * data, then use that. If not, try the FW_ACCESS_TIMEOUT from the * access.conf file (if there is one). Otherwise use the default. */ if(spadat.client_timeout > 0) spadat.fw_access_timeout = spadat.client_timeout; else if(acc->fw_access_timeout > 0) spadat.fw_access_timeout = acc->fw_access_timeout; else spadat.fw_access_timeout = DEF_FW_ACCESS_TIMEOUT; if(res != FKO_SUCCESS) { log_msg(LOG_ERR, "Unexpected error pulling SPA data from the context: %s", fko_errstr(res)); res = SPA_MSG_ERROR; goto clean_and_bail; } /* Check packet age if so configured. */ if(strncasecmp(opts->config[CONF_ENABLE_SPA_PACKET_AGING], "Y", 1) == 0) { time(&now_ts); ts_diff = now_ts - spadat.timestamp; if(ts_diff > atoi(opts->config[CONF_MAX_SPA_PACKET_AGE])) { log_msg(LOG_WARNING, "SPA data is too old (%i seconds).", ts_diff); res = SPA_MSG_TOO_OLD; goto clean_and_bail; } } /* At this point, we have enough to check the embedded (or packet source) * IP address against the defined access rights. We start by splitting * the spa msg source IP from the remainder of the message. */ spa_ip_demark = strchr(spadat.spa_message, ','); if(spa_ip_demark == NULL) { log_msg(LOG_WARNING, "Error parsing SPA message string: %s", fko_errstr(res)); res = SPA_MSG_ERROR; goto clean_and_bail; } strlcpy(spadat.spa_message_src_ip, spadat.spa_message, (spa_ip_demark-spadat.spa_message)+1); strlcpy(spadat.spa_message_remain, spa_ip_demark+1, 1024); /* If use source IP was requested (embedded IP of 0.0.0.0), make sure it * is allowed. */ if(strcmp(spadat.spa_message_src_ip, "0.0.0.0") == 0) { if(acc->require_source_address) { log_msg(LOG_WARNING, "Got 0.0.0.0 when valid source IP was required." ); res = SPA_MSG_ACCESS_DENIED; goto clean_and_bail; } spadat.use_src_ip = spadat.pkt_source_ip; } else spadat.use_src_ip = spadat.spa_message_src_ip; /* If REQUIRE_USERNAME is set, make sure the username in this SPA data * matches. */ if(acc->require_username != NULL) { if(strcmp(spadat.username, acc->require_username) != 0) { log_msg(LOG_WARNING, "Username in SPA data (%s) does not match required username: %s", spadat.username, acc->require_username ); res = SPA_MSG_ACCESS_DENIED; goto clean_and_bail; } } /* Take action based on SPA message type. */ /* Command messages. */ if(spadat.message_type == FKO_COMMAND_MSG) { if(!acc->enable_cmd_exec) { log_msg(LOG_WARNING, "SPA Command message are not allowed in the current configuration." ); res = SPA_MSG_ACCESS_DENIED; } else { log_msg(LOG_INFO, "Processing SPA Command message: command='%s'.", spadat.spa_message_remain ); /* Do we need to become another user? If so, we call * run_extcmd_as and pass the cmd_exec_uid. */ if(acc->cmd_exec_user != NULL && strncasecmp(acc->cmd_exec_user, "root", 4) != 0) { if(opts->verbose) log_msg(LOG_INFO, "Setting effective user to %s (UID=%i) before running command.", acc->cmd_exec_user, acc->cmd_exec_uid); res = run_extcmd_as(acc->cmd_exec_uid, spadat.spa_message_remain, NULL, 0, 0); } else /* Just run it as we are (root that is). */ res = run_extcmd(spadat.spa_message_remain, NULL, 0, 5); /* --DSS XXX: I have found that the status (and res for that * matter) have been unreliable indicators of the * actual exit status of some commands. Not sure * why yet. For now, we will take what we get. */ status = WEXITSTATUS(res); if(opts->verbose > 2) log_msg(LOG_WARNING, "CMD_EXEC: command returned %i", status); if(status != 0) res = SPA_MSG_COMMAND_ERROR; } goto clean_and_bail; } /* From this point forward, we have some kind of access message. So * we first see if access is allowed by checking access against * restrict_ports and open_ports. * * --DSS TODO: We should add BLACKLIST support here as well. */ if(! acc_check_port_access(acc, spadat.spa_message_remain)) { log_msg(LOG_WARNING, "One or more requested protocol/ports was denied per access.conf." ); res = SPA_MSG_ACCESS_DENIED; goto clean_and_bail; } /* At this point, we can process the SPA request. */ res = process_spa_request(opts, &spadat); clean_and_bail: if(ctx != NULL) fko_destroy(ctx); return(res); }
/* Quietly flush and delete all fwknop custom chains. */ static void delete_all_chains(const fko_srv_options_t *opts) { int i, res; int jump_rule_num; 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. */ if((jump_rule_num = jump_rule_exists(i)) > 0) { zero_cmd_buffers(); snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPT_DEL_RULE_ARGS, fwc.fw_command, fwc.chain[i].table, fwc.chain[i].from_chain, jump_rule_num ); res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, 0); 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(); /* Now flush and remove the chain. */ snprintf(cmd_buf, CMD_BUFSIZE-1, "(%s " IPT_FLUSH_CHAIN_ARGS "; %s " IPT_DEL_CHAIN_ARGS ")", // > /dev/null 2>&1", fwc.fw_command, fwc.chain[i].table, fwc.chain[i].to_chain, fwc.fw_command, fwc.chain[i].table, fwc.chain[i].to_chain ); res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, 0); 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); } }
/* Rule Processing - Create an access request... */ int process_spa_request(const fko_srv_options_t *opts, const acc_stanza_t *acc, spa_data_t *spadat) { char nat_ip[MAX_IPV4_STR_LEN] = {0}; char snat_target[SNAT_TARGET_BUFSIZE] = {0}; char *ndx; unsigned int nat_port = 0; acc_port_list_t *port_list = NULL; acc_port_list_t *ple; unsigned int fst_proto; unsigned int fst_port; struct fw_chain *in_chain = &(opts->fw_config->chain[IPT_INPUT_ACCESS]); struct fw_chain *out_chain = &(opts->fw_config->chain[IPT_OUTPUT_ACCESS]); struct fw_chain *fwd_chain = &(opts->fw_config->chain[IPT_FORWARD_ACCESS]); struct fw_chain *dnat_chain = &(opts->fw_config->chain[IPT_DNAT_ACCESS]); struct fw_chain *snat_chain; /* We assign this later (if we need to). */ int res = 0; 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) 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; /* For straight access requests, we currently support multiple proto/port * request. */ if((spadat->message_type == FKO_ACCESS_MSG || spadat->message_type == FKO_CLIENT_TIMEOUT_ACCESS_MSG) && !acc->force_nat) { /* Check to make sure that the jump rules exist for each * required chain */ if(jump_rule_exists(IPT_INPUT_ACCESS) == 0) add_jump_rule(opts, IPT_INPUT_ACCESS); if(out_chain->to_chain != NULL && strlen(out_chain->to_chain)) if(jump_rule_exists(IPT_OUTPUT_ACCESS) == 0) add_jump_rule(opts, IPT_OUTPUT_ACCESS); /* Create an access command for each proto/port for the source ip. */ while(ple != NULL) { zero_cmd_buffers(); snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPT_ADD_RULE_ARGS, opts->fw_config->fw_command, in_chain->table, in_chain->to_chain, ple->proto, spadat->use_src_ip, ple->port, exp_ts, in_chain->target ); res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, 0); if (opts->verbose) log_msg(LOG_INFO, "process_spa_request() CMD: '%s' (res: %d, err: %s)", cmd_buf, res, err_buf); if(EXTCMD_IS_SUCCESS(res)) { log_msg(LOG_INFO, "Added Rule to %s for %s, %s expires at %u", in_chain->to_chain, spadat->use_src_ip, spadat->spa_message_remain, exp_ts ); in_chain->active_rules++; /* Reset the next expected expire time for this chain if it * is warranted. */ if(in_chain->next_expire < now || exp_ts < in_chain->next_expire) in_chain->next_expire = exp_ts; } else log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); /* If we have to make an corresponding OUTPUT rule if out_chain target * is not NULL. */ if(out_chain->to_chain != NULL && strlen(out_chain->to_chain)) { zero_cmd_buffers(); snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPT_ADD_OUT_RULE_ARGS, opts->fw_config->fw_command, out_chain->table, out_chain->to_chain, ple->proto, spadat->use_src_ip, ple->port, exp_ts, out_chain->target ); res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, 0); if (opts->verbose) log_msg(LOG_INFO, "process_spa_request() CMD: '%s' (res: %d, err: %s)", cmd_buf, res, err_buf); if(EXTCMD_IS_SUCCESS(res)) { log_msg(LOG_INFO, "Added OUTPUT Rule to %s for %s, %s expires at %u", out_chain->to_chain, spadat->use_src_ip, spadat->spa_message_remain, exp_ts ); out_chain->active_rules++; /* Reset the next expected expire time for this chain if it * is warranted. */ if(out_chain->next_expire < now || exp_ts < out_chain->next_expire) out_chain->next_expire = exp_ts; } else log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); } ple = ple->next; } } /* NAT requests... */ else 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) { /* Parse out the NAT IP and Port components. */ if(acc->force_nat) { strlcpy(nat_ip, acc->force_nat_ip, MAX_IPV4_STR_LEN); nat_port = acc->force_nat_port; } else { ndx = strchr(spadat->nat_access, ','); if(ndx != NULL) { strlcpy(nat_ip, spadat->nat_access, (ndx-spadat->nat_access)+1); nat_port = atoi(ndx+1); } } if(spadat->message_type == FKO_LOCAL_NAT_ACCESS_MSG) { /* Need to add an ACCEPT rule into the INPUT chain */ zero_cmd_buffers(); snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPT_ADD_RULE_ARGS, opts->fw_config->fw_command, in_chain->table, in_chain->to_chain, fst_proto, spadat->use_src_ip, nat_port, exp_ts, in_chain->target ); res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, 0); if (opts->verbose) log_msg(LOG_INFO, "process_spa_request() CMD: '%s' (res: %d, err: %s)", cmd_buf, res, err_buf); if(EXTCMD_IS_SUCCESS(res)) { log_msg(LOG_INFO, "Added Rule to %s for %s, %s expires at %u", in_chain->to_chain, spadat->use_src_ip, spadat->spa_message_remain, exp_ts ); in_chain->active_rules++; /* Reset the next expected expire time for this chain if it * is warranted. */ if(in_chain->next_expire < now || exp_ts < in_chain->next_expire) in_chain->next_expire = exp_ts; } else log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); } else if(fwd_chain->to_chain != NULL && strlen(fwd_chain->to_chain)) { /* Make our FORWARD and NAT rules, and make sure the * required jump rule exists */ if (jump_rule_exists(IPT_FORWARD_ACCESS) == 0) add_jump_rule(opts, IPT_FORWARD_ACCESS); zero_cmd_buffers(); snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPT_ADD_FWD_RULE_ARGS, opts->fw_config->fw_command, fwd_chain->table, fwd_chain->to_chain, fst_proto, spadat->use_src_ip, nat_ip, nat_port, exp_ts, fwd_chain->target ); res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, 0); if (opts->verbose) log_msg(LOG_INFO, "process_spa_request() CMD: '%s' (res: %d, err: %s)", cmd_buf, res, err_buf); if(EXTCMD_IS_SUCCESS(res)) { log_msg(LOG_INFO, "Added FORWARD Rule to %s for %s, %s expires at %u", fwd_chain->to_chain, spadat->use_src_ip, spadat->spa_message_remain, exp_ts ); fwd_chain->active_rules++; /* Reset the next expected expire time for this chain if it * is warranted. */ if(fwd_chain->next_expire < now || exp_ts < fwd_chain->next_expire) fwd_chain->next_expire = exp_ts; } else log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); } if(dnat_chain->to_chain != NULL && strlen(dnat_chain->to_chain)) { /* Make sure the required jump rule exists */ if (jump_rule_exists(IPT_DNAT_ACCESS) == 0) add_jump_rule(opts, IPT_DNAT_ACCESS); zero_cmd_buffers(); snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPT_ADD_DNAT_RULE_ARGS, opts->fw_config->fw_command, dnat_chain->table, dnat_chain->to_chain, fst_proto, spadat->use_src_ip, fst_port, exp_ts, dnat_chain->target, nat_ip, nat_port ); res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, 0); if (opts->verbose) log_msg(LOG_INFO, "process_spa_request() CMD: '%s' (res: %d, err: %s)", cmd_buf, res, err_buf); if(EXTCMD_IS_SUCCESS(res)) { log_msg(LOG_INFO, "Added DNAT Rule to %s for %s, %s expires at %u", dnat_chain->to_chain, spadat->use_src_ip, spadat->spa_message_remain, exp_ts ); dnat_chain->active_rules++; /* Reset the next expected expire time for this chain if it * is warranted. */ if(dnat_chain->next_expire < now || exp_ts < dnat_chain->next_expire) dnat_chain->next_expire = exp_ts; } else log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); } /* If SNAT (or MASQUERADE) is wanted, then we add those rules here as well. */ if(strncasecmp(opts->config[CONF_ENABLE_IPT_SNAT], "Y", 1) == 0) { zero_cmd_buffers(); /* Setup some parameter depending on whether we are using SNAT * or MASQUERADE. */ if(strncasecmp(opts->config[CONF_SNAT_TRANSLATE_IP], "__CHANGEME__", 10)!=0) { /* Using static SNAT */ snat_chain = &(opts->fw_config->chain[IPT_SNAT_ACCESS]); snprintf(snat_target, SNAT_TARGET_BUFSIZE-1, "--to-source %s:%i", opts->config[CONF_SNAT_TRANSLATE_IP], fst_port); } else { /* Using MASQUERADE */ snat_chain = &(opts->fw_config->chain[IPT_MASQUERADE_ACCESS]); snprintf(snat_target, SNAT_TARGET_BUFSIZE-1, "--to-ports %i", fst_port); } snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPT_ADD_SNAT_RULE_ARGS, opts->fw_config->fw_command, snat_chain->table, snat_chain->to_chain, fst_proto, nat_ip, nat_port, exp_ts, snat_chain->target, snat_target ); res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, 0); if (opts->verbose) log_msg(LOG_INFO, "process_spa_request() CMD: '%s' (res: %d, err: %s)", cmd_buf, res, err_buf); if(EXTCMD_IS_SUCCESS(res)) { log_msg(LOG_INFO, "Added Source NAT Rule to %s for %s, %s expires at %u", snat_chain->to_chain, spadat->use_src_ip, spadat->spa_message_remain, exp_ts ); snat_chain->active_rules++; /* Reset the next expected expire time for this chain if it * is warranted. */ if(snat_chain->next_expire < now || exp_ts < snat_chain->next_expire) snat_chain->next_expire = exp_ts; } else log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); } } /* Done with the port list for access rules. */ free_acc_port_list(port_list); return(res); }
/* Rule Processing - Create an access request... */ int process_spa_request(const fko_srv_options_t *opts, const acc_stanza_t *acc, spa_data_t *spadat) { char new_rule[MAX_PF_NEW_RULE_LEN]; char write_cmd[CMD_BUFSIZE]; FILE *pfctl_fd = NULL; acc_port_list_t *port_list = NULL; acc_port_list_t *ple; unsigned int fst_proto; unsigned int fst_port; int res = 0; time_t now; unsigned int exp_ts; /* Parse and expand our access message. */ expand_acc_port_list(&port_list, spadat->spa_message_remain); /* 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; /* For straight access requests, we currently support multiple proto/port * request. */ if(spadat->message_type == FKO_ACCESS_MSG || spadat->message_type == FKO_CLIENT_TIMEOUT_ACCESS_MSG) { /* Create an access command for each proto/port for the source ip. */ while(ple != NULL) { zero_cmd_buffers(); snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " PF_LIST_ANCHOR_RULES_ARGS, opts->fw_config->fw_command, opts->fw_config->anchor ); /* Cache the current anchor rule set */ res = run_extcmd(cmd_buf, cmd_out, STANDARD_CMD_OUT_BUFSIZE, 0); /* Build the new rule string */ memset(new_rule, 0x0, MAX_PF_NEW_RULE_LEN); snprintf(new_rule, MAX_PF_NEW_RULE_LEN-1, PF_ADD_RULE_ARGS "\n", ple->proto, spadat->use_src_ip, ple->port, exp_ts ); if (strlen(cmd_out) + strlen(new_rule) < STANDARD_CMD_OUT_BUFSIZE) { /* We add the rule to the running policy */ strlcat(cmd_out, new_rule, STANDARD_CMD_OUT_BUFSIZE); memset(write_cmd, 0x0, CMD_BUFSIZE); snprintf(write_cmd, CMD_BUFSIZE-1, "%s " PF_WRITE_ANCHOR_RULES_ARGS, opts->fw_config->fw_command, opts->fw_config->anchor ); if ((pfctl_fd = popen(write_cmd, "w")) == NULL) { log_msg(LOG_WARNING, "Could not execute command: %s", write_cmd); return(-1); } if (fwrite(cmd_out, strlen(cmd_out), 1, pfctl_fd) == 1) { log_msg(LOG_INFO, "Added Rule for %s, %s expires at %u", spadat->use_src_ip, spadat->spa_message_remain, exp_ts ); fwc.active_rules++; /* Reset the next expected expire time for this chain if it * is warranted. */ if(fwc.next_expire < now || exp_ts < fwc.next_expire) fwc.next_expire = exp_ts; } else log_msg(LOG_WARNING, "Could not write rule to pf anchor"); pclose(pfctl_fd); } else { /* We don't have enough room to add the new firewall rule, * so throw a warning and bail. Once some of the existing * rules are expired the user will once again be able to gain * access. Note that we don't expect to really ever hit this * limit because of STANDARD_CMD_OUT_BUFSIZE is quite a number * of anchor rules. */ log_msg(LOG_WARNING, "Max anchor rules reached, try again later."); return 0; } ple = ple->next; } } else { /* No other SPA request modes are supported yet. */ if(spadat->message_type == FKO_LOCAL_NAT_ACCESS_MSG || spadat->message_type == FKO_CLIENT_TIMEOUT_LOCAL_NAT_ACCESS_MSG) { log_msg(LOG_WARNING, "Local NAT requests are not currently supported."); } else if(spadat->message_type == FKO_NAT_ACCESS_MSG || spadat->message_type == FKO_CLIENT_TIMEOUT_NAT_ACCESS_MSG) { log_msg(LOG_WARNING, "Forwarding/NAT requests are not currently supported."); } return(-1); } return(res); }
static void ipt_chk_support(const fko_srv_options_t * const opts) { int res = 1; struct fw_chain *in_chain = &(opts->fw_config->chain[IPT_INPUT_ACCESS]); zero_cmd_buffers(); /* Add a harmless rule to the iptables INPUT chain and see if iptables * supports '-C' to check for it. Set "have_ipt_chk_support" accordingly, * delete the rule, and return. */ snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPT_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, "ipt_chk_support() CMD: '%s' (res: %d, err: %s)", cmd_buf, res, err_buf); zero_cmd_buffers(); /* Now see if '-C' works - any output indicates failure */ snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPT_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, "ipt_chk_support() CMD: '%s' (res: %d, err: %s)", cmd_buf, res, err_buf); if(EXTCMD_IS_SUCCESS(res) && strlen(err_buf)) { log_msg(LOG_DEBUG, "ipt_chk_support() -C not supported"); have_ipt_chk_support = 0; } else { log_msg(LOG_DEBUG, "ipt_chk_support() -C supported"); have_ipt_chk_support = 1; } /* Delete the tmp rule */ zero_cmd_buffers(); snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPT_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[IPT_INPUT_ACCESS]); zero_cmd_buffers(); /* Add a harmless rule to the iptables 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 " IPT_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); 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 " IPT_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, "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 " IPT_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; }
/* Iterate over the configure firewall access chains and purge expired * firewall rules. */ void check_firewall_rules(const fko_srv_options_t *opts) { char exp_str[12]; char anchor_rules_copy[STANDARD_CMD_OUT_BUFSIZE]; char write_cmd[CMD_BUFSIZE]; char *ndx, *tmp_mark, *tmp_ndx, *newline_tmp_ndx; time_t now, rule_exp, min_exp=0; int i=0, res=0, anchor_ndx=0, is_delete=0; FILE *pfctl_fd = NULL; /* If we have not yet reached our expected next expire time, continue. */ if(fwc.next_expire == 0) return; time(&now); if (fwc.next_expire > now) return; zero_cmd_buffers(); /* There should be a rule to delete. Get the current list of * rules and delete the ones that are expired. */ snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " PF_LIST_ANCHOR_RULES_ARGS, opts->fw_config->fw_command, opts->fw_config->anchor ); res = run_extcmd(cmd_buf, cmd_out, STANDARD_CMD_OUT_BUFSIZE, 0); if(!EXTCMD_IS_SUCCESS(res)) { log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, cmd_out); return; } /* Find the first _exp_ string (if any). */ ndx = strstr(cmd_out, EXPIRE_COMMENT_PREFIX); if(ndx == NULL) { /* we did not find an expected rule. */ log_msg(LOG_ERR, "Did not find expire comment in rules list %i.\n", i); return; } memset(anchor_rules_copy, 0x0, STANDARD_CMD_OUT_BUFSIZE); /* 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, 11); rule_exp = (time_t)atoll(exp_str); if(rule_exp <= now) { /* We are going to delete this rule, and because we rebuild the * PF anchor to include all rules that haven't expired, to delete * this rule we just skip to the next one. */ log_msg(LOG_INFO, "Deleting rule with expire time of %u.", rule_exp); if (fwc.active_rules > 0) fwc.active_rules--; is_delete = 1; } else { /* The rule has not expired, so copy it into the anchor string that * lists current rules and will be used to feed * 'pfctl -a <anchor> -f -'. */ /* back up to the previous newline or the beginning of the rules * output string. */ tmp_ndx = ndx; while(--tmp_ndx > cmd_out) { if(*tmp_ndx == '\n') break; } if(*tmp_ndx == '\n') { tmp_ndx++; } /* may sure the rule begins with the string "pass", and make sure * it ends with a newline. Bail if either test fails. */ if (strlen(tmp_ndx) <= strlen("pass") || strncmp(tmp_ndx, "pass", strlen("pass")) != 0) { break; } newline_tmp_ndx = tmp_ndx; while (*newline_tmp_ndx != '\n' && *newline_tmp_ndx != '\0') { newline_tmp_ndx++; } if (*newline_tmp_ndx != '\n') break; /* copy the whole rule to the next newline (includes the expiration time). */ while (*tmp_ndx != '\n' && *tmp_ndx != '\0' && anchor_ndx < STANDARD_CMD_OUT_BUFSIZE) { anchor_rules_copy[anchor_ndx] = *tmp_ndx; tmp_ndx++; anchor_ndx++; } anchor_rules_copy[anchor_ndx] = '\n'; anchor_ndx++; /* Track the minimum future rule expire time. */ if(rule_exp > now) 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); } if (is_delete) { /* We re-instantiate the anchor rules with the new rules string that * has the rule(s) deleted. If there isn't at least one "pass" rule, * then we just flush the anchor. */ if (strlen(anchor_rules_copy) > strlen("pass") && strncmp(anchor_rules_copy, "pass", strlen("pass")) == 0) { memset(write_cmd, 0x0, CMD_BUFSIZE); snprintf(write_cmd, CMD_BUFSIZE-1, "%s " PF_WRITE_ANCHOR_RULES_ARGS, opts->fw_config->fw_command, opts->fw_config->anchor ); if ((pfctl_fd = popen(write_cmd, "w")) == NULL) { log_msg(LOG_WARNING, "Could not execute command: %s", write_cmd); return; } if (fwrite(anchor_rules_copy, strlen(anchor_rules_copy), 1, pfctl_fd) != 1) { log_msg(LOG_WARNING, "Could not write rules to pf anchor"); } pclose(pfctl_fd); } else { delete_all_anchor_rules(opts); } } /* 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(fwc.active_rules < 1) fwc.next_expire = 0; else if(min_exp) fwc.next_expire = min_exp; return; }
/* 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, res, got_err = 0; struct fw_chain *ch = opts->fw_config->chain; if (opts->fw_list_all == 1) { fprintf(stdout, "Listing all iptables 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 " IPT_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, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); got_err++; } } } else { fprintf(stdout, "Listing rules in fwknopd iptables 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 " IPT_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, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); got_err++; } } } return(got_err); }
/* Process command messages */ static int process_cmd_msg(fko_srv_options_t *opts, acc_stanza_t *acc, spa_data_t *spadat, const int stanza_num, int *res) { int pid_status=0; char cmd_buf[MAX_SPA_CMD_LEN] = {0}; if(!acc->enable_cmd_exec) { log_msg(LOG_WARNING, "[%s] (stanza #%d) SPA Command messages are not allowed in the current configuration.", spadat->pkt_source_ip, stanza_num ); return 0; } else if(opts->test) { log_msg(LOG_WARNING, "[%s] (stanza #%d) --test mode enabled, skipping command execution.", spadat->pkt_source_ip, stanza_num ); return 0; } else { log_msg(LOG_INFO, "[%s] (stanza #%d) Processing SPA Command message: command='%s'.", spadat->pkt_source_ip, stanza_num, spadat->spa_message_remain ); memset(cmd_buf, 0x0, sizeof(cmd_buf)); if(acc->enable_cmd_sudo_exec) { /* Run the command via sudo - this allows sudo filtering * to apply to the incoming command */ strlcpy(cmd_buf, opts->config[CONF_SUDO_EXE], sizeof(cmd_buf)); if(acc->cmd_sudo_exec_user != NULL && strncasecmp(acc->cmd_sudo_exec_user, "root", 4) != 0) { strlcat(cmd_buf, " -u ", sizeof(cmd_buf)); strlcat(cmd_buf, acc->cmd_sudo_exec_user, sizeof(cmd_buf)); } if(acc->cmd_exec_group != NULL && strncasecmp(acc->cmd_sudo_exec_group, "root", 4) != 0) { strlcat(cmd_buf, " -g ", sizeof(cmd_buf)); strlcat(cmd_buf, acc->cmd_sudo_exec_group, sizeof(cmd_buf)); } strlcat(cmd_buf, " ", sizeof(cmd_buf)); strlcat(cmd_buf, spadat->spa_message_remain, sizeof(cmd_buf)); } else strlcpy(cmd_buf, spadat->spa_message_remain, sizeof(cmd_buf)); if(acc->cmd_exec_user != NULL && strncasecmp(acc->cmd_exec_user, "root", 4) != 0) { log_msg(LOG_INFO, "[%s] (stanza #%d) Running command '%s' setuid/setgid user/group to %s/%s (UID=%i,GID=%i)", spadat->pkt_source_ip, stanza_num, cmd_buf, acc->cmd_exec_user, acc->cmd_exec_group == NULL ? acc->cmd_exec_user : acc->cmd_exec_group, acc->cmd_exec_uid, acc->cmd_exec_gid); *res = run_extcmd_as(acc->cmd_exec_uid, acc->cmd_exec_gid, cmd_buf, NULL, 0, WANT_STDERR, NO_TIMEOUT, &pid_status, opts); } else /* Just run it as we are (root that is). */ { log_msg(LOG_INFO, "[%s] (stanza #%d) Running command '%s'", spadat->pkt_source_ip, stanza_num, cmd_buf); *res = run_extcmd(cmd_buf, NULL, 0, WANT_STDERR, 5, &pid_status, opts); } /* should only call WEXITSTATUS() if WIFEXITED() is true */ log_msg(LOG_INFO, "[%s] (stanza #%d) CMD_EXEC: command returned %i, pid_status: %d", spadat->pkt_source_ip, stanza_num, *res, WIFEXITED(pid_status) ? WEXITSTATUS(pid_status) : pid_status); if(WIFEXITED(pid_status)) { if(WEXITSTATUS(pid_status) != 0) *res = SPA_MSG_COMMAND_ERROR; } else *res = SPA_MSG_COMMAND_ERROR; } return 1; }
/* 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 " IPT_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, "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 " IPT_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, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); zero_cmd_buffers(); snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPT_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, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); } return; }