From e9f3673bfd381350d26870d8debcfbc436990223 Mon Sep 17 00:00:00 2001 From: Bogomil Vasilev Date: Mon, 8 Aug 2016 11:12:06 +0300 Subject: [PATCH] Initial commit. It's far from finished. --- CREDITS | 17 +++ Makefile | 44 ++++++ agent/LSBs | 26 ++++ agent/Makefile | 35 +++++ agent/agent.c | 203 ++++++++++++++++++++++++ agent/agent_ssl.c | 126 +++++++++++++++ agent/agent_ssl.h | 13 ++ agent/detect-unix.sh | 33 ++++ agent/job.c | 152 ++++++++++++++++++ agent/job.h | 30 ++++ agent/pkgctl.sh | 27 ++++ agent/runme.sh | 2 + confparser.c | 356 +++++++++++++++++++++++++++++++++++++++++++ confparser.h | 43 ++++++ db_scripts/readme | 26 ++++ enum_codes | 15 ++ enum_functions.c | 29 ++++ enum_functions.h | 24 +++ log_trace.c | 64 ++++++++ log_trace.h | 13 ++ main.c | 94 ++++++++++++ protocol.c | 3 + protocol.h | 40 +++++ rmps.c | 298 ++++++++++++++++++++++++++++++++++++ rmps.conf | 32 ++++ rmps.h | 6 + rmpsd.service | 14 ++ runme.sh | 1 + thread_pool.c | 167 ++++++++++++++++++++ thread_pool.h | 15 ++ 30 files changed, 1948 insertions(+) create mode 100644 CREDITS create mode 100644 Makefile create mode 100644 agent/LSBs create mode 100644 agent/Makefile create mode 100644 agent/agent.c create mode 100644 agent/agent_ssl.c create mode 100644 agent/agent_ssl.h create mode 100755 agent/detect-unix.sh create mode 100644 agent/job.c create mode 100644 agent/job.h create mode 100755 agent/pkgctl.sh create mode 100755 agent/runme.sh create mode 100644 confparser.c create mode 100644 confparser.h create mode 100644 db_scripts/readme create mode 100644 enum_codes create mode 100644 enum_functions.c create mode 100644 enum_functions.h create mode 100644 log_trace.c create mode 100644 log_trace.h create mode 100644 main.c create mode 100644 protocol.c create mode 100644 protocol.h create mode 100644 rmps.c create mode 100644 rmps.conf create mode 100644 rmps.h create mode 100644 rmpsd.service create mode 100755 runme.sh create mode 100644 thread_pool.c create mode 100644 thread_pool.h diff --git a/CREDITS b/CREDITS new file mode 100644 index 0000000..0d6a109 --- /dev/null +++ b/CREDITS @@ -0,0 +1,17 @@ +# This file is intended to show my gratitude +# to the people who made contribtions to this +# project one way or another. The fields are: +# name (N), email (E), web-address (W), PGP +# key ID or fingerprint (P), description (D), +# and other notes (O) + +N: mancha +E: N/A +D: Triage in SSL/TLS communication issues. Thank you! +O: Enthusiast in #openssl (on freenode IRC server) + +N: Borislav Nikolov +E: borislav@otkachalki.net +W: https://git.otkachalki.net/ +D: For general knowledge and best practice advises in C programming + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..33a3201 --- /dev/null +++ b/Makefile @@ -0,0 +1,44 @@ +CC ?= cc +CCFLAGS ?= -O2 +CCFLAGS += -Wall \ + -Wextra \ + -pipe \ + -Wmissing-declarations \ + -pedantic \ + -fstack-protector-strong + +LDFLAGS = -O1 -lcrypto -lssl -lpthread +LDFLAGS += -Wl,-z,relro,-z,now + +SOURCES = main.c \ + confparser.c \ + rmps.c \ + enum_functions.c \ + log_trace.c \ + thread_pool.c + +OBJECTS = $(SOURCES:.c=.o) +EXECUTABLE = rmpsd +INSTALLDIR := $(DESTDIR)/usr/bin + +all: $(SOURCES) $(EXECUTABLE) + +$(EXECUTABLE): $(OBJECTS) + @echo ' LD $@' + @$(CC) $(LDFLAGS) $(OBJECTS) -o $@ + +.c.o: + @echo ' CC $@' + @$(CC) $(CCFLAGS) -c $< -o $@ + +clean: + rm -rf $(OBJECTS) $(EXECUTABLE) + +distclean: + rm -rf $(OBJECTS) + +install: + install -m755 -D $(EXECUTABLE) "$(INSTALLDIR)/$(EXECUTABLE)" + +uninstall: + rm -f "$(INSTALLDIR)/$(EXECUTABLE)" diff --git a/agent/LSBs b/agent/LSBs new file mode 100644 index 0000000..ce5e2d5 --- /dev/null +++ b/agent/LSBs @@ -0,0 +1,26 @@ +###### UBUNTU ###### + 127 « smirky » ~ » cat /etc/os-release +NAME="Ubuntu" +VERSION="14.04.2 LTS, Trusty Tahr" +ID=ubuntu +ID_LIKE=debian +PRETTY_NAME="Ubuntu 14.04.2 LTS" +VERSION_ID="14.04" +HOME_URL="http://www.ubuntu.com/" +SUPPORT_URL="http://help.ubuntu.com/" +BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/" + 0 « smirky » ~ » cat /etc/lsb-release +DISTRIB_ID=Ubuntu +DISTRIB_RELEASE=14.04 +DISTRIB_CODENAME=trusty +DISTRIB_DESCRIPTION="Ubuntu 14.04.2 LTS" + + +###### DEBIAN ###### +PRETTY_NAME="Debian GNU/Linux stretch/sid" +NAME="Debian GNU/Linux" +ID=debian +HOME_URL="https://www.debian.org/" +SUPPORT_URL="https://www.debian.org/support" +BUG_REPORT_URL="https://bugs.debian.org/" + diff --git a/agent/Makefile b/agent/Makefile new file mode 100644 index 0000000..8838eea --- /dev/null +++ b/agent/Makefile @@ -0,0 +1,35 @@ +CC ?= cc +CCFLAGS ?= -O2 +CCFLAGS += -Wall \ + -Wextra \ + -pipe \ + -Wmissing-declarations \ + -pedantic \ + -fstack-protector-strong + +LDFLAGS = -O1, -lcrypto -lssl -lpthread +LDFLAGS += -Wl,-z,relro,-z,now + +SOURCES = agent.c \ + agent_ssl.c \ + job.c + +OBJECTS = $(SOURCES:.c=.o) +EXECUTABLE = agent + +all: $(SOURCES) $(EXECUTABLE) + +$(EXECUTABLE): $(OBJECTS) + @echo ' LD $@' + @$(CC) $(LDFLAGS) $(OBJECTS) -o $@ + +.c.o: + @echo ' CC $@' + @$(CC) $(CCFLAGS) -c $< -o $@ + +clean: + rm -rf $(OBJECTS) $(EXECUTABLE) + +distclean: + rm -rf $(OBJECTS) + diff --git a/agent/agent.c b/agent/agent.c new file mode 100644 index 0000000..f1ccb2a --- /dev/null +++ b/agent/agent.c @@ -0,0 +1,203 @@ +#include +#include +#include +#include "job.h" +#include "../protocol.h" +#include "agent_ssl.h" + +#define FAIL -1 + +static struct job_args *args; +static pthread_t *job_thread; + +static short get_job_slot(); + +static short get_job_slot() +{ + unsigned short i; + for (i = 0; i < MAX_AGENT_JOBS; i++) + if (args[i].slot != FULL) + return i; + return -1; +} + +static short find_job(unsigned id) +{ + unsigned short i; + for (i = 0; i < MAX_AGENT_JOBS; i++) + if (args[i].buf.meta.id == id) + return i; + return -1; +} + +int main(int count, char *strings[]) +{ + SSL_CTX *ctx; + int server; + SSL *ssl; + int bytes, i; + char *hostname, *portnum; + + if (count != 6) { + printf("usage: %s \n", strings[0]); + _exit(EXIT_SUCCESS); + } + hostname=strings[1]; + portnum=strings[2]; + if ((ctx = init_ctx()) == NULL) + _exit(EXIT_FAILURE); + server = connect_to_rmps(hostname, atoi(portnum)); + if (!server) { + fprintf(stderr, "Failed to connect to RMPS: %s:%d\n", hostname, atoi(portnum)); + _exit(EXIT_FAILURE); + } + load_certs(ctx, strings[3], strings[4], strings[5]); + ssl = SSL_new(ctx); /* create new SSL connection state */ + SSL_set_fd(ssl, server); /* attach the socket descriptor */ + + if (SSL_connect(ssl) == FAIL) { /* perform the connection */ + ERR_print_errors_fp(stderr); + close(server); + SSL_CTX_free(ctx); + _exit(EXIT_FAILURE); + } + printf("Connected with %s encryption\n", SSL_get_cipher(ssl)); + show_certs(ssl); + if (!(args = (struct job_args*)calloc(1, sizeof(struct job_args) * MAX_AGENT_JOBS))) { + fprintf( stderr, + "Failed to calloc() %d bytes for job_args! Exiting...\n", + (int)sizeof(struct job_args) * MAX_AGENT_JOBS ); + SSL_shutdown(ssl); + SSL_free(ssl); + close(server); + SSL_CTX_free(ctx); + _exit(EXIT_FAILURE); + } + if (!(job_thread = (pthread_t*)calloc(1, sizeof(pthread_t) * MAX_AGENT_JOBS))) { + fprintf( stderr, + "Failed to calloc() %d bytes for job_threads! Exiting...\n", + (int)sizeof(pthread_t) * MAX_AGENT_JOBS ); + SSL_shutdown(ssl); + SSL_free(ssl); + close(server); + SSL_CTX_free(ctx); + free(args); + _exit(EXIT_FAILURE); + } + for (i = 0; i < MAX_AGENT_JOBS; i++) { + args[i].slot = FREE; + args[i].ssl = ssl; + } + + do { + struct msg buf; + memset(&buf, 0, sizeof(struct msg)); + bytes = SSL_read(ssl, &buf, sizeof(struct msg)); + if (bytes > 0) { + short index; + if (bytes != sizeof(struct msg)) { + fprintf( stderr, + "Received non-standard data from server!\n" ); + continue; + } + if (buf.chunk.id == 0) { + if ((index = get_job_slot()) == FAIL) { + buf.chunk.id = -1; /* ID -1 means reject (full) */ + sprintf((char*)buf.chunk.data, "The agent's queue is full!"); + SSL_write(ssl, &buf, sizeof(struct msg)); + continue; + } + args[index].slot = FULL; + memcpy(&args[index].buf, &buf, sizeof(struct msg)); + switch (args[index].buf.meta.type) { + case UNIX: + pthread_create( &job_thread[index], + NULL, + exec_unix, + &args[index] ); + continue; + case INSTALL_PKG: + pthread_create( &job_thread[index], + NULL, + install_pkg, + &args[index] ); + continue; + case QUERY_PKG: + pthread_create( &job_thread[index], + NULL, + query_pkg, + &args[index] ); + continue; + case DELETE_PKG: + pthread_create( &job_thread[index], + NULL, + delete_pkg, + &args[index] ); + continue; + case LIST_PKGS: + pthread_create( &job_thread[index], + NULL, + list_pkgs, + &args[index] ); + continue; + case UPDATE_PKG: + pthread_create( &job_thread[index], + NULL, + update_pkg, + &args[index] ); + continue; + case UPDATE_PKGS: + pthread_create( &job_thread[index], + NULL, + update_pkgs, + &args[index] ); + continue; + case GET_OS: + pthread_create( &job_thread[index], + NULL, + get_os, + &args[index] ); + continue; + case GET_KERNEL: + pthread_create( &job_thread[index], + NULL, + get_kernel, + &args[index] ); + continue; + case GET_UPTIME: + pthread_create( &job_thread[index], + NULL, + get_uptime, + &args[index] ); + continue; + case GET_MEMORY: + pthread_create( &job_thread[index], + NULL, + get_memory, + &args[index] ); + continue; + default: + buf.chunk.id = -1; + sprintf( (char*)buf.chunk.data, + "Unsupported job type with ID: %d", + buf.meta.type ); + SSL_write(ssl, &buf, sizeof(struct msg)); + continue; + } + } else { + index = find_job(buf.meta.id); + if (index == FAIL) { + sprintf( (char*)buf.chunk.data, + "Data was sent for an invalid job ID" ); + SSL_write(ssl, &buf, sizeof(struct msg)); + } else + memcpy(&args[index].buf, &buf, sizeof(struct msg)); + } + } + + SSL_shutdown(ssl); + SSL_free(ssl); /* release connection state */ + } while (bytes); + close(server); /* close socket */ + SSL_CTX_free(ctx); /* release context */ +} diff --git a/agent/agent_ssl.c b/agent/agent_ssl.c new file mode 100644 index 0000000..abd4e34 --- /dev/null +++ b/agent/agent_ssl.c @@ -0,0 +1,126 @@ +#include +#include +#include +#include "agent_ssl.h" + +#define FAIL -1 + +/*---------------------------------------------------------------------*/ +/*--- OpenConnection - create socket and connect to server. ---*/ +/*---------------------------------------------------------------------*/ + + +int connect_to_rmps(const char *hostname, int port) +{ + int sd; + struct hostent *host; + struct sockaddr_in addr; + + if ((host = gethostbyname(hostname)) == NULL) { + perror(hostname); + return 0; + } + sd = socket(PF_INET, SOCK_STREAM, 0); + bzero(&addr, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = *(long*)(host->h_addr); + if (connect(sd, (struct sockaddr*)&addr, sizeof(addr)) == FAIL) { + close(sd); + perror(hostname); + return 0; + } + return sd; +} + +/*----------------------------------*/ +/*--- Initialize the SSL engine. ---*/ +/*----------------------------------*/ +SSL_CTX* init_ctx(void) +{ + SSL_CTX *ctx; + char ciphers[2048]; + + //OpenSSL_add_all_algorithms(); /* Load cryptos, et.al. */ + //OpenSSL_add_all_ciphers(); /* Load cryptos, et.al. */ + SSL_load_error_strings(); /* Bring in and register error messages */ + SSL_library_init(); + ctx = SSL_CTX_new(TLSv1_2_method()); /* Create new context */ + ciphers[0] = 0; + SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, verify_callback); + strcat(ciphers, "-ALL"); + if (!SSL_CTX_set_cipher_list(ctx, "ECDHE-RSA-AES128-SHA256:AES256-SHA")) { + fprintf(stderr, "Failed to set the cipher list. Exiting...\n"); + return NULL; + } + if (ctx == NULL) { + ERR_print_errors_fp(stderr); + return NULL; + } + return ctx; +} + +int verify_callback (int ok, X509_STORE_CTX *store) +{ + char data[256]; + if (!ok) { + X509 *cert = X509_STORE_CTX_get_current_cert(store); + int depth = X509_STORE_CTX_get_error_depth(store); + int err = X509_STORE_CTX_get_error(store); + fprintf(stderr, "-Error with certificate at depth: %i\n", depth); + X509_NAME_oneline(X509_get_issuer_name(cert), data, 256); + fprintf(stderr, " issuer = %s\n", data); + X509_NAME_oneline(X509_get_subject_name(cert), data, 256); + fprintf(stderr, " subject = %s\n", data); + fprintf(stderr, " err %i:%s\n", err, X509_verify_cert_error_string(err) ); + } + return ok; +} + +/*-----------------------------------*/ +/*--- Print out the certificates. ---*/ +/*-----------------------------------*/ +void show_certs(SSL* ssl) +{ + X509 *cert; + char *line; + char *cipher; + int index = 0; + + cert = SSL_get_peer_certificate(ssl); /* get the server's certificate */ + + if (cert != NULL) { + printf("Server certificates:\n"); + line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0); + printf("Subject: %s\n", line); + free(line); /* free the malloc'ed string */ + line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0); + printf("Issuer: %s\n", line); + free(line); /* free the malloc'ed string */ + X509_free(cert); /* free the malloc'ed certificate copy */ + } else + printf("No certificates.\n"); + + do { + cipher = (char*)SSL_get_cipher_list(ssl, index); + if (cipher) { + printf("Cipher = %s\n", cipher); + index++; + } + } while (cipher); +} + +void load_certs(SSL_CTX *ctx, char *key, char *cert, char *ca) +{ + SSL_CTX_use_certificate_file(ctx, cert, SSL_FILETYPE_PEM); + SSL_CTX_use_PrivateKey_file(ctx, key, SSL_FILETYPE_PEM); + if (!SSL_CTX_check_private_key(ctx)) { + fprintf(stderr, "Private key doesn't match the cert!\n"); + exit(EXIT_FAILURE); + } + SSL_CTX_set_client_CA_list(ctx, SSL_load_client_CA_file(ca)); + SSL_CTX_load_verify_locations(ctx, ca, NULL); + if (SSL_CTX_get_client_CA_list(ctx) == NULL) { + fprintf(stderr, "Could not set client CA list from %s\n", ca); + } +} diff --git a/agent/agent_ssl.h b/agent/agent_ssl.h new file mode 100644 index 0000000..338b4b0 --- /dev/null +++ b/agent/agent_ssl.h @@ -0,0 +1,13 @@ +#ifndef AGENT_SSL +#define AGENT_SSL + +#include +#include + +int connect_to_rmps(const char *hostname, int port); +SSL_CTX* init_ctx(); +int verify_callback (int ok, X509_STORE_CTX *store); +void show_certs(SSL* ssl); +void load_certs(SSL_CTX *ctx, char *key, char *cert, char *ca); + +#endif /* AGENT_SSL */ diff --git a/agent/detect-unix.sh b/agent/detect-unix.sh new file mode 100755 index 0000000..948df5b --- /dev/null +++ b/agent/detect-unix.sh @@ -0,0 +1,33 @@ +#!/bin/sh +# Detects which OS and if it is Linux then it will detect which Linux Distribution. + +OS=$(uname -s) +MARCH=$(uname -m) + +if [ "${OS}" = "SunOS" ] ; then + OS=Solaris + ARCH=$(uname -p) + OSSTR="${OS} ${REV}(${ARCH} $(uname -v))" +elif [ "${OS}" = "AIX" ] ; then + OSSTR="${OS} $(oslevel) ($(oslevel -r))" +elif [ "${OS}" = "Linux" ] ; then + KERNEL=$(uname -r) + if [ -f /etc/redhat-release ] ; then + DIST="$(cat /etc/redhat-release)" + elif [ -f /etc/SuSE-release ] ; then + DIST=$(tr "\n" ' ' < /etc/SuSE-release | sed s/VERSION.*//) + elif [ -f /etc/debian_version ] ; then + DIST="Debian $(cat /etc/debian_version)" + elif [ -f /etc/slackware-version ] ; then + DIST="$(cat /etc/slackware-version)" + elif [ -f /etc/os-release ] ; then + DIST=$(grep PRETTY_NAME /etc/os-release | tr -d '"=' | sed 's/PRETTY_NAME//') + else + DIST="$OS $MARCH" + fi + + OSSTR="${DIST}" +fi + + +printf "%s" "${OSSTR}" diff --git a/agent/job.c b/agent/job.c new file mode 100644 index 0000000..f170f9b --- /dev/null +++ b/agent/job.c @@ -0,0 +1,152 @@ +#include +#include +#include +#include +#include "job.h" +#include +#include + +struct job_t { + long id; + long parent_id; + int type; + char code[8192]; + char std_out[8192]; + char std_err[8192]; + short ret_code; +}; + +static void gen_fname_hash(char *s, const int len) +{ + static const char alphanum[] = + "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"; + int i; + for (i = 0; i < len; ++i) + s[i] = alphanum[rand() % (sizeof(alphanum) - 1)]; + s[len] = 0; +} + +void* exec_unix(void *args) +{ + struct job_args *job = args; + return 0; +} + +void* install_pkg(void *args) +{ + struct job_args *job = args; + return 0; +} + +void* query_pkg(void *args) +{ + struct job_args *job = args; + return 0; +} + +void* delete_pkg(void *args) +{ + struct job_args *job = args; + return 0; +} + +void* list_pkgs(void *args) +{ + struct job_args *job = args; + return 0; +} + +void* update_pkg(void *args) +{ + struct job_args *job = args; + return 0; +} + +void* update_pkgs(void *args) +{ + struct job_args *job = args; + return 0; +} + +void* get_os(void *args) +{ + struct job_args *job = args; + char cmd[40], dump_path[14]; + int fd; + sprintf(dump_path, "/tmp/"); + gen_fname_hash(&dump_path[5], 8); + sprintf(cmd, "sh detect-unix.sh > %s", dump_path); + system(cmd); /* TODO: set proper path */ + if ((fd = open(dump_path, O_RDONLY)) == -1) { + fprintf(stderr, "Cannot open file: %s\n", dump_path); + job->buf.meta.len = sprintf((char*)job->buf.chunk.data, "Unknown"); + SSL_write(job->ssl, &job->buf, sizeof(struct msg)); + } else { + job->buf.meta.len = read(fd, job->buf.chunk.data, sizeof(job->buf.chunk.data)); + if ((signed)job->buf.meta.len == -1) { + perror("Failed to read from file: "); + } + close(fd); + SSL_write(job->ssl, &job->buf, sizeof(struct msg)); + job->slot = FREE; + } + unlink(dump_path); + return 0; +} + +void* get_kernel(void *args) +{ + struct utsname name; + struct job_args *job = args; + if (uname(&name) == -1) { + job->buf.meta.len = sprintf((char*)job->buf.chunk.data, "Unknown"); + SSL_write(job->ssl, &job->buf, sizeof(struct msg)); + } else { + job->buf.meta.len = sprintf( (char*)job->buf.chunk.data, + "%s %s %s %s %s", + name.sysname, + name.nodename, + name.release, + name.version, + name.machine ); + SSL_write(job->ssl, &job->buf, sizeof(struct msg)); + } + job->slot = FREE; + return 0; +} + +void* get_uptime(void *args) +{ + struct job_args *job = args; + struct sysinfo sys; + + if (sysinfo(&sys) != 0) { + job->buf.meta.len = sprintf((char*)job->buf.chunk.data, "Unknown"); + SSL_write(job->ssl, &job->buf, sizeof(struct msg)); + } else { + memcpy(&job->buf.chunk.data, &sys.uptime, sizeof(long)); + //sprintf((char*)&job->buf.chunk.data, "%ld", sys.uptime); + SSL_write(job->ssl, &job->buf, sizeof(struct msg)); + } + job->slot = FREE; + return 0; +} + +void* get_memory(void *args) +{ + long pages, pagesize, freepages; + struct job_args *job = args; + + pages = sysconf(_SC_PHYS_PAGES); + pagesize = sysconf (_SC_PAGESIZE); + freepages = sysconf(_SC_AVPHYS_PAGES); + job->buf.meta.len = sprintf( (char*)job->buf.chunk.data, + "%ld / %ld (MB)", + (freepages * pagesize) / 1048576 /* 1024*1024 */, + (pages * pagesize) / 1048576 ); + SSL_write(job->ssl, &job->buf, sizeof(struct msg)); + job->slot = FREE; + return 0; +} diff --git a/agent/job.h b/agent/job.h new file mode 100644 index 0000000..57af392 --- /dev/null +++ b/agent/job.h @@ -0,0 +1,30 @@ +#ifndef JOB_H +#define JOB_H + +#include "agent_ssl.h" +#include "../protocol.h" + +enum pthread_state { WAIT, WORK }; +enum job_slot_state { FREE, FULL }; + +struct job_args { + struct msg buf; + unsigned short slot; + SSL *ssl; +}; + +void* exec_unix(void *args); +void* install_pkg(void *args); +void* query_pkg(void *args); +void* delete_pkg(void *args); +void* list_pkgs(void *args); +void* update_pkg(void *args); +void* update_pkgs(void *args); +void* get_os(void *args); +void* get_kernel(void *args); +void* get_uptime(void *args); +void* get_memory(void *args); + +#define MAX_AGENT_JOBS 10 + +#endif /* JOB_H */ diff --git a/agent/pkgctl.sh b/agent/pkgctl.sh new file mode 100755 index 0000000..7d37a77 --- /dev/null +++ b/agent/pkgctl.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +OS=`sh detect-unix.sh` + +# Determine task +#case $1 in +# install) +# remove) +# update) +# query) +#esac + +# Determine package manager +case "$OS" in + "Arch Linux") PM="pacman" ;; + "Fedora") PM="yum" ;; + "Red Hat") PM="yum" ;; + "CentOS") PM="yum" ;; + "SUSE") PM="yum" ;; + "Ubuntu") PM="apt" ;; + "Debian") PM="apt" ;; +esac + +#if [ "$OS" = "Arch Linux" ] ; then +# PM="pacman" +#fi +echo $PM \ No newline at end of file diff --git a/agent/runme.sh b/agent/runme.sh new file mode 100755 index 0000000..7c1adc1 --- /dev/null +++ b/agent/runme.sh @@ -0,0 +1,2 @@ +#!/bin/bash +./agent localhost 7000 /etc/rmps/certs/client_1/client.key /etc/rmps/certs/client_1/client.crt /etc/rmps/certs/ca.pem \ No newline at end of file diff --git a/confparser.c b/confparser.c new file mode 100644 index 0000000..75b5c3d --- /dev/null +++ b/confparser.c @@ -0,0 +1,356 @@ +#include +#include +#include +#include +#include +#include +#include "log_trace.h" +#include "confparser.h" +#include "enum_functions.h" + +static int test_conf_perms(); +static int test_conf_syntax(); + +struct conf_table conf = { + { + "", /* db.type */ + "", /* db.hostname */ + "" /* db.port */ + }, + { + "127.0.0.1", /* rmps.bind_on_ip */ + "7000", /* rmps.bind_on_port */ + "/var/log/rmps/rmpsd.log", + "/var/log/rmps/rmpsd.err", + '1', /* rmps.loglevel */ + "/run/rmps/rmpsd.pid", + "/etc/rmps/cert.pem", + "/etc/rmps/key.pem", + "/etc/rmps/ca.crt", + "", /* rmps.cipherlist */ + 2 /* rmps.threadpoolsize */ + }, + { + 0 /* nfs -> TODO */ + } +}; + +void confexport() +{ + printf( "db.type=%s\n" + "db.hostname=%s\n" + "db.port=%s\n" + "rmps.bind_on_ip=%s\n" + "rmps.bind_on_port=%s\n" + "rmps.logfile=%s\n" + "rmps.errlog=%s\n" + "rmps.loglevel=%c\n" + "rmps.pidfile=%s\n" + "rmps.certfile=%s\n" + "rmps.keyfile=%s\n" + "rmps.cafile=%s\n" + "rmps.cipherlist=%s\n" + "rmps.threadpoolsize=%d\n", + conf.db.type, + conf.db.hostname, + conf.db.port, + conf.rmps.bind_on_ip, + conf.rmps.bind_on_port, + conf.rmps.logfile, + conf.rmps.errlog, + conf.rmps.loglevel, + conf.rmps.pidfile, + conf.rmps.certfile, + conf.rmps.keyfile, + conf.rmps.cafile, + conf.rmps.cipherlist, + conf.rmps.threadpoolsize + ); +} + +static int fopen_and_mkdir(const char *dir) { + char tmp[256]; + char *p = NULL; + size_t len; + FILE *fd; + + snprintf(tmp, sizeof(tmp),"%s",dir); + len = strlen(tmp); + if(tmp[len - 1] == '/') + tmp[len - 1] = 0; + for (p = tmp + 1; *p; p++) + if(*p == '/') { + *p = 0; + if (mkdir(tmp, S_IRWXU) == -1 && errno != EEXIST) { + fprintf( stderr, + "Permission denied to create directory: %s\n", + tmp ); + return 1; + } + *p = '/'; + } + fd = fopen(dir, "a"); + if (!fd) { + fprintf(stderr, "Permission denied to write into: %s\n", dir); + return 1; + } + fclose(fd); + return 0; +} + + +static int test_conf_perms() +{ + struct stat s; + char confresult[128]; + int err = stat("/etc/rmps", &s); + + if (err == -1) { + if (errno == ENOENT) { + enumtostr(confresult, CONF_DIR_MISSING); + log_trace(ERROR, confresult); + return 1; + } + } else { + if (!S_ISDIR(s.st_mode)) { + enumtostr(confresult, CONF_DIR_NOTDIR); + log_trace(ERROR, confresult); + return 1; + } + if ( !(S_IRUSR & s.st_mode) || + !(S_IXUSR & s.st_mode) ) { + enumtostr(confresult, CONF_DIR_PERM); + log_trace(ERROR, confresult); + return 1; + } + if (s.st_uid != 0) { + enumtostr(confresult, CONF_DIR_UID_INSECURE); + log_trace(WARNING, confresult); + } + else if (s.st_gid != 0) { + enumtostr(confresult, CONF_DIR_GID_INSECURE); + log_trace(WARNING, confresult); + } + else if ( (S_IROTH & s.st_mode) || + (S_IWOTH & s.st_mode) ) { + enumtostr(confresult, CONF_DIR_PERM_INSECURE); + log_trace(WARNING, confresult); + } + } + + err = stat("/etc/rmps/rmps.conf", &s); + + if (err == -1) { + if (errno == ENOENT) { + enumtostr(confresult, CONF_MISSING); + log_trace(ERROR, confresult); + return 1; + } + } else { + if (!S_ISREG(s.st_mode)) { + enumtostr(confresult, CONF_NOTFILE); + log_trace(ERROR, confresult); + return 1; + } + if (!(S_IRUSR & s.st_mode)) { + enumtostr(confresult, CONF_PERM); + log_trace(ERROR, confresult); + return 1; + } + if (s.st_uid != 0) { + enumtostr(confresult, CONF_FILE_UID_INSECURE); + log_trace(WARNING, confresult); + } + else if (s.st_gid != 0) { + enumtostr(confresult, CONF_FILE_GID_INSECURE); + log_trace(WARNING, confresult); + } + else if ( (S_IROTH & s.st_mode) || + (S_IWOTH & s.st_mode) ) { + enumtostr(confresult, CONF_FILE_PERM_INSECURE); + log_trace(WARNING, confresult); + } + } + + return 0; /* conf is readable */ +} + +static int test_conf_syntax() +{ + int i, j = 0, ok = 1, failed = 0; + char buf[CFGLINESIZE], *tmp; + FILE *fd = fopen("/etc/rmps/rmps.conf", "r"); + + if (fd == NULL) { + log_trace(ERROR, "Failed to read /etc/rmps/rmps.conf"); + return 1; + } + + while (fgets(buf, CFGLINESIZE, fd) != NULL) { + j++; + /* kill comments and ignore BLANK lines */ + if ((tmp = strstr(buf, "#"))) + *tmp = '\0'; + if (buf[strspn(buf, " \t\v\r\n")] == '\0') + continue; + + /* If we have "=", it's a possible var */ + if ((tmp = strstr(buf, "="))) + *tmp = '\0'; + else { + fprintf( stderr, + "Bad entry in /etc/rmps/rmps.conf, line %d: %s\n", + j, buf ); + ok = 0; + failed = 1; + continue; + } + /* Check if there actually is a value after '=' */ + i = strlen(tmp + 1); + if (tmp[i] == '\n') + tmp[i] = '\0'; + if (tmp[strspn(tmp + 1, " \t\v\r\n") + 1] == '\0') { + fprintf( stderr, + "Specified entry without value, line %d: %s\n", + j, buf ); + failed = 1; + continue; + } + + /* Here we check every single entry manually */ + if (!strcmp(buf, "db.type")) { + if (!strcmp(tmp + 1, "mysql")) { + /* || !strcmp(tmp[1], "postgresql") */ + /* || !strcmp(tmp[1], "oracle") */ + strcpy(conf.db.type, tmp + 1); + if (conf.db.port[0] == '\0') + strcpy(conf.db.port, "3306"); + } else { + ok = 0; + failed = 1; + } + } else if (!strcmp(buf, "db.hostname")) + /* Just save it, launch_rmps will check it */ + strcpy(conf.db.hostname, tmp + 1); + else if (!strcmp(buf, "db.port")) { + if ((i = strlen(tmp + 1)) < 6) { + if ((signed int)strspn(tmp + 1, "1234567890") == i) { + i = atoi(tmp + 1); + if (i > 0 && i < 65536) { + strcpy(conf.db.port, tmp + 1); + continue; + } + } + } + ok = 0; + failed = 1; + } else if (!strcmp(buf, "rmps.bind_on_ip")) { + /* TODO */ + + } else if (!strcmp(buf, "rmps.bind_on_port")) { + if ((i = strlen(tmp + 1)) < 6) { + if ((signed int)strspn(tmp + 1, "1234567890") == i) { + i = atoi(tmp + 1); + if (i > 0 && i < 65536) { + strcpy(conf.rmps.bind_on_port, tmp + 1); + continue; + } + } + } + ok = 0; + failed = 1; + } else if (!strcmp(buf, "rmps.logfile")) { + strcpy(conf.rmps.logfile, tmp + 1); + if (fopen_and_mkdir(conf.rmps.logfile) != 0) + failed = 1; + } else if (!strcmp(buf, "rmps.errlog")) { + strcpy(conf.rmps.errlog, tmp + 1); + if (fopen_and_mkdir(conf.rmps.errlog) != 0) + failed = 1; + } else if (!strcmp(buf, "rmps.pidfile")) { + strcpy(conf.rmps.pidfile, tmp + 1); + /*if (fopen_and_mkdir(conf.rmps.pidfile) != 0) + failed = 1;*/ + } else if (!strcmp(buf, "rmps.loglevel")) { + if (strlen(tmp + 1) == 1 && (tmp[1] > '0' && tmp[1] < '5')) + conf.rmps.loglevel = tmp[1]; + else + failed = 1; + } else if (!strcmp(buf, "rmps.certfile")) { + if (access(tmp + 1, F_OK) == -1) { + fprintf( stderr, + "%s is missing\n", tmp + 1); + failed = 1; + } + else if (access(tmp + 1, R_OK) == -1) { + fprintf( stderr, + "%s is not readable\n", + tmp + 1 + ); + failed = 1; + } else + strncpy(conf.rmps.certfile, tmp + 1, sizeof(conf.rmps.certfile)); + } + + else if (!strcmp(buf, "rmps.keyfile")) { + if (access(tmp + 1, F_OK) == -1) { + fprintf( stderr, + "%s is missing\n", conf.rmps.keyfile); + failed = 1; + } + else if (access(tmp + 1, R_OK) == -1) { + fprintf( stderr, + "%s is not readable\n", + tmp + 1 + ); + failed = 1; + } else + strncpy(conf.rmps.keyfile, tmp + 1, sizeof(conf.rmps.keyfile)); + } else if (!strcmp(buf, "rmps.cipherlist")) { + strncpy(conf.rmps.cipherlist, tmp + 1, sizeof(conf.rmps.cipherlist)); + } else if (!strcmp(buf, "rmps.cafile")) { + if (access(tmp + 1, F_OK) == -1) { + fprintf( stderr, + "%s is missing\n", tmp + 1); + failed = 1; + } + else if (access(tmp + 1, R_OK) == -1) { + fprintf( stderr, + "%s is not readable\n", + tmp + 1 + ); + failed = 1; + } else + strncpy(conf.rmps.cafile, tmp + 1, sizeof(conf.rmps.cafile)); + } else + fprintf( stderr, + "Unknown config entry on line %d: %s\n", + j, buf ); + if (!ok) { + fprintf( stderr, + "Invalid value for \"%s\", line %d: \"%s\"\n", + buf, j, tmp + 1 ); + ok = !ok; + } + } + fclose(fd); + + if (failed) + return 1; + return 0; +} + +int confparse() +{ + int result; + + result = test_conf_perms(); + if (result) + return 1; /* Bad conf perms */ + + result = test_conf_syntax(); + if (result != 0) + return 1; /* Bad conf syntax */ + + return 0; /* seems legit */ +} diff --git a/confparser.h b/confparser.h new file mode 100644 index 0000000..b2a05ae --- /dev/null +++ b/confparser.h @@ -0,0 +1,43 @@ +#ifndef CONFPARSER_H +#define CONFPARSER_H + +#define MAXPATHSIZE 256 +#define HOSTNAMESIZE 128 +#define CFGLINESIZE 300 + +struct conf_db { + char type[15]; + char hostname[HOSTNAMESIZE]; + char port[6]; +}; + +struct conf_rmps { + char bind_on_ip[13]; + char bind_on_port[6]; + char logfile[MAXPATHSIZE]; + char errlog[MAXPATHSIZE]; + char loglevel; + char pidfile[MAXPATHSIZE]; + char certfile[MAXPATHSIZE]; + char keyfile[MAXPATHSIZE]; + char cafile[MAXPATHSIZE]; + char cipherlist[1024]; + int threadpoolsize; +}; + +struct conf_nfs { + int TODO; +}; + +struct conf_table { + struct conf_db db; + struct conf_rmps rmps; + struct conf_nfs nfs; +}; + +extern struct conf_table conf; +extern int confparse(); +extern void confexport(); + +#endif /* CONFPARSER_H */ + diff --git a/db_scripts/readme b/db_scripts/readme new file mode 100644 index 0000000..a40fa69 --- /dev/null +++ b/db_scripts/readme @@ -0,0 +1,26 @@ +##### static_groups ##### +ID | NAME | CREATED | OWNER(FK-users) | LAST_MOD_TIME | LAST_MOD_USER | COMMENT + +##### dynamic_groups ##### + + +##### managed_servers ##### +ID | NAME | IP | MAC | FQDN | OS | ADDED | ACTIVE | COMMENT + +##### scripts ##### +ID | NAME | TYPE(FK-script_types) | CREATED | OWNER(FK-users) | LAST_MOD_TIME | CONTENT | COMMENT + +##### script_types ##### +ID | DESCR + +##### script_logs ##### +ID | JID | STARTED | ENDED | STDOUT | STDERR | EXIT_CODE + +##### jobs ##### +ID | TYPEID(FK-jobs_types) | OWNER(FK-users) | STARTED | ENDED | STATUS + +##### job_types ##### +ID | TYPE + +##### users ##### +ID | USERNAME | NAME | ADDED | LAST_LOG | LAST_OUT | EMAIL | COMMENT diff --git a/enum_codes b/enum_codes new file mode 100644 index 0000000..74e07ff --- /dev/null +++ b/enum_codes @@ -0,0 +1,15 @@ +# Error codes +100:CONF_DIR_MISSING:"Config directory /etc/rmps is missing!" +101:CONF_DIR_PERM:"Config dir /etc/rmps cannot be accessed, check permissions!" +102:CONF_DIR_NOTDIR:"It appears that /etc/rmps is a file. Should be a directory!" +103:CONF_MISSING:"Config file /etc/rmps/rmps.conf is missing!" +104:CONF_PERM:"Config file /etc/rmps/rmps.conf cannot be accessed, check permissions!" +105:CONF_NOTFILE:"It appears that /etc/rmps/rmps.conf is not a regular file!" + +# Warning codes +200:CONF_DIR_GUI_INSECURE:"Insecure group for /etc/rmps. Should be 'rmps'!" +201:CONF_DIR_UID_INSECURE:"Insecure owner for /etc/rmps. Should be 'root'!" +202:CONF_DIR_PERM_INSECURE:"Insecure global permissions for /etc/rmps. Should be 0770!" +203:CONF_FILE_GID_INSECURE:"Insecure group for /etc/rmps/rmps.conf. Should be 'rmps'!" +204:CONF_FILE_UID_INSECURE:"Insecure owner for /etc/rmps/rmps.conf. Should be 'root'!" +205:CONF_FILE_PERM_INSECURE:"Insecure global permissions /etc/rmps/rmps.conf". Shold be 0660!" diff --git a/enum_functions.c b/enum_functions.c new file mode 100644 index 0000000..f41024f --- /dev/null +++ b/enum_functions.c @@ -0,0 +1,29 @@ +#include +#include +#include +#include "enum_functions.h" +#include "log_trace.h" + +void enumtostr(char *scode, int code) +{ + char line[128]; + FILE *fd; + + snprintf(scode, 10, "%d", code); + + fd = fopen("/usr/lib/rmps/resources/enum_codes", "r"); + if (fd == NULL) { + log_trace(ERROR, "Failed to fetch error enum code!"); + return; + } + while (fgets(line, sizeof(line), fd) != NULL) + if (strstr(line, scode) != NULL) { + sprintf(scode, "%s", line); + break; + } + strncpy( scode, + memchr(scode, '\"', strlen(scode)), + strlen(scode) ); + fclose(fd); + return; +} diff --git a/enum_functions.h b/enum_functions.h new file mode 100644 index 0000000..485b891 --- /dev/null +++ b/enum_functions.h @@ -0,0 +1,24 @@ +#ifndef ENUM_FUNCTIONS_H +#define ENUM_FUNCTIONS_H + +enum ERROR_CODES { + CONF_DIR_MISSING = 100, + CONF_DIR_PERM, /* 101 */ + CONF_DIR_NOTDIR, /* 102 */ + CONF_MISSING, /* 103 */ + CONF_PERM, /* 104 */ + CONF_NOTFILE /* 105 */ +}; + +enum WARN_CODES { + CONF_DIR_GID_INSECURE = 200, + CONF_DIR_UID_INSECURE, + CONF_DIR_PERM_INSECURE, + CONF_FILE_GID_INSECURE, + CONF_FILE_UID_INSECURE, + CONF_FILE_PERM_INSECURE +}; + +extern void enumtostr(char *scode, int code); + +#endif /* ENUM_FUNCTIONS_H */ diff --git a/log_trace.c b/log_trace.c new file mode 100644 index 0000000..63e2e66 --- /dev/null +++ b/log_trace.c @@ -0,0 +1,64 @@ +#include +#include +#include +#include "log_trace.h" +#include "confparser.h" + +void log_trace(LOG_LEVEL lvl, char *fmt, ... ) +{ + LOG_LEVEL cur_lvl = conf.rmps.loglevel - '0'; + if (lvl <= cur_lvl) { + va_list list; + int fallback = 0; + FILE *fd; + time_t t = time(NULL); + struct tm tm = *localtime(&t); + if (lvl == ERROR || lvl == WARNING) { + fd = fopen(conf.rmps.errlog, "a"); + if (!fd) { + fprintf( stderr, + "Failed to append errlog: %s. Redirecting to stderr:\n", + conf.rmps.errlog ); + fd = stderr; + fallback = 1; + } + fprintf( fd, + "[%d-%02d-%02d %02d:%02d:%02d] %s: ", + tm.tm_year + 1900, + tm.tm_mon + 1, + tm.tm_mday, + tm.tm_hour, + tm.tm_min, + tm.tm_sec, + lvl == ERROR ? "ERROR" : "WARNING" ); + } else { + fd = fopen(conf.rmps.logfile, "a"); + if (!fd) { + fprintf( stderr, + "Failed to append logfile: %s. Redirecting to stderr:\n", + conf.rmps.logfile ); + fd = stderr; + fallback = 1; + } + fprintf( fd, + "[%d-%02d-%02d %02d:%02d:%02d] %s: ", + tm.tm_year + 1900, + tm.tm_mon + 1, + tm.tm_mday, + tm.tm_hour, + tm.tm_min, + tm.tm_sec, + lvl == INFO ? "INFO" : "VERBOSE" ); + } + + va_start(list, fmt); + vfprintf(fd, fmt, list); + fprintf(fd, "\n"); + va_end(list); + if (!fallback) + fclose(fd); + } else + return; + fflush(stdout); +} + diff --git a/log_trace.h b/log_trace.h new file mode 100644 index 0000000..7136ade --- /dev/null +++ b/log_trace.h @@ -0,0 +1,13 @@ +#ifndef LOG_TRACE_H +#define LOG_TRACE_H + +typedef enum { + ERROR = 1, /* Errors only */ + WARNING, /* Errors & warnings */ + INFO, /* Errors, warnings & events */ + VERBOSE, /* Errors, warnings, events & more? */ +} LOG_LEVEL; + +void log_trace(LOG_LEVEL lvl, char *fmt, ... ); + +#endif /* LOG_TRACE_H */ diff --git a/main.c b/main.c new file mode 100644 index 0000000..4d12254 --- /dev/null +++ b/main.c @@ -0,0 +1,94 @@ +#include +#include +#include +#include +#include +#include +#include "confparser.h" +#include "log_trace.h" +#include "rmps.h" + +static void usage(char *argv) +{ + fprintf( stderr, + "Usage:\n%s start|stop|restart [--daemonize=yes|no]\n", + argv ); +} + +int main(int argc, char *argv[]) +{ + int fork_flag = 1, task; + + if (argc < 2 || argc > 3) { + usage(argv[0]); + exit(EXIT_FAILURE); + } + + if (!strcmp(argv[1], "start")) + task = 1; + else if (!strcmp(argv[1], "stop")) + task = 2; + else if (!strcmp(argv[1], "restart")) + task = 3; + else { + usage(argv[0]); + exit(EXIT_FAILURE); + } + + if (argc == 3) { + if (!strcmp("--daemonize=yes", argv[2])); + else if (!strcmp("--daemonize=no", argv[2])) + fork_flag = 0; + else { + usage(argv[0]); + exit(EXIT_FAILURE); + } + } + if (confparse() != 0) { + fprintf(stderr, "Failed to parse the conf!"); + exit(EXIT_FAILURE); + } + //confexport(); + + if (task == 2 || task == 3) { + char buf[10]; + int pid; + FILE *fd; + if (task == 2) + log_trace(VERBOSE, "We got a stop signal!"); + else if (task == 3) + log_trace(VERBOSE, "We got a restart signal!"); + + fd = fopen(conf.rmps.pidfile, "r"); + + switch (errno) { + case EEXIST: + if (!fgets(buf, 10, fd)) { + log_trace(ERROR, "Failed to read %s!", conf.rmps.pidfile); + exit(EXIT_FAILURE); + } + pid = strtol(buf, NULL, 10); + kill(pid, SIGTERM); + //waitpid(TODO); + break; + case EACCES: + log_trace(ERROR, "Permission denied to read PID. Exiting!"); + exit(EXIT_FAILURE); + case ENOENT: + if (task == 2) + exit(EXIT_FAILURE); + break; + default: + log_trace( ERROR, + "Unhandled errno while opening PID: %d. Exiting!", + errno + ); + exit(EXIT_FAILURE); + } + } + if (task == 1 || task == 3) { + launch_rmps(&conf, fork_flag); + } + + return 0; +} diff --git a/protocol.c b/protocol.c new file mode 100644 index 0000000..f9ee753 --- /dev/null +++ b/protocol.c @@ -0,0 +1,3 @@ +#include "protocol.h" + + diff --git a/protocol.h b/protocol.h new file mode 100644 index 0000000..cccce49 --- /dev/null +++ b/protocol.h @@ -0,0 +1,40 @@ +#ifndef PROTOCOL_H +#define PROTOCOL_H + +#define CHUNKSIZE 2048 + +enum msg_types { + UNIX, + POWERSHELL, + INSTALL_PKG, + QUERY_PKG, + DELETE_PKG, + LIST_PKGS, + UPDATE_PKG, + UPDATE_PKGS, + GET_OS, + GET_KERNEL, + GET_UPTIME, + GET_MEMORY +}; + +struct msg_meta { + unsigned short id; /* Agent job ID */ + unsigned short type; /* Data type */ + unsigned len; /* Data size to expect in buffer */ + unsigned chunks; + short is_recv; + short locking; +}; + +struct msg_chunk { + int id; + unsigned char data[CHUNKSIZE]; +}; + +struct msg { + struct msg_meta meta; + struct msg_chunk chunk; +}; + +#endif /* PROTOCOL_H */ diff --git a/rmps.c b/rmps.c new file mode 100644 index 0000000..f21b4b0 --- /dev/null +++ b/rmps.c @@ -0,0 +1,298 @@ +#include "log_trace.h" +#include "confparser.h" +#include "thread_pool.h" +#include "rmps.h" +#include +#include +#include + +/* included for openssl and sockets */ +#include +#include +#include +#include +#include + +static void rmps_shutdown(); +static void signal_handler(int sig); +static void set_env(); +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(); +static void signal_handler(int sig); +//static void show_certs(SSL *ssl); +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); +//static void servlet(SSL *ssl); + +static int pid_file_handle; + +static void cleanup() +{ + log_trace(VERBOSE, "Deleting pidfile %s", conf.rmps.pidfile); + if (unlink(conf.rmps.pidfile) != 0) + log_trace( WARNING, + "Failed to delete pidfile %s. Reason code: %d", + conf.rmps.pidfile, errno ); +} + +static void signal_handler(int sig) +{ + switch (sig) { + case SIGHUP: + log_trace(WARNING, "Received SIGHUP signal. Ignoring..."); + break; + case SIGINT: + case SIGTERM: + log_trace(INFO, "Received SIGTERM signal."); + log_trace(INFO, "RMPS is shutting down..."); + rmps_shutdown(); + log_trace(INFO, "RMPS has been stopped properly."); + _exit(EXIT_SUCCESS); + break; + default: + log_trace(WARNING, "Unhandled signal %s", strsignal(sig)); + break; + } +} + +static void rmps_shutdown() +{ + close(pid_file_handle); + cleanup(); +} + +static void set_env() +{ + 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 - i.e. we don't need to wait for it */ + 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 the above specified 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 */ +} + +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(); + /*printf("fork() = %d\n", pid);*/ + if (pid < 0) { + /* Could not fork */ + log_trace(ERROR, "Failed to fork the parent process. Exiting!"); + exit(EXIT_FAILURE); + } + if (pid > 0) { + /* Child created ok, so exit parent process */ + log_trace(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_trace(ERROR, "Failed to create a process group. Exiting!"); + exit(EXIT_FAILURE); + } + /* Close all file descriptors because we fork */ + for (i = getdtablesize(); i >= 0; --i) + close(i); + + /* 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_trace(ERROR, "Could not create PID file %s - Exiting!", pidfile); + exit(EXIT_FAILURE); + } + + /* Try to lock file */ + if (lockf(pid_file_handle, F_TLOCK, 0) == -1) { + /* Couldn't get lock on lock file */ + log_trace(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_trace(ERROR, "Failed to create socket"); + log_trace(INFO, "RMPS failed to start, shutting down..."); + atexit(cleanup); + } + if (set_reuse_addr(sd) < 0) { + log_trace(ERROR, "Failed to set reuse on address - Aborting...", port); + log_trace(INFO, "RMPS failed to start, shutting down..."); + atexit(cleanup); + } + if (bind(sd, (struct sockaddr*)&addr, sizeof(addr)) != 0) { + log_trace(ERROR, "Failed to bind on port: %d - Aborting...", port); + log_trace(INFO, "RMPS failed to start, shutting down..."); + atexit(cleanup); + } + if (listen(sd, 10) != 0) { + log_trace(ERROR, "Failed to start listener on port %d - Aborting...", port); + log_trace(INFO, "RMPS failed to start, shutting down..."); + atexit(cleanup); + } + return sd; +} + +/* Init server and create context */ +static SSL_CTX* init_server_ctx(const char *cipherlist) +{ + 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(); + + ctx = SSL_CTX_new(TLSv1_2_method()); /* create new context from method */ + if (ctx == NULL) { + log_trace(ERROR, "SSL_CTX_new() returned NULL - Aborting..."); + log_trace(ERROR, "RMPS failed to start, shutting down..."); + exit(EXIT_FAILURE); + } + SSL_CTX_set_verify( ctx, SSL_VERIFY_PEER | + SSL_VERIFY_CLIENT_ONCE | + SSL_VERIFY_FAIL_IF_NO_PEER_CERT, + 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_trace(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) +{ + long ssl_errnum; + char ssl_errstr[2048]; + /* set the local certificate from certfile */ + if (SSL_CTX_use_certificate_file(ctx, certfile, SSL_FILETYPE_PEM) <= 0) { + ssl_errnum = ERR_get_error(); + ERR_error_string_n(ssl_errnum, ssl_errstr, sizeof(ssl_errstr)); + log_trace(ERROR, "Failed to load certfile! SSL error below:\n%s", ssl_errstr); + log_trace(INFO, "RMPS failed to start, shutting down..."); + atexit(cleanup); + } + /* set the private key from KeyFile (may be the same as CertFile) */ + if (SSL_CTX_use_PrivateKey_file(ctx, keyfile, SSL_FILETYPE_PEM) <= 0) { + ssl_errnum = ERR_get_error(); + ERR_error_string_n(ssl_errnum, ssl_errstr, sizeof(ssl_errstr)); + log_trace(ERROR, "Failed to load keyfile! SSL error below:\n%s", ssl_errstr); + log_trace(INFO, "RMPS failed to start, shutting down..."); + atexit(cleanup); + } + /* verify private key */ + if (!SSL_CTX_check_private_key(ctx)) { + log_trace(ERROR, "Private key does not match the public certificate."); + log_trace(INFO, "RMPS failed to start, shutting down..."); + atexit(cleanup); + } + 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); + } + +} + +int launch_rmps(struct conf_table *conf, int fork_flag) +{ + int server; + log_trace(INFO, "Starting up RMPS..."); + + /* Set signal handling */ + set_env(); + /* Deamonize */ + if (fork_flag) + daemonize("/tmp/"); + /* Spawn & lock pidfile */ + spawn_pidfile(conf->rmps.pidfile); + + SSL_CTX *ctx; + ctx = init_server_ctx(conf->rmps.cipherlist); + /* 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 + */ + log_trace(VERBOSE, "Loading crypto certs and keys."); + load_certificates(ctx, conf->rmps.certfile, conf->rmps.keyfile, conf->rmps.cafile); + + log_trace(VERBOSE, "Starting listener on port: %d", atoi(conf->rmps.bind_on_port)); + server = open_listener(atoi(conf->rmps.bind_on_port)); + + log_trace(VERBOSE, "Creating mutex for thread pool."); + ssl_pt_mutex(server, ctx, conf->rmps.threadpoolsize); + + return 0; +} diff --git a/rmps.conf b/rmps.conf new file mode 100644 index 0000000..2fcf726 --- /dev/null +++ b/rmps.conf @@ -0,0 +1,32 @@ +# This is a configuration file for the +# Remote Management and Provisioning Server +# +# NOTE: In order to apply changes, the server +# must be restarted afterwards. + +# RMPS DB settings +# db.type=[mysql|postgresql|oracle] +# NOTE: Only "mysql" is implemented so far. +db.type=mysql +db.hostname=127.0.0.1 +db.port=3306 + +# RMPS Core settings +rmps.bind_on_ip=127.0.0.1 +rmps.bind_on_port=7000 +# Loglevel modes: 1:ERROR,2:WARNING,3:INFO,4:VERBOSE +# The default is 2. +rmps.loglevel=4 +rmps.logfile=/home/smirky/stuff/projects/rmps/tmp/rmpsd.log +rmps.errlog=/home/smirky/stuff/projects/rmps/tmp/rmpsd.err +rmps.pidfile=/home/smirky/stuff/projects/rmps/tmp/rmpsd.pig +#rmps.pidfile=/home/smirky/stuff/projects/rmps/rmpsd/rmpsd.pid + +# Cryptography settings +rmps.certfile=/etc/rmps/certs/server/server.crt +rmps.keyfile=/etc/rmps/certs/server/server.key +rmps.cafile=/etc/rmps/certs/ca.pem +rmps.cipherlist=ECDHE-RSA-AES128-SHA256:AES256-SHA:RSA + +# NFS settings TODO + diff --git a/rmps.h b/rmps.h new file mode 100644 index 0000000..359b758 --- /dev/null +++ b/rmps.h @@ -0,0 +1,6 @@ +#ifndef RMPS_H +#define RMPS_H + +extern int launch_rmps(struct conf_table *conf, int fork_flag); + +#endif /* RMPS_H */ diff --git a/rmpsd.service b/rmpsd.service new file mode 100644 index 0000000..e800bec --- /dev/null +++ b/rmpsd.service @@ -0,0 +1,14 @@ +[Unit] +Description=Remote Management and Provisioning Server +After=network.target + +[Service] +Type=simple +#PIDFile=/run/rmps/rmpsd.pid +ExecStart=/usr/bin/rmpsd start --daemonize=no +ExecStop=/usr/bin/rmpsd stop +ExecReload=/usr/bin/rmpsd reload +Restart=always + +[Install] +WantedBy=multi-user.target diff --git a/runme.sh b/runme.sh new file mode 100755 index 0000000..06cad4f --- /dev/null +++ b/runme.sh @@ -0,0 +1 @@ +./rmpsd start --daemonize=no \ No newline at end of file diff --git a/thread_pool.c b/thread_pool.c new file mode 100644 index 0000000..e8ef622 --- /dev/null +++ b/thread_pool.c @@ -0,0 +1,167 @@ +#include "thread_pool.h" +#include "log_trace.h" +#include "protocol.h" +#include +#include + +#define MAXJOBS 10 + +struct agent_args { + int busy; + SSL *ssl; + int sd; + char ip[16]; /* IPv4 */ +}; + +static void show_certs(SSL *ssl); +static void* servlet(void *args); +static void send_reject_msg(SSL *ssl); + +static void show_certs(SSL *ssl) +{ + X509 *cert; + char *line; + + cert = SSL_get_peer_certificate(ssl); /* Get certificates (if available) */ + if (SSL_get_verify_result(ssl)==X509_V_OK) + log_trace(VERBOSE, "get_verify_result == ok"); + if (cert != NULL) { + log_trace(VERBOSE, "Server certificates:"); + line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0); + log_trace(VERBOSE, "Subject: %s", line); + free(line); + line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0); + log_trace(VERBOSE, "Issuer: %s", line); + free(line); + X509_free(cert); + } else + log_trace(VERBOSE, "No certificates from peer"); +} + +static void* servlet(void *args) /* Serve the connection -- threadable */ +{ + struct msg buf; + char reply[2048]; + int bytes, ret; + //unsigned short job[MAXJOBS] = { 0 }; + struct agent_args *agent = (struct agent_args*)args; + + SSL_load_error_strings(); + ret = SSL_accept(agent->ssl); + /* We check for unclean (ret < 0) and clean (ret == 0) failures */ + if (ret <= 0) { + char ret_str[1024]; + ERR_error_string_n(SSL_get_error(agent->ssl, ret), ret_str, sizeof(ret_str)); + log_trace(INFO, "SSL_accept() failed. Reason below:\n%s", ret_str); + } else { + show_certs(agent->ssl); + do { + buf.meta.type = GET_MEMORY; + sleep(1); + SSL_write(agent->ssl, &buf, sizeof(buf)); + bytes = SSL_read(agent->ssl, &buf, sizeof(buf)); + if (bytes > 0) { + if (bytes != sizeof(struct msg)) { + log_trace( WARNING, + "Agent [%s] sent non-standard data!", + agent->ip ); + continue; + } + //buf.chunk.data[bytes] = 0; + + log_trace(VERBOSE, "Client msg: \"%s\"", buf.chunk.data); + SSL_write(agent->ssl, buf.chunk.data, buf.meta.len); /* send reply */ + continue; + } + if (SSL_get_shutdown(agent->ssl) == SSL_RECEIVED_SHUTDOWN) + log_trace(VERBOSE, "SSL_RECEIVED_SHUTDOWN from agent [%s]", agent->ip); + else { + char ssl_errstr[2048]; + long ssl_errnum = ERR_get_error(); + ERR_error_string_n(ssl_errnum, ssl_errstr, sizeof(ssl_errstr)); + ERR_print_errors_fp(stderr); + log_trace( VERBOSE, + "Client didn't send data! SSL error below:\n%s", + ssl_errstr); + sprintf(reply, "%s", "Where's the data, m8?"); + SSL_write(agent->ssl, reply, strlen(reply)); + } + log_trace(INFO, "Agent [%s] disconnected.", agent->ip); + } while (bytes); + } + SSL_free(agent->ssl); /* release SSL state */ + close(agent->sd); /* close connection */ + agent->busy = 0; + return 0; +} + +static void send_reject_msg(SSL *ssl) +{ + char *reply = "FAILURE - The connection queue is full!\n"; + SSL_write(ssl, reply, strlen(reply)); +} + +void ssl_pt_mutex(int srv, SSL_CTX *ctx, int poolsize) +{ + pthread_mutex_t mutex; + pthread_attr_t attr; + pthread_t *agent_thread = (pthread_t*)malloc(poolsize * sizeof(pthread_t)); + struct agent_args *agent_struct = + (struct agent_args*)malloc(poolsize * sizeof(struct agent_args)); + int i; + + memset(agent_thread, 0, sizeof(pthread_t) * poolsize); + memset(agent_struct, 0, sizeof(struct agent_args) * poolsize); + + pthread_mutex_init(&mutex, NULL); + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + + while (1) { + struct sockaddr_in addr; + char address[INET6_ADDRSTRLEN]; + socklen_t len = sizeof(addr); + SSL *ssl; + int agent = accept(srv, (struct sockaddr*)&addr, &len); + log_trace( VERBOSE, + "Connection: %s:%d", + inet_ntop(AF_INET, &addr.sin_addr, address, sizeof(address)), + ntohs(addr.sin_port) + ); + + for (i = 0; i < poolsize; i++) { + if (!agent_struct[i].busy) { + agent_struct[i].busy = 1; + agent_struct[i].ssl = SSL_new(ctx); + agent_struct[i].sd = agent; + memcpy( agent_struct[i].ip, + inet_ntop(AF_INET, &addr.sin_addr, address, sizeof(address)), + sizeof(agent_struct[i].ip) ); + SSL_set_fd(agent_struct[i].ssl, agent_struct[i].sd); + pthread_create( &agent_thread[i], + &attr, + servlet, + &agent_struct[i] ); + break; + } + } + if (i == poolsize) { + log_trace( WARNING, + "Agent [%s] dropped. Poolsize limit reached.", + inet_ntop(AF_INET, &addr.sin_addr, address, sizeof(address)) + ); + ssl = SSL_new(ctx); + SSL_set_fd(ssl, agent); + if (SSL_accept(ssl) == FAIL) { + SSL_free(ssl); + close(agent); + continue; + } + send_reject_msg(ssl); + SSL_free(ssl); + close(agent); + } + } + pthread_attr_destroy(&attr); + pthread_mutex_destroy(&mutex); +} diff --git a/thread_pool.h b/thread_pool.h new file mode 100644 index 0000000..5857518 --- /dev/null +++ b/thread_pool.h @@ -0,0 +1,15 @@ +#ifndef THREAD_POOL_H +#define THREAD_POOL_H + +/* included for openssl and sockets */ +#include +#include +#include +#include +#include + +#define FAIL -1 + +void ssl_pt_mutex(int srv, SSL_CTX *ctx, int poolsize); + +#endif /* THREAD_POOL_H */