aboutsummaryrefslogtreecommitdiff
path: root/rcon_host.c
diff options
context:
space:
mode:
Diffstat (limited to 'rcon_host.c')
-rw-r--r--rcon_host.c265
1 files changed, 265 insertions, 0 deletions
diff --git a/rcon_host.c b/rcon_host.c
new file mode 100644
index 0000000..a5ba9b4
--- /dev/null
+++ b/rcon_host.c
@@ -0,0 +1,265 @@
+#include "rcon_host.h"
+#include "rcon.h"
+#include "net.h"
+#include "plugin/plugin.h"
+#include "md5.h"
+
+#include <string.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "common.h"
+#include <errno.h>
+#include <stdbool.h>
+
+static bool pthread_key_init = false;
+static pthread_key_t key_rcon_fd;
+
+static struct rcon_host_connarg *connarg;
+static uint64_t connarg_existing_hash_1;
+static uint64_t connarg_existing_hash_2;
+
+struct rcon_thread_data {
+ int fd;
+ uint64_t connarg_hash_1;
+ uint64_t connarg_hash_2;
+};
+
+static void destructor(void *data)
+{
+ DEBUGF("rcon_host.c#destructor: (%p)\n", data);
+ struct rcon_thread_data *dat = (struct rcon_thread_data *)data;
+ if(dat->fd != -1)
+ {
+ DEBUGF("rcon_host.c#destructor: Closing rcon socket %d\n", dat->fd);
+ close(dat->fd);
+ }
+ free(dat);
+}
+
+/* https://stackoverflow.com/a/25669375/6792243 */
+static uint64_t uint8ArrtoUint64(uint8_t *var, uint32_t lowest_pos)
+{
+ return (((uint64_t)var[lowest_pos+7]) << 56) |
+ (((uint64_t)var[lowest_pos+6]) << 48) |
+ (((uint64_t)var[lowest_pos+5]) << 40) |
+ (((uint64_t)var[lowest_pos+4]) << 32) |
+ (((uint64_t)var[lowest_pos+3]) << 24) |
+ (((uint64_t)var[lowest_pos+2]) << 16) |
+ (((uint64_t)var[lowest_pos+1]) << 8) |
+ (((uint64_t)var[lowest_pos]) << 0);
+}
+
+static void connarg_hash_update(struct rcon_host_connarg *connarg)
+{
+ uint64_t hash_1 = 0;
+ uint64_t hash_2 = 0;
+ if(connarg != NULL)
+ {
+ MD5Context ctx;
+ md5Init(&ctx);
+ if(connarg->host != NULL) md5Update(&ctx, (uint8_t *)connarg->host, sizeof(char) * strlen(connarg->host));
+ if(connarg->port != NULL) md5Update(&ctx, (uint8_t *)connarg->port, sizeof(char) * strlen(connarg->port));
+ if(connarg->password != NULL) md5Update(&ctx, (uint8_t *)connarg->password, sizeof(char) * strlen(connarg->password));
+ md5Finalize(&ctx);
+ hash_1 = uint8ArrtoUint64(ctx.digest, 0);
+ hash_2 = uint8ArrtoUint64(ctx.digest, 8);
+ }
+ connarg_existing_hash_1 = hash_1;
+ connarg_existing_hash_2 = hash_2;
+ DEBUGF("rcon_host.c#connarg_hash: %lu%lu.\n", hash_1, hash_2);
+}
+
+static int rcon_host_clear_current_thread_socket()
+{
+ struct rcon_thread_data *data = pthread_getspecific(key_rcon_fd);
+ if(data != NULL) data->fd = -1;
+ return 0;
+}
+
+static int rcon_host_get_current_thread_socket(int *out)
+{
+ int r = 0;
+ struct rcon_thread_data *data = pthread_getspecific(key_rcon_fd);
+ if(data == NULL)
+ {
+ DEBUG("rcon_host.c#rcon_host_get_current_thread_socket: Allocating new thread specific data.\n");
+ data = malloc(sizeof(struct rcon_thread_data));
+ r = pthread_setspecific(key_rcon_fd, data);
+ if(r)
+ {
+ fprintf(stderr, _("Cannot set thread specific data: %d\n"), r);
+ free(data);
+ data = NULL;
+ goto cleanup;
+ }
+ data->fd = -1;
+ data->connarg_hash_1 = 0;
+ data->connarg_hash_2 = 0;
+ }
+ DEBUGF("rcon_host.c#rcon_host_get_current_thread_socket: Hash: %lu%lu -> %lu%lu.\n",
+ data->connarg_hash_1,
+ data->connarg_hash_2,
+ connarg_existing_hash_1,
+ connarg_existing_hash_2);
+
+ if(data->connarg_hash_1 != connarg_existing_hash_1 || data->connarg_hash_2 != connarg_existing_hash_2)
+ {
+ DEBUGF("rcon_host.c#rcon_host_get_current_thread_socket: Hash mismatch (%lu%lu -> %lu%lu). Recreating.\n",
+ data->connarg_hash_1,
+ data->connarg_hash_2,
+ connarg_existing_hash_1,
+ connarg_existing_hash_2);
+ if(data->fd != -1)
+ {
+ DEBUGF("rcon_host.c#rcon_host_get_current_thread_socket: Disconnecting existing socket %d.\n", data->fd);
+ close(data->fd);
+ data->fd = -1;
+ data->connarg_hash_1 = 0;
+ data->connarg_hash_2 = 0;
+ }
+ if(connarg != NULL)
+ {
+ struct rc_packet pkgt = {0, 0, 0, { 0x00 }};
+ r = rcon_build_packet(&pkgt, RCON_PID, RCON_AUTHENTICATE, connarg->password);
+ if(r) goto cleanup;
+ int fd = -1;
+ r = net_connect(connarg->host, connarg->port, &fd);
+ if(r)
+ {
+ fprintf(stderr, _("Cannot connect to %s:%s: %d\n"), connarg->host, connarg->port, r);
+ goto cleanup;
+ }
+ r = rcon_send_packet(fd, &pkgt);
+ if(r)
+ {
+ close(fd);
+ goto cleanup;
+ }
+ r = rcon_recv_packet(&pkgt, fd);
+ if(r)
+ {
+ close(fd);
+ goto cleanup;
+ }
+ if(pkgt.id == -1)
+ {
+ fprintf(stderr, _("Incorrect rcon password.\n"));
+ close(fd);
+ r = 77;
+ goto cleanup;
+ }
+ data->fd = fd;
+ data->connarg_hash_1 = connarg_existing_hash_1;
+ data->connarg_hash_2 = connarg_existing_hash_2;
+ }
+ else
+ {
+ r = EPG_RCON_DISABLED;
+ }
+ DEBUGF("rcon_host.c#rcon_host_get_current_thread_socket: Socket updated: %d.\n", data->fd);
+ }
+ else if(data->fd == -1) /* Disabled */
+ {
+ r = EPG_RCON_DISABLED;
+ }
+ *out = data->fd;
+ goto cleanup;
+cleanup:
+ return r;
+}
+
+int rcon_host_send(const int pkt_id, const char *command)
+{
+ int r = 0;
+ int fd = 0;
+ r = rcon_host_get_current_thread_socket(&fd);
+ if(r) goto cleanup;
+ struct rc_packet pkgt = {0, 0, 0, { 0x00 }};
+ r = rcon_build_packet(&pkgt, pkt_id, RCON_EXEC_COMMAND, (char *)command);
+ if(r) goto cleanup;
+ r = rcon_send_packet(fd, &pkgt);
+ if(r)
+ {
+ close(fd);
+ rcon_host_clear_current_thread_socket();
+ goto cleanup;
+ }
+ goto cleanup;
+cleanup:
+ return r;
+}
+
+int rcon_host_recv(int *pkt_id, char *out)
+{
+ int r = 0;
+ int fd = 0;
+ r = rcon_host_get_current_thread_socket(&fd);
+ if(r) goto cleanup;
+ struct rc_packet pkgt = {0, 0, 0, { 0x00 }};
+ r = rcon_recv_packet(&pkgt, fd);
+ if(r)
+ {
+ close(fd);
+ rcon_host_clear_current_thread_socket();
+ goto cleanup;
+ }
+ // TODO: Size issue? Memory issue?
+ *pkt_id = pkgt.id;
+ strcpy(out, pkgt.data);
+ goto cleanup;
+cleanup:
+ return r;
+}
+
+int rcon_host_init()
+{
+ int r = 0;
+ r = pthread_key_create(&key_rcon_fd, &destructor);
+ if(r) goto cleanup;
+ pthread_key_init = true;
+cleanup:
+ if(r) rcon_host_free();
+ return r;
+}
+
+void rcon_host_free()
+{
+ if(pthread_key_init)
+ {
+ pthread_key_delete(key_rcon_fd);
+ pthread_key_init = false;
+ }
+}
+
+struct rcon_host_connarg *rcon_host_getconnarg()
+{
+ return connarg;
+}
+
+void rcon_host_setconnarg(struct rcon_host_connarg *arg)
+{
+ connarg = arg;
+ connarg_hash_update(arg);
+}
+
+void rcon_host_connarg_free(struct rcon_host_connarg *arg)
+{
+ if(arg->host != NULL)
+ {
+ free(arg->host);
+ arg->host = NULL;
+ }
+ if(arg->port != NULL)
+ {
+ free(arg->port);
+ arg->port = NULL;
+ }
+ if(arg->password != NULL)
+ {
+ free(arg->password);
+ arg->password = NULL;
+ }
+ free(arg);
+}