diff --git a/server/access.c b/server/access.c index 8b62cd9e..9c9bb142 100644 --- a/server/access.c +++ b/server/access.c @@ -29,6 +29,7 @@ #include #endif #include +#include "pwd.h" #include "fwknopd_common.h" #include "access.h" @@ -434,8 +435,8 @@ free_acc_stanza_data(acc_stanza_t *acc) if(acc->key != NULL) free(acc->key); - if(acc->cmd_regex != NULL) - free(acc->cmd_regex); + if(acc->cmd_exec_user != NULL) + free(acc->cmd_exec_user); if(acc->gpg_home_dir != NULL) free(acc->gpg_home_dir); @@ -611,6 +612,7 @@ parse_access_file(fko_srv_options_t *opts) char tmp1[MAX_LINE_LEN] = {0}; char tmp2[MAX_LINE_LEN] = {0}; + struct passwd *pw; struct stat st; acc_stanza_t *curr_acc = NULL; @@ -736,9 +738,21 @@ parse_access_file(fko_srv_options_t *opts) { add_acc_bool(&(curr_acc->enable_cmd_exec), val); } - else if(CONF_VAR_IS(var, "CMD_REGEX")) + else if(CONF_VAR_IS(var, "CMD_EXEC_USER")) { - add_acc_string(&(curr_acc->cmd_regex), val); + add_acc_string(&(curr_acc->cmd_exec_user), val); + + errno = 0; + pw = getpwnam(val); + + if(pw == NULL) + { + fprintf(stderr, "Unable to determine UID for CMD_EXEC_USER: %s.\n", + errno ? strerror(errno) : "Not a user on this system"); + exit(EXIT_FAILURE); + } + + curr_acc->cmd_exec_uid = pw->pw_uid; } else if(CONF_VAR_IS(var, "REQUIRE_USERNAME")) { @@ -970,7 +984,7 @@ dump_access_list(fko_srv_options_t *opts) " KEY: %s\n" " FW_ACCESS_TIMEOUT: %i\n" " ENABLE_CMD_EXEC: %s\n" - " CMD_REGEX: %s\n" + " CMD_EXEC_USER: %s\n" " REQUIRE_USERNAME: %s\n" " REQUIRE_SOURCE_ADDRESS: %s\n" " GPG_HOME_DIR: %s\n" @@ -984,7 +998,7 @@ dump_access_list(fko_srv_options_t *opts) (acc->key == NULL) ? "" : acc->key, acc->fw_access_timeout, acc->enable_cmd_exec ? "Yes" : "No", - (acc->cmd_regex == NULL) ? "" : acc->cmd_regex, + (acc->cmd_exec_user == NULL) ? "" : acc->cmd_exec_user, (acc->require_username == NULL) ? "" : acc->require_username, acc->require_source_address ? "Yes" : "No", (acc->gpg_home_dir == NULL) ? "" : acc->gpg_home_dir, diff --git a/server/extcmd.c b/server/extcmd.c index 0ed1daa9..0e705679 100644 --- a/server/extcmd.c +++ b/server/extcmd.c @@ -46,7 +46,7 @@ set_nonblock(int fd) * buffers with STDOUT an STDERR up to the size provided. */ int -run_extcmd(char *cmd, char *so_buf, char *se_buf, size_t so_buf_sz, size_t se_buf_sz, int *status) +_run_extcmd(uid_t user_uid, char *cmd, char *so_buf, char *se_buf, size_t so_buf_sz, size_t se_buf_sz, int *status) { pid_t pid; @@ -102,9 +102,20 @@ run_extcmd(char *cmd, char *so_buf, char *se_buf, size_t so_buf_sz, size_t se_bu close(so[0]); close(se[0]); - /* --DSS XXX: It would be more efficient to use one of the exec() - * calls. (i.e. 'return(execvp(ext_cmd, &argv[1]));' ). - * but for now, we use system() and exit with the external + /* If user is not null, then we setuid to that user before running the + * command. + */ + if(user_uid > 0) + { + if(setuid(user_uid) < 0) + { + exit(EXTCMD_SETUID_ERROR); + } + } + + /* --DSS XXX: Would it be more efficient to use one of the exec() + * calls (i.e. 'return(execvp(ext_cmd, &argv[1]));')? + * For now, we use system() and exit with the external * command exit status. */ exit(WEXITSTATUS(system(cmd))); @@ -267,3 +278,18 @@ run_extcmd(char *cmd, char *so_buf, char *se_buf, size_t so_buf_sz, size_t se_bu */ return(retval); } + +/* Run an external command. This is wrapper around _run_extcmd() +*/ +run_extcmd(char *cmd, char *so_buf, char *se_buf, size_t so_buf_sz, size_t se_buf_sz, int *status) +{ + return _run_extcmd(0, cmd, so_buf, se_buf, so_buf_sz, se_buf_sz, status); +} + +/* Run an external command as the specified user. This is wrapper around _run_extcmd() +*/ +run_extcmd_as(uid_t user_uid, char *cmd, char *so_buf, char *se_buf, size_t so_buf_sz, size_t se_buf_sz, int *status) +{ + return _run_extcmd(user_uid, cmd, so_buf, se_buf, so_buf_sz, se_buf_sz, status); +} + diff --git a/server/extcmd.h b/server/extcmd.h index 881dcf77..9f7f17a8 100644 --- a/server/extcmd.h +++ b/server/extcmd.h @@ -33,6 +33,7 @@ * may end up in. */ enum { + EXTCMD_SETUID_ERROR = -4, EXTCMD_SELECT_ERROR = -3, EXTCMD_PIPE_ERROR = -2, EXTCMD_FORK_ERROR = -1, @@ -66,6 +67,7 @@ enum { /* Function prototypes */ int run_extcmd(char *cmd, char *so_buf, char *se_buf, size_t so_buf_sz, size_t se_buf_sz, int *status); +int run_extcmd_as(uid_t uid, char *cmd, char *so_buf, char *se_buf, size_t so_buf_sz, size_t se_buf_sz, int *status); #endif /* EXTCMD_H */ diff --git a/server/fw_util.c b/server/fw_util.c index 368b5a84..9a1511d2 100644 --- a/server/fw_util.c +++ b/server/fw_util.c @@ -372,7 +372,7 @@ fw_initialize(fko_srv_options_t *opts) * */ if(opts->config[CONF_SNAT_TRANSLATE_IP] != NULL - && strncasecmp(opts->config[CONF_SNAT_TRANSLATE_IP], "_CHANGEME_", 10)!=0) + && strncasecmp(opts->config[CONF_SNAT_TRANSLATE_IP], "__CHANGEME__", 10)!=0) { if(opts->config[CONF_IPT_SNAT_ACCESS] != NULL) set_fw_chain_conf(IPT_SNAT_ACCESS, opts->config[CONF_IPT_SNAT_ACCESS]); @@ -661,7 +661,7 @@ process_spa_request(fko_srv_options_t *opts, spa_data_t *spadat) * or MASQUERADE. */ if(opts->config[CONF_SNAT_TRANSLATE_IP] != NULL - && strncasecmp(opts->config[CONF_SNAT_TRANSLATE_IP], "_CHANGEME_", 10)!=0) + && strncasecmp(opts->config[CONF_SNAT_TRANSLATE_IP], "__CHANGEME__", 10)!=0) { /* Using static SNAT */ snat_chain = &(opts->fw_config->chain[IPT_SNAT_ACCESS]); diff --git a/server/fwknopd.conf b/server/fwknopd.conf index a7fc141a..bbbcfe9d 100644 --- a/server/fwknopd.conf +++ b/server/fwknopd.conf @@ -19,7 +19,7 @@ # Machine hostname. If not set, fwknopd will attempt to use gethostname() # to determine the local hostname and use that. # -#HOSTNAME _CHANGEME_; +#HOSTNAME __CHANGEME__; # Define the firewall type. The default is "iptables" for Linux systems, # but this can be set to "ipfw" for *BSD systems. Also supported is @@ -108,7 +108,7 @@ ENABLE_IPT_LOCAL_NAT Y; # variable. # ENABLE_IPT_SNAT N; -#SNAT_TRANSLATE_IP _CHANGEME_; +#SNAT_TRANSLATE_IP __CHANGEME__; # Add ACCEPT rules to the FWKNOP_OUTPUT chain. This is usually only useful # if there are no state tracking rules to allow connection responses out and diff --git a/server/fwknopd_common.h b/server/fwknopd_common.h index 45a401e6..0a4968a7 100644 --- a/server/fwknopd_common.h +++ b/server/fwknopd_common.h @@ -102,6 +102,7 @@ enum { SPA_MSG_REPLAY, SPA_MSG_TOO_OLD, SPA_MSG_ACCESS_DENIED, + SPA_MSG_COMMAND_ERROR, SPA_MSG_NOT_SUPPORTED, SPA_MSG_ERROR }; @@ -315,7 +316,8 @@ typedef struct acc_stanza char *key; int fw_access_timeout; unsigned char enable_cmd_exec; - char *cmd_regex; + char *cmd_exec_user; + uid_t cmd_exec_uid; char *require_username; unsigned char require_source_address; char *gpg_home_dir; diff --git a/server/incoming_spa.c b/server/incoming_spa.c index 76215847..57bb699a 100644 --- a/server/incoming_spa.c +++ b/server/incoming_spa.c @@ -26,6 +26,7 @@ #include "fwknopd_common.h" #include "incoming_spa.h" #include "access.h" +#include "extcmd.h" #include "log_msg.h" /* Validate and in some cases preprocess/reformat the SPA data. Return an @@ -36,7 +37,7 @@ preprocess_spa_data(fko_srv_options_t *opts, char *src_ip) { spa_pkt_info_t *spa_pkt = &(opts->spa_pkt); - unsigned char *ndx = &(spa_pkt->packet_data); + unsigned char *ndx = (unsigned char *)&(spa_pkt->packet_data); int pkt_data_len = spa_pkt->packet_data_len; int tc, i; @@ -170,7 +171,8 @@ incoming_spa(fko_srv_options_t *opts) char *spa_ip_demark; time_t now_ts; - int res, ts_diff, enc_type; + int res, status, ts_diff, enc_type; + uid_t myuid; spa_pkt_info_t *spa_pkt = &(opts->spa_pkt); @@ -423,11 +425,42 @@ incoming_spa(fko_srv_options_t *opts) } else { - /* --DSS TODO: Finish Me */ - log_msg(LOG_WARNING, - "SPA Command message are not yet supported." + log_msg(LOG_INFO, + "Processing SPA Command message: command='%s'.", + spadat.spa_message_remain ); - res = SPA_MSG_NOT_SUPPORTED; + + /* 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, NULL, 0, 0, &status); + } + else /* Just run it as we are (root that is). */ + res = run_extcmd(spadat.spa_message_remain, + NULL, NULL, 0, 0, &status); + + /* --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(status); + + if(opts->verbose > 1) + log_msg(LOG_WARNING, + "CMD_EXEC: result: %i, command returned %i", res, status); + + if(res != 0 || status != 0) + res = SPA_MSG_COMMAND_ERROR; } goto clean_and_bail; @@ -450,45 +483,10 @@ incoming_spa(fko_srv_options_t *opts) goto clean_and_bail; } - /* If we are here we will at least need to have the base access. So we - * can go ahead an generate that now. + /* At this point, we can process the SPA request. */ res = process_spa_request(opts, &spadat); -#if 0 - if(res != FKO_SUCCESS) - goto clean_and_bail; - - /* Now see if we are looking for a local NAT access of some sort. - */ - if(spadat.message_type == FKO_LOCAL_NAT_ACCESS_MSG - || spadat.message_type == FKO_CLIENT_TIMEOUT_LOCAL_NAT_ACCESS_MSG) - { - log_msg(LOG_WARNING, - "SPA Local NAT access messages are not yet supported." - ); - - /* --DSS TODO: Finish Me */ - res = SPA_MSG_NOT_SUPPORTED; - - goto clean_and_bail; - } - - /* NAT access messages. - */ - if(spadat.message_type == FKO_NAT_ACCESS_MSG - || spadat.message_type == FKO_CLIENT_TIMEOUT_NAT_ACCESS_MSG) - { - log_msg(LOG_WARNING, - "SPA NAT access messages are not yet supported." - ); - - res = SPA_MSG_NOT_SUPPORTED; - - /* --DSS TODO: Finish Me */ - } -#endif - clean_and_bail: if(ctx != NULL) fko_destroy(ctx);