Added additional sanity checks and clean-up of access.conf processing and functionality. Fixes require source and added check for required username. Added fallback to use GPG_DECRYPT_PW if it was set and the normal KEY failed with a decyption error. Fixed packet count checks to allow a limit of 0 to mean unlimited number of packets.
git-svn-id: file:///home/mbr/svn/fwknop/trunk@203 510a4753-2344-4c79-9c09-4d669213fbeb
This commit is contained in:
@@ -103,6 +103,7 @@ AC_TYPE_UINT64_T
|
||||
AC_TYPE_OFF_T
|
||||
AC_TYPE_PID_T
|
||||
AC_TYPE_SIZE_T
|
||||
AC_TYPE_SSIZE_T
|
||||
|
||||
AC_CHECK_SIZEOF(unsigned int)
|
||||
dnl AC_CHECK_TYPES([uint8_t, uint32_t])
|
||||
|
||||
+6
-4
@@ -406,16 +406,18 @@ the correct message format and content. These message types are:
|
||||
@table @code
|
||||
@item FKO_COMMAND_MSG
|
||||
A request to have the fwknop server execute the given command. The format
|
||||
for this type is: @samp{<ip of requestor>:<command text>}
|
||||
for this type is: @samp{<ip of requestor>,<command text>}.
|
||||
@example
|
||||
"192.168.1.2:uname -a"
|
||||
"192.168.1.2,uname -a"
|
||||
@end example
|
||||
|
||||
@item FKO_ACCESS_MSG
|
||||
A basic access request. This is the most common type in use. The format
|
||||
for this type is: @samp{<ip of requestor>:<protocol>/<port>}.
|
||||
for this type is: @samp{<ip of requestor>,<protocol>/<port>}. Note that
|
||||
mulitple protocol/port entries are allowed.
|
||||
@example
|
||||
"192.168.1.2:tcp/22"
|
||||
"192.168.1.2,tcp/22"
|
||||
"192.168.1.2,tcp/22,udp/5005"
|
||||
@end example
|
||||
|
||||
@item FKO_NAT_ACCESS_MSG
|
||||
|
||||
+61
-9
@@ -578,6 +578,23 @@ set_acc_defaults(fko_srv_options_t *opts)
|
||||
}
|
||||
}
|
||||
|
||||
/* Perform some sanity checks on an acc stanza data.
|
||||
*/
|
||||
int
|
||||
acc_data_is_valid(acc_stanza_t *acc)
|
||||
{
|
||||
if((acc->key == NULL || !strlen(acc->key))
|
||||
&& (acc->gpg_decrypt_pw == NULL || !strlen(acc->gpg_decrypt_pw)))
|
||||
{
|
||||
fprintf(stderr,
|
||||
"[*] No keys found for access stanza source: '%s'\n", acc->source
|
||||
);
|
||||
return(0);
|
||||
}
|
||||
|
||||
return(1);
|
||||
}
|
||||
|
||||
/* Read and parse the access file, popluating the access data as we go.
|
||||
*/
|
||||
void
|
||||
@@ -585,7 +602,7 @@ parse_access_file(fko_srv_options_t *opts)
|
||||
{
|
||||
FILE *file_ptr;
|
||||
char *ndx;
|
||||
int got_source = 0, got_open_ports = 0, got_key = 0;
|
||||
int got_source = 0;
|
||||
unsigned int num_lines = 0;
|
||||
|
||||
char access_line_buf[MAX_LINE_LEN] = {0};
|
||||
@@ -665,11 +682,25 @@ parse_access_file(fko_srv_options_t *opts)
|
||||
|
||||
if(CONF_VAR_IS(var, "SOURCE"))
|
||||
{
|
||||
/* If this is not the first stanza, sanity check the previous
|
||||
* stanza for the minimum required data.
|
||||
*/
|
||||
if(curr_acc != NULL) {
|
||||
if(!acc_data_is_valid(curr_acc))
|
||||
{
|
||||
fprintf(stderr,
|
||||
"[*] Data error in access file: '%s'\n",
|
||||
opts->config[CONF_ACCESS_FILE]);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
/* Start new stanza.
|
||||
*/
|
||||
curr_acc = acc_stanza_add(opts);
|
||||
|
||||
add_acc_string(&(curr_acc->source), val);
|
||||
|
||||
got_source++;
|
||||
}
|
||||
else if (curr_acc == NULL)
|
||||
@@ -681,7 +712,6 @@ parse_access_file(fko_srv_options_t *opts)
|
||||
else if(CONF_VAR_IS(var, "OPEN_PORTS"))
|
||||
{
|
||||
add_acc_string(&(curr_acc->open_ports), val);
|
||||
got_open_ports++;
|
||||
}
|
||||
else if(CONF_VAR_IS(var, "RESTRICT_PORTS"))
|
||||
{
|
||||
@@ -689,8 +719,14 @@ parse_access_file(fko_srv_options_t *opts)
|
||||
}
|
||||
else if(CONF_VAR_IS(var, "KEY"))
|
||||
{
|
||||
if(strcasecmp(val, "__CHANGEME__") == 0)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"[*] KEY value is not properly set in stanza source '%s' in access file: '%s'\n",
|
||||
curr_acc->source, opts->config[CONF_ACCESS_FILE]);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
add_acc_string(&(curr_acc->key), val);
|
||||
got_key++;
|
||||
}
|
||||
else if(CONF_VAR_IS(var, "FW_ACCESS_TIMEOUT"))
|
||||
{
|
||||
@@ -706,7 +742,7 @@ parse_access_file(fko_srv_options_t *opts)
|
||||
}
|
||||
else if(CONF_VAR_IS(var, "REQUIRE_USERNAME"))
|
||||
{
|
||||
add_acc_bool(&(curr_acc->require_username), val);
|
||||
add_acc_string(&(curr_acc->require_username), val);
|
||||
}
|
||||
else if(CONF_VAR_IS(var, "REQUIRE_SOURCE_ADDRESS"))
|
||||
{
|
||||
@@ -722,6 +758,13 @@ parse_access_file(fko_srv_options_t *opts)
|
||||
}
|
||||
else if(CONF_VAR_IS(var, "GPG_DECRYPT_PW"))
|
||||
{
|
||||
if(strcasecmp(val, "__CHANGEME__") == 0)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"[*] GPG_DECRYPT_PW value is not properly set in stanza source '%s' in access file: '%s'\n",
|
||||
curr_acc->source, opts->config[CONF_ACCESS_FILE]);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
add_acc_string(&(curr_acc->gpg_decrypt_pw), val);
|
||||
}
|
||||
else if(CONF_VAR_IS(var, "GPG_REMOTE_ID"))
|
||||
@@ -740,11 +783,10 @@ parse_access_file(fko_srv_options_t *opts)
|
||||
fclose(file_ptr);
|
||||
|
||||
/* Basic check to ensure that we got at least one SOURCE stanza with
|
||||
* the OPEN_PORTS and KEY variables defined.
|
||||
* a valid KEY defined (valid meaning it has a value that is not
|
||||
* "__CHANGEME__".
|
||||
*/
|
||||
if (got_source == 0
|
||||
|| got_open_ports == 0
|
||||
|| got_key == 0)
|
||||
if (got_source == 0)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"[*] Could not find valid SOURCE stanza in access file: '%s'\n",
|
||||
@@ -752,6 +794,16 @@ parse_access_file(fko_srv_options_t *opts)
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* Sanity check the last stanza
|
||||
*/
|
||||
if(!acc_data_is_valid(curr_acc))
|
||||
{
|
||||
fprintf(stderr,
|
||||
"[*] Data error in access file: '%s'\n",
|
||||
opts->config[CONF_ACCESS_FILE]);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* Expand our the expandable fields into their respective data buckets.
|
||||
*/
|
||||
expand_acc_ent_lists(opts);
|
||||
@@ -933,7 +985,7 @@ dump_access_list(fko_srv_options_t *opts)
|
||||
acc->fw_access_timeout,
|
||||
acc->enable_cmd_exec ? "Yes" : "No",
|
||||
(acc->cmd_regex == NULL) ? "<not set>" : acc->cmd_regex,
|
||||
acc->require_username ? "Yes" : "No",
|
||||
(acc->require_username == NULL) ? "<not set>" : acc->require_username,
|
||||
acc->require_source_address ? "Yes" : "No",
|
||||
(acc->gpg_home_dir == NULL) ? "<not set>" : acc->gpg_home_dir,
|
||||
(acc->gpg_decrypt_id == NULL) ? "<not set>" : acc->gpg_decrypt_id,
|
||||
|
||||
+1
-1
@@ -155,5 +155,5 @@ FW_ACCESS_TIMEOUT: 30;
|
||||
#
|
||||
#GPG_HOME_DIR: /root/.gnupg;
|
||||
#GPG_DECRYPT_ID: ABCD1234;
|
||||
#GPG_DECRYPT_PW: myGpgPassword;
|
||||
#GPG_DECRYPT_PW: __CHANGEME__;
|
||||
#GPG_REMOTE_ID: 1234ABCD;
|
||||
|
||||
@@ -308,7 +308,7 @@ typedef struct acc_stanza
|
||||
int fw_access_timeout;
|
||||
unsigned char enable_cmd_exec;
|
||||
char *cmd_regex;
|
||||
unsigned char require_username;
|
||||
char *require_username;
|
||||
unsigned char require_source_address;
|
||||
char *gpg_home_dir;
|
||||
char *gpg_decrypt_id;
|
||||
|
||||
+87
-24
@@ -34,9 +34,9 @@ int
|
||||
incoming_spa(fko_srv_options_t *opts)
|
||||
{
|
||||
fko_ctx_t ctx;
|
||||
char *ndx;
|
||||
char spa_src_ip[16];
|
||||
char spa_protoport[128];
|
||||
char *spa_ip_demark;
|
||||
char spa_msg_src_ip[16];
|
||||
char spa_msg_remain[1024]; /* --DSS should not have arbitrary limit */
|
||||
time_t now_ts;
|
||||
int res;
|
||||
int ts_diff;
|
||||
@@ -48,6 +48,7 @@ incoming_spa(fko_srv_options_t *opts)
|
||||
*/
|
||||
time_t spa_ts;
|
||||
short spa_msg_type;
|
||||
char *spa_username;
|
||||
char *spa_msg;
|
||||
char *spa_nat_access;
|
||||
int spa_client_timeout;
|
||||
@@ -75,14 +76,35 @@ incoming_spa(fko_srv_options_t *opts)
|
||||
fprintf(stderr, "SPA Packet: '%s'\n", spa_pkt->packet_data);
|
||||
/* --DSS temp */
|
||||
|
||||
/* Decode the packet data
|
||||
/* Decode the packet data. Try the plain key, then fallback to the gpg
|
||||
* decrypt pw.
|
||||
*/
|
||||
res = fko_new_with_data(&ctx, spa_pkt->packet_data, acc->key);
|
||||
if(acc->key != NULL)
|
||||
{
|
||||
res = fko_new_with_data(&ctx, spa_pkt->packet_data, acc->key);
|
||||
|
||||
/* Reset the packet data length to 0.
|
||||
/* If we had a decryption failure, fallback to gpg if we have a
|
||||
* decryption key to try.
|
||||
*/
|
||||
if(res == FKO_ERROR_DECRYPTION_FAILURE && acc->gpg_decrypt_pw != NULL)
|
||||
res = fko_new_with_data(&ctx, spa_pkt->packet_data, acc->gpg_decrypt_pw);
|
||||
}
|
||||
else if(acc->gpg_decrypt_pw != NULL)
|
||||
{
|
||||
/* Otherwise this is probably a GPG-only stanza...
|
||||
*/
|
||||
res = fko_new_with_data(&ctx, spa_pkt->packet_data, acc->gpg_decrypt_pw);
|
||||
}
|
||||
|
||||
/* Reset the packet data length to 0. This our indicator to the rest of
|
||||
* the program that we do not have a current spa packet to process
|
||||
* (whcih we won't be the time we return from this function for whatever
|
||||
* reason.
|
||||
*/
|
||||
spa_pkt->packet_data_len = 0;
|
||||
|
||||
/* Do we have a valid FKO context?
|
||||
*/
|
||||
if(res != FKO_SUCCESS)
|
||||
{
|
||||
log_msg(LOG_WARNING|LOG_STDERR, "Error creating fko context: %s",
|
||||
@@ -141,6 +163,60 @@ display_ctx(ctx);
|
||||
fko_errstr(res));
|
||||
got_spa_error++;
|
||||
}
|
||||
|
||||
/* 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(spa_msg, ',');
|
||||
if(spa_ip_demark == NULL)
|
||||
{
|
||||
log_msg(LOG_WARNING|LOG_STDERR, "Error parsing SPA message string: %s",
|
||||
fko_errstr(res));
|
||||
res = SPA_MSG_ERROR;
|
||||
goto clean_and_bail;
|
||||
}
|
||||
|
||||
strlcpy(spa_msg_src_ip, spa_msg, (spa_ip_demark-spa_msg)+1);
|
||||
strlcpy(spa_msg_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(spa_msg_src_ip, "0.0.0.0") == 0) && acc->require_source_address)
|
||||
{
|
||||
log_msg(LOG_WARNING|LOG_STDERR,
|
||||
"Got 0.0.0.0 when valid source IP was required."
|
||||
);
|
||||
res = SPA_MSG_ACCESS_DENIED;
|
||||
goto clean_and_bail;
|
||||
}
|
||||
|
||||
/* If REQUIRE_USERNAME is set, make sure the username in this SPA data
|
||||
* matches.
|
||||
*/
|
||||
if(acc->require_username != NULL)
|
||||
{
|
||||
if(fko_get_username(ctx, &spa_username) != FKO_SUCCESS)
|
||||
{
|
||||
log_msg(LOG_WARNING|LOG_STDERR, "Error getting SPA username string: %s",
|
||||
fko_errstr(res));
|
||||
got_spa_error++;
|
||||
}
|
||||
|
||||
if(strcmp(spa_username, acc->require_username) != 0)
|
||||
{
|
||||
log_msg(LOG_WARNING|LOG_STDERR,
|
||||
"Username in SPA data (%s) does not match required username: %s",
|
||||
spa_username, acc->require_username
|
||||
);
|
||||
res = SPA_MSG_ACCESS_DENIED;
|
||||
goto clean_and_bail;
|
||||
}
|
||||
}
|
||||
|
||||
/* Get the rest of our SPA data fields.
|
||||
*/
|
||||
if(fko_get_spa_nat_access(ctx, &spa_nat_access) != FKO_SUCCESS)
|
||||
{
|
||||
log_msg(LOG_WARNING|LOG_STDERR, "Error getting SPA nat access string: %s",
|
||||
@@ -170,7 +246,7 @@ display_ctx(ctx);
|
||||
if(!acc->enable_cmd_exec)
|
||||
{
|
||||
log_msg(LOG_WARNING|LOG_STDERR,
|
||||
"SPA Command message are not allowed or supported."
|
||||
"SPA Command message are not yet allowed or supported."
|
||||
);
|
||||
res = SPA_MSG_ACCESS_DENIED;
|
||||
}
|
||||
@@ -210,29 +286,16 @@ display_ctx(ctx);
|
||||
*/
|
||||
case FKO_ACCESS_MSG:
|
||||
case FKO_CLIENT_TIMEOUT_ACCESS_MSG:
|
||||
/* Parse it into its components (source IP and proto/port list).
|
||||
*/
|
||||
ndx = strchr(spa_msg, ',');
|
||||
if(ndx == NULL)
|
||||
{
|
||||
log_msg(LOG_WARNING|LOG_STDERR, "Error parsing SPA message string: %s",
|
||||
fko_errstr(res));
|
||||
res = SPA_MSG_ERROR;
|
||||
break;
|
||||
}
|
||||
|
||||
strlcpy(spa_src_ip, spa_msg, (ndx-spa_msg)+1);
|
||||
strlcpy(spa_protoport, ndx+1, 128);
|
||||
|
||||
/* Check access against restrict_ports and open_ports.
|
||||
*/
|
||||
res = acc_check_port_access(acc, spa_protoport);
|
||||
res = acc_check_port_access(acc, spa_msg_remain);
|
||||
|
||||
// --DSS temp
|
||||
log_msg(LOG_WARNING|LOG_STDERR,
|
||||
"This SPA access msg would%s be allowed.\n Either way we need code to implement it. :)",
|
||||
res ? "":" *NOT*"
|
||||
"<<<< This SPA access msg would be %s >>>>",
|
||||
res ? "allowed":"DENIED due to port restictions"
|
||||
);
|
||||
|
||||
res = SPA_MSG_NOT_SUPPORTED;
|
||||
|
||||
/* --DSS TODO: Finish Me */
|
||||
|
||||
+15
-13
@@ -31,6 +31,7 @@
|
||||
#include "incoming_spa.h"
|
||||
#include "config_init.h"
|
||||
#include "sig_handler.h"
|
||||
#include "log_msg.h"
|
||||
|
||||
/* The pcap capture routine.
|
||||
*/
|
||||
@@ -61,18 +62,18 @@ pcap_capture(fko_srv_options_t *opts)
|
||||
|
||||
if(pcap == NULL)
|
||||
{
|
||||
fprintf(stderr, "* pcap_open_live error: %s\n", errstr);
|
||||
log_msg(LOG_ERR|LOG_STDERR, "* pcap_open_live error: %s\n", errstr);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* We are only interested on seeing packets coming into the interface.
|
||||
*/
|
||||
if (pcap_setdirection(pcap, PCAP_D_IN) < 0)
|
||||
fprintf(stderr, "* Warning: pcap error on setdirection\n");
|
||||
log_msg(LOG_WARNING|LOG_STDERR, "* Warning: pcap error on setdirection");
|
||||
|
||||
if (pcap == NULL)
|
||||
{
|
||||
fprintf(stderr, "[*] pcap error: %s\n", errstr);
|
||||
log_msg(LOG_ERR|LOG_STDERR, "[*] pcap error: %s", errstr);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
@@ -83,7 +84,7 @@ pcap_capture(fko_srv_options_t *opts)
|
||||
{
|
||||
if(pcap_compile(pcap, &fp, opts->config[CONF_PCAP_FILTER], 1, 0) == -1)
|
||||
{
|
||||
fprintf(stderr, "[*] Error compiling pcap filter: %s\n",
|
||||
log_msg(LOG_ERR|LOG_STDERR, "[*] Error compiling pcap filter: %s",
|
||||
pcap_geterr(pcap)
|
||||
);
|
||||
exit(EXIT_FAILURE);
|
||||
@@ -91,7 +92,7 @@ pcap_capture(fko_srv_options_t *opts)
|
||||
|
||||
if(pcap_setfilter(pcap, &fp) == -1)
|
||||
{
|
||||
fprintf(stderr, "[*] Error setting pcap filter: %s\n",
|
||||
log_msg(LOG_ERR|LOG_STDERR, "[*] Error setting pcap filter: %s",
|
||||
pcap_geterr(pcap)
|
||||
);
|
||||
exit(EXIT_FAILURE);
|
||||
@@ -121,7 +122,7 @@ pcap_capture(fko_srv_options_t *opts)
|
||||
*/
|
||||
if((pcap_setnonblock(pcap, 1, errstr)) == -1)
|
||||
{
|
||||
fprintf(stderr, "[*] Error setting pcap to non-blocking: %s\n",
|
||||
log_msg(LOG_ERR|LOG_STDERR, "[*] Error setting pcap to non-blocking: %s",
|
||||
errstr
|
||||
);
|
||||
exit(EXIT_FAILURE);
|
||||
@@ -159,28 +160,29 @@ pcap_capture(fko_srv_options_t *opts)
|
||||
* of SPA packet validity at this point.
|
||||
*/
|
||||
opts->packet_ctr++;
|
||||
if (opts->packet_ctr >= opts->packet_ctr_limit)
|
||||
if (opts->packet_ctr_limit && opts->packet_ctr >= opts->packet_ctr_limit)
|
||||
{
|
||||
log_msg(LOG_WARNING|LOG_STDERR,
|
||||
"* Incoming packet count limit of %i reached",
|
||||
opts->packet_ctr_limit
|
||||
);
|
||||
|
||||
pcap_breakloop(pcap);
|
||||
pending_break = 1;
|
||||
}
|
||||
|
||||
pcap_errcnt = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* If there was an error, complain and go on (to an extent
|
||||
* before giving up).
|
||||
*/
|
||||
else if(res == -1)
|
||||
{
|
||||
fprintf(stderr, "[*] Error from pcap_dispatch: %s\n",
|
||||
log_msg(LOG_ERR|LOG_STDERR, "[*] Error from pcap_dispatch: %s",
|
||||
pcap_geterr(pcap)
|
||||
);
|
||||
|
||||
if(pcap_errcnt++ > MAX_PCAP_ERRORS_BEFORE_BAIL)
|
||||
{
|
||||
fprintf(stderr, "[*] %i consecutive pcap errors. Giving up\n",
|
||||
log_msg(LOG_ERR|LOG_STDERR, "[*] %i consecutive pcap errors. Giving up",
|
||||
pcap_errcnt
|
||||
);
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
Reference in New Issue
Block a user