Files
rmps/src/rmps.c

341 lines
9.7 KiB
C

/*
* rmps.c
*
* Copyright (C) 2018 by Bogomil Vasilev <b.vasilev@smirky.net>
*
* This file is part of Remote Management and Provisioning System (RMPS).
*
* RMPS 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.
*
* RMPS 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 RMPS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "log.h"
#include "confparser.h"
#include "agent_pool.h"
#include "client_pool.h"
#include "job_queue.h"
#include "rmps.h"
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
/* included for openssl */
#include <openssl/ssl.h>
#include <openssl/err.h>
static void rmps_shutdown(void);
static void signal_handler(int sig);
static void set_env(void);
static void daemonize(const char *rundir);
static void spawn_pidfile(const char *pidfile);
static inline int set_reuse_addr(int sockfd);
static int open_listener(int port);
static void cleanup(void);
static void signal_handler(int sig);
static void load_certificates(SSL_CTX *ctx, const char *certfile,
const char *keyfile, const char *cafile);
static SSL_CTX *init_server_ctx(const char *cipherlist, int mode);
static int pid_file_handle;
static void cleanup(void)
{
log(VERBOSE, "Deleting pidfile %s", conf.rmps.pidfile);
if (unlink(conf.rmps.pidfile) != 0)
log(WARNING,
"Failed to delete pidfile %s. Reason code: %d",
conf.rmps.pidfile, errno);
}
static void signal_handler(int sig)
{
switch (sig) {
case SIGHUP:
log(WARNING, "Received SIGHUP signal. Ignoring...");
break;
case SIGINT:
case SIGTERM:
log(INFO, "Received SIGTERM signal.");
log(INFO, "RMPS is shutting down...");
rmps_shutdown();
log(INFO, "RMPS has been stopped properly.");
_exit(EXIT_SUCCESS);
break;
default:
log(WARNING, "Unhandled signal %s", strsignal(sig));
break;
}
}
static void rmps_shutdown(void)
{
close(pid_file_handle);
cleanup();
}
static void set_env(void)
{
struct sigaction new_sigaction;
sigset_t new_sigset;
/* Set signal mask - signals we want to block */
sigemptyset(&new_sigset);
sigaddset(&new_sigset, SIGCHLD); /* ignore child */
sigaddset(&new_sigset, SIGTSTP); /* ignore Tty stop signals */
sigaddset(&new_sigset, SIGTTOU); /* ignore Tty background writes */
sigaddset(&new_sigset, SIGTTIN); /* ignore Tty background reads */
sigprocmask(SIG_BLOCK, &new_sigset, NULL); /* Block above signals */
/* Set up a signal handler */
new_sigaction.sa_handler = signal_handler;
sigemptyset(&new_sigaction.sa_mask);
new_sigaction.sa_flags = 0;
/* Signals to handle */
sigaction(SIGHUP, &new_sigaction, NULL); /* catch hangup signal */
sigaction(SIGTERM, &new_sigaction, NULL); /* catch term signal */
sigaction(SIGINT, &new_sigaction, NULL); /* catch interrupt signal */
signal(SIGPIPE, SIG_IGN); /* prevent crashing from bad writes */
}
static void daemonize(const char *rundir)
{
int pid, sid, i;
/* Check if parent process id is set */
if (getppid() == 1) {
/* PPID exists, therefore we are already a daemon */
return;
}
/* Fork*/
pid = fork();
if (pid < 0) {
/* Could not fork */
log(ERROR, "Failed to fork the parent process. Exiting!");
exit(EXIT_FAILURE);
}
if (pid > 0) {
/* Child created ok, so exit parent process */
log(INFO, "Child process created: %d", pid);
exit(EXIT_SUCCESS);
}
/* Child continues */
umask(027); /* Set file permissions 750 */
/* Get a new process group */
sid = setsid();
if (sid < 0) {
log(ERROR, "Failed to create a process group. Exiting!");
exit(EXIT_FAILURE);
}
/* Close all file descriptors because we fork */
close(0); /* stdin */
close(1); /* stdout */
close(2); /* stderr */
/* Route I/O connections */
/* Open STDIN */
i = open("/dev/null", O_RDWR);
/* STDOUT */
dup(i);
/* STDERR */
dup(i);
chdir(rundir); /* change running directory */
}
static void spawn_pidfile(const char *pidfile)
{
char str[10];
/* Ensure only one copy */
pid_file_handle = open(pidfile, O_RDWR|O_CREAT, 0600);
if (pid_file_handle == -1) {
/* Couldn't open lock file */
log(ERROR, "Could not create PID file %s - Exiting!", pidfile);
exit(EXIT_FAILURE);
}
atexit(cleanup);
/* Try to lock file */
if (lockf(pid_file_handle, F_TLOCK, 0) == -1) {
/* Couldn't get lock on lock file */
log(ERROR, "Could not lock PID file %s - Exiting!", pidfile);
exit(EXIT_FAILURE);
}
/* Get and format PID */
sprintf(str, "%d\n", getpid());
/* write pid to lockfile */
write(pid_file_handle, str, strlen(str));
}
static inline int set_reuse_addr(int sockfd)
{
int yes = 1;
return setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR,
&yes, sizeof(yes));
}
static int open_listener(int port)
{
int sd;
struct sockaddr_in addr;
bzero(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;
sd = socket(addr.sin_family, SOCK_STREAM, 0);
if (sd < 0) {
log(ERROR, "Failed to create socket - Aborting RMPS...");
exit(EXIT_FAILURE);
}
if (set_reuse_addr(sd) < 0) {
log(ERROR,
"Failed to set reuse on address - Aborting RMPS...",
port);
exit(EXIT_FAILURE);
}
if (bind(sd, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
log(ERROR,
"Failed to bind on port: %d - Aborting RMPS...",
port);
exit(EXIT_FAILURE);
}
if (listen(sd, 10) != 0) {
log(ERROR,
"Failed to start listener on port %d - Aborting RMPS...",
port);
exit(EXIT_FAILURE);
}
return sd;
}
/* Init server and create context */
static SSL_CTX *init_server_ctx(const char *cipherlist, int mode)
{
SSL_CTX *ctx;
char ciphers[1024];
// OpenSSL_add_all_algorithms(); /* load & register all cryptos, etc. */
// OpenSSL_add_all_ciphers(); /* load & register all cryptos, etc. */
SSL_load_error_strings(); /* load all error messages */
SSL_library_init();
/* create new context from method */
ctx = SSL_CTX_new(TLS_method());
if (ctx == NULL) {
log(ERROR, "SSL_CTX_new() returned NULL - Aborting...");
log(ERROR, "RMPS failed to start, shutting down...");
exit(EXIT_FAILURE);
}
SSL_CTX_set_verify(ctx, mode, NULL);
ciphers[0] = 0;
strcat(ciphers, "-ALL"); /* Disable any ciphers we have by default */
strcat(ciphers, cipherlist);
/* This is very delicate, try to understand the ciphers */
SSL_CTX_set_cipher_list(ctx, cipherlist);
log(VERBOSE, "cipherlist = %s", cipherlist);
return ctx;
}
/*-------------------------------------------*/
/*--- LoadCertificates - load from files. ---*/
/*-------------------------------------------*/
void load_certificates(SSL_CTX *ctx, const char *certfile,
const char *keyfile, const char *cafile)
{
/* set the local certificate from certfile */
if (SSL_CTX_use_certificate_file(ctx, certfile,
SSL_FILETYPE_PEM) <= 0) {
log(ERROR, "Failed to load certfile! SSL error below:");
log_ssl();
log(INFO, "RMPS failed to start, shutting down...");
exit(EXIT_FAILURE);
}
/* set the private key from KeyFile (may be the same as CertFile) */
if (SSL_CTX_use_PrivateKey_file(ctx, keyfile, SSL_FILETYPE_PEM) <= 0) {
log(ERROR, "Failed to load keyfile! SSL error below:");
log_ssl();
log(INFO, "RMPS failed to start, shutting down...");
exit(EXIT_FAILURE);
}
/* verify private key */
if (!SSL_CTX_check_private_key(ctx)) {
log(ERROR,
"Private key does not match the public certificate.");
log(INFO, "RMPS failed to start, shutting down...");
exit(EXIT_FAILURE);
}
if (cafile != NULL) {
SSL_CTX_set_client_CA_list(ctx,
SSL_load_client_CA_file(cafile));
SSL_CTX_load_verify_locations(ctx, cafile, NULL);
//SSL_CTX_set_verify_depth(ctx, 1);
}
}
void launch_rmps(struct conf_table *conf, int fork_flag)
{
pthread_t pool[2];
struct pool_data pool_args[2];
log(INFO, "Starting up RMPS...");
/* Set signal handling */
set_env();
/* Deamonize */
if (fork_flag)
daemonize("/tmp/");
/* Spawn & lock pidfile */
spawn_pidfile(conf->rmps.pidfile);
/* openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days XXX -nodes
* -nodes is for not protecing with a passphrase
* http://stackoverflow.com/questions/10175812/how-to-create-a-self-signed-certificate-with-openssl
*/
pool_args[0].ctx = init_server_ctx(conf->rmps.cipherlist,
SSL_VERIFY_PEER |
SSL_VERIFY_CLIENT_ONCE |
SSL_VERIFY_FAIL_IF_NO_PEER_CERT);
log(VERBOSE, "Loading agent certs and keys.");
load_certificates(pool_args[0].ctx, conf->rmps.agent_tls_crt,
conf->rmps.agent_tls_key, conf->rmps.cafile);
log(VERBOSE, "Starting agent listener on port: %d",
atoi(conf->rmps.agent_port));
pool_args[0].srv = open_listener(atoi(conf->rmps.agent_port));
pool_args[0].size = conf->rmps.agent_poolsize;
log(VERBOSE, "Creating agent thread pool (mutex).");
pthread_create(&pool[0], NULL, agent_pool, &pool_args[0]);
pool_args[1].ctx = init_server_ctx(conf->rmps.cipherlist,
SSL_VERIFY_NONE);
log(VERBOSE, "Loading client certs and keys.");
load_certificates(pool_args[1].ctx, conf->rmps.client_tls_crt,
conf->rmps.client_tls_key, conf->rmps.cafile);
log(VERBOSE, "Starting client listener on port: %d",
atoi(conf->rmps.client_port));
pool_args[1].srv = open_listener(atoi(conf->rmps.client_port));
pool_args[1].size = conf->rmps.client_poolsize;
log(VERBOSE, "Creating client thread pool (mutex).");
pthread_create(&pool[1], NULL, client_pool, &pool_args[1]);
if (start_job_queue(conf->rmps.agent_poolsize) == FAIL) {
log(ERROR,
"On start_job_queue(), RMPS failed to start, shutting down...");
exit(EXIT_FAILURE);
}
pthread_join(pool[0], NULL);
pthread_join(pool[1], NULL);
}