summaryrefslogtreecommitdiff
path: root/src/server.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/server.c')
-rw-r--r--src/server.c946
1 files changed, 946 insertions, 0 deletions
diff --git a/src/server.c b/src/server.c
new file mode 100644
index 0000000..a246fe5
--- /dev/null
+++ b/src/server.c
@@ -0,0 +1,946 @@
+#include <dirent.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <unistd.h>
+// dependency: zlib
+#include <zlib.h>
+//
+#include "common.c"
+#include "config.c"
+#include "firehose.c"
+#include "omen.c"
+#include "packet_storage.c"
+#include "plugin.c"
+#include "security.c"
+#include "sha256.c"
+//
+
+const uint64_t max_neighbors = 1 << 10; // probably more than enough for a lot of folks
+
+uint64_t current_neighbor_ptr = 0;
+uint64_t neighbor_count = 0;
+struct relay_neighbor* neighbor_storage;
+bool* empty_neighbor_slots;
+pthread_mutex_t neighbor_storage_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+struct server_stats {
+ byte major_version;
+ byte minor_version;
+ byte motd[ 128 ];
+ //
+ uint16_t retention_hours; // should default to 1w (168h)
+ byte leading_zeros; // i recommend 10
+ bool accepts_plain; // for legal reasons
+} server_stats = {
+ .major_version = 0,
+ .minor_version = 1,
+ .motd = "A moonpaste server.", // minecraft much?
+ .retention_hours = 168, // 1 week
+ .leading_zeros = 12, // the moon guarantee
+ .accepts_plain = true, // probably legal
+};
+
+bool add_neighbor(struct relay_neighbor new_neighbor) {
+ if (decide_neighbor_registry_func) {
+ if (!decide_neighbor_registry_func(new_neighbor)) return false;
+ }
+
+ log(_info, "adding neighbor");
+
+ pthread_mutex_lock(&neighbor_storage_mutex);
+ if (neighbor_count >= max_neighbors) {
+ pthread_mutex_unlock(&neighbor_storage_mutex);
+ return false;
+ }
+
+ uint64_t add_idx = -1;
+ for (uint64_t i = 0; i < max_neighbors; i++) {
+ if (empty_neighbor_slots[ i ]) {
+ add_idx = i;
+ break;
+ }
+ }
+
+ if (add_idx != -1) {
+ neighbor_storage[ add_idx ] = new_neighbor;
+ empty_neighbor_slots[ add_idx ] = false;
+ neighbor_count++;
+ pthread_mutex_unlock(&neighbor_storage_mutex);
+ return true;
+ }
+
+ pthread_mutex_unlock(&neighbor_storage_mutex);
+ return false;
+}
+
+#define hash_fmt \
+ "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x" \
+ "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
+
+#define hash_ref(_hash) \
+ _hash[ 0 ], _hash[ 1 ], _hash[ 2 ], _hash[ 3 ], _hash[ 4 ], _hash[ 5 ], _hash[ 6 ], \
+ _hash[ 7 ], _hash[ 8 ], _hash[ 9 ], _hash[ 10 ], _hash[ 11 ], _hash[ 12 ], _hash[ 13 ], \
+ _hash[ 14 ], _hash[ 15 ], _hash[ 16 ], _hash[ 17 ], _hash[ 18 ], _hash[ 19 ], _hash[ 20 ], \
+ _hash[ 21 ], _hash[ 22 ], _hash[ 23 ], _hash[ 24 ], _hash[ 25 ], _hash[ 26 ], _hash[ 27 ], \
+ _hash[ 28 ], _hash[ 29 ], _hash[ 30 ], _hash[ 31 ]
+
+void* propagate_packet(void* _packet) {
+ struct data_packet packet = *(struct data_packet*) _packet;
+
+ if (decide_packet_propogation_func) {
+ if (!decide_packet_propogation_func(packet)) return NULL;
+ }
+
+ log(_debug, "propagating packet " hash_fmt, hash_ref(packet.hash));
+
+ size_t send_len = 1 + sizeof(struct data_packet);
+ byte send_data[ send_len * sizeof(byte) ];
+ send_data[ 0 ] = push_packet;
+ data_packet2s(packet, send_data + 1);
+
+ pthread_mutex_lock(&neighbor_storage_mutex);
+ for (uint64_t i = 0; i < neighbor_count; i++) {
+ struct relay_neighbor neighbor = neighbor_storage[ i ];
+ if (neighbor.active) {
+ byte* response_data = NULL;
+ size_t response_len = 0;
+ int result = -1;
+
+ if (neighbor.has_ipv4) {
+ char ip_string[ 16 ];
+ sprintf(
+ ip_string,
+ "%d.%d.%d.%d",
+ neighbor.v4_addr[ 0 ],
+ neighbor.v4_addr[ 1 ],
+ neighbor.v4_addr[ 2 ],
+ neighbor.v4_addr[ 3 ]);
+
+ result = study(
+ ip_string, default_port, send_data, send_len, &response_data, &response_len);
+ } else if (neighbor.has_ipv6) {
+ char ip_string[ 46 ];
+ int offset = 0;
+ for (int j = 0; j < 8; j++) {
+ uint16_t word
+ = (neighbor.v6_addr[ j * 2 ] << 8) | neighbor.v6_addr[ j * 2 + 1 ];
+ offset += sprintf(ip_string + offset, "%x%s", word, (j < 7) ? ":" : "");
+ }
+
+ result = study(
+ ip_string, default_port, send_data, send_len, &response_data, &response_len);
+ }
+
+ if (result != 0) { log(_error, "failed to send packet to neighbor"); }
+
+ if (response_data) { free(response_data); }
+ }
+ }
+ pthread_mutex_unlock(&neighbor_storage_mutex);
+
+ free(_packet);
+ return NULL;
+}
+
+void save_packet(const struct data_packet packet);
+
+#define reply(__len, ...) \
+ ({ \
+ *out = (byte*) __VA_ARGS__; \
+ *out_len = __len; \
+ })
+
+void paste_server(
+ char* ip_addr, size_t ip_size, byte* in, size_t in_len, byte** out, size_t* out_len) {
+ log(_info, "<%.*s> incoming request with size %li", (int) ip_size, ip_addr, in_len);
+
+ if (paste_server_start_func) {
+ paste_server_start_func(&ip_addr, &ip_size, &in, &in_len, &out, &out_len);
+ }
+
+ // tip: if you want the server to not mandate the 8 bytes minimum, modify in_len using the
+ // plugin.
+
+ if (in_len < 8) { // server mandates 8 bytes minimum. pad it if you don't need it!
+ reply(17, strdup("\x01input too small"));
+ return;
+ }
+
+ enum client_input input_type = in[ 0 ];
+
+ switch (input_type) {
+ case audit_server: {
+ log(_info, "<%.*s> request type: audit", (int) ip_size, ip_addr);
+
+ byte* output_data = calloc(1 + 237, sizeof(byte));
+ if (!output_data) {
+ byte* err = calloc(1 + 14, sizeof(byte));
+ if (err) {
+ err[ 0 ] = error;
+ memcpy(err + 1, "server OOM", 10);
+ reply(11, err);
+ }
+ return;
+ }
+ output_data[ 0 ] = audit_response;
+
+ struct relay_info info = { 0 };
+
+ pthread_mutex_lock(&neighbor_storage_mutex);
+ pthread_mutex_lock(&packet_storage_mutex);
+
+ info.major_version = server_stats.major_version;
+ info.minor_version = server_stats.minor_version;
+ info.capabilities.retention_hours = server_stats.retention_hours;
+ info.capabilities.can_store = packet_count < max_storage - 1;
+ info.capabilities.leading_zeros = server_stats.leading_zeros;
+ info.capabilities.accepts_plain = server_stats.accepts_plain;
+
+ info.statistics.neighbors = neighbor_count;
+ info.statistics.held_packets = packet_count;
+
+ // TODO: for now we return the first 4
+ for (int i = 0; i < 4; ++i) {
+ info.statistics.quick_connect[ i ] = neighbor_storage[ i ];
+ }
+
+ memcpy(info.motd, server_stats.motd, 128);
+
+ if (modify_server_stats_func) { modify_server_stats_func(&info); }
+
+ pthread_mutex_unlock(&packet_storage_mutex);
+ pthread_mutex_unlock(&neighbor_storage_mutex);
+
+ relay_info2s(info, output_data + 1);
+
+ reply(1 + 237, output_data);
+ return;
+ }
+
+ case get_neighbors: {
+ byte count = in[ 1 ];
+ log(_info,
+ "<%.*s> request type: get neighbors, count %i",
+ (int) ip_size,
+ ip_addr,
+ count);
+
+ size_t output_len = 1 + (RELAY_NEIGHBOR_WIRE_LEN * count);
+ byte* output_data = calloc(output_len, sizeof(byte));
+ if (!output_data) {
+ byte* err = calloc(1 + 14, sizeof(byte));
+ if (err) {
+ err[ 0 ] = error;
+ memcpy(err + 1, "server OOM", 10);
+ reply(11, err);
+ }
+ return;
+ }
+ output_data[ 0 ] = neighbors_info;
+
+ size_t offset = 1;
+
+ for (byte i = 0; i < count; i++) {
+ // TODO: maybe mutex?
+ relay_neighbor2s(neighbor_storage[ i ], output_data + offset);
+ offset += RELAY_NEIGHBOR_WIRE_LEN;
+ }
+
+ reply(output_len, output_data);
+ return;
+ }
+
+ case get_packet: {
+ if (in_len < 1 + 32) {
+ byte* output_data = calloc(1 + 17, sizeof(byte));
+ if (!output_data) return;
+ output_data[ 0 ] = error;
+ memcpy(output_data + 1, "input too small", 17);
+ reply(18, output_data);
+ return;
+ }
+
+ hash packet_hash;
+ bool success;
+ s2hash(32, in + 1, &success, packet_hash);
+
+ log(_info,
+ "<%.*s> request type: get packet with hash " hash_fmt,
+ (int) ip_size,
+ ip_addr,
+ hash_ref(packet_hash));
+
+ if (!success) {
+ byte* output_data = calloc(1 + 12, sizeof(byte));
+ if (!output_data) return;
+ output_data[ 0 ] = error;
+ memcpy(output_data + 1, "invalid hash", 12);
+ reply(13, output_data);
+ return;
+ }
+
+ struct data_packet_entry* entry = get_data_packet(packet_hash);
+ if (entry == NULL) {
+ byte* output_data = calloc(1 + 14, sizeof(byte));
+ if (!output_data) return;
+ output_data[ 0 ] = error;
+ memcpy(output_data + 1, "packet not found", 14);
+ reply(15, output_data);
+ return;
+ }
+
+ size_t output_len = 1 + PACKET_SIZE;
+ byte* output_data = calloc(output_len, sizeof(byte));
+ if (!output_data) {
+ byte* err = calloc(1 + 14, sizeof(byte));
+ if (err) {
+ err[ 0 ] = error;
+ memcpy(err + 1, "server OOM", 10);
+ reply(11, err);
+ }
+ return;
+ }
+
+ output_data[ 0 ] = get_packet_result;
+ data_packet2s(entry->packet, output_data + 1);
+
+ reply(output_len, output_data);
+ return;
+ }
+
+ case push_packet: {
+ bool success;
+ struct data_packet packet = s2data_packet(in_len - 1, in + 1, &success);
+
+ log(_info,
+ "<%.*s> request type: push packet with hash " hash_fmt,
+ (int) ip_size,
+ ip_addr,
+ hash_ref(packet.hash));
+
+ size_t output_len = 1 + 1;
+ byte* output_data = calloc(output_len, sizeof(byte));
+ if (!output_data) { return; }
+ output_data[ 0 ] = push_packet_result;
+
+ if (!success) {
+ log(_warn, "<%.*s> malformed data", (int) ip_size, ip_addr);
+ output_data[ 1 ] = false;
+ reply(output_len, output_data);
+ return;
+ }
+
+ if (modify_packet_func) {
+ // things you can do here:
+ // - prefer certain packet authors
+ // - run PoW
+ // - increase leading zeroes
+ // - recompute the hash
+ modify_packet_func(&packet);
+ }
+
+ if (!check_leading_zeros(packet.hash, server_stats.leading_zeros)) {
+ log(_warn, "bad leads");
+ output_data[ 1 ] = false;
+ reply(output_len, output_data);
+ return;
+ }
+
+ const size_t hash_input_len = 4 + CONTENTS_SIZE;
+ byte hash_input_buffer[ hash_input_len ];
+ write_u32_le(hash_input_buffer, packet.nonce);
+ byte* contents_buffer = hash_input_buffer + 4;
+
+ contents_buffer[ 0 ] = packet.contents.encrypted ? 1 : 0;
+ write_u64_le(contents_buffer + 1, packet.contents.created_time_ms);
+ memcpy(contents_buffer + 9, packet.contents.author, 32);
+ memcpy(contents_buffer + 41, packet.contents.author_signing_pub, 32);
+ contents_buffer[ 73 ] = packet.contents.recipient_count;
+ memcpy(contents_buffer + 74, packet.contents.recipients, 32 * MAX_RECIPIENTS);
+ memcpy(contents_buffer + 74 + 32 * MAX_RECIPIENTS, packet.contents.data, 65536);
+ memcpy(
+ contents_buffer + 74 + 32 * MAX_RECIPIENTS + 65536, packet.contents.signature, 64);
+
+ struct sha256 calculated_hash_struct = sha256_hash(hash_input_buffer, hash_input_len);
+ if (memcmp(calculated_hash_struct.hash, packet.hash, sizeof(hash)) != 0) {
+ log(_warn,
+ "bad hash " hash_fmt " vs " hash_fmt,
+ hash_ref(calculated_hash_struct.hash),
+ hash_ref(packet.hash));
+ output_data[ 1 ] = false;
+ reply(output_len, output_data);
+ return;
+ }
+
+ uint64_t current_time = get_current_time_ms();
+ if (packet.contents.created_time_ms > current_time) {
+ log(_warn, "packet timestamp is in the future");
+ output_data[ 1 ] = false;
+ reply(output_len, output_data);
+ return;
+ }
+
+ if (current_time - packet.contents.created_time_ms
+ > server_stats.retention_hours * (60 * 60 * 1000)) {
+ log(_warn, "packet expired");
+ output_data[ 1 ] = false;
+ reply(output_len, output_data);
+ return;
+ }
+
+ if (!packet.contents.encrypted && !server_stats.accepts_plain) {
+ log(_warn, "plaintext not accepted by server");
+ output_data[ 1 ] = false;
+ reply(output_len, output_data);
+ return;
+ }
+
+ bool sig_ok = false;
+ if (!packet.contents.encrypted) {
+ size_t msg_len = CONTENTS_SIZE - sizeof(signature);
+ sig_ok = verify_raw_signature(
+ contents_buffer,
+ msg_len,
+ packet.contents.signature,
+ packet.contents.author_signing_pub);
+ } else {
+ secure_payload sp;
+ memcpy(sp.nonce, packet.contents.data, sizeof(sp.nonce));
+ memcpy(
+ sp.encrypted_data,
+ packet.contents.data + sizeof(sp.nonce),
+ sizeof(sp.encrypted_data));
+ memcpy(sp.signature, packet.contents.signature, sizeof(sp.signature));
+ sig_ok = verify_signature(sp, packet.contents.author_signing_pub);
+ }
+
+ if (!sig_ok) {
+ log(_warn, "bad signature");
+ output_data[ 1 ] = false;
+ reply(output_len, output_data);
+ return;
+ }
+
+ uint64_t storage_index = set_data_packet(&packet);
+ firehose_push(packet.hash, storage_index);
+
+ if (storage_index != minus_one) {
+ output_data[ 1 ] = true;
+
+ save_packet(packet);
+
+ if (((float) rand() / (float) RAND_MAX) > 0.5) {
+ struct data_packet* thread_arg = malloc(sizeof(struct data_packet));
+ if (thread_arg) {
+ *thread_arg = packet;
+ pthread_t prop_thread;
+ pthread_create(&prop_thread, NULL, propagate_packet, thread_arg);
+ pthread_detach(prop_thread);
+ }
+ }
+ } else {
+ output_data[ 1 ] = false;
+ }
+
+ reply(output_len, output_data);
+ return;
+ }
+
+ case firehose: {
+ if (in_len < 1 + 9) {
+ byte* output_data = calloc(1 + 17, sizeof(byte));
+ output_data[ 0 ] = error;
+ memcpy(output_data + 1, "input too small", 17);
+ reply(18, output_data);
+ return;
+ }
+ struct firehose_request req;
+ bool success;
+ req = s2firehose_request(in_len - 1, in + 1, &success);
+
+ if (!success) {
+ byte* output_data = calloc(1 + 14, sizeof(byte));
+ output_data[ 0 ] = error;
+ memcpy(output_data + 1, "invalid request", 14);
+ reply(15, output_data);
+ return;
+ }
+
+ if (unlikely(req.count > 64)) req.count = 64; // clamp
+
+ log(_info,
+ "<%.*s> request type: firehose count %i offset %li",
+ (int) ip_size,
+ ip_addr,
+ req.count,
+ req.offset);
+
+ water_splash entries[ req.count ];
+ uint16_t copied_count = firehose_get_packets(req.offset, req.count, entries);
+
+ size_t total_packets_size = PACKET_SIZE * copied_count;
+
+ size_t output_len = 1 + 8 + total_packets_size;
+ byte* output_data = calloc(output_len, sizeof(byte));
+ output_data[ 0 ] = firehose_reply;
+ size_t offset = 1;
+
+ uint64_t new_offset = req.offset + copied_count;
+ output_data[ offset++ ] = new_offset & 0xFF;
+ output_data[ offset++ ] = (new_offset >> 8) & 0xFF;
+ output_data[ offset++ ] = (new_offset >> 16) & 0xFF;
+ output_data[ offset++ ] = (new_offset >> 24) & 0xFF;
+ output_data[ offset++ ] = (new_offset >> 32) & 0xFF;
+ output_data[ offset++ ] = (new_offset >> 40) & 0xFF;
+ output_data[ offset++ ] = (new_offset >> 48) & 0xFF;
+ output_data[ offset++ ] = (new_offset >> 56) & 0xFF;
+
+ for (int i = 0; i < copied_count; i++) {
+ struct data_packet_entry* entry = &packet_storage[ entries[ i ].storage_index ];
+ data_packet2s(entry->packet, output_data + offset);
+ offset += PACKET_SIZE;
+ }
+
+ reply(output_len, output_data);
+ return;
+ }
+
+ case register_neighbor: {
+ bool success;
+ struct relay_neighbor neighbor = s2relay_neighbor(in_len - 1, in + 1, &success);
+
+ if (!success) {
+ byte* output_data = calloc(1 + 14, sizeof(byte));
+ output_data[ 0 ] = error;
+ memcpy(output_data + 1, "invalid request", 14);
+ reply(15, output_data);
+ return;
+ }
+
+ log(_info, "<%.*s> request type: neighbor registration", (int) ip_size, ip_addr);
+
+ char ip_string[ INET6_ADDRSTRLEN ];
+
+ if (neighbor.has_ipv4) {
+ sprintf(
+ ip_string,
+ "%d.%d.%d.%d",
+ neighbor.v4_addr[ 0 ],
+ neighbor.v4_addr[ 1 ],
+ neighbor.v4_addr[ 2 ],
+ neighbor.v4_addr[ 3 ]);
+ } else if (neighbor.has_ipv6) {
+ int offset = 0;
+ for (int j = 0; j < 8; j++) {
+ uint16_t word
+ = (neighbor.v6_addr[ j * 2 ] << 8) | neighbor.v6_addr[ j * 2 + 1 ];
+ offset += sprintf(ip_string + offset, "%x%s", word, (j < 7) ? ":" : "");
+ }
+ }
+
+ struct relay_info audit_result = run_server_audit(ip_string, default_port);
+ if (!audit_result.capabilities.can_store && audit_result.major_version == 0
+ && audit_result.minor_version == 0) {
+ // invalid
+ reply(2, (byte[ 2 ]) { error, 0 });
+ break;
+ }
+
+ reply(2, (byte[ 2 ]) { register_neighbor_result, add_neighbor(neighbor) });
+ } break;
+
+ default: {
+ log(_info, "<%.*s> request type: invalid method", (int) ip_size, ip_addr);
+
+ byte* output_data = calloc(1 + 14, sizeof(byte));
+ output_data[ 0 ] = error;
+ memcpy(output_data + 1, "invalid input", 14);
+ reply(15, output_data);
+ return;
+ }
+ }
+}
+
+bool try_hugepages = true;
+
+void* alloc_map(uint64_t size) {
+ static const byte RW = PROT_READ | PROT_WRITE;
+ static const byte PA = MAP_PRIVATE | MAP_ANONYMOUS;
+
+ void* alloc;
+ if (try_hugepages) {
+ alloc = mmap(NULL, size, RW, PA | MAP_HUGE_2MB, -1, 0);
+ if (alloc == MAP_FAILED) {
+ // alloc failed. maybe the fault of hugepages?
+ goto NO_HP;
+ }
+ } else {
+ NO_HP:
+ alloc = mmap(NULL, size, RW, PA, -1, 0);
+ if (alloc != MAP_FAILED) {
+ // hugepages failed, remove flag
+ printf("[note] no hugepages support present");
+ try_hugepages = false;
+ }
+ }
+
+ if (alloc == MAP_FAILED) return NULL;
+ return alloc;
+}
+
+pthread_t packet_cleanup_thread;
+volatile bool packet_cleanup_thread_running = true;
+
+pthread_t disk_persistence_thread;
+volatile bool disk_persistence_thread_running = true;
+
+bool file_exists(const char* filename) { return access(filename, F_OK) == 0; }
+
+void save_packet(const struct data_packet packet) {
+ if (decide_packet_save_to_disk_func) {
+ if (!decide_packet_save_to_disk_func(packet)) return;
+ }
+
+ log(_debug, "saving packet " hash_fmt " to disk", hash_ref(packet.hash));
+
+ mkdir("packets", 0755);
+
+ char filename_tmp[ 256 ];
+ char filename_final[ 256 ];
+ snprintf(
+ filename_tmp, sizeof(filename_tmp), "packets/" hash_fmt ".bin.tmp", hash_ref(packet.hash));
+ snprintf(
+ filename_final, sizeof(filename_final), "packets/" hash_fmt ".bin", hash_ref(packet.hash));
+
+ /* serialize packet into buffer */
+ byte buffer[ PACKET_SIZE ];
+ data_packet2s(packet, buffer);
+
+ /* compress */
+ uLongf comp_bound = compressBound(PACKET_SIZE);
+ byte comp_buf[ comp_bound ];
+
+ uLongf comp_len = comp_bound;
+ int zres = compress2(comp_buf, &comp_len, buffer, PACKET_SIZE, Z_BEST_COMPRESSION);
+ if (zres != Z_OK) {
+ log(_error, "save_packet: compress2 failed (%d)", zres);
+ return;
+ }
+
+ FILE* f = fopen(filename_tmp, "wb");
+ if (!f) {
+ perror("save_packet: fopen tmp");
+ return;
+ }
+
+ if (fwrite(comp_buf, 1, comp_len, f) != comp_len) {
+ perror("save_packet: write failed");
+ fclose(f);
+ unlink(filename_tmp);
+ return;
+ }
+
+ fflush(f);
+#if defined(__unix__) || defined(__APPLE__)
+ int fd = fileno(f);
+ if (fd >= 0) fsync(fd);
+#endif
+ fclose(f);
+
+ if (rename(filename_tmp, filename_final) != 0) {
+ perror("save_packet: rename failed");
+ unlink(filename_tmp);
+ }
+}
+
+void save_neighbors() {
+ FILE* file = fopen("neighbors.bin", "wb");
+ if (!file) {
+ perror("failed to save neighbors");
+ return;
+ }
+
+ pthread_mutex_lock(&neighbor_storage_mutex);
+ for (uint64_t i = 0; i < max_neighbors; i++) {
+ if (!empty_neighbor_slots[ i ]) {
+ byte buffer[ RELAY_NEIGHBOR_WIRE_LEN ];
+ relay_neighbor2s(neighbor_storage[ i ], buffer);
+ if (fwrite(buffer, 1, RELAY_NEIGHBOR_WIRE_LEN, file) != RELAY_NEIGHBOR_WIRE_LEN) {
+ perror("failed to write neighbor entry");
+ }
+ }
+ }
+ pthread_mutex_unlock(&neighbor_storage_mutex);
+ fclose(file);
+}
+
+void load_neighbors() {
+ FILE* file = fopen("neighbors.bin", "rb");
+ if (!file) { return; }
+
+ size_t count = 0;
+
+ byte buffer[ RELAY_NEIGHBOR_WIRE_LEN ];
+ while (fread(buffer, 1, RELAY_NEIGHBOR_WIRE_LEN, file) == RELAY_NEIGHBOR_WIRE_LEN) {
+ bool success;
+ struct relay_neighbor neighbor
+ = s2relay_neighbor(RELAY_NEIGHBOR_WIRE_LEN, buffer, &success);
+ if (success) {
+ count++;
+ add_neighbor(neighbor);
+ }
+ }
+ fclose(file);
+
+ log(_info, "loaded %li neighbors", count);
+}
+
+void* fs_persistence(void* arg) {
+ while (disk_persistence_thread_running) {
+ sleep(2 * 60); // 2m
+ log(_info, "saving neighbors...");
+ save_neighbors();
+ }
+
+ return NULL;
+}
+
+void* cleanup_packets(void* arg) {
+ while (packet_cleanup_thread_running) {
+ sleep(30 * 60); // 30m
+
+ log(_warn, "running cleanup");
+ uint64_t current_time_ms = get_current_time_ms();
+ uint64_t retention_ms = (uint64_t) server_stats.retention_hours * 60 * 60 * 1000;
+
+ // memory
+ for (uint64_t i = 0; i < max_storage; i++) {
+ pthread_mutex_lock(&packet_storage_mutex);
+
+ if (!packet_storage[ i ].is_full) {
+ pthread_mutex_unlock(&packet_storage_mutex);
+ continue;
+ }
+
+ bool result
+ = decide_packet_cleanup_func
+ ? decide_packet_cleanup_func(packet_storage[ i ].packet) // delegate to user
+ : ( // do it natively
+ current_time_ms - packet_storage[ i ].packet.contents.created_time_ms
+ > retention_ms);
+
+ if (result) {
+ log(_debug,
+ "cleaning up old packet hashed " hash_fmt,
+ hash_ref(packet_storage[ i ].packet.hash));
+
+ char filename[ 128 ];
+ sprintf(
+ filename,
+ "packets/" hash_fmt ".bin",
+ hash_ref(packet_storage[ i ].packet.hash));
+
+ if (remove(filename) != 0) log(_error, "failed to delete packet %s", filename);
+
+ packet_storage[ i ].is_full = false;
+ packet_count--;
+ }
+
+ pthread_mutex_unlock(&packet_storage_mutex);
+ }
+
+ // external cleanup
+ system("find packets/ -mtime +7 -delete");
+
+ // disk cleanup, tho not sure this is needed
+ DIR* dir = opendir("packets");
+ if (dir) {
+ struct dirent* entry;
+ while ((entry = readdir(dir)) != NULL) {
+ if (entry->d_type == DT_REG) {
+ char filepath[ 256 ];
+ snprintf(filepath, sizeof(filepath), "packets/%s", entry->d_name);
+
+ FILE* fd = decide_packet_cleanup_func ? fopen(filepath, "rb") : NULL;
+ struct data_packet* new = decide_packet_cleanup_func ? load_packet(fd) : NULL;
+
+ struct stat st;
+ bool result = decide_packet_cleanup_func
+ ? decide_packet_cleanup_func(*new) // delegate to user
+ : ( // do it natively
+ stat(filepath, &st) == 0
+ && current_time_ms - (uint64_t) st.st_mtime * 1000
+ > retention_ms);
+
+ if (decide_packet_cleanup_func) {
+ fclose(fd);
+ free(new);
+ }
+
+ if (result) {
+ if (remove(filepath) != 0) log(_error, "failed to delete %s", filepath);
+ }
+ }
+ }
+ closedir(dir);
+ }
+ }
+
+ return NULL;
+}
+
+pthread_t neighbor_check_thread;
+volatile bool neighbor_check_thread_running = true;
+
+void* check_neighbors(void* arg) {
+ sleep(5 * 30);
+
+ while (neighbor_check_thread_running) {
+ pthread_mutex_lock(&neighbor_storage_mutex);
+ for (uint64_t i = 0; i < max_neighbors; i++) {
+ if (!empty_neighbor_slots[ i ]) {
+ struct relay_neighbor neighbor = neighbor_storage[ i ];
+
+ byte* response_data = NULL;
+ size_t response_len = 0;
+ int result = -1;
+
+ byte audit_request[ 1 ] = { audit_server };
+
+ if (neighbor.has_ipv4) {
+ char ip_string[ 16 ];
+ sprintf(
+ ip_string,
+ "%d.%d.%d.%d",
+ neighbor.v4_addr[ 0 ],
+ neighbor.v4_addr[ 1 ],
+ neighbor.v4_addr[ 2 ],
+ neighbor.v4_addr[ 3 ]);
+ result = study(
+ ip_string, default_port, audit_request, 1, &response_data, &response_len);
+ } else if (neighbor.has_ipv6) {
+ char ip_string[ 46 ];
+ int offset = 0;
+
+ for (int j = 0; j < 8; j++) {
+ uint16_t word
+ = (neighbor.v6_addr[ j * 2 ] << 8) | neighbor.v6_addr[ j * 2 + 1 ];
+
+ offset += snprintf(
+ ip_string + offset,
+ sizeof(ip_string) - offset,
+ "%x%s",
+ word,
+ (j < 7) ? ":" : "");
+ }
+
+ result = study(
+ ip_string, default_port, audit_request, 1, &response_data, &response_len);
+ }
+
+ if (result != 0 || response_data[ 0 ] != audit_response) {
+ empty_neighbor_slots[ i ] = true;
+ neighbor_count--;
+ log(_warn, "removed dead neighbor");
+ }
+
+ if (response_data) { free(response_data); }
+ }
+ }
+ pthread_mutex_unlock(&neighbor_storage_mutex);
+
+ sleep(60 * 5); // 5m
+ }
+ return NULL;
+}
+
+int main(int argc, char** argv) {
+ parse_arguments(argc, argv);
+
+ char* __log_level = get_config("log", false);
+ if (__log_level) {
+ if (isdigit(*__log_level)) {
+ int _log_level = atoi(__log_level);
+ if (_log_level < 1 || _log_level > 4) {
+ fprintf(
+ stderr,
+ "invalid log level %d (1 = debug, 2 = info, 3 = warn, 4 = error)\n",
+ _log_level);
+ exit(1);
+ }
+ log_level = _log_level;
+ } else {
+ if (strcmp(__log_level, "debug") == 0) log_level = 1;
+ else if (strcmp(__log_level, "info") == 0)
+ log_level = 2;
+ else if (strcmp(__log_level, "warn") == 0)
+ log_level = 3;
+ else if (strcmp(__log_level, "error") == 0)
+ log_level = 4;
+ else {
+ fprintf(
+ stderr,
+ "invalid log level %s (1 = debug, 2 = info, 3 = warn, 4 = error)\n",
+ __log_level);
+ exit(1);
+ }
+ }
+
+ free(__log_level);
+ }
+
+ fprintf(
+ stderr,
+ "-- moonpaste server v%i.%i --\n"
+ "port %i\n\n",
+ server_stats.major_version,
+ server_stats.minor_version,
+ default_port);
+
+ // init
+ packet_storage = alloc_map(max_storage * sizeof(struct data_packet_entry));
+
+ neighbor_storage = alloc_map(max_neighbors * sizeof(struct relay_neighbor));
+ empty_neighbor_slots = alloc_map(max_neighbors * sizeof(bool));
+ memset(empty_neighbor_slots, 1, max_neighbors * sizeof(bool));
+
+ if (!packet_storage || !neighbor_storage || !empty_neighbor_slots) {
+ perror("init alloc failed");
+ return 1;
+ }
+
+ if (pthread_create(&packet_cleanup_thread, NULL, cleanup_packets, NULL) != 0) {
+ perror("failed to create packet cleanup thread");
+ return 1;
+ }
+
+ load_neighbors();
+
+ if (pthread_create(&disk_persistence_thread, NULL, fs_persistence, NULL) != 0) {
+ perror("failed to create persistence thread");
+ return 1;
+ }
+
+ if (pthread_create(&neighbor_check_thread, NULL, check_neighbors, NULL) != 0) {
+ perror("failed to create neighbor audit thread");
+ return 1;
+ }
+
+ firehose_init();
+
+ // load the plugins!
+ char* plugin_path = get_config("plugin", false);
+ if (plugin_path) {
+ load_plugins(plugin_path);
+ free(plugin_path);
+ }
+
+ omen(default_port, paste_server);
+
+ packet_cleanup_thread_running = false;
+ pthread_join(packet_cleanup_thread, NULL);
+
+ return 0;
+}