minor update to switch to stdout when exiting with success

This commit is contained in:
Michael Rash 2011-10-12 23:36:04 -04:00
parent 41c0be29b7
commit 88d8eb03b3
5 changed files with 1575 additions and 5 deletions

View File

@ -76,7 +76,7 @@ main(int argc, char **argv)
res = kill(old_pid, SIGTERM);
if(res == 0)
{
fprintf(stderr, "Killed fwknopd (pid=%i)\n", old_pid);
fprintf(stdout, "Killed fwknopd (pid=%i)\n", old_pid);
exit(EXIT_SUCCESS);
}
else
@ -99,9 +99,9 @@ main(int argc, char **argv)
old_pid = write_pid_file(&opts);
if(old_pid > 0)
fprintf(stderr, "Detected fwknopd is running (pid=%i).\n", old_pid);
fprintf(stdout, "Detected fwknopd is running (pid=%i).\n", old_pid);
else
fprintf(stderr, "No running fwknopd detected.\n");
fprintf(stdout, "No running fwknopd detected.\n");
exit(EXIT_SUCCESS);
}
@ -117,7 +117,7 @@ main(int argc, char **argv)
res = kill(old_pid, SIGHUP);
if(res == 0)
{
fprintf(stderr, "Sent restart signal to fwknopd (pid=%i)\n", old_pid);
fprintf(stdout, "Sent restart signal to fwknopd (pid=%i)\n", old_pid);
exit(EXIT_SUCCESS);
}
else
@ -128,7 +128,7 @@ main(int argc, char **argv)
}
else
{
fprintf(stderr, "No running fwknopd detected.\n");
fprintf(stdout, "No running fwknopd detected.\n");
exit(EXIT_FAILURE);
}
}

664
server/fwknopd.c.orig Normal file
View File

@ -0,0 +1,664 @@
/*
*****************************************************************************
*
* File: fwknopd.c
*
* Author: Damien S. Stuart
*
* Purpose: An implementation of an fwknop server.
*
* 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.h"
#include "config_init.h"
#include "access.h"
#include "process_packet.h"
#include "pcap_capture.h"
#include "log_msg.h"
#include "utils.h"
#include "fw_util.h"
#include "sig_handler.h"
#include "replay_cache.h"
#include "tcp_server.h"
/* Prototypes
*/
static void check_dir_path(const char *path, const char *path_name, unsigned char use_basename);
static int make_dir_path(const char *path);
static void daemonize_process(fko_srv_options_t *opts);
static int write_pid_file(fko_srv_options_t *opts);
static pid_t get_running_pid(fko_srv_options_t *opts);
int
main(int argc, char **argv)
{
int res, last_sig, rpdb_count;
char *locale;
pid_t old_pid;
fko_srv_options_t opts;
while(1)
{
/* Handle command line
*/
config_init(&opts, argc, argv);
/* Process any options that do their thing and exit. */
/* Kill the currently running fwknopd?
*/
if(opts.kill == 1)
{
old_pid = get_running_pid(&opts);
if(old_pid > 0)
{
res = kill(old_pid, SIGTERM);
if(res == 0)
{
fprintf(stderr, "Killed fwknopd (pid=%i)\n", old_pid);
exit(EXIT_SUCCESS);
}
else
{
perror("Unable to kill fwknop: ");
exit(EXIT_FAILURE);
}
}
else
{
fprintf(stderr, "No running fwknopd detected.\n");
exit(EXIT_FAILURE);
}
}
/* Status of the currently running fwknopd?
*/
if(opts.status == 1)
{
old_pid = write_pid_file(&opts);
if(old_pid > 0)
fprintf(stderr, "Detected fwknopd is running (pid=%i).\n", old_pid);
else
fprintf(stderr, "No running fwknopd detected.\n");
exit(EXIT_SUCCESS);
}
/* Restart the currently running fwknopd?
*/
if(opts.restart == 1 || opts.status == 1)
{
old_pid = get_running_pid(&opts);
if(old_pid > 0)
{
res = kill(old_pid, SIGHUP);
if(res == 0)
{
fprintf(stderr, "Sent restart signal to fwknopd (pid=%i)\n", old_pid);
exit(EXIT_SUCCESS);
}
else
{
perror("Unable to send signal to fwknop: ");
exit(EXIT_FAILURE);
}
}
else
{
fprintf(stderr, "No running fwknopd detected.\n");
exit(EXIT_FAILURE);
}
}
/* Initialize logging.
*/
init_logging(&opts);
#if HAVE_LOCALE_H
/* Set the locale if specified.
*/
if(opts.config[CONF_LOCALE] != NULL
&& strncasecmp(opts.config[CONF_LOCALE], "NONE", 4) != 0)
{
locale = setlocale(LC_ALL, opts.config[CONF_LOCALE]);
if(locale == NULL)
{
log_msg(LOG_ERR,
"WARNING: Unable to set locale to '%s'.",
opts.config[CONF_LOCALE]
);
}
else
{
if(opts.verbose)
log_msg(LOG_INFO,
"Locale set to '%s'.", opts.config[CONF_LOCALE]
);
}
}
#endif
/* Make sure we have a valid run dir and path leading to digest file
* in case it configured to be somewhere other than the run dir.
*/
check_dir_path((const char *)opts.config[CONF_FWKNOP_RUN_DIR], "Run", 0);
check_dir_path((const char *)opts.config[CONF_DIGEST_FILE], "Run", 1);
/* Process the access.conf file.
*/
parse_access_file(&opts);
/* Show config (including access.conf vars) and exit dump config was
* wanted.
*/
if(opts.dump_config == 1)
{
dump_config(&opts);
dump_access_list(&opts);
exit(EXIT_SUCCESS);
}
/* Initialize the firewall rules handler based on the fwknopd.conf
* file, but (for iptables firewalls) don't flush any rules or create
* any chains yet. This allows us to dump the current firewall rules
* via fw_rules_dump() in --fw-list mode before changing around any rules
* of an existing fwknopd process.
*/
fw_config_init(&opts);
if(opts.fw_list == 1)
{
fw_dump_rules(&opts);
exit(EXIT_SUCCESS);
}
/* If we are a new process (just being started), proceed with normal
* start-up. Otherwise, we are here as a result of a signal sent to an
* existing process and we want to restart.
*/
if(get_running_pid(&opts) != getpid())
{
/* If foreground mode is not set, the fork off and become a daemon.
* Otherwise, attempt to get the pid file lock and go on.
*/
if(opts.foreground == 0)
{
daemonize_process(&opts);
}
else
{
old_pid = write_pid_file(&opts);
if(old_pid > 0)
{
fprintf(stderr,
"* An instance of fwknopd is already running: (PID=%i).\n", old_pid
);
exit(EXIT_FAILURE);
}
else if(old_pid < 0)
{
fprintf(stderr, "* PID file error. The lock may not be effective.\n");
}
}
log_msg(LOG_INFO, "Starting %s", MY_NAME);
}
else
{
log_msg(LOG_INFO, "Re-starting %s", MY_NAME);
}
if(opts.verbose > 1 && opts.foreground)
{
dump_config(&opts);
dump_access_list(&opts);
}
/* Initialize the digest cache for replay attack detection
* if so configured.
*/
if(strncasecmp(opts.config[CONF_ENABLE_DIGEST_PERSISTENCE], "Y", 1) == 0)
{
rpdb_count = replay_db_init(&opts);
if(rpdb_count < 0)
{
log_msg(LOG_WARNING,
"Error opening digest cache file. Incoming digests will not be remembered."
);
strcpy(opts.config[CONF_ENABLE_DIGEST_PERSISTENCE], "N");
}
if(opts.verbose)
log_msg(LOG_ERR,
"Using Digest Cache: '%s' (entry count = %i)",
opts.config[CONF_DIGEST_FILE], rpdb_count
);
}
/* Prepare the firewall - i.e. flush any old rules and (for iptables)
* create fwknop chains.
*/
fw_initialize(&opts);
/* If the TCP server option was set, fire it up here.
*/
if(strncasecmp(opts.config[CONF_ENABLE_TCP_SERVER], "Y", 1) == 0)
{
if(atoi(opts.config[CONF_TCPSERV_PORT]) <= 0
|| atoi(opts.config[CONF_TCPSERV_PORT]) > 65535)
{
log_msg(LOG_WARNING,
"WARNING: ENABLE_TCP_SERVER is set, but TCPSERV_PORT is not valid. TCP server not started!"
);
}
else
{
run_tcp_server(&opts);
}
}
/* Intiate pcap capture mode...
*/
pcap_capture(&opts);
if(got_signal) {
last_sig = got_signal;
got_signal = 0;
if(got_sighup)
{
log_msg(LOG_WARNING, "Got SIGHUP. Re-reading configs.");
free_configs(&opts);
kill(opts.tcp_server_pid, SIGTERM);
usleep(1000000);
got_sighup = 0;
}
else if(got_sigint)
{
log_msg(LOG_WARNING, "Got SIGINT. Exiting...");
got_sigint = 0;
break;
}
else if(got_sigterm)
{
log_msg(LOG_WARNING, "Got SIGTERM. Exiting...");
got_sigterm = 0;
break;
}
else
{
log_msg(LOG_WARNING,
"Got signal %i. No defined action but to exit.", last_sig);
break;
}
}
else if (opts.packet_ctr_limit > 0
&& opts.packet_ctr >= opts.packet_ctr_limit)
{
log_msg(LOG_INFO,
"Packet count limit (%d) reached. Exiting...",
opts.packet_ctr_limit);
break;
}
else /* got_signal was not set (should be if we are here) */
{
log_msg(LOG_WARNING,
"Capture ended without signal. Exiting...");
break;
}
}
log_msg(LOG_INFO, "Shutting Down fwknopd.");
/* Kill the TCP server (if we have one running).
*/
if(opts.tcp_server_pid > 0)
{
log_msg(LOG_INFO, "Killing the TCP server (pid=%i)",
opts.tcp_server_pid);
kill(opts.tcp_server_pid, SIGTERM);
/* --DSS XXX: This seems to be necessary if the tcp server
* was restarted byt this program. We need to
* investigate an fix this. For now, this works
* (it is kludgy, but does no harm afaik).
*/
kill(opts.tcp_server_pid, SIGKILL);
}
/* Other cleanup.
*/
fw_cleanup();
free_logging();
free_configs(&opts);
return(0);
}
/* Ensure the specified directory exists. If not, create it or die.
*/
static void
check_dir_path(const char *filepath, const char *fp_desc, unsigned char use_basename)
{
struct stat st;
int res;
char tmp_path[MAX_PATH_LEN];
char *ndx;
/*
* FIXME: We shouldn't use a hard-coded dir-separator here.
*/
/* But first make sure we are using an absolute path.
*/
if(*filepath != PATH_SEP)
{
log_msg(LOG_ERR,
"Configured %s directory (%s) is not an absolute path.", fp_desc, filepath
);
exit(EXIT_FAILURE);
}
/* If this is a file path that we want to use only the basename, strip
* the trailing filename here.
*/
if(use_basename && ((ndx = strrchr(filepath, PATH_SEP)) != NULL))
strlcpy(tmp_path, filepath, (ndx-filepath)+1);
else
strcpy(tmp_path, filepath);
/* At this point, we should make the path is more than just the
* PATH_SEP. If it is not, silently return.
*/
if(strlen(tmp_path) < 2)
return;
/* Make sure we have a valid directory.
*/
res = stat(tmp_path, &st);
if(res != 0)
{
if(errno == ENOENT)
{
log_msg(LOG_WARNING,
"%s directory: %s does not exist. Attempting to create it.",
fp_desc, tmp_path
);
/* Directory does not exist, so attempt to create it.
*/
res = make_dir_path(tmp_path);
if(res != 0)
{
log_msg(LOG_ERR,
"Unable to create %s directory: %s (error: %i)",
fp_desc, tmp_path, errno
);
exit(EXIT_FAILURE);
}
log_msg(LOG_ERR,
"Successfully created %s directory: %s", fp_desc, tmp_path
);
}
else
{
log_msg(LOG_ERR,
"Stat of %s returned error %i", tmp_path, errno
);
exit(EXIT_FAILURE);
}
}
else
{
/* It is a file, but is it a directory?
*/
if(! S_ISDIR(st.st_mode))
{
log_msg(LOG_ERR,
"Specified %s directory: %s is NOT a directory\n\n", fp_desc, tmp_path
);
exit(EXIT_FAILURE);
}
}
}
static int
make_dir_path(const char *run_dir)
{
struct stat st;
int res, len;
char tmp_path[MAX_PATH_LEN];
char *ndx;
strlcpy(tmp_path, run_dir, MAX_PATH_LEN);
len = strlen(tmp_path);
/* Strip any trailing dir sep char.
*/
if(tmp_path[len-1] == PATH_SEP)
tmp_path[len-1] = '\0';
for(ndx = tmp_path+1; *ndx; ndx++)
{
if(*ndx == '/')
{
*ndx = '\0';
/* Stat this part of the path to see if it is a valid directory.
* If it does not exist, attempt to create it. If it does, and
* it is a directory, go on. Otherwise, any other error cause it
* to bail.
*/
if(stat(tmp_path, &st) != 0)
{
if(errno == ENOENT)
res = mkdir(tmp_path, S_IRWXU);
if(res != 0)
return res;
}
if(! S_ISDIR(st.st_mode))
{
log_msg(LOG_ERR,
"Component: %s of %s is NOT a directory\n\n", tmp_path, run_dir
);
return(ENOTDIR);
}
*ndx = '/';
}
}
res = mkdir(tmp_path, S_IRWXU);
return(res);
}
/* Become a daemon: fork(), start a new session, chdir "/",
* and close unneeded standard filehandles.
*/
static void
daemonize_process(fko_srv_options_t *opts)
{
pid_t pid, old_pid;
/* Reset the our umask
*/
umask(0);
if ((pid = fork()) < 0)
{
perror("Unable to fork: ");
exit(EXIT_FAILURE);
}
else if (pid != 0) /* parent */
{
exit(EXIT_SUCCESS);
}
/* Child process from here on out */
/* Start a new session
*/
setsid();
/* Create the PID file (or be blocked by an existing one).
*/
old_pid = write_pid_file(opts);
if(old_pid > 0)
{
fprintf(stderr,
"* An instance of fwknopd is already running: (PID=%i).\n", old_pid
);
exit(EXIT_FAILURE);
}
else if(old_pid < 0)
{
fprintf(stderr, "* PID file error. The lock may not be effective.\n");
}
/* Chdir to the root of the filesystem
*/
if ((chdir("/")) < 0) {
perror("Could not chdir() to /: ");
exit(EXIT_FAILURE);
}
/* Close un-needed file handles
*/
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
return;
}
static int
write_pid_file(fko_srv_options_t *opts)
{
pid_t old_pid, my_pid;
int op_fd, lck_res, num_bytes;
char buf[6] = {0};
/* Reset errno (just in case)
*/
errno = 0;
/* Open the PID file
*/
op_fd = open(
opts->config[CONF_FWKNOP_PID_FILE], O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR
);
if(op_fd == -1)
{
perror("Error trying to open PID file: ");
return -1;
}
fcntl(op_fd, F_SETFD, FD_CLOEXEC);
/* Attempt to lock the PID file. If we get an EWOULDBLOCK
* error, another instance already has the lock. So we grab
* the pid from the existing lock file, complain and bail.
*/
lck_res = lockf(op_fd, F_TLOCK, 0);
if(lck_res == -1)
{
if(errno != EAGAIN)
{
perror("Unexpected error from lockf: ");
return -1;
}
close(op_fd);
/* Look for an existing lock holder. If we get a pid return it.
*/
old_pid = get_running_pid(opts);
if(old_pid)
return old_pid;
/* Otherwise, consider it an error.
*/
perror("Unable read existing PID file: ");
return -1;
}
/* Write our PID to the file
*/
my_pid = getpid();
snprintf(buf, 6, "%i\n", my_pid);
if(opts->verbose > 1)
log_msg(LOG_INFO, "[+] Writing my PID (%i) to the lock file: %s\n",
my_pid, opts->config[CONF_FWKNOP_PID_FILE]);
num_bytes = write(op_fd, buf, strlen(buf));
if(errno || num_bytes != strlen(buf))
perror("Lock may not be valid. PID file write error: ");
/* Sync/flush regardless...
*/
fsync(op_fd);
/* Put the lock file discriptor in out options struct so any
* child processes we my spawn can close and release it.
*/
opts->lock_fd = op_fd;
return 0;
}
static pid_t
get_running_pid(fko_srv_options_t *opts)
{
int op_fd;
char buf[6] = {0};
pid_t rpid = 0;
op_fd = open(opts->config[CONF_FWKNOP_PID_FILE], O_RDONLY);
if(op_fd > 0)
{
if (read(op_fd, buf, 6) > 0)
rpid = (pid_t)atoi(buf);
close(op_fd);
}
return(rpid);
}
/***EOF***/

39
server/fwknopd.c.rej Normal file
View File

@ -0,0 +1,39 @@
--- server/fwknopd.c
+++ server/fwknopd.c
@@ -51,7 +51,7 @@
int
main(int argc, char **argv)
{
- int res, last_sig, rp_cache_count;
+ int res, last_sig, rpdb_count;
char *locale;
pid_t old_pid;
@@ -239,15 +239,14 @@
dump_access_list(&opts);
}
- /* Initialize the digest cache for replay attack detection (either
- * with dbm support or with the default simple cache file strategy
+ /* Initialize the digest cache (replay attack detection dbm)
* if so configured.
*/
if(strncasecmp(opts.config[CONF_ENABLE_DIGEST_PERSISTENCE], "Y", 1) == 0)
{
- rp_cache_count = replay_cache_init(&opts);
+ rpdb_count = replay_db_init(&opts);
- if(rp_cache_count < 0)
+ if(rpdb_count < 0)
{
log_msg(LOG_WARNING,
"Error opening digest cache file. Incoming digests will not be remembered."
@@ -258,7 +257,7 @@
if(opts.verbose)
log_msg(LOG_ERR,
"Using Digest Cache: '%s' (entry count = %i)",
- opts.config[CONF_DIGEST_FILE], rp_cache_count
+ opts.config[CONF_DIGEST_FILE], rpdb_count
);
}

541
server/incoming_spa.c.orig Normal file
View File

@ -0,0 +1,541 @@
/*
*****************************************************************************
*
* 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 <sys/wait.h>
#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; i<pkt_data_len; i++)
{
if(isspace(*ndx)) /* The first space marks the end of the req */
{
*ndx = '\0';
break;
}
else if(*ndx == '-') /* Convert '-' to '+' */
*ndx = '+';
else if(*ndx == '_') /* Convert '_' to '/' */
*ndx = '/';
/* Make sure it is a valid base64 char. */
else if(!(isalnum(*ndx) || *ndx == '/' || *ndx == '+' || *ndx == '='))
return(SPA_MSG_NOT_SPA_DATA);
ndx++;
}
}
else
{
/* Make sure the data is valid Base64-encoded characters
* (at least the first MIN_SPA_DATA_SIZE bytes).
*/
ndx = (char *)spa_pkt->packet_data;
for(i=0; i<MIN_SPA_DATA_SIZE; i++)
{
if(!(isalnum(*ndx) || *ndx == '/' || *ndx == '+' || *ndx == '='))
return(SPA_MSG_NOT_SPA_DATA);
ndx++;
}
}
/* --DSS: Are there other checks we can do here ??? */
/* If we made it here, we have no reason to assume this is not SPA data
* (at least until we come up with more checks).
*/
return(FKO_SUCCESS);
}
/* Popluate a spa_data struct from an initialized (and populated) FKO context.
*/
static int
get_spa_data_fields(fko_ctx_t ctx, spa_data_t *spdat)
{
int res = FKO_SUCCESS;
res = fko_get_username(ctx, &(spdat->username));
if(res != FKO_SUCCESS)
return(res);
res = fko_get_timestamp(ctx, &(spdat->timestamp));
if(res != FKO_SUCCESS)
return(res);
res = fko_get_version(ctx, &(spdat->version));
if(res != FKO_SUCCESS)
return(res);
res = fko_get_spa_message_type(ctx, &(spdat->message_type));
if(res != FKO_SUCCESS)
return(res);
res = fko_get_spa_message(ctx, &(spdat->spa_message));
if(res != FKO_SUCCESS)
return(res);
res = fko_get_spa_nat_access(ctx, &(spdat->nat_access));
if(res != FKO_SUCCESS)
return(res);
res = fko_get_spa_server_auth(ctx, &(spdat->server_auth));
if(res != FKO_SUCCESS)
return(res);
res = fko_get_spa_client_timeout(ctx, (int *)&(spdat->client_timeout));
if(res != FKO_SUCCESS)
return(res);
return(res);
}
/* Process the SPA packet data
*/
int
incoming_spa(fko_srv_options_t *opts)
{
/* Always a good idea to initialize ctx to null if it will be used
* repeatedly (especially when using fko_new_with_data().
*/
fko_ctx_t ctx = NULL;
char *spa_ip_demark, *gpg_id;
time_t now_ts;
int res, status, ts_diff, enc_type;
spa_pkt_info_t *spa_pkt = &(opts->spa_pkt);
/* This will hold our pertinent SPA data.
*/
spa_data_t spadat;
/* Get the access.conf data for the stanza that matches this incoming
* source IP address.
*/
acc_stanza_t *acc = acc_check_source(opts, spa_pkt->packet_src_ip);
inet_ntop(AF_INET, &(spa_pkt->packet_src_ip),
spadat.pkt_source_ip, sizeof(spadat.pkt_source_ip));
/* At this point, we want to validate and (if needed) preprocess the
* SPA data and/or to be reasonably sure we have a SPA packet (i.e
* try to eliminate obvious non-spa packets).
*/
res = preprocess_spa_data(opts, spadat.pkt_source_ip);
if(res != FKO_SUCCESS)
return(SPA_MSG_NOT_SPA_DATA);
log_msg(LOG_INFO, "SPA Packet from IP: %s received.", spadat.pkt_source_ip);
if(acc == NULL)
{
log_msg(LOG_WARNING,
"No access data found for source IP: %s", spadat.pkt_source_ip
);
return(SPA_MSG_ACCESS_DENIED);
}
if(opts->verbose > 1)
log_msg(LOG_INFO, "SPA Packet: '%s'\n", spa_pkt->packet_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***/

326
server/replay_cache.c.orig Normal file
View File

@ -0,0 +1,326 @@
/*
*****************************************************************************
*
* 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 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 "replay_cache.h"
#include "log_msg.h"
#include "fwknopd_errors.h"
#include <time.h>
#if HAVE_LIBGDBM
#include <gdbm.h>
#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 <ndbm.h>
#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
#error "No GDBM or NDBM header file found. WTF?"
#endif
#if HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#include <arpa/inet.h>
#include <fcntl.h>
#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.");
new_file = malloc(strlen(opts->config[CONF_DIGEST_FILE])+5);
if(new_file == NULL)
{
log_msg(LOG_ERR, "rotate_digest_cache_file: Memory allocation error.");
exit(EXIT_FAILURE);
}
/* The new filename is just the original with a trailing '-old'.
*/
strcpy(new_file, opts->config[CONF_DIGEST_FILE]);
strcat(new_file, "-old");
res = rename(opts->config[CONF_DIGEST_FILE], new_file);
if(res < 0)
log_msg(LOG_ERR, "Unable to rename digest file: %s to %s: %s",
opts->config[CONF_DIGEST_FILE], new_file, strerror(errno)
);
#endif /* NO_DIGEST_CACHE */
}
/* Check for the existence of the replay dbm file, and create it if it does
* not exist. Returns the number of db entries or -1 on error.
*/
int
replay_db_init(fko_srv_options_t *opts)
{
#ifdef NO_DIGEST_CACHE
return 0;
#else
#ifdef HAVE_LIBGDBM
GDBM_FILE rpdb;
#elif HAVE_LIBNDBM
DBM *rpdb;
#endif
datum db_key, db_ent, db_next_key;
int db_count = 0;
/* If rotation was specified, do it.
*/
if(opts->rotate_digest_cache)
rotate_digest_cache_file(opts);
#ifdef HAVE_LIBGDBM
rpdb = gdbm_open(
opts->config[CONF_DIGEST_FILE], 512, GDBM_WRCREAT, S_IRUSR|S_IWUSR, 0
);
#elif HAVE_LIBNDBM
rpdb = dbm_open(
opts->config[CONF_DIGEST_FILE], O_RDWR|O_CREAT, S_IRUSR|S_IWUSR
);
#endif
if(!rpdb)
{
log_msg(LOG_ERR,
"Unable to open digest cache file: '%s': %s",
opts->config[CONF_DIGEST_FILE],
MY_DBM_STRERROR(errno)
);
return(-1);
}
#ifdef HAVE_LIBGDBM
db_key = gdbm_firstkey(rpdb);
while (db_key.dptr != NULL)
{
db_count++;
db_next_key = gdbm_nextkey(rpdb, db_key);
free(db_key.dptr);
db_key = db_next_key;
}
#elif HAVE_LIBNDBM
for (db_key = dbm_firstkey(rpdb); db_ent.dptr != NULL; db_key = dbm_nextkey(rpdb))
db_count++;
#endif
MY_DBM_CLOSE(rpdb);
return(db_count);
#endif /* NO_DIGEST_CACHE */
}
/* Take an fko context, pull the digest and use it as the key to check the
* replay db (digest cache). Returns 1 if there was a match (a replay),
* 0 for no match, and -1 on error.
*/
int
replay_check(fko_srv_options_t *opts, fko_ctx_t ctx)
{
#ifdef NO_DIGEST_CACHE
return 0;
#else
#ifdef HAVE_LIBGDBM
GDBM_FILE rpdb;
#elif HAVE_LIBNDBM
DBM *rpdb;
#endif
datum db_key, db_ent;
char created[18], first[18], last[18];
char curr_ip[INET_ADDRSTRLEN+1] = {0};
char last_ip[INET_ADDRSTRLEN+1] = {0};
char *digest;
int digest_len, res;
digest_cache_info_t dc_info, *dci_p;
res = fko_get_spa_digest(ctx, &digest);
if(res != FKO_SUCCESS)
{
log_msg(LOG_WARNING, "Error getting digest from SPA data: %s",
fko_errstr(res));
return(SPA_MSG_DIGEST_ERROR);
}
digest_len = strlen(digest);
db_key.dptr = digest;
db_key.dsize = digest_len;
/* Check the db for the key
*/
#ifdef HAVE_LIBGDBM
rpdb = gdbm_open(
opts->config[CONF_DIGEST_FILE], 512, GDBM_WRCREAT, S_IRUSR|S_IWUSR, 0
);
#elif HAVE_LIBNDBM
rpdb = dbm_open(opts->config[CONF_DIGEST_FILE], O_RDWR, 0);
#endif
if(!rpdb)
{
log_msg(LOG_WARNING, "Error opening digest_cache: '%s': %s",
opts->config[CONF_DIGEST_FILE],
MY_DBM_STRERROR(errno)
);
return(SPA_MSG_DIGEST_CACHE_ERROR);
}
db_ent = MY_DBM_FETCH(rpdb, db_key);
/* If the datum is not null, we have a match. Otherwise, we add
* this entry to the cache.
*/
if(db_ent.dptr != NULL)
{
dci_p = (digest_cache_info_t *)db_ent.dptr;
/* Convert the IPs to a human readable form
*/
inet_ntop(AF_INET, &(opts->spa_pkt.packet_src_ip),
curr_ip, INET_ADDRSTRLEN);
inet_ntop(AF_INET, &(dci_p->src_ip), last_ip, INET_ADDRSTRLEN);
/* Mark the last_replay time.
*/
dci_p->last_replay = time(NULL);
/* Increment the replay count and check to see if it is the first one.
*/
if(++(dci_p->replay_count) == 1)
{
/* This is the first replay so make it the same as last_replay
*/
dci_p->first_replay = dci_p->last_replay;
}
strftime(created, 18, "%D %H:%M:%S", localtime(&(dci_p->created)));
strftime(first, 18, "%D %H:%M:%S", localtime(&(dci_p->first_replay)));
strftime(last, 18, "%D %H:%M:%S", localtime(&(dci_p->last_replay)));
log_msg(LOG_WARNING,
"Replay detected from source IP: %s\n"
" Original source IP: %s\n"
" Entry created: %s\n"
" First replay: %s\n"
" Last replay: %s\n"
" Replay count: %i\n",
curr_ip, last_ip,
created,
first,
last,
dci_p->replay_count
);
/* Save it back to the digest cache
*/
if(MY_DBM_STORE(rpdb, db_key, db_ent, MY_DBM_REPLACE) != 0)
log_msg(LOG_WARNING, "Error updating entry in digest_cache: '%s': %s",
opts->config[CONF_DIGEST_FILE],
MY_DBM_STRERROR(errno)
);
#ifdef HAVE_LIBGDBM
free(db_ent.dptr);
#endif
res = SPA_MSG_REPLAY;
} else {
/* This is a new SPA packet that needs to be added to the cache.
*/
dc_info.src_ip = opts->spa_pkt.packet_src_ip;
dc_info.created = time(NULL);
dc_info.first_replay = dc_info.last_replay = dc_info.replay_count = 0;
db_ent.dsize = sizeof(digest_cache_info_t);
db_ent.dptr = (char*)&(dc_info);
if(MY_DBM_STORE(rpdb, db_key, db_ent, MY_DBM_INSERT) != 0)
{
log_msg(LOG_WARNING, "Error adding entry digest_cache: %s",
MY_DBM_STRERROR(errno)
);
res = SPA_MSG_DIGEST_CACHE_ERROR;
}
res = SPA_MSG_SUCCESS;
}
MY_DBM_CLOSE(rpdb);
return(res);
#endif /* NO_DIGEST_CACHE */
}
/***EOF***/