/* ***************************************************************************** * * File: incoming_spa.c * * Author: Damien S. Stuart * * Purpose: Process an incoming SPA data packet for fwknopd. * * Copyright 2010 Damien Stuart (dstuart@dstuart.org) * * License (GNU Public License): * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA * ***************************************************************************** */ #include "fwknopd_common.h" #include "netinet_common.h" #if HAVE_SYS_WAIT_H #include #endif #include "incoming_spa.h" #include "access.h" #include "extcmd.h" #include "log_msg.h" #include "utils.h" #include "fw_util.h" #include "fwknopd_errors.h" #include "replay_cache.h" /* Validate and in some cases preprocess/reformat the SPA data. Return an * error code value if there is any indication the data is not valid spa data. */ static int preprocess_spa_data(fko_srv_options_t *opts, char *src_ip) { spa_pkt_info_t *spa_pkt = &(opts->spa_pkt); char *ndx = (char *)&(spa_pkt->packet_data); int pkt_data_len = spa_pkt->packet_data_len; int i; /* At this point, we can 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 (after this one that is). */ spa_pkt->packet_data_len = 0; /* Expect the data to be at least the minimum required size. */ if(pkt_data_len < MIN_SPA_DATA_SIZE) return(SPA_MSG_LEN_TOO_SMALL); /* Detect and parse out SPA data from an HTTP reqest. If the SPA data * starts with "GET /" and the user agent starts with "Fwknop", then * assume it is a SPA over HTTP request. */ if(strncasecmp(ndx, "GET /", 5) == 0 && strstr(ndx, "User-Agent: Fwknop") != NULL) { /* This looks like an HTTP request, so let's see if we are * configured to accept such request and if so, find the SPA * data. */ if(strncasecmp(opts->config[CONF_ENABLE_SPA_OVER_HTTP], "N", 1) == 0) { log_msg(LOG_WARNING, "HTTP request from %s detected, but not enabled.", src_ip ); return(SPA_MSG_HTTP_NOT_ENABLED); } /* Now extract, adjust (convert characters translated by the fwknop * client), and reset the SPA message itself. */ strlcpy((char *)spa_pkt->packet_data, ndx+5, pkt_data_len); for(i=0; ipacket_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); } /***EOF***/