diff options
-rw-r--r-- | .gitignore | 5 | ||||
-rw-r--r-- | Makefile | 29 | ||||
-rw-r--r-- | README.org | 7 | ||||
-rw-r--r-- | flake.lock | 26 | ||||
-rw-r--r-- | flake.nix | 42 | ||||
-rw-r--r-- | include/array.h | 18 | ||||
-rw-r--r-- | include/better_string.h | 22 | ||||
-rw-r--r-- | include/hash_table.h | 14 | ||||
-rw-r--r-- | include/helpers.h | 12 | ||||
-rw-r--r-- | include/ht.h | 3 | ||||
-rw-r--r-- | include/opcodes.h | 49 | ||||
-rw-r--r-- | include/protocol.h | 14 | ||||
-rw-r--r-- | src/array.c | 37 | ||||
-rw-r--r-- | src/better_string.c | 51 | ||||
-rw-r--r-- | src/helpers.c | 28 | ||||
-rw-r--r-- | src/main.c | 211 | ||||
-rw-r--r-- | src/opcodes.c | 26 |
17 files changed, 594 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..720c57f --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +obj/** +bin/** +.cache/** +result/** +compile_commands.json diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..cafa911 --- /dev/null +++ b/Makefile @@ -0,0 +1,29 @@ +SRC_DIR := src +OBJ_DIR := obj +BIN_DIR := bin + +EXE := $(BIN_DIR)/umami +SRC := $(wildcard $(SRC_DIR)/*.c) +OBJ := $(SRC:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o) + +CFLAGS := -Wall -Iinclude +LDFLAGS := -Llib +LDLIBS := -lm + +.PHONY: all clean + +all: $(EXE) + +$(EXE): $(OBJ) | $(BIN_DIR) + $(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@ + +$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(OBJ_DIR) + $(CC) $(CFLAGS) -c $< -o $@ + +$(BIN_DIR) $(OBJ_DIR): + mkdir -p $@ + +clean: + @$(RM) -rv $(BIN_DIR) $(OBJ_DIR) result + +-include $(OBJ:.o=.d) diff --git a/README.org b/README.org new file mode 100644 index 0000000..b81e4f0 --- /dev/null +++ b/README.org @@ -0,0 +1,7 @@ +* Introduction +Umami is a simple messaging protocol built for simplicity, which integrates user permissions +(such as mod, admin, etc...), log persistence, and account management in layer one. In other +words, it is IRC, but modern. It will also support simple encryption as a layer one protocol. + +This first server will implement the minimal functionality that satisfies the following properties: +1. It should store logs for all public channels, and should diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..bce6f7b --- /dev/null +++ b/flake.lock @@ -0,0 +1,26 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1734424634, + "narHash": "sha256-cHar1vqHOOyC7f1+tVycPoWTfKIaqkoe1Q6TnKzuti4=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "d3c42f187194c26d9f0309a8ecc469d6c878ce33", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixos-unstable", + "type": "indirect" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..7d8c456 --- /dev/null +++ b/flake.nix @@ -0,0 +1,42 @@ +{ + description = "A Nix flake for creating a devshell and build environment for umami"; + + inputs = { + nixpkgs.url = "nixpkgs/nixos-unstable"; + }; + + outputs = { self, nixpkgs }: { + packages.x86_64-linux = let + pkgs = import nixpkgs { + system = "x86_64-linux"; # Adjust for your system, e.g., aarch64-darwin + }; + in + { + default = pkgs.stdenv.mkDerivation { + pname = "umami"; + version = "1.0.0"; + + src = ./.; + + buildInputs = with pkgs; [ clang gnumake valgrind bear ]; + + buildPhase = "make"; + + installPhase = '' + mkdir -p $out/bin + cp ./bin/umami $out/bin/ + ''; + }; + }; + + devShell.x86_64-linux = let + pkgs = import nixpkgs { + system = "x86_64-linux"; + }; + in + pkgs.mkShell { + buildInputs = with pkgs; [ clang gnumake valgrind bear ]; + }; + }; +} + diff --git a/include/array.h b/include/array.h new file mode 100644 index 0000000..48d82db --- /dev/null +++ b/include/array.h @@ -0,0 +1,18 @@ +#ifndef ARRAY_H +#define ARRAY_H +#include <stdlib.h> +#define DEFAULT_ARR_LEN 10 +typedef struct { + void **items; + size_t size; + size_t capacity; +} array_t; + +array_t *init_array(); + +void array_push(array_t *a, void *item); + +void *array_pop(array_t *a); + +void array_free(void *a, void (*freefunc)(void *)); +#endif diff --git a/include/better_string.h b/include/better_string.h new file mode 100644 index 0000000..b23010e --- /dev/null +++ b/include/better_string.h @@ -0,0 +1,22 @@ +#ifndef STRING_H +#define STRING_H +#include <stdlib.h> +#define DEFAULT_STR_SIZE 10 +typedef struct { + char *buf; + size_t len; + size_t size; +} string_t; + +string_t *init_string(const char *source); + +void string_append(string_t *s, char c); + +void string_concat(string_t *s1, string_t *s2); + +void string_concat_const(string_t *s, const char *src); + +char string_pop(string_t *s); + +void string_free(void *s); +#endif diff --git a/include/hash_table.h b/include/hash_table.h new file mode 100644 index 0000000..16d8fe9 --- /dev/null +++ b/include/hash_table.h @@ -0,0 +1,14 @@ +#ifndef HASH_TABLE_H +#define HASH_TABLE_H +typedef struct NODE_STRUCT { + +} node_t; + +typedef struct { + +} sll_t; + +typedef struct { + +} ht_t; +#endif diff --git a/include/helpers.h b/include/helpers.h new file mode 100644 index 0000000..c685e09 --- /dev/null +++ b/include/helpers.h @@ -0,0 +1,12 @@ +#ifndef HELPERS_H +#define HELPERS_H +#include <stdlib.h> + +void die(const char *msg); + +void *safe_calloc(unsigned int i, size_t size); + +void *safe_realloc(void *x, size_t size); + +void alloc_zero(void *ptr, size_t size); +#endif diff --git a/include/ht.h b/include/ht.h new file mode 100644 index 0000000..9172961 --- /dev/null +++ b/include/ht.h @@ -0,0 +1,3 @@ +#ifndef HT_H +#define HT_H +#endif diff --git a/include/opcodes.h b/include/opcodes.h new file mode 100644 index 0000000..3d5baf8 --- /dev/null +++ b/include/opcodes.h @@ -0,0 +1,49 @@ +#ifndef OPCODES_H +#define OPCODES_H + +typedef enum { + CO_NOP, + CO_JN, /* join */ + CO_DM, + CO_PST, /* post */ + CO_MSG, + CO_REG, + CO_NCK, /* nick */ + CO_PNG, /* pong */ + CO_LVE, /* leave */ + CO_QT, /* quit*/ + + CO_CLM, /* claim channel */ + CO_DESC, /* change description */ + CO_TRSFR, /* ownership transfer of admin */ + CO_KCK, /* kick */ + CO_BAN, + CO_KNGT, /* knight */ + CO_DMT, /* demote */ + + CO_LOGS, /* allows users to download log file from date specified in unix time */ + + CO_UNRECOGNIZED +} copcode_t; + +typedef enum { + SO_SUCCESS, + SO_FAIL_PARSE, + SO_FAIL_NOPERM, + SO_FAIL_USER_TAKEN, + SO_FAIL_RATE, /* rate limit */ + SO_PNG, /* ping */ + SO_BYE, +} sopcode_t; + +void die_lz(int code, const char *msg); + +char *decode_client_opcode(int op); + +char *decode_server_opcode(int op); + +int encode_client_opcode(char *c); + +int encode_server_opcode(char *c); + +#endif diff --git a/include/protocol.h b/include/protocol.h new file mode 100644 index 0000000..7b5eb07 --- /dev/null +++ b/include/protocol.h @@ -0,0 +1,14 @@ +#ifndef PROTOCOL_H +#define PROTOCOL_H + +#define MAX_OP_LEN 10 +#define MAX_ARG_LEN 50 +#define MAX_ARGS 5 +#define MAX_BUFSIZE 4096 +#define DEFAULT_PORT 11111 +#define MAX_CONNECTIONS 32768 +#define DEFAULT_TIMEOUT 6000 +#define USERNAME_SIZE 30 +#define KEYLEN 512 + +#endif diff --git a/src/array.c b/src/array.c new file mode 100644 index 0000000..84ed938 --- /dev/null +++ b/src/array.c @@ -0,0 +1,37 @@ +#include <array.h> +#include <helpers.h> +#include <stdlib.h> + +array_t *init_array() { + array_t *a = safe_calloc(1, sizeof(array_t)); + a->capacity = DEFAULT_ARR_LEN; + a->size = 0; + a->items = safe_calloc(a->capacity, sizeof(void *)); + return a; +} + +void array_push(array_t *a, void *item) { + if (a->size >= a->capacity - 2) { + a->capacity *= 2; + a->items = realloc(a->items, a->capacity); + } + a->items[a->size] = item; + a->size++; +} + +void *array_pop(array_t *a) { + if (a->size <= 0) + return NULL; + void *retval = a->items[a->size]; + a->size--; + return retval; +} + +void array_free(void *x, void (*freefunc)(void *)) { + array_t *a = (array_t *)x; + for (int i = 0; i < a->size; i++) { + freefunc(a->items[i]); + } + free(a->items); + free(a); +} diff --git a/src/better_string.c b/src/better_string.c new file mode 100644 index 0000000..4413f77 --- /dev/null +++ b/src/better_string.c @@ -0,0 +1,51 @@ +#include <better_string.h> +#include <helpers.h> +#include <stdlib.h> +#include <string.h> + +string_t *init_string(const char *src) { + string_t *s = safe_calloc(1, sizeof(string_t)); + size_t len = src ? strlen(src) : DEFAULT_STR_SIZE; + size_t size = len * 2; + s->buf = safe_calloc(size, sizeof(char)); + s->buf[0] = '\0'; + + if (src) + strcpy(s->buf, src); + s->len = len; + s->size = size; + return s; +} + +void string_push(string_t *s, char c) { + if (s->len >= s->size - 2) { + s->size *= 2; + s->buf = safe_realloc(s->buf, s->size); + } + s->buf[s->len] = c; + s->len++; +} + +char string_pop(string_t *s) { + char c = s->buf[s->len]; + s->len--; + return c; +} + +void string_concat_const(string_t *s1, const char *s2) { + for (int i = 0; i < strlen(s2); i++) { + string_push(s1, s2[i]); + } +} + +void string_concat(string_t *s1, string_t *s2) { + for (int i = 0; i < s2->len; i++) { + string_push(s1, s2->buf[i]); + } +} + +void string_free(void *x) { + string_t *s = x; + free(s->buf); + free(s); +} diff --git a/src/helpers.c b/src/helpers.c new file mode 100644 index 0000000..ad7228e --- /dev/null +++ b/src/helpers.c @@ -0,0 +1,28 @@ +#include <helpers.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +void die(const char *msg) { + fprintf(stderr, "panic: "); + perror(msg); + exit(EXIT_FAILURE); +} + +void *safe_calloc(unsigned int i, size_t size) { + void *x = calloc(i, size); + if (x == NULL) { + die("abort: calloc()"); + } + return x; +} + +void *safe_realloc(void *x, size_t size) { + void *p = realloc(x, size); + if (x == NULL) { + die("abort: realloc()"); + } + return p; +} + +void alloc_zero(void *ptr, size_t size) { memset(ptr, 0, size); } diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..8555da1 --- /dev/null +++ b/src/main.c @@ -0,0 +1,211 @@ +#include <errno.h> +#include <fcntl.h> +#include <netinet/in.h> +#include <signal.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/poll.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <unistd.h> + +#include <array.h> +#include <better_string.h> +#include <helpers.h> +#include <opcodes.h> +#include <protocol.h> + +int PORT = DEFAULT_PORT; +int nfds = 1; +struct pollfd fds[MAX_CONNECTIONS * 2]; + +void handle_sigint(int sig) { + for (int i = 0; i < nfds; i++) { + if (fds[i].fd >= 0) { + close(fds[i].fd); + } + } + + exit(0); +} + +/* modifying function: modifies `in` to point to new location after opcode */ +int parse_op(char *in) { + char buf[MAX_OP_LEN]; + buf[0] = '\0'; + + char cur; + cur = in[0]; + + bool found = false; + for (int i = 0; i < 11; i++) { + if (cur == '\t') { + found = true; + in++; + break; + } else if (cur == '\0') { + break; + } + + buf[i] = in[0]; + in++; + cur = in[0]; + } + + return found ? encode_client_opcode(buf) : CO_UNRECOGNIZED; +} + +array_t *parse_args(char *buf) { return NULL; } + +void set_non_blocking(int sock) { + int flags = fcntl(sock, F_GETFL, 0); + int code = fcntl(sock, F_SETFL, flags | O_NONBLOCK); + die_lz(code, "fcntl()"); +} + +int main(int argc, char **argv) { + char buffer[MAX_BUFSIZE]; + char res_buffer[MAX_BUFSIZE]; + struct pollfd *local_fds1 = fds; + struct pollfd *local_fds2 = fds + MAX_CONNECTIONS; + struct pollfd *local_fds = local_fds1; + + /* We start by initializing our net-related structs */ + signal(SIGINT, handle_sigint); + + int listen_sd = socket(AF_INET6, SOCK_STREAM, 0); + int optval = 1; + die_lz(listen_sd, "socket()"); + + int reuse = setsockopt(listen_sd, SOL_SOCKET, SO_REUSEADDR, (char *)&optval, + sizeof(optval)); + die_lz(reuse, "setsockopt()"); + + int nonblock = ioctl(listen_sd, FIONBIO, (char *)&optval); + die_lz(nonblock, "ioctl()"); + + set_non_blocking(listen_sd); + + /* Make new address */ + struct sockaddr_in6 addr; + alloc_zero(&addr, sizeof(addr)); + + addr.sin6_family = AF_INET6; + memcpy(&addr.sin6_addr, &in6addr_any, sizeof(in6addr_any)); + addr.sin6_port = htons(DEFAULT_PORT); + + int sock_bind = bind(listen_sd, (struct sockaddr *)&addr, sizeof(addr)); + die_lz(sock_bind, "bind()"); + + int sock_listen = listen(listen_sd, 32); + die_lz(sock_listen, "listen()"); + + /* + The first fd entry is the listening socket for new connections; + all the rest are sockets we read from and write to. Whenever a client + connects, we know from the listening socket. + */ + + alloc_zero(fds, sizeof(fds)); + fds[0].fd = listen_sd; + fds[0].events = POLLIN; + + int timeout = 6000; + int sock_poll; + bool end_server = false; + + /* mainloop */ + do { + bool compress_array = false; + /* + if an fd loses connection, we want to remove all -1's from the fds array + */ + + sock_poll = poll(local_fds, nfds, timeout); + die_lz(sock_poll, "poll()"); + + for (int i = 0; i < nfds; i++) { + if (local_fds[i].revents == 0) + continue; + + if (local_fds[i].revents != POLLIN) { + end_server = true; + break; + } + + if (local_fds[i].fd == listen_sd) { + printf("socket is readable\n"); + int new_sd; + + do { + new_sd = accept(listen_sd, NULL, NULL); + + if (new_sd < 0 && errno != EWOULDBLOCK) { + perror("accept() failed"); + end_server = true; + break; + } + + local_fds[nfds].fd = new_sd; + local_fds[nfds].events = POLLIN; + nfds++; + } while (new_sd >= 0); + } else { + bool close_conn = false; + int fd_recv; + int fd_send; + fd_recv = recv(local_fds[i].fd, buffer, sizeof(buffer), 0); + + if (fd_recv < 0 && errno != EWOULDBLOCK) { + perror("recv() failed"); + close_conn = true; + } else if (fd_recv == 0) { + printf("Connection closed\n"); + close_conn = true; + } else { + /* echo server -- replace this with buffer parsing */ + /* TODO: reply to client based on user input */ + fd_send = send(local_fds[i].fd, buffer, fd_recv, 0); + if (fd_send < 0) { + perror("send()"); + close_conn = true; + } + } + + if (close_conn) { + close(local_fds[i].fd); + local_fds[i].fd = 0; + compress_array = true; + } + } + } + + if (compress_array) { + printf("switching...\n"); + int cur_nfds = nfds; + nfds = 0; + for (int i = 0; i < cur_nfds; i++) { + if (local_fds[i].fd != 0) { + local_fds2[nfds] = local_fds[i]; + nfds ++; + } + } + + local_fds1 = local_fds2; + local_fds2 = local_fds; + local_fds = local_fds1; + alloc_zero(local_fds2, MAX_CONNECTIONS); + } + } while (!end_server); + + for (int i = 0; i < nfds; i++) { + if (fds[i].fd >= 0) { + close(fds[i].fd); + } + } + + return 0; +} diff --git a/src/opcodes.c b/src/opcodes.c new file mode 100644 index 0000000..66742db --- /dev/null +++ b/src/opcodes.c @@ -0,0 +1,26 @@ +#include <stdlib.h> +#include <opcodes.h> +#include <helpers.h> + + +void die_lz(int code, const char *msg) { + if (code < 0) { + die(msg); + } +} + +char *decode_client_opcode(int op) { + return "implement me"; +} + +char *decode_server_opcode(int op) { + return "implement me"; +} + +int encode_client_opcode(char *c) { + return -1; +} + +int encode_server_opcode(char *c) { + return -1; +} |