diff options
| -rw-r--r-- | .gitignore | 3 | ||||
| -rw-r--r-- | Makefile | 19 | ||||
| -rw-r--r-- | client.c | 465 | ||||
| -rw-r--r-- | common.c | 424 | ||||
| -rw-r--r-- | firehose.c | 94 | ||||
| -rw-r--r-- | omen.c | 467 | ||||
| -rw-r--r-- | packet_storage.c | 236 | ||||
| -rw-r--r-- | security.c | 161 | ||||
| -rw-r--r-- | server.c | 752 | ||||
| -rw-r--r-- | sha256.c | 104 |
10 files changed, 2725 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aff9426 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +bin/ +neighbors.bin +packets/
\ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..fb50ec3 --- /dev/null +++ b/Makefile @@ -0,0 +1,19 @@ +clean: + rm -rf bin/* + mkdir bin 2> /dev/null || true + +client: + clang client.c -o bin/client -lsodium -O3 -g + +server: + clang server.c -o bin/server -lsodium -O3 -g + +release: clean + clang client.c -o bin/client -O3 + clang server.c -o bin/server -O3 + +crun: + bin/client + +srun: + bin/server
\ No newline at end of file diff --git a/client.c b/client.c new file mode 100644 index 0000000..6dd9f00 --- /dev/null +++ b/client.c @@ -0,0 +1,465 @@ +#include <arpa/inet.h> +#include <fcntl.h> +#include <pthread.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <unistd.h> +// +#include "common.c" +#include "firehose.c" +#include "omen.c" +#include "packet_storage.c" +#include "security.c" +#include "sha256.c" + +struct data_packet_list { + struct data_packet* packets; + uint16_t count; + uint64_t new_offset; +}; + +struct data_packet create_packet_from_bytes( + const byte* input, + size_t input_len, + byte recipient_count, + hash* recipients, + keypair author_keys) { + struct data_packet packet; + memset(&packet, 0, sizeof(packet)); + + if (input_len > 65536) input_len = 65536; + if (input_len && input) memcpy(packet.contents.data, input, input_len); + if (input_len < 65536) memset(packet.contents.data + input_len, 0, 65536 - input_len); + + if (recipient_count > MAX_RECIPIENTS) recipient_count = MAX_RECIPIENTS; + packet.contents.recipient_count = recipient_count; + if (recipients) memcpy(packet.contents.recipients, recipients, recipient_count * 32); + + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + packet.contents.created_time_ms = (uint64_t) ts.tv_sec * 1000 + (uint64_t) ts.tv_nsec / 1000000; + + memcpy(packet.contents.author, author_keys.encryption_pub, sizeof(public_key)); + memcpy(packet.contents.author_signing_pub, author_keys.signing_pub, sizeof(public_key)); + packet.contents.encrypted = false; + + size_t msg_len = CONTENTS_SIZE - sizeof(signature); + byte msg_buf[ msg_len ]; + msg_buf[ 0 ] = packet.contents.encrypted ? 1 : 0; + write_u64_le(msg_buf + 1, packet.contents.created_time_ms); + memcpy(msg_buf + 9, packet.contents.author, 32); + memcpy(msg_buf + 41, packet.contents.author_signing_pub, 32); + msg_buf[ 73 ] = packet.contents.recipient_count; + memcpy(msg_buf + 74, packet.contents.recipients, 32 * MAX_RECIPIENTS); + memcpy(msg_buf + 74 + 32 * MAX_RECIPIENTS, packet.contents.data, 65536); + + crypto_sign_detached( + packet.contents.signature, NULL, msg_buf, msg_len, author_keys.signing_priv); + + return packet; +} + +struct data_packet +create_packet(const char* filename, keypair author, byte recipient_count, hash* recipients) { + byte file_buf[ 65536 ]; + memset(file_buf, 0, sizeof(file_buf)); + + long file_size = 0; + FILE* file = fopen(filename, "rb"); + if (!file) { + perror("Failed to open file"); + exit(1); + } + if (fseek(file, 0, SEEK_END) == 0) { + file_size = ftell(file); + fseek(file, 0, SEEK_SET); + } + if (file_size < 0) file_size = 0; + if (file_size > 65536) file_size = 65536; + if (file_size > 0) fread(file_buf, 1, (size_t) file_size, file); + fclose(file); + + return create_packet_from_bytes( + file_buf, (size_t) file_size, recipient_count, recipients, author); +} + +struct data_packet run_pow(struct data_packet packet, byte leading_zeros) { + printf("running Proof-of-Work with %i leading zeros...\n", leading_zeros); + uint64_t start_time = get_current_time_ms(); + + const size_t hash_input_len = 4 + CONTENTS_SIZE; + byte hash_input_buffer[ hash_input_len ]; + + 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); + + packet.nonce = 0; + while (true) { + memcpy(hash_input_buffer, &packet.nonce, 4); + struct sha256 calculated_hash_struct = sha256_hash(hash_input_buffer, hash_input_len); + memcpy(packet.hash, calculated_hash_struct.hash, 32); + if (check_leading_zeros(packet.hash, leading_zeros)) { break; } + packet.nonce++; + } + + uint64_t end_time = get_current_time_ms(); + printf("PoW completed in %lu ms. nonce: %u\n", end_time - start_time, packet.nonce); + return packet; +} + +struct relay_info run_server_audit(const char* ip, int port) { + byte data[] = { audit_server, 0, 0, 0, 0, 0, 0, 0 }; + byte* response = NULL; + size_t resp_len = 0; + bool success; + + int ret = study(ip, port, data, sizeof(data), &response, &resp_len); + if (ret != 0) { + printf("error: study returned %i\n", ret); + if (response) free(response); + return (struct relay_info) { 0 }; + } + + struct relay_info info = s2relay_info(resp_len - 1, response + 1, &success); + if (!success) { printf("error: failed to deserialize relay_info\n"); } + + free(response); + return info; +} + +struct relay_neighbor* get_server_neighbors(const char* ip, int port, byte count) { + byte data[ 8 ] = { get_neighbors, count, 0, 0, 0, 0, 0, 0 }; + byte* response = NULL; + size_t resp_len = 0; + + int ret = study(ip, port, data, sizeof(data), &response, &resp_len); + if (ret != 0) { + printf("Error: study returned %i\n", ret); + if (response) free(response); + return NULL; + } + + if (resp_len < 1) { + printf("Error: Invalid get_neighbors response\n"); + if (response) free(response); + return NULL; + } + + if (response[ 0 ] != neighbors_info) { + printf("Error: Server returned an error for get_neighbors request.\n"); + if (response) free(response); + return NULL; + } + + size_t expected_wire = 1 + (size_t) RELAY_NEIGHBOR_WIRE_LEN * count; + if (resp_len < expected_wire) { + printf( + "Error: get_neighbors response too short (expected %zu, got %zu)\n", + expected_wire, + resp_len); + if (response) free(response); + return NULL; + } + + struct relay_neighbor* neighbors = malloc(count * sizeof(struct relay_neighbor)); + if (!neighbors) { + perror("Failed to allocate memory for neighbors"); + if (response) free(response); + return NULL; + } + + for (byte i = 0; i < count; ++i) { + bool success; + neighbors[ i ] = s2relay_neighbor( + RELAY_NEIGHBOR_WIRE_LEN, response + 1 + (i * RELAY_NEIGHBOR_WIRE_LEN), &success); + if (!success) { printf("Error: Failed to deserialize neighbor %i\n", i); } + } + + free(response); + return neighbors; +} + +bool push_packet_to_server(const char* ip, int port, struct data_packet packet) { + const size_t send_len = 1 + PACKET_SIZE; + byte send_data[ send_len ]; + + send_data[ 0 ] = push_packet; + data_packet2s(packet, send_data + 1); + + byte* response = NULL; + size_t resp_len = 0; + bool success = false; + + int ret = study(ip, port, send_data, send_len, &response, &resp_len); + if (ret != 0) { + printf("error: study returned %i\n", ret); + goto cleanup; + } + + if (resp_len == 2 && response[ 0 ] == push_packet_result) { + success = response[ 1 ]; + } else { + printf("error: invalid push response\n"); + } + +cleanup: + if (response) free(response); + return success; +} + +struct data_packet get_packet_from_server(const char* ip, int port, hash packet_hash) { + const size_t send_len = 1 + 32; + byte send_data[ send_len ]; + send_data[ 0 ] = get_packet; + memcpy(send_data + 1, packet_hash, 32); + + byte* response = NULL; + size_t resp_len = 0; + bool success = false; + + int ret = study(ip, port, send_data, send_len, &response, &resp_len); + if (ret != 0) { + printf("Error: study returned %i\n", ret); + if (response) free(response); + return (struct data_packet) { 0 }; + } + + if (resp_len < 1 || response[ 0 ] != get_packet_result) { + printf("Error: Invalid get_packet response\n"); + if (response) free(response); + return (struct data_packet) { 0 }; + } + + struct data_packet packet = s2data_packet(resp_len - 1, response + 1, &success); + if (!success) { printf("Error: Failed to deserialize data packet\n"); } + + free(response); + return packet; +} + +struct data_packet_list +get_firehose_packets(const char* ip, int port, uint64_t offset, uint16_t count) { + const size_t send_len = 1 + 1 + 8; + byte send_data[ send_len ]; + send_data[ 0 ] = firehose; + firehose_request2s( + (struct firehose_request) { .count = (uint8_t) count, .offset = offset }, send_data + 1); + + byte* response = NULL; + size_t resp_len = 0; + bool success = false; + + int ret = study(ip, port, send_data, send_len, &response, &resp_len); + if (ret != 0) { + printf("Error: study returned %i\n", ret); + if (response) free(response); + return (struct data_packet_list) { 0 }; + } + + if (resp_len < 9 || response[ 0 ] != firehose_reply) { + printf("Error: Invalid firehose response\n"); + if (response) free(response); + return (struct data_packet_list) { 0 }; + } + + uint64_t new_offset = read_u64_le(response + 1); + struct data_packet_list list = { .count = 0, .new_offset = new_offset, .packets = NULL }; + + size_t available_bytes = resp_len - 9; + size_t available_packets = available_bytes / PACKET_SIZE; + if (available_packets == 0) { + free(response); + return list; + } + + if (available_packets > SIZE_MAX / sizeof(struct data_packet)) { + perror("allocation overflow"); + free(response); + return (struct data_packet_list) { 0 }; + } + + list.packets = malloc(available_packets * sizeof(struct data_packet)); + if (!list.packets) { + perror("Failed to allocate memory for packet list"); + free(response); + return (struct data_packet_list) { 0 }; + } + + size_t current_offset = 9; + while (current_offset + PACKET_SIZE <= resp_len && list.count < available_packets) { + struct data_packet packet = s2data_packet(PACKET_SIZE, response + current_offset, &success); + if (success) { + list.packets[ list.count ] = packet; + list.count++; + current_offset += PACKET_SIZE; + } else { + printf("Error: Failed to deserialize a packet from the firehose stream\n"); + break; + } + } + + free(response); + return list; +} + +void get_firehose_stream(const char* ip, int port, uint64_t offset, uint16_t count) { + size_t send_len = 1 + 1 + 8; + byte send_data[ send_len ]; + send_data[ 0 ] = (byte) firehose; + + byte* response = NULL; + size_t resp_len = 0; + bool success = false; + + uint64_t last_offset = offset; + while (true) { + firehose_request2s( + (struct firehose_request) { .count = (uint8_t) count, .offset = last_offset }, + send_data + 1); + int ret = study(ip, port, send_data, send_len, &response, &resp_len); + if (ret == -1) { + printf("study returned -1\n"); + return; + } + + if (ret != 0 || resp_len < 9 || response[ 0 ] != firehose_reply) { + free(response); + sleep(100); + continue; + } + + uint64_t server_offset = read_u64_le(response + 1); + size_t available_bytes = resp_len - 9; + size_t available_packets = available_bytes / PACKET_SIZE; + + if (available_packets == 0) { + free(response); + sleep(100); + continue; + } + + size_t current_offset = 9; + for (size_t i = 0; i < available_packets; i++) { + struct data_packet packet + = s2data_packet(PACKET_SIZE, response + current_offset, &success); + if (!success) break; + printf("streamed " hash_fmt "\n", hash_ref(packet.hash)); + current_offset += PACKET_SIZE; + } + + printf("offset: %li\n", last_offset); + last_offset += available_packets; + free(response); + sleep(100); + } +} + +int main() { + struct relay_info info = run_server_audit("127.0.0.1", 8040); + + printf("Server Audit Complete:\n"); + printf(" Version: v%i.%i\n", info.major_version, info.minor_version); + printf(" MOTD: %s\n", info.motd); + printf(" Leading Zeros Required: %i\n", info.capabilities.leading_zeros); + printf(" Retention: %i hours\n", info.capabilities.retention_hours); + printf(" Neighbors: %u\n", info.statistics.neighbors); + printf(" Held Packets: %lu\n", info.statistics.held_packets); + + FILE* temp_file = fopen("test_packet.txt", "w"); + if (temp_file) { + fprintf(temp_file, "this is a test message from the moonpaste client."); + fclose(temp_file); + } + + keypair author = generate_keypair(); + + struct data_packet new_packet = create_packet("test_packet.txt", author, 0, NULL); + new_packet = run_pow(new_packet, info.capabilities.leading_zeros); + printf("hash: " hash_fmt "\n", hash_ref(new_packet.hash)); + + printf("pushing new packet to server...\n"); + if (push_packet_to_server("127.0.0.1", 8040, new_packet)) { + printf("packet pushed successfully!\n"); + } else { + printf("failed to push packet.\n"); + } + + printf( + "attempting to retrieve the packet with hash " hash_fmt "...\n", hash_ref(new_packet.hash)); + struct data_packet retrieved_packet + = get_packet_from_server("127.0.0.1", 8040, new_packet.hash); + if (retrieved_packet.nonce != 0) { + printf("packet retrieved successfully! Contents:\n"); + for (int i = 0; i < 65536; ++i) { + printf("%c", retrieved_packet.contents.data[ i ]); + if (!retrieved_packet.contents.data[ i ]) break; + } + printf("\n"); + } else { + printf("failed to retrieve packet.\n"); + } + + printf("requesting neighbor list...\n"); + byte requested_neighbors = 5; + struct relay_neighbor* neighbors = get_server_neighbors("127.0.0.1", 8040, requested_neighbors); + if (neighbors) { + printf("received %i neighbors.\n", (int) requested_neighbors); + for (int i = 0; i < requested_neighbors; ++i) { + printf("Neighbor %i: ", i + 1); + if (neighbors[ i ].has_ipv4) { + printf( + "%d.%d.%d.%d\n", + neighbors[ i ].v4_addr[ 0 ], + neighbors[ i ].v4_addr[ 1 ], + neighbors[ i ].v4_addr[ 2 ], + neighbors[ i ].v4_addr[ 3 ]); + } else if (neighbors[ i ].has_ipv6) { + char ip_string[ 46 ]; + int offset = 0; + for (int j = 0; j < 8; ++j) { + uint16_t word = (neighbors[ i ].v6_addr[ j * 2 ] << 8) + | neighbors[ i ].v6_addr[ j * 2 + 1 ]; + offset += snprintf( + ip_string + offset, + sizeof(ip_string) - offset, + "%x%s", + word, + (j < 7) ? ":" : ""); + } + printf("%s\n", ip_string); + } + } + free(neighbors); + } else { + printf("Failed to get neighbors.\n"); + } + + printf("\nstarting firehose stream. Press Ctrl+C to exit.\n"); + get_firehose_stream("127.0.0.1", 8040, 0, 10); + + remove("test_packet.txt"); + + return 0; +} + +#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 ] diff --git a/common.c b/common.c new file mode 100644 index 0000000..183cfb2 --- /dev/null +++ b/common.c @@ -0,0 +1,424 @@ +#pragma once + +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> +#include <time.h> + +#define likely(A) __builtin_expect(A, 1) +#define unlikely(A) __builtin_expect(A, 0) + +typedef uint8_t byte; + +typedef byte ipv4[ 4 ]; +typedef byte ipv6[ 16 ]; + +typedef byte hash[ 32 ]; +typedef byte public_key[ 32 ]; +typedef byte private_key[ 32 ]; +typedef byte signature[ 64 ]; + +const uint16_t default_port = 8040; + +enum client_input { + // ask server for its information (responds with audit_response) + audit_server = 0x9a, + + // ask server for a random set of neighbors + // (input uint8_t neighbor_count, responds with neighbors_info) + get_neighbors = 0x20, + + // monitor incoming packets + // (input struct firehose_request, responds with firehose_reply) + firehose = 0xf1, + + // give a packet to the relay + // (input struct data_packet, responds with push_packet_result) + push_packet = 0x88, + + // get a packet from the server + // (input hash, responds with get_packet_result) + get_packet = 0x87, + + // register a new neighbor relay to this server + // (input struct relay_neighbor, responds with register_neighbor_result) + register_neighbor = 0x73, +}; + +enum server_reply { + // pretty self-explanatory. + error = 0x1, + + // type `struct relay_info` + audit_response = 0xaf, + // type `struct relay_neighbor[ neighbor_count ]` + neighbors_info = 0x43, + // type `uint64_t new_offset, struct data_packet[ count ]` + firehose_reply = 0x6c, + // type `bool result_code` + push_packet_result = 0x4a, + // type `struct data_packet` + get_packet_result = 0x42, + // type `bool result_code` + register_neighbor_result = 0x6e, +}; + +typedef uint8_t get_neighbors_request; + +struct firehose_request { + uint8_t count; + uint64_t offset; +}; + +typedef uint64_t firehose_reply_new_offset; + +struct relay_neighbor { + bool active; // set to 0 to indicate empty quick connect slot + + bool has_ipv4; // whether the neighbor supports ipv4 connection + bool has_ipv6; // whether the neighbor supports ipv6 connection + ipv4 v4_addr; // ipv4 address of the neighbor (if supported) + ipv6 v6_addr; // ipv6 address of the neighbor (if supported) +}; + +struct relay_info { + // -- version info -- + byte major_version; + byte minor_version; + // -- motd -- + byte motd[ 128 ]; + // -- capabilities -- + struct { + uint16_t retention_hours; // how many hours packets will be retained for + bool can_store; // whether or not the relay is accepting new packets + byte leading_zeros; // relay challenge level + bool accepts_plain; // whether or not the relay accepts unencrypted packets + } capabilities; + // -- statistics -- + struct { + uint16_t neighbors; // current neighboring relay count for this relay + uint64_t held_packets; // how many packets the relay is holding + struct relay_neighbor quick_connect[ 4 ]; // four best neighbors for quick connection + } statistics; +}; + +// do not change +#define MAX_RECIPIENTS 32 + +struct data_packet { + // nonce + hash for PoW + uint32_t nonce; + hash hash; + + // content inside to be nonce + hashed + struct { + bool encrypted; // whether the contents is encrypted or plaintext + uint64_t created_time_ms; // unix timestamp for creation time in milliseconds + public_key author; // message author + public_key author_signing_pub; // message signing pubkey + uint8_t recipient_count; // number of recipients. leave as 0 for public. + public_key recipients[ 32 ]; // up to 32 recipients for packet + byte data[ 65536 ]; // packet data + signature signature; // data signature + } contents; +}; + +const size_t CONTENTS_SIZE = 1 + 8 + 32 + 32 + 1 + (32 * MAX_RECIPIENTS) + 65536 + 64; +const size_t PACKET_SIZE = 4 + 32 + (CONTENTS_SIZE); + +struct relay_neighbor s2relay_neighbor(size_t len, byte *stream, bool *success) { + if (len != 1 + 1 + 1 + 4 + 16) { + *success = false; + return (struct relay_neighbor) { 0 }; + } + + struct relay_neighbor out; + out.active = stream[ 0 ] != 0; + out.has_ipv4 = stream[ 1 ] != 0; + out.has_ipv6 = stream[ 2 ] != 0; + + memcpy(out.v4_addr, stream + 3, 4); + memcpy(out.v6_addr, stream + 7, 16); + + *success = true; + return out; +} + +void relay_neighbor2s(struct relay_neighbor data, byte *stream) { + stream[ 0 ] = data.active ? 1 : 0; + stream[ 1 ] = data.has_ipv4 ? 1 : 0; + stream[ 2 ] = data.has_ipv6 ? 1 : 0; + + memcpy(stream + 3, data.v4_addr, 4); + memcpy(stream + 7, data.v6_addr, 16); +} + +struct relay_info s2relay_info(size_t len, byte *stream, bool *success) { + if (len != 1 + 1 + 128 + 2 + 1 + 1 + 1 + 2 + 8 + (4 * (1 + 1 + 1 + 4 + 16))) { + *success = false; + return (struct relay_info) { 0 }; + } + + struct relay_info out; + size_t off = 0; + + out.major_version = stream[ off++ ]; + out.minor_version = stream[ off++ ]; + + memcpy(out.motd, stream + off, 128); + off += 128; + + out.capabilities.retention_hours = stream[ off ] + (stream[ off + 1 ] << 8); + off += 2; + out.capabilities.can_store = stream[ off++ ] != 0; + out.capabilities.leading_zeros = stream[ off++ ]; + out.capabilities.accepts_plain = stream[ off++ ] != 0; + + out.statistics.neighbors = stream[ off ] + (stream[ off + 1 ] << 8); + off += 2; + out.statistics.held_packets + = ((uint64_t) stream[ off ]) | ((uint64_t) stream[ off + 1 ] << 8) + | ((uint64_t) stream[ off + 2 ] << 16) | ((uint64_t) stream[ off + 3 ] << 24) + | ((uint64_t) stream[ off + 4 ] << 32) | ((uint64_t) stream[ off + 5 ] << 40) + | ((uint64_t) stream[ off + 6 ] << 48) | ((uint64_t) stream[ off + 7 ] << 56); + off += 8; + + for (int i = 0; i < 4; i++) { + bool ok; + out.statistics.quick_connect[ i ] = s2relay_neighbor(1 + 1 + 1 + 4 + 16, stream + off, &ok); + if (!ok) { + *success = false; + return (struct relay_info) { 0 }; + } + off += 1 + 1 + 1 + 4 + 16; + } + + *success = true; + return out; +} + +void relay_info2s(struct relay_info data, byte *stream) { + size_t off = 0; + + stream[ off++ ] = data.major_version; + stream[ off++ ] = data.minor_version; + + memcpy(stream + off, data.motd, 128); + off += 128; + + stream[ off++ ] = data.capabilities.retention_hours & 0xFF; + stream[ off++ ] = (data.capabilities.retention_hours >> 8) & 0xFF; + stream[ off++ ] = data.capabilities.can_store ? 1 : 0; + stream[ off++ ] = data.capabilities.leading_zeros; + stream[ off++ ] = data.capabilities.accepts_plain ? 1 : 0; + + stream[ off++ ] = data.statistics.neighbors & 0xFF; + stream[ off++ ] = (data.statistics.neighbors >> 8) & 0xFF; + + stream[ off++ ] = data.statistics.held_packets & 0xFF; + stream[ off++ ] = (data.statistics.held_packets >> 8) & 0xFF; + stream[ off++ ] = (data.statistics.held_packets >> 16) & 0xFF; + stream[ off++ ] = (data.statistics.held_packets >> 24) & 0xFF; + stream[ off++ ] = (data.statistics.held_packets >> 32) & 0xFF; + stream[ off++ ] = (data.statistics.held_packets >> 40) & 0xFF; + stream[ off++ ] = (data.statistics.held_packets >> 48) & 0xFF; + stream[ off++ ] = (data.statistics.held_packets >> 56) & 0xFF; + + for (int i = 0; i < 4; i++) { + relay_neighbor2s(data.statistics.quick_connect[ i ], stream + off); + off += 1 + 1 + 1 + 4 + 16; + } +} + +struct data_packet s2data_packet(size_t len, byte *stream, bool *success) { + *success = false; + struct data_packet out = { 0 }; + if (len != PACKET_SIZE) { + printf("s2data_packet: length mismatch. expected %zu, got %zu\n", PACKET_SIZE, len); + return out; + } + + size_t off = 0; + + out.nonce = stream[ off ] | (stream[ off + 1 ] << 8) | (stream[ off + 2 ] << 16) + | (stream[ off + 3 ] << 24); + off += 4; + + memcpy(out.hash, stream + off, 32); + off += 32; + + out.contents.encrypted = stream[ off++ ] != 0; + + out.contents.created_time_ms + = ((uint64_t) stream[ off ]) | ((uint64_t) stream[ off + 1 ] << 8) + | ((uint64_t) stream[ off + 2 ] << 16) | ((uint64_t) stream[ off + 3 ] << 24) + | ((uint64_t) stream[ off + 4 ] << 32) | ((uint64_t) stream[ off + 5 ] << 40) + | ((uint64_t) stream[ off + 6 ] << 48) | ((uint64_t) stream[ off + 7 ] << 56); + off += 8; + + memcpy(out.contents.author, stream + off, 32); + off += 32; + + memcpy(out.contents.author_signing_pub, stream + off, 32); + off += 32; + + out.contents.recipient_count = stream[ off++ ]; + memcpy(out.contents.recipients, stream + off, 32 * MAX_RECIPIENTS); + off += 32 * MAX_RECIPIENTS; + + memcpy(out.contents.data, stream + off, 65536); + off += 65536; + + memcpy(out.contents.signature, stream + off, 64); + off += 64; + + *success = true; + return out; +} + +bool data_packet2s(struct data_packet data, byte *stream) { + if (data.contents.recipient_count > MAX_RECIPIENTS) { return false; } + + size_t off = 0; + + stream[ off++ ] = data.nonce & 0xFF; + stream[ off++ ] = (data.nonce >> 8) & 0xFF; + stream[ off++ ] = (data.nonce >> 16) & 0xFF; + stream[ off++ ] = (data.nonce >> 24) & 0xFF; + + memcpy(stream + off, data.hash, 32); + off += 32; + + stream[ off++ ] = data.contents.encrypted ? 1 : 0; + + for (int i = 0; i < 8; i++) stream[ off++ ] = (data.contents.created_time_ms >> (i * 8)) & 0xFF; + + memcpy(stream + off, data.contents.author, 32); + off += 32; + + memcpy(stream + off, data.contents.author_signing_pub, 32); + off += 32; + + stream[ off++ ] = data.contents.recipient_count; + memcpy(stream + off, data.contents.recipients, 32 * MAX_RECIPIENTS); + off += 32 * MAX_RECIPIENTS; + + memcpy(stream + off, data.contents.data, 65536); + off += 65536; + + memcpy(stream + off, data.contents.signature, 64); + off += 64; + + if (off != PACKET_SIZE) { + printf("data_packet2s: size mismatch wrote %zu expected %zu\n", off, PACKET_SIZE); + return false; + } + + return true; +} + +get_neighbors_request s2get_neighbors_request(size_t len, byte *stream, bool *success) { + if (len != 1) { + *success = false; + return 0; + } + *success = true; + return stream[ 0 ]; +} + +void get_neighbors_request2s(get_neighbors_request data, byte *stream) { stream[ 0 ] = data; } + +struct firehose_request s2firehose_request(size_t len, byte *stream, bool *success) { + if (len != 1 + 8) { + *success = false; + return (struct firehose_request) { 0 }; + } + struct firehose_request out; + out.count = stream[ 0 ]; + out.offset = ((uint64_t) stream[ 1 ]) | ((uint64_t) stream[ 2 ] << 8) + | ((uint64_t) stream[ 3 ] << 16) | ((uint64_t) stream[ 4 ] << 24) + | ((uint64_t) stream[ 5 ] << 32) | ((uint64_t) stream[ 6 ] << 40) + | ((uint64_t) stream[ 7 ] << 48) | ((uint64_t) stream[ 8 ] << 56); + *success = true; + return out; +} + +void firehose_request2s(struct firehose_request data, byte *stream) { + stream[ 0 ] = data.count; + stream[ 1 ] = data.offset & 0xFF; + stream[ 2 ] = (data.offset >> 8) & 0xFF; + stream[ 3 ] = (data.offset >> 16) & 0xFF; + stream[ 4 ] = (data.offset >> 24) & 0xFF; + stream[ 5 ] = (data.offset >> 32) & 0xFF; + stream[ 6 ] = (data.offset >> 40) & 0xFF; + stream[ 7 ] = (data.offset >> 48) & 0xFF; + stream[ 8 ] = (data.offset >> 56) & 0xFF; +} + +void s2hash(size_t len, byte *stream, bool *success, hash out) { + if (len != 32) { + *success = false; + return; + } + memcpy(out, stream, 32); + *success = true; +} + +void hash2s(hash data, byte *stream) { memcpy(stream, data, 32); } + +bool check_leading_zeros(const hash _hash, byte required_zeros) { + if (required_zeros == 0) return true; + + int full_bytes = required_zeros / 8; + int remaining_bits = required_zeros % 8; + + for (int i = 0; i < full_bytes; i++) { + if (_hash[ i ] != 0) return false; + } + + if (remaining_bits > 0) { + byte mask = (0xFF << (8 - remaining_bits)); + if ((_hash[ full_bytes ] & mask) != 0) return false; + } + + return true; +} + +uint64_t get_current_time_ms() { + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + return (uint64_t) ts.tv_sec * 1000 + (uint64_t) ts.tv_nsec / 1000000; +} + +#define RELAY_NEIGHBOR_WIRE_LEN (1 + 1 + 1 + 4 + 16) + +static inline uint64_t read_u64_le(const byte *b) { + uint64_t v = 0; + for (int i = 0; i < 8; ++i) v |= (uint64_t) b[ i ] << (8 * i); + return v; +} + +static inline void write_u64_le(byte *b, uint64_t v) { + for (int i = 0; i < 8; ++i) b[ i ] = (v >> (8 * i)) & 0xFF; +} + +static inline uint32_t read_u32_le(const byte *b) { + uint32_t v = 0; + for (int i = 0; i < 4; ++i) v |= (uint32_t) b[ i ] << (8 * i); + return v; +} + +static inline void write_u32_le(byte *b, uint32_t v) { + for (int i = 0; i < 4; ++i) b[ i ] = (v >> (8 * i)) & 0xFF; +} + +static inline uint16_t read_u16_le(const byte *b) { + return (uint16_t) b[ 0 ] | ((uint16_t) b[ 1 ] << 8); +} + +static inline void write_u16_le(byte *b, uint16_t v) { + b[ 0 ] = v & 0xFF; + b[ 1 ] = (v >> 8) & 0xFF; +}
\ No newline at end of file diff --git a/firehose.c b/firehose.c new file mode 100644 index 0000000..240dfd8 --- /dev/null +++ b/firehose.c @@ -0,0 +1,94 @@ +#include "common.c" +#include <pthread.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> + +#define FIREHOSE_BUFFER_SIZE 1 << 14 + +typedef struct { + hash packet_hash; + uint64_t storage_index; +} water_splash; + +typedef struct { + water_splash entries[ FIREHOSE_BUFFER_SIZE ]; + uint16_t head; + uint16_t tail; + uint16_t count; + uint64_t total_packets; + pthread_mutex_t mutex; +} fire_buffer; + +static fire_buffer firehose_buffer; + +void firehose_init(); +void firehose_push(hash new_hash, uint64_t storage_index); +uint16_t firehose_get_count(); +uint64_t firehose_get_total_packets(); +uint16_t firehose_get_packets(uint64_t offset, uint16_t count, water_splash* output_entries); + +void firehose_init() { + memset(firehose_buffer.entries, 0, sizeof(firehose_buffer.entries)); + firehose_buffer.head = 0; + firehose_buffer.tail = 0; + firehose_buffer.count = 0; + firehose_buffer.total_packets = 0; + pthread_mutex_init(&firehose_buffer.mutex, NULL); +} + +uint64_t current_base_offset = 0; + +void firehose_push(hash new_hash, uint64_t storage_index) { + pthread_mutex_lock(&firehose_buffer.mutex); + + memcpy(firehose_buffer.entries[ firehose_buffer.tail ].packet_hash, new_hash, sizeof(hash)); + firehose_buffer.entries[ firehose_buffer.tail ].storage_index = storage_index; + if (firehose_buffer.count == FIREHOSE_BUFFER_SIZE) { + firehose_buffer.head = (firehose_buffer.head + 1) % FIREHOSE_BUFFER_SIZE; + current_base_offset++; + } else { + firehose_buffer.count++; + } + firehose_buffer.tail = (firehose_buffer.tail + 1) % FIREHOSE_BUFFER_SIZE; + + firehose_buffer.total_packets++; + + pthread_mutex_unlock(&firehose_buffer.mutex); +} + +uint16_t firehose_get_count() { return firehose_buffer.count; } +uint64_t firehose_get_total_packets() { return firehose_buffer.total_packets; } + +uint16_t firehose_get_packets(uint64_t offset, uint16_t count, water_splash* output_entries) { + pthread_mutex_lock(&firehose_buffer.mutex); + + uint16_t copied_count = 0; + uint64_t start_offset = firehose_buffer.total_packets - firehose_buffer.count; + + if (offset < start_offset || offset >= firehose_buffer.total_packets) { + pthread_mutex_unlock(&firehose_buffer.mutex); + return 0; + } + + uint64_t relative_offset = offset - current_base_offset; + if (offset < current_base_offset || relative_offset >= firehose_buffer.count) { + pthread_mutex_unlock(&firehose_buffer.mutex); + return 0; // offset too old or beyond current entries + } + uint16_t start_index = (firehose_buffer.head + relative_offset) % FIREHOSE_BUFFER_SIZE; + + for (int i = 0; i < count; i++) { + if (relative_offset + i >= firehose_buffer.count) { break; } + + uint16_t current_index = (start_index + i) % FIREHOSE_BUFFER_SIZE; + memcpy( + &output_entries[ i ], &firehose_buffer.entries[ current_index ], sizeof(water_splash)); + copied_count++; + } + + pthread_mutex_unlock(&firehose_buffer.mutex); + return copied_count; +} @@ -0,0 +1,467 @@ +#pragma once + +#include <arpa/inet.h> +#include <errno.h> +#include <netinet/in.h> +#include <pthread.h> +#include <signal.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <sys/socket.h> +#include <unistd.h> +// +#include "common.c" + +typedef uint8_t byte; +typedef void (*tome_ptr)( + char *ip_addr, size_t addr_size, byte *in, size_t in_len, byte **out, size_t *out_len); +struct Task { + int fd; + struct Task *next; +}; + +#define MAX_BUFFER 1 << 18 +#define MAX_BODY 1 << 17 +#define MAX_CONNECTIONS 2048 + +// vars + +int worker_count = 16; + +pthread_mutex_t queue_mutex = PTHREAD_MUTEX_INITIALIZER; +pthread_cond_t queue_cond = PTHREAD_COND_INITIALIZER; +struct Task *head = NULL; +struct Task *tail = NULL; +size_t queue_size = 0; +tome_ptr global_handler = NULL; + +// utils + +static ssize_t write_all(int fd, const void *buf, size_t len) { + size_t off = 0; + const char *p = buf; + while (off < len) { + ssize_t r = send(fd, p + off, len - off, MSG_NOSIGNAL); + if (r < 0) { + if (errno == EINTR) continue; + if (errno == EAGAIN || errno == EWOULDBLOCK) continue; + return -1; + } + off += (size_t) r; + } + return (ssize_t) off; +} + +static int read_until_headers(int fd, char **out_buf, size_t *out_len) { + size_t cap = 4096; + size_t len = 0; + char *buf = malloc(cap); + if (!buf) return -1; + while (1) { + if (cap - len < 1024) { + size_t ncap = cap * 2; + char *n = realloc(buf, ncap); + if (!n) { + free(buf); + return -1; + } + buf = n; + cap = ncap; + } + ssize_t r = recv(fd, buf + len, cap - len, 0); + if (r < 0) { + if (errno == EINTR) continue; + free(buf); + return -1; + } else if (r == 0) { + len = len; + break; + } + len += (size_t) r; + if (len >= 4 && memmem(buf, len, "\r\n\r\n", 4) != NULL) break; + if (len > MAX_BUFFER) break; + } + *out_buf = buf; + *out_len = len; + return 0; +} + +static int read_exact(int fd, void *buf, size_t count) { + size_t off = 0; + char *p = buf; + while (off < count) { + ssize_t r = recv(fd, p + off, count - off, 0); + if (r < 0) { + if (errno == EINTR) continue; + return -1; + } else if (r == 0) { + return -1; + } + off += (size_t) r; + } + return 0; +} + +static ssize_t parse_content_length(const char *hdr, size_t hdr_len) { + const char *p = hdr; + const char *end = hdr + hdr_len; + while (p < end) { + const char *line_end = memchr(p, '\n', (size_t) (end - p)); + if (!line_end) line_end = end; + size_t line_len = (size_t) (line_end - p); + if (line_len >= 15) { + if (strncasecmp(p, "Content-Length:", 15) == 0) { + const char *num = p + 15; + while (num < line_end && (*num == ' ' || *num == '\t')) num++; + char tmp[ 32 ] = { 0 }; + size_t to_copy = line_len - (num - p); + if (to_copy >= sizeof(tmp)) to_copy = sizeof(tmp) - 1; + memcpy(tmp, num, to_copy); + for (int i = (int) strlen(tmp) - 1; i >= 0 + && (tmp[ i ] == '\r' || tmp[ i ] == '\n' + || tmp[ i ] == ' ' || tmp[ i ] == '\t'); + --i) + tmp[ i ] = 0; + long v = strtol(tmp, NULL, 10); + if (v < 0) return -1; + return (ssize_t) v; + } + } + p = line_end + 1; + } + return -1; +} + +static int send_http_response(int fd, const byte *payload, size_t payload_len) { + const char *template_hdr + = "HTTP/1.1 200 OK\r\nContent-Length: %zu\r\nConnection: close\r\n\r\n"; + char hdr[ 128 ]; + int n = snprintf(hdr, sizeof(hdr), template_hdr, payload_len); + if (n < 0 || (size_t) n >= sizeof(hdr)) return -1; + if (write_all(fd, hdr, (size_t) n) < 0) return -1; + if (payload_len > 0 && payload) { + if (write_all(fd, payload, payload_len) < 0) return -1; + } + return 0; +} + +// server + +void *omen_bound(void *arg) { + (void) arg; + while (1) { + int client_fd = -1; + struct sockaddr_in client_addr; + socklen_t client_addr_len = sizeof(client_addr); + + pthread_mutex_lock(&queue_mutex); + while (head == NULL) { pthread_cond_wait(&queue_cond, &queue_mutex); } + struct Task *task = head; + head = head->next; + if (head == NULL) tail = NULL; + queue_size--; + client_fd = task->fd; + + if (getpeername(client_fd, (struct sockaddr *) &client_addr, &client_addr_len) < 0) { + memset(&client_addr, 0, sizeof(client_addr)); + } + + free(task); + pthread_mutex_unlock(&queue_mutex); + + char client_ip[ INET_ADDRSTRLEN ] = { 0 }; + if (client_addr.sin_family == AF_INET) { + inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN); + } + + struct timeval timeout = { 5, 0 }; + setsockopt(client_fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); + + char *hdr_buf = NULL; + size_t hdr_len = 0; + if (read_until_headers(client_fd, &hdr_buf, &hdr_len) < 0) { + close(client_fd); + free(hdr_buf); + continue; + } + + char *sep = memmem(hdr_buf, hdr_len, "\r\n\r\n", 4); + size_t body_len = 0; + byte *body_ptr = NULL; + + if (sep) { + size_t headers_bytes = (size_t) (sep - hdr_buf) + 4; + ssize_t cl = parse_content_length(hdr_buf, headers_bytes); + if (cl > 0) { + size_t already = hdr_len - headers_bytes; + if ((size_t) cl > MAX_BODY) cl = MAX_BODY; + body_ptr = malloc((size_t) cl); + if (!body_ptr) { + free(hdr_buf); + close(client_fd); + continue; + } + if (already > 0) { + size_t copy = already; + if (copy > (size_t) cl) copy = (size_t) cl; + memcpy(body_ptr, sep + 4, copy); + body_len = copy; + } + if ((size_t) body_len < (size_t) cl) { + if (read_exact(client_fd, body_ptr + body_len, (size_t) cl - body_len) < 0) { + free(body_ptr); + free(hdr_buf); + close(client_fd); + continue; + } + body_len = (size_t) cl; + } + } else { + size_t already = hdr_len - (size_t) (sep - hdr_buf) - 4; + if (already > 0) { + if (already > MAX_BODY) already = MAX_BODY; + body_ptr = malloc(already); + if (body_ptr) { + memcpy(body_ptr, sep + 4, already); + body_len = already; + } + } else { + body_ptr = NULL; + body_len = 0; + } + } + } else { + size_t use = hdr_len; + if (use > MAX_BODY) use = MAX_BODY; + body_ptr = malloc(use); + if (body_ptr) { + memcpy(body_ptr, hdr_buf, use); + body_len = use; + } + } + + byte *out = NULL; + size_t out_len = 0; + + if (global_handler) + global_handler(client_ip, sizeof(client_ip), body_ptr, body_len, &out, &out_len); + if (out_len > MAX_BODY) out_len = MAX_BODY; + + send_http_response(client_fd, out, out_len); + + free(out); + free(body_ptr); + free(hdr_buf); + close(client_fd); + } + return NULL; +} + +int omen(int port, tome_ptr handler) { + global_handler = handler; + signal(SIGPIPE, SIG_IGN); + + int server_fd = socket(AF_INET, SOCK_STREAM, 0); + if (server_fd < 0) return -1; + + int opt = 1; + setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + struct sockaddr_in addr = { 0 }; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = INADDR_ANY; + addr.sin_port = htons(port); + + if (bind(server_fd, (struct sockaddr *) &addr, sizeof(addr)) < 0 + || listen(server_fd, MAX_CONNECTIONS) < 0) { + close(server_fd); + return -1; + } + + pthread_t *threads = malloc((size_t) worker_count * sizeof(pthread_t)); + if (!threads) { + close(server_fd); + return -1; + } + + for (int i = 0; i < worker_count; i++) { + if (pthread_create(&threads[ i ], NULL, omen_bound, NULL) != 0) { + perror("pthread_create failed"); + } else { + pthread_detach(threads[ i ]); + } + } + + while (1) { + struct sockaddr_in client_addr; + socklen_t len = sizeof(client_addr); + int client_fd = accept(server_fd, (struct sockaddr *) &client_addr, &len); + if (client_fd < 0) continue; + + pthread_mutex_lock(&queue_mutex); + if (queue_size >= (size_t) MAX_CONNECTIONS) { + // note: The server converts into a teapot if the number of concurrent connections + // reaches the max amount. this can be mitigated by disconnecting some clients, + // which will turn the teapot back to an actual server. + char err_resp[] = "HTTP/1.1 418 I'm a teapot\r\n" + "Content-Length: 24\r\n" + "Connection: close\r\n\r\n" + "Max connections reached"; + write_all(client_fd, err_resp, 92); + close(client_fd); + pthread_mutex_unlock(&queue_mutex); + continue; + } + + struct Task *new_task = malloc(sizeof(struct Task)); + if (!new_task) { + close(client_fd); + pthread_mutex_unlock(&queue_mutex); + continue; + } + new_task->fd = client_fd; + new_task->next = NULL; + if (tail) tail->next = new_task; + else + head = new_task; + tail = new_task; + queue_size++; + pthread_cond_signal(&queue_cond); + pthread_mutex_unlock(&queue_mutex); + } + + close(server_fd); + return 0; +} + +// client + +int study( + const char *ip, + int port, + const byte *data, + size_t data_len, + byte **response, + size_t *resp_len) { + int sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock < 0) return -1; + + struct sockaddr_in addr = { 0 }; + addr.sin_family = AF_INET; + if (inet_pton(AF_INET, ip, &addr.sin_addr) != 1) { + close(sock); + return -1; + } + addr.sin_port = htons(port); + + struct timeval timeout = { 5, 0 }; + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); + setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)); + + if (connect(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + close(sock); + return -1; + } + + if (data_len > MAX_BODY) data_len = MAX_BODY; + char req_hdr[ 256 ]; + int hdr_n = snprintf( + req_hdr, + sizeof(req_hdr), + "POST /data HTTP/1.1\r\nHost: %s\r\nContent-Length: %zu\r\nConnection: close\r\n\r\n", + ip, + data_len); + if (hdr_n < 0 || (size_t) hdr_n >= sizeof(req_hdr)) { + close(sock); + return -1; + } + + if (write_all(sock, req_hdr, (size_t) hdr_n) < 0 + || (data_len > 0 && write_all(sock, data, data_len) < 0)) { + close(sock); + return -1; + } + + char *hdr_buf = NULL; + size_t hdr_len = 0; + if (read_until_headers(sock, &hdr_buf, &hdr_len) < 0) { + close(sock); + free(hdr_buf); + return -1; + } + + if (memmem(hdr_buf, hdr_len, "HTTP/1.1 418 I'm a teapot", 34) != NULL) { + close(sock); + free(hdr_buf); + return -2; + } + + char *sep = memmem(hdr_buf, hdr_len, "\r\n\r\n", 4); + size_t body_len = 0; + byte *body = NULL; + if (sep) { + size_t headers_bytes = (size_t) (sep - hdr_buf) + 4; + ssize_t cl = parse_content_length(hdr_buf, headers_bytes); + size_t already = hdr_len - headers_bytes; + if (cl > 0) { + if ((size_t) cl > MAX_BODY) cl = MAX_BODY; + body = malloc((size_t) cl); + if (!body) { + free(hdr_buf); + close(sock); + return -1; + } + if (already > 0) { + size_t copy = already; + if (copy > (size_t) cl) copy = (size_t) cl; + memcpy(body, sep + 4, copy); + body_len = copy; + } + if ((size_t) body_len < (size_t) cl) { + if (read_exact(sock, body + body_len, (size_t) cl - body_len) < 0) { + free(body); + free(hdr_buf); + close(sock); + return -1; + } + body_len = (size_t) cl; + } + } else { + if (already > 0) { + if (already > MAX_BODY) already = MAX_BODY; + body = malloc(already); + if (body) { + memcpy(body, sep + 4, already); + body_len = already; + } + } else { + body = NULL; + body_len = 0; + } + } + } else { + size_t use = hdr_len; + if (use > MAX_BODY) use = MAX_BODY; + body = malloc(use); + if (body) { + memcpy(body, hdr_buf, use); + body_len = use; + } + } + + free(hdr_buf); + close(sock); + + if (body_len > 0 && body) { + *response = body; + *resp_len = body_len; + } else { + *response = NULL; + *resp_len = 0; + free(body); + } + return 0; +}
\ No newline at end of file diff --git a/packet_storage.c b/packet_storage.c new file mode 100644 index 0000000..e618490 --- /dev/null +++ b/packet_storage.c @@ -0,0 +1,236 @@ +#include <pthread.h> +#include <stdint.h> +#include <stdlib.h> +// +#include "common.c" + +const uint64_t max_storage = 1 << 16; + +#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 ] + +uint64_t current_packet_ptr = 0; +uint64_t packet_count = 0; +struct data_packet_entry { + struct data_packet packet; + uint64_t next; + uint64_t prev; + bool is_full; +}* packet_storage; + +// TODO: yes, i know it's weird, but this has to be zero +#define minus_one (uint64_t) 0 + +pthread_mutex_t packet_storage_mutex = PTHREAD_MUTEX_INITIALIZER; + +struct data_packet* load_packet(const hash packet_hash) { + char filename[ 128 ]; + sprintf(filename, "packets/" hash_fmt ".bin", hash_ref(packet_hash)); + + FILE* file = fopen(filename, "rb"); + if (!file) { return NULL; } + + fseek(file, 0, SEEK_END); + size_t file_size = ftell(file); + fseek(file, 0, SEEK_SET); + + if (file_size != PACKET_SIZE) { + fclose(file); + return NULL; + } + + byte* buffer = malloc(file_size); + if (!buffer) { + fclose(file); + return NULL; + } + + size_t r = fread(buffer, 1, file_size, file); + fclose(file); + if (r != file_size) { + free(buffer); + return NULL; + } + + bool success; + struct data_packet* packet = malloc(sizeof(struct data_packet)); + if (!packet) { + free(buffer); + return NULL; + } + *packet = s2data_packet(file_size, buffer, &success); + free(buffer); + + if (!success) { + free(packet); + return NULL; + } + + return packet; +} + +uint64_t set_data_packet(struct data_packet* new_packet); + +struct data_packet_entry* get_data_packet(hash _hash) { + uint64_t hash_val; + memcpy(&hash_val, _hash, sizeof(uint64_t)); + uint64_t cur_index = hash_val % max_storage; + + pthread_mutex_lock(&packet_storage_mutex); + + struct data_packet_entry* current_entry = &packet_storage[ cur_index ]; + while (cur_index != minus_one) { + if (current_entry->is_full + && memcmp(current_entry->packet.hash, _hash, sizeof(hash)) == 0) { + pthread_mutex_unlock(&packet_storage_mutex); + return current_entry; + } + + cur_index = current_entry->next; + if (cur_index != minus_one) { current_entry = &packet_storage[ cur_index ]; } + } + + pthread_mutex_unlock(&packet_storage_mutex); + struct data_packet* packet = load_packet(_hash); + + if (packet) { + uint64_t add_idx = set_data_packet(packet); + + if (add_idx != minus_one) { + return &packet_storage[ add_idx ]; + } else { + static struct data_packet_entry temp_entry; + temp_entry.packet = *packet; + return &temp_entry; + } + } + + return NULL; +} + +uint64_t set_data_packet(struct data_packet* new_packet) { + pthread_mutex_lock(&packet_storage_mutex); + + uint64_t hash_val; + memcpy(&hash_val, new_packet->hash, sizeof(uint64_t)); + uint64_t start_index = hash_val % max_storage; + uint64_t cur_index = start_index; + + struct data_packet_entry* current_entry = &packet_storage[ cur_index ]; + + while (true) { + if (current_entry->is_full + && memcmp(current_entry->packet.hash, new_packet->hash, sizeof(hash)) == 0) { + memcpy(¤t_entry->packet, new_packet, sizeof(struct data_packet)); + pthread_mutex_unlock(&packet_storage_mutex); + return cur_index; + } + + if (current_entry->next == minus_one) break; + cur_index = current_entry->next; + current_entry = &packet_storage[ cur_index ]; + } + + if (!current_entry->is_full) { + memcpy(¤t_entry->packet, new_packet, sizeof(struct data_packet)); + current_entry->is_full = true; + current_entry->next = minus_one; + current_entry->prev = minus_one; + packet_count++; + pthread_mutex_unlock(&packet_storage_mutex); + return cur_index; + } + + uint64_t probes = 0; + uint64_t linear_probe_index = (cur_index + 1) % max_storage; + + while (probes < max_storage) { + struct data_packet_entry* empty_slot = &packet_storage[ linear_probe_index ]; + + if (!empty_slot->is_full) { + memcpy(&empty_slot->packet, new_packet, sizeof(struct data_packet)); + empty_slot->is_full = true; + empty_slot->next = minus_one; + empty_slot->prev = cur_index; + current_entry->next = linear_probe_index; + packet_count++; + pthread_mutex_unlock(&packet_storage_mutex); + return linear_probe_index; + } + + linear_probe_index = (linear_probe_index + 1) % max_storage; + probes++; + } + + // table full + pthread_mutex_unlock(&packet_storage_mutex); + return minus_one; +} + +#define overload __attribute__((overloadable)) + +bool _delete_at_index(uint64_t index); + +overload void delete_data_packet(hash _hash) { + pthread_mutex_lock(&packet_storage_mutex); + + uint64_t hash_val; + memcpy(&hash_val, _hash, sizeof(uint64_t)); + uint64_t start_index = hash_val % max_storage; + uint64_t cur_index = start_index; + + while (cur_index != minus_one) { + struct data_packet_entry* current_entry = &packet_storage[ cur_index ]; + if (current_entry->is_full + && memcmp(current_entry->packet.hash, _hash, sizeof(hash)) == 0) { + _delete_at_index(cur_index); + pthread_mutex_unlock(&packet_storage_mutex); + return; + } + + if (current_entry->next == minus_one) { + pthread_mutex_unlock(&packet_storage_mutex); + return; + } + cur_index = current_entry->next; + } + + pthread_mutex_unlock(&packet_storage_mutex); +} + +overload void delete_data_packet(uint64_t index) { + pthread_mutex_lock(&packet_storage_mutex); + _delete_at_index(index); + pthread_mutex_unlock(&packet_storage_mutex); +} + +// caller must already have a lock! +bool _delete_at_index(uint64_t index) { + if (index >= max_storage) return false; + struct data_packet_entry* current_entry = &packet_storage[ index ]; + if (!current_entry->is_full) return false; + + if (current_entry->prev != minus_one) { + struct data_packet_entry* prev_entry = &packet_storage[ current_entry->prev ]; + prev_entry->next = current_entry->next; + } + + if (current_entry->next != minus_one) { + struct data_packet_entry* next_entry = &packet_storage[ current_entry->next ]; + next_entry->prev = current_entry->prev; + } + + current_entry->is_full = false; + current_entry->next = minus_one; + current_entry->prev = minus_one; + + return true; +}
\ No newline at end of file diff --git a/security.c b/security.c new file mode 100644 index 0000000..79dd2c2 --- /dev/null +++ b/security.c @@ -0,0 +1,161 @@ +// dependency: libsodium +#include <sodium.h> +#include <string.h> +// +#include "common.c" + +#if crypto_box_PUBLICKEYBYTES != 32 || crypto_box_SECRETKEYBYTES != 32 + #error currently, moonpaste requires 32 bytes length for public key and private key each. +#endif + +#if crypto_sign_BYTES != 64 + #error moonpaste needs 64 bytes length for signature. +#endif + +#ifndef SIGN_PUBLICKEYBYTES + #define SIGN_PUBLICKEYBYTES crypto_sign_PUBLICKEYBYTES +#endif +#ifndef SIGN_SECRETKEYBYTES + #define SIGN_SECRETKEYBYTES crypto_sign_SECRETKEYBYTES +#endif + +typedef unsigned char sign_public_key[ SIGN_PUBLICKEYBYTES ]; +typedef unsigned char sign_secret_key[ SIGN_SECRETKEYBYTES ]; + +typedef struct { + public_key encryption_pub; + private_key encryption_priv; + sign_public_key signing_pub; + sign_secret_key signing_priv; +} keypair; + +typedef struct { + bool is_valid; + byte data[ 65536 - crypto_box_MACBYTES - crypto_box_NONCEBYTES ]; +} plain_payload; + +typedef struct { + byte nonce[ crypto_box_NONCEBYTES ]; + byte encrypted_data[ 65536 - crypto_box_NONCEBYTES ]; + byte signature[ crypto_sign_BYTES ]; +} secure_payload; + +bool has_called_sodium_init = false; + +void init_sec() { + if (unlikely(!has_called_sodium_init)) { + if (sodium_init() == -1) { + fprintf(stderr, "sodium init failed\n"); + exit(1); + } + has_called_sodium_init = true; + } +} + +keypair generate_keypair() { + init_sec(); + keypair new; + crypto_box_keypair(new.encryption_pub, new.encryption_priv); + crypto_sign_keypair(new.signing_pub, new.signing_priv); + return new; +} + +secure_payload +encrypt_and_sign(plain_payload input, keypair author_keys, public_key target_encryption_public) { + init_sec(); + + secure_payload output; + randombytes_buf(output.nonce, crypto_box_NONCEBYTES); + + if (crypto_box_easy( + output.encrypted_data, + input.data, + sizeof(input.data), + output.nonce, + target_encryption_public, + author_keys.encryption_priv) + != 0) { + fprintf(stderr, "sodium encryption failed\n"); + exit(1); + } + + byte message_to_sign[ crypto_box_NONCEBYTES + sizeof(output.encrypted_data) ]; + memcpy(message_to_sign, output.nonce, crypto_box_NONCEBYTES); + memcpy( + message_to_sign + crypto_box_NONCEBYTES, + output.encrypted_data, + sizeof(output.encrypted_data)); + + if (crypto_sign_detached( + output.signature, + NULL, + message_to_sign, + sizeof(message_to_sign), + author_keys.signing_priv) + != 0) { + fprintf(stderr, "sodium signing failed\n"); + exit(1); + } + + return output; +} + +plain_payload decrypt(secure_payload input, keypair target_keys, public_key author_signing_public) { + init_sec(); + + plain_payload output; + output.is_valid = true; + + byte message_to_verify[ crypto_box_NONCEBYTES + sizeof(input.encrypted_data) ]; + memcpy(message_to_verify, input.nonce, crypto_box_NONCEBYTES); + memcpy( + message_to_verify + crypto_box_NONCEBYTES, + input.encrypted_data, + sizeof(input.encrypted_data)); + + if (crypto_sign_verify_detached( + input.signature, message_to_verify, sizeof(message_to_verify), author_signing_public) + != 0) { + output.is_valid = false; + return output; + } + + if (crypto_box_open_easy( + output.data, + input.encrypted_data, + sizeof(input.encrypted_data), + input.nonce, + target_keys.encryption_pub, + target_keys.encryption_priv) + != 0) { + fprintf(stderr, "sodium decryption failed\n"); + exit(1); + } + + return output; +} + +bool verify_signature(secure_payload input, public_key author_signing_public) { + init_sec(); + + byte message_to_verify[ crypto_box_NONCEBYTES + sizeof(input.encrypted_data) ]; + memcpy(message_to_verify, input.nonce, crypto_box_NONCEBYTES); + memcpy( + message_to_verify + crypto_box_NONCEBYTES, + input.encrypted_data, + sizeof(input.encrypted_data)); + + return crypto_sign_verify_detached( + input.signature, message_to_verify, sizeof(message_to_verify), author_signing_public) + == 0; +} + +bool verify_raw_signature( + const byte* msg, + size_t msg_len, + const signature signature, + const sign_public_key author_signing_public) { + init_sec(); + if (msg == NULL || signature == NULL || author_signing_public == NULL) return false; + return crypto_sign_verify_detached(signature, msg, msg_len, author_signing_public) == 0; +}
\ No newline at end of file diff --git a/server.c b/server.c new file mode 100644 index 0000000..9c1a710 --- /dev/null +++ b/server.c @@ -0,0 +1,752 @@ +#include <fcntl.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <unistd.h> +// +#include "common.c" +#include "omen.c" +#include "security.c" +#include "sha256.c" +// +#include "firehose.c" +#include "packet_storage.c" + +pthread_mutex_t log_io_mutex = PTHREAD_MUTEX_INITIALIZER; + +enum { _debug = 1, _info = 2, _warn = 3, _error = 4 } log_level = _debug; +const char* log_level_names[] = { "debug", "info", "warn", "error" }; +#define log(__level, fmt, ...) \ + ({ \ + pthread_mutex_lock(&log_io_mutex); \ + if (__level >= log_level) \ + fprintf(stderr, "[%s] " fmt, (#__level) + 1 __VA_OPT__(, ) __VA_ARGS__); \ + pthread_mutex_unlock(&log_io_mutex); \ + }) + +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 { + 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 = 10, // the moon guarantee + .accepts_plain = true, // probably legal +}; + +bool add_neighbor(struct relay_neighbor new_neighbor) { + log(_info, "adding neighbor\n"); + + 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; + log(_debug, "propagating packet " hash_fmt "\n", hash_ref(packet.hash)); + + size_t send_len = 1 + sizeof(struct data_packet); + byte* send_data = calloc(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) { fprintf(stderr, "failed to send packet to neighbor\n"); } + + if (response_data) { free(response_data); } + } + } + pthread_mutex_unlock(&neighbor_storage_mutex); + + free(send_data); + free(_packet); + return NULL; +} + +void save_packet(const struct data_packet packet); + +#define reply(__len, __stream) \ + ({ \ + *out = (byte*) __stream; \ + *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\n", (int) ip_size, ip_addr, in_len); + + 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\n", (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 ]; + } + + pthread_mutex_unlock(&packet_storage_mutex); + pthread_mutex_unlock(&neighbor_storage_mutex); + + memcpy(info.motd, server_stats.motd, 128); + + 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\n", + (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 "\n", + (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 "\n", + (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\n", (int) ip_size, ip_addr); + output_data[ 1 ] = false; + reply(output_len, output_data); + return; + } + + if (!check_leading_zeros(packet.hash, server_stats.leading_zeros)) { + log(_warn, "bad leads\n"); + 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 "\n", + 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, "future\n"); + 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\n"); + 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\n"); + 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); + + 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 (unlikely(req.count > 64)) req.count = 64; // clamp + + log(_info, + "<%.*s> request type: firehose count %i offset %li\n", + (int) ip_size, + ip_addr, + req.count, + req.offset); + + 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; + } + + 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; + } + + default: { + log(_info, "<%.*s> request type: invalid method\n", (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\n"); + 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) { + log(_debug, "saving packet " hash_fmt " to disk\n", hash_ref(packet.hash)); + + mkdir("packets", 0755); + + char filename[ 128 ]; + sprintf(filename, "packets/" hash_fmt ".bin", hash_ref(packet.hash)); + + FILE* file = fopen(filename, "wb"); + if (!file) { + perror("failed to save packet"); + return; + } + + byte* buffer = malloc(PACKET_SIZE); + if (!buffer) { + fclose(file); + return; + } + + data_packet2s(packet, buffer); + size_t written = fwrite(buffer, 1, PACKET_SIZE, file); + if (written != PACKET_SIZE) { perror("failed to write full packet to disk"); } + free(buffer); + fclose(file); +} + +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\n", count); +} + +void* fs_persistence(void* arg) { + while (disk_persistence_thread_running) { + sleep(2 * 60); // 2m + log(_info, "saving neighbors...\n"); + save_neighbors(); + } + + return NULL; +} + +void* cleanup_packets(void* arg) { + while (packet_cleanup_thread_running) { + sleep(30 * 60); // 30m + + log(_warn, "running cleanup\n"); + uint64_t current_time_ms = get_current_time_ms(); + uint64_t retention_ms = (uint64_t) server_stats.retention_hours * 60 * 60 * 1000; + + for (uint64_t i = 0; i < max_storage; i++) { + pthread_mutex_lock(&packet_storage_mutex); + if (packet_storage[ i ].is_full + && current_time_ms - packet_storage[ i ].packet.contents.created_time_ms + > retention_ms) { + log(_debug, + "cleaning up old packet hashed " hash_fmt "\n", + 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\n", filename); + + packet_storage[ i ].is_full = false; + packet_count--; + } + pthread_mutex_unlock(&packet_storage_mutex); + } + } + + 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\n"); + } + + if (response_data) { free(response_data); } + } + } + pthread_mutex_unlock(&neighbor_storage_mutex); + + sleep(60 * 5); // 5m + } + return NULL; +} + +int main() { + 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(); + + omen(default_port, paste_server); + + packet_cleanup_thread_running = false; + pthread_join(packet_cleanup_thread, NULL); + + return 0; +} diff --git a/sha256.c b/sha256.c new file mode 100644 index 0000000..0d19110 --- /dev/null +++ b/sha256.c @@ -0,0 +1,104 @@ +#include <stdint.h> +#include <string.h> + +typedef uint8_t byte; + +typedef byte hash[ 32 ]; + +static uint32_t ror(uint32_t x, int n) { return (x >> n) | (x << (32 - n)); } + +static uint32_t ch(uint32_t x, uint32_t y, uint32_t z) { return (x & y) ^ (~x & z); } + +static uint32_t maj(uint32_t x, uint32_t y, uint32_t z) { return (x & y) ^ (x & z) ^ (y & z); } + +static uint32_t sigma0(uint32_t x) { return ror(x, 2) ^ ror(x, 13) ^ ror(x, 22); } + +static uint32_t sigma1(uint32_t x) { return ror(x, 6) ^ ror(x, 11) ^ ror(x, 25); } + +static uint32_t gamma0(uint32_t x) { return ror(x, 7) ^ ror(x, 18) ^ (x >> 3); } + +static uint32_t gamma1(uint32_t x) { return ror(x, 17) ^ ror(x, 19) ^ (x >> 10); } + +static const uint32_t K[ 64 ] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 +}; + +static void compress(uint32_t state[ 8 ], const byte block[ 64 ]) { + uint32_t w[ 64 ]; + int i; + for (i = 0; i < 16; i++) { + w[ i ] = (block[ i * 4 ] << 24) | (block[ i * 4 + 1 ] << 16) | (block[ i * 4 + 2 ] << 8) + | block[ i * 4 + 3 ]; + } + for (i = 16; i < 64; i++) { + w[ i ] = gamma1(w[ i - 2 ]) + w[ i - 7 ] + gamma0(w[ i - 15 ]) + w[ i - 16 ]; + } + uint32_t a = state[ 0 ], b = state[ 1 ], c = state[ 2 ], d = state[ 3 ], e = state[ 4 ], + f = state[ 5 ], g = state[ 6 ], h = state[ 7 ]; + for (i = 0; i < 64; i++) { + uint32_t t1 = h + sigma1(e) + ch(e, f, g) + K[ i ] + w[ i ]; + uint32_t t2 = sigma0(a) + maj(a, b, c); + h = g; + g = f; + f = e; + e = d + t1; + d = c; + c = b; + b = a; + a = t1 + t2; + } + state[ 0 ] += a; + state[ 1 ] += b; + state[ 2 ] += c; + state[ 3 ] += d; + state[ 4 ] += e; + state[ 5 ] += f; + state[ 6 ] += g; + state[ 7 ] += h; +} + +struct sha256 { + hash hash; +} sha256_hash(const byte *in, size_t in_len) { + struct sha256 out; + + uint32_t state[ 8 ] = { 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, + 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 }; + size_t off = 0; + for (; off + 64 <= in_len; off += 64) { compress(state, in + off); } + byte pad[ 128 ]; + memset(pad, 0, sizeof(pad)); + size_t rem = in_len - off; + memcpy(pad, in + off, rem); + pad[ rem ] = 0x80; + int use_two_blocks = (rem >= 56); + size_t pad_len = 64 + 64 * use_two_blocks; + size_t pos = rem + 1; + while (pos < pad_len - 8) { pad[ pos++ ] = 0; } + uint64_t bit_len = in_len * 8; + pad[ pad_len - 1 ] = bit_len & 0xff; + pad[ pad_len - 2 ] = (bit_len >> 8) & 0xff; + pad[ pad_len - 3 ] = (bit_len >> 16) & 0xff; + pad[ pad_len - 4 ] = (bit_len >> 24) & 0xff; + pad[ pad_len - 5 ] = (bit_len >> 32) & 0xff; + pad[ pad_len - 6 ] = (bit_len >> 40) & 0xff; + pad[ pad_len - 7 ] = (bit_len >> 48) & 0xff; + pad[ pad_len - 8 ] = (bit_len >> 56) & 0xff; + compress(state, pad); + if (use_two_blocks) { compress(state, pad + 64); } + for (int i = 0; i < 8; i++) { + out.hash[ 4 * i ] = (state[ i ] >> 24) & 0xff; + out.hash[ 4 * i + 1 ] = (state[ i ] >> 16) & 0xff; + out.hash[ 4 * i + 2 ] = (state[ i ] >> 8) & 0xff; + out.hash[ 4 * i + 3 ] = state[ i ] & 0xff; + } + + return out; +}
\ No newline at end of file |
