/* ***************************************************************************** * * File: replay_cache.c * * Author: Damien S. Stuart * * Purpose: Provides the functions to check for possible replay attacks * by using a cache of previously seen digests. This cache is a * simple file by default, but can be made to use a dbm solution * (ndbm or gdbm in ndbm compatibility mode) file to store the digest * of a previously received SPA packets. * * Copyright 2010-2013 Damien Stuart (dstuart@dstuart.org) * * 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 "replay_cache.h" #include "log_msg.h" #include "fwknopd_errors.h" #include "utils.h" #include #include #include #if HAVE_LIBGDBM #include #define MY_DBM_FETCH(d, k) gdbm_fetch(d, k) #define MY_DBM_STORE(d, k, v, m) gdbm_store(d, k, v, m) #define MY_DBM_STRERROR(x) gdbm_strerror(x) #define MY_DBM_CLOSE(d) gdbm_close(d) #define MY_DBM_REPLACE GDBM_REPLACE #define MY_DBM_INSERT GDBM_INSERT #elif HAVE_LIBNDBM #include #define MY_DBM_FETCH(d, k) dbm_fetch(d, k) #define MY_DBM_STORE(d, k, v, m) dbm_store(d, k, v, m) #define MY_DBM_STRERROR(x) strerror(x) #define MY_DBM_CLOSE(d) dbm_close(d) #define MY_DBM_REPLACE DBM_REPLACE #define MY_DBM_INSERT DBM_INSERT #else #if ! USE_FILE_CACHE #error "File cache method disabled, and No GDBM or NDBM header file found. WTF?" #endif #endif #if HAVE_SYS_SOCKET_H #include #endif #include #include #define DATE_LEN 18 #define MAX_DIGEST_SIZE 64 /* Rotate the digest file by simply renaming it. */ static void rotate_digest_cache_file(fko_srv_options_t *opts) { #ifdef NO_DIGEST_CACHE log_msg(LOG_WARNING, "Digest cache not supported. Nothing to rotate."); #else int res; char *new_file = NULL; log_msg(LOG_INFO, "Rotating digest cache file."); #if USE_FILE_CACHE new_file = malloc(strlen(opts->config[CONF_DIGEST_FILE])+5); #else new_file = malloc(strlen(opts->config[CONF_DIGEST_DB_FILE])+5); #endif if(new_file == NULL) { log_msg(LOG_ERR, "rotate_digest_cache_file: Memory allocation error."); clean_exit(opts, NO_FW_CLEANUP, EXIT_FAILURE); } /* The new filename is just the original with a trailing '-old'. */ #if USE_FILE_CACHE strlcpy(new_file, opts->config[CONF_DIGEST_FILE], strlen(opts->config[CONF_DIGEST_FILE])+5); strlcat(new_file, "-old", strlen(opts->config[CONF_DIGEST_FILE])+5); #else strlcpy(new_file, opts->config[CONF_DIGEST_DB_FILE], strlen(opts->config[CONF_DIGEST_DB_FILE])+5); strlcat(new_file, "-old", strlen(opts->config[CONF_DIGEST_DB_FILE])+5); #endif #if USE_FILE_CACHE res = rename(opts->config[CONF_DIGEST_FILE], new_file); #else res = rename(opts->config[CONF_DIGEST_DB_FILE], new_file); #endif if(res < 0) log_msg(LOG_ERR, "Unable to rename digest file: %s to %s: %s", #if USE_FILE_CACHE opts->config[CONF_DIGEST_FILE], new_file, strerror(errno) #else opts->config[CONF_DIGEST_DB_FILE], new_file, strerror(errno) #endif ); #endif /* NO_DIGEST_CACHE */ if(new_file != NULL) free(new_file); } static void replay_warning(fko_srv_options_t *opts, digest_cache_info_t *digest_info) { char src_ip[INET_ADDRSTRLEN+1] = {0}; char orig_src_ip[INET_ADDRSTRLEN+1] = {0}; char created[DATE_LEN] = {0}; #if ! USE_FILE_CACHE char first[DATE_LEN] = {0}, last[DATE_LEN] = {0}; #endif /* Convert the IPs to a human readable form */ inet_ntop(AF_INET, &(opts->spa_pkt.packet_src_ip), src_ip, INET_ADDRSTRLEN); inet_ntop(AF_INET, &(digest_info->src_ip), orig_src_ip, INET_ADDRSTRLEN); #if ! USE_FILE_CACHE /* Mark the last_replay time. */ digest_info->last_replay = time(NULL); /* Increment the replay count and check to see if it is the first one. */ if(++(digest_info->replay_count) == 1) { /* This is the first replay so make it the same as last_replay */ digest_info->first_replay = digest_info->last_replay; } strftime(first, DATE_LEN, "%D %H:%M:%S", localtime(&(digest_info->first_replay))); strftime(last, DATE_LEN, "%D %H:%M:%S", localtime(&(digest_info->last_replay))); #endif strftime(created, DATE_LEN, "%D %H:%M:%S", localtime(&(digest_info->created))); log_msg(LOG_WARNING, "Replay detected from source IP: %s, " "Destination proto/port: %d/%d, " "Original source IP: %s, " "Original dst proto/port: %d/%d, " #if USE_FILE_CACHE "Entry created: %s", #else "Entry created: %s, " "First replay: %s, " "Last replay: %s, " "Replay count: %i", #endif src_ip, opts->spa_pkt.packet_proto, opts->spa_pkt.packet_dst_port, orig_src_ip, digest_info->proto, digest_info->dst_port, #if USE_FILE_CACHE created #else created, first, last, digest_info->replay_count #endif ); return; } #if USE_FILE_CACHE static int replay_file_cache_init(fko_srv_options_t *opts) { FILE *digest_file_ptr = NULL; unsigned int num_lines = 0, digest_ctr = 0; char line_buf[MAX_LINE_LEN] = {0}; char src_ip[INET_ADDRSTRLEN+1] = {0}; char dst_ip[INET_ADDRSTRLEN+1] = {0}; long int time_tmp; int digest_file_fd = -1; char digest_header[] = "#