diff --git a/configure.ac b/configure.ac index 0525b3d3..9b897001 100644 --- a/configure.ac +++ b/configure.ac @@ -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]) diff --git a/doc/libfko.texi b/doc/libfko.texi index f69fe410..c3464f5c 100644 --- a/doc/libfko.texi +++ b/doc/libfko.texi @@ -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{:} +for this type is: @samp{,}. @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{:/}. +for this type is: @samp{,/}. 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 diff --git a/server/access.c b/server/access.c index 1ac63259..417cf8c9 100644 --- a/server/access.c +++ b/server/access.c @@ -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) ? "" : acc->cmd_regex, - acc->require_username ? "Yes" : "No", + (acc->require_username == NULL) ? "" : acc->require_username, acc->require_source_address ? "Yes" : "No", (acc->gpg_home_dir == NULL) ? "" : acc->gpg_home_dir, (acc->gpg_decrypt_id == NULL) ? "" : acc->gpg_decrypt_id, diff --git a/server/access.conf b/server/access.conf index ef9fb3cf..890dba68 100644 --- a/server/access.conf +++ b/server/access.conf @@ -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; diff --git a/server/fwknopd_common.h b/server/fwknopd_common.h index a3c0e0c4..549674ca 100644 --- a/server/fwknopd_common.h +++ b/server/fwknopd_common.h @@ -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; diff --git a/server/incoming_spa.c b/server/incoming_spa.c index 52f83872..741e1e51 100644 --- a/server/incoming_spa.c +++ b/server/incoming_spa.c @@ -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 */ diff --git a/server/pcap_capture.c b/server/pcap_capture.c index 6589e6a9..671c40fe 100644 --- a/server/pcap_capture.c +++ b/server/pcap_capture.c @@ -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);