/** * \file server/cmd_cycle.c * * \brief Fwknop routines for managing command cycles as defined via * access.conf stanzas (CMD_CYCLE_OPEN and CMD_CYCLE_CLOSE). */ /* Fwknop is developed primarily by the people listed in the file 'AUTHORS'. * Copyright (C) 2009-2015 fwknop developers and contributors. For a full * list of contributors, see the file 'CREDITS'. * * License (GNU General 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 "log_msg.h" #include "extcmd.h" #include "cmd_cycle.h" #include "access.h" static char cmd_buf[CMD_CYCLE_BUFSIZE]; static char err_buf[CMD_CYCLE_BUFSIZE]; static void zero_cmd_buffers(void) { memset(cmd_buf, 0x0, CMD_CYCLE_BUFSIZE); memset(err_buf, 0x0, CMD_CYCLE_BUFSIZE); return; } static int pid_status = 0; static int is_var(const char * const var, const char * const cmd_str) { int i; for(i=0; i < strlen(var); i++) { if(cmd_str[i] != var[i]) return 0; } return 1; } static int build_cmd(spa_data_t *spadat, const char * const cmd_cycle_str, int timer) { char port_str[MAX_PORT_STR_LEN+1] = {0}; char proto_str[MAX_PROTO_STR_LEN+1] = {0}; char timestamp_str[20] = {0}; char client_timeout_str[10] = {0}; acc_port_list_t *port_list = NULL; int i=0, buf_idx=0; if(expand_acc_port_list(&port_list, spadat->spa_message_remain) != 1) { free_acc_port_list(port_list); return 0; } /* We only look at the first port/proto combination for command * open/close cycles even if the SPA message had multiple ports * and protocols set. */ snprintf(port_str, MAX_PORT_STR_LEN+1, "%d", port_list->port); snprintf(proto_str, MAX_PROTO_STR_LEN+1, "%d", port_list->proto); zero_cmd_buffers(); /* Look for the following variables for substitution: * IP, SRC, PKT_SRC, DST, PORT, and PROTO */ for(i=0; i < strnlen(cmd_cycle_str, CMD_CYCLE_BUFSIZE); i++) { if(cmd_cycle_str[i] == '$') { /* Found the start of a variable, now validate it and * swap in the IP/port/proto. */ if(is_var("IP", (cmd_cycle_str+i+1))) { strlcat(cmd_buf, spadat->use_src_ip, CMD_CYCLE_BUFSIZE); i += strlen("IP"); buf_idx += strlen(spadat->use_src_ip); } /* SRC is a synonym for IP */ else if(is_var("SRC", (cmd_cycle_str+i+1))) { strlcat(cmd_buf, spadat->use_src_ip, CMD_CYCLE_BUFSIZE); i += strlen("SRC"); buf_idx += strlen(spadat->use_src_ip); } /* Special case for the SPA packet source IP in * the IP header (i.e. not from the decrypted SPA * payload) if the user really wants this. */ else if(is_var("PKT_SRC", (cmd_cycle_str+i+1))) { strlcat(cmd_buf, spadat->pkt_source_ip, CMD_CYCLE_BUFSIZE); i += strlen("PKT_SRC"); buf_idx += strlen(spadat->pkt_source_ip); } else if(is_var("DST", (cmd_cycle_str+i+1))) { strlcat(cmd_buf, spadat->pkt_destination_ip, CMD_CYCLE_BUFSIZE); i += strlen("DST"); buf_idx += strlen(spadat->pkt_destination_ip); } else if (is_var("PORT", (cmd_cycle_str+i+1))) { strlcat(cmd_buf, port_str, CMD_CYCLE_BUFSIZE); i += strlen("PORT"); buf_idx += strlen(port_str); } else if (is_var("PROTO", (cmd_cycle_str+i+1))) { strlcat(cmd_buf, proto_str, CMD_CYCLE_BUFSIZE); i += strlen("PROTO"); buf_idx += strlen(proto_str); } else if (is_var("TIMEOUT", (cmd_cycle_str+i+1))) { snprintf(timestamp_str, sizeof(timestamp_str), "%lli", (long long)spadat->timestamp + (spadat->client_timeout == 0 ? timer : spadat->client_timeout)); strlcat(cmd_buf, timestamp_str, CMD_CYCLE_BUFSIZE); i += strlen("TIMEOUT"); buf_idx += strlen(timestamp_str); } else if (is_var("CLIENT_TIMEOUT", (cmd_cycle_str+i+1))) { snprintf(client_timeout_str, sizeof(client_timeout_str), "%u", spadat->client_timeout == 0 ? timer : spadat->client_timeout); strlcat(cmd_buf, client_timeout_str, CMD_CYCLE_BUFSIZE); i += strlen("CLIENT_TIMEOUT"); buf_idx += strlen(client_timeout_str); } continue; } if(cmd_cycle_str[i] != '\0') cmd_buf[buf_idx++] = cmd_cycle_str[i]; if(buf_idx == CMD_CYCLE_BUFSIZE) { free_acc_port_list(port_list); return 0; } } free_acc_port_list(port_list); return 1; } static int cmd_open(fko_srv_options_t *opts, acc_stanza_t *acc, spa_data_t *spadat, const int stanza_num) { /* CMD_CYCLE_OPEN: Build the open command by taking care of variable * substitutions if necessary. */ if(build_cmd(spadat, acc->cmd_cycle_open, acc->cmd_cycle_timer)) { log_msg(LOG_INFO, "[%s] (stanza #%d) Running CMD_CYCLE_OPEN command: %s", spadat->pkt_source_ip, stanza_num, cmd_buf); /* Run the open command */ run_extcmd(cmd_buf, err_buf, CMD_CYCLE_BUFSIZE, WANT_STDERR, NO_TIMEOUT, &pid_status, opts); } else { log_msg(LOG_ERR, "[%s] (stanza #%d) Could not build CMD_CYCLE_OPEN command.", spadat->pkt_source_ip, stanza_num ); return 0; } return 1; } static int add_cmd_close(fko_srv_options_t *opts, acc_stanza_t *acc, spa_data_t *spadat, const int stanza_num) { cmd_cycle_list_t *last_clist=NULL, *new_clist=NULL, *tmp_clist=NULL; time_t now; int cmd_close_len = 0; /* CMD_CYCLE_CLOSE: Build the close command, but don't execute it until * the expiration timer has passed. */ if(build_cmd(spadat, acc->cmd_cycle_close, acc->cmd_cycle_timer)) { /* Now the corresponding close command is now in cmd_buf * for later execution when the timer expires. */ cmd_close_len = strnlen(cmd_buf, CMD_CYCLE_BUFSIZE-1)+1; log_msg(LOG_INFO, "[%s] (stanza #%d) Running CMD_CYCLE_CLOSE command in %d seconds: %s", spadat->pkt_source_ip, stanza_num, (spadat->client_timeout == 0 ? acc->cmd_cycle_timer : spadat->client_timeout), cmd_buf); } else { log_msg(LOG_ERR, "[%s] (stanza #%d) Could not build CMD_CYCLE_CLOSE command.", spadat->pkt_source_ip, stanza_num ); return 0; } /* Add the corresponding close command - to be executed after the * designated timer has expired. */ if((new_clist = calloc(1, sizeof(cmd_cycle_list_t))) == NULL) { log_msg(LOG_ERR, "[*] Fatal memory allocation error creating string list entry" ); clean_exit(opts, FW_CLEANUP, EXIT_FAILURE); } if(opts->cmd_cycle_list == NULL) { opts->cmd_cycle_list = new_clist; } else { tmp_clist = opts->cmd_cycle_list; do { last_clist = tmp_clist; } while((tmp_clist = tmp_clist->next)); last_clist->next = new_clist; } /* Set the source IP */ strlcpy(new_clist->src_ip, spadat->use_src_ip, sizeof(new_clist->src_ip)); /* Set the expiration timer */ time(&now); new_clist->expire = now + (spadat->client_timeout == 0 ? acc->cmd_cycle_timer : spadat->client_timeout); /* Set the close command */ if((new_clist->close_cmd = calloc(1, cmd_close_len)) == NULL) { log_msg(LOG_ERR, "[*] Fatal memory allocation error creating command close string" ); clean_exit(opts, FW_CLEANUP, EXIT_FAILURE); } strlcpy(new_clist->close_cmd, cmd_buf, cmd_close_len); /* Set the access.conf stanza number */ new_clist->stanza_num = stanza_num; return 1; } /* This is the main driver for open/close command cycles */ int cmd_cycle_open(fko_srv_options_t *opts, acc_stanza_t *acc, spa_data_t *spadat, const int stanza_num, int *res) { if(! cmd_open(opts, acc, spadat, stanza_num)) return 0; if(acc->cmd_cycle_do_close) if(! add_cmd_close(opts, acc, spadat, stanza_num)) return 0; return 1; } static void free_cycle_list_node(cmd_cycle_list_t *list_node) { if(list_node != NULL) { if(list_node->close_cmd != NULL) free(list_node->close_cmd); free(list_node); } return; } /* Run all close commands based on the expiration timer */ void cmd_cycle_close(fko_srv_options_t *opts) { cmd_cycle_list_t *curr=NULL, *prev=NULL; int do_delete=1; time_t now; time(&now); if(opts->cmd_cycle_list == NULL) { return; /* No active command cycles */ } else { while(do_delete) { do_delete = 0; /* Keep going through the command list for as long as * there are commands to be executed (and expired). */ for(curr = opts->cmd_cycle_list; curr != NULL; prev = curr, curr=curr->next) { if(curr->expire <= now) { log_msg(LOG_INFO, "[%s] (stanza #%d) Timer expired, running CMD_CYCLE_CLOSE command: %s", curr->src_ip, curr->stanza_num, curr->close_cmd); zero_cmd_buffers(); /* Run the close command */ run_extcmd(curr->close_cmd, err_buf, CMD_CYCLE_BUFSIZE, WANT_STDERR, NO_TIMEOUT, &pid_status, opts); if(prev == NULL) opts->cmd_cycle_list = curr->next; else prev->next = curr->next; free_cycle_list_node(curr); do_delete = 1; break; } } } } return; } void free_cmd_cycle_list(fko_srv_options_t *opts) { cmd_cycle_list_t *tmp_clist=NULL, *clist=NULL; clist = opts->cmd_cycle_list; while(clist != NULL) { tmp_clist = clist->next; free_cycle_list_node(clist); clist = tmp_clist; } return; }