commit e9f3673bfd381350d26870d8debcfbc436990223 Author: Bogomil Vasilev Date: Mon Aug 8 11:12:06 2016 +0300 Initial commit. It's far from finished. 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 */