commit 7f3fab3aab6a4de059277330fa52049033f022fc Author: Enrique <enriquedelacalhacar@gmail.com> Date: Mon Jan 6 16:40:25 2025 +0100 Initial commit diff --git a/Makefile b/Makefile new file mode 100755 index 0000000..07ff46c --- /dev/null +++ b/Makefile @@ -0,0 +1,40 @@ +CC = cyg +CFLAGS = -O0 -Wall -ggdb3 +LDFLAGS = -lssl -lcrypto + +SRCDIR = src +OBJDIR = obj +ifeq ($(OS),Windows_NT) + LIBDIR = C:/Users/quique/Documents/Programming/libs + RUNCMD = server.exe +else + LIBDIR = /mnt/c/Users/quique/Documents/Programming/libs + RUNCMD = ./server.exe +endif + +OBJS = $(addprefix $(OBJDIR)/, bit.o str.o log.o list.o crc64.o dir.o net.o ipc.o) + +INCL = -I$(LIBDIR) -I$(SRCDIR) + + + +all: server.exe worker.exe + +server.exe: $(SRCDIR)/main.c $(OBJS) + $(CC) $< $(OBJS) $(INCL) $(CFLAGS) -o server.exe $(LDFLAGS) + +worker.exe: $(SRCDIR)/worker.c $(OBJS) + $(CC) $< $(OBJS) $(INCL) $(CFLAGS) -o worker.exe $(LDFLAGS) + +$(OBJDIR)/%.o: $(SRCDIR)/%/*.c $(SRCDIR)/%/*.h + $(CC) -c $(SRCDIR)/$*/$*.c $(INCL) $(CFLAGS) -o $(OBJDIR)/$*.o $(LDFLAGS) + +$(OBJDIR)/%.o: $(LIBDIR)/%/*.c $(LIBDIR)/%/*.h + $(CC) -c $(LIBDIR)/$*/$*.c $(INCL) $(CFLAGS) -o $(OBJDIR)/$*.o $(LDFLAGS) + +clean: + rm -f worker.exe server.exe $(OBJDIR)/*.o + +memcheck: + sudo valgrind --show-leak-kinds=all --leak-check=full --track-origins=yes --trace-children=yes -s $(RUNCMD) 443 + diff --git a/src/crc64/crc64.c b/src/crc64/crc64.c new file mode 100755 index 0000000..9a3f845 --- /dev/null +++ b/src/crc64/crc64.c @@ -0,0 +1,191 @@ +/* Redis uses the CRC64 variant with "Jones" coefficients and init value of 0. + * + * Specification of this CRC64 variant follows: + * Name: crc-64-jones + * Width: 64 bites + * Poly: 0xad93d23594c935a9 + * Reflected In: True + * Xor_In: 0xffffffffffffffff + * Reflected_Out: True + * Xor_Out: 0x0 + * Check("123456789"): 0xe9c6d914c4b8d9ca + * + * Copyright (c) 2012, Salvatore Sanfilippo <antirez at gmail dot com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. */ + +#include <stdint.h> + +static const uint64_t crc64_tab[256] = { + UINT64_C(0x0000000000000000), UINT64_C(0x7ad870c830358979), + UINT64_C(0xf5b0e190606b12f2), UINT64_C(0x8f689158505e9b8b), + UINT64_C(0xc038e5739841b68f), UINT64_C(0xbae095bba8743ff6), + UINT64_C(0x358804e3f82aa47d), UINT64_C(0x4f50742bc81f2d04), + UINT64_C(0xab28ecb46814fe75), UINT64_C(0xd1f09c7c5821770c), + UINT64_C(0x5e980d24087fec87), UINT64_C(0x24407dec384a65fe), + UINT64_C(0x6b1009c7f05548fa), UINT64_C(0x11c8790fc060c183), + UINT64_C(0x9ea0e857903e5a08), UINT64_C(0xe478989fa00bd371), + UINT64_C(0x7d08ff3b88be6f81), UINT64_C(0x07d08ff3b88be6f8), + UINT64_C(0x88b81eabe8d57d73), UINT64_C(0xf2606e63d8e0f40a), + UINT64_C(0xbd301a4810ffd90e), UINT64_C(0xc7e86a8020ca5077), + UINT64_C(0x4880fbd87094cbfc), UINT64_C(0x32588b1040a14285), + UINT64_C(0xd620138fe0aa91f4), UINT64_C(0xacf86347d09f188d), + UINT64_C(0x2390f21f80c18306), UINT64_C(0x594882d7b0f40a7f), + UINT64_C(0x1618f6fc78eb277b), UINT64_C(0x6cc0863448deae02), + UINT64_C(0xe3a8176c18803589), UINT64_C(0x997067a428b5bcf0), + UINT64_C(0xfa11fe77117cdf02), UINT64_C(0x80c98ebf2149567b), + UINT64_C(0x0fa11fe77117cdf0), UINT64_C(0x75796f2f41224489), + UINT64_C(0x3a291b04893d698d), UINT64_C(0x40f16bccb908e0f4), + UINT64_C(0xcf99fa94e9567b7f), UINT64_C(0xb5418a5cd963f206), + UINT64_C(0x513912c379682177), UINT64_C(0x2be1620b495da80e), + UINT64_C(0xa489f35319033385), UINT64_C(0xde51839b2936bafc), + UINT64_C(0x9101f7b0e12997f8), UINT64_C(0xebd98778d11c1e81), + UINT64_C(0x64b116208142850a), UINT64_C(0x1e6966e8b1770c73), + UINT64_C(0x8719014c99c2b083), UINT64_C(0xfdc17184a9f739fa), + UINT64_C(0x72a9e0dcf9a9a271), UINT64_C(0x08719014c99c2b08), + UINT64_C(0x4721e43f0183060c), UINT64_C(0x3df994f731b68f75), + UINT64_C(0xb29105af61e814fe), UINT64_C(0xc849756751dd9d87), + UINT64_C(0x2c31edf8f1d64ef6), UINT64_C(0x56e99d30c1e3c78f), + UINT64_C(0xd9810c6891bd5c04), UINT64_C(0xa3597ca0a188d57d), + UINT64_C(0xec09088b6997f879), UINT64_C(0x96d1784359a27100), + UINT64_C(0x19b9e91b09fcea8b), UINT64_C(0x636199d339c963f2), + UINT64_C(0xdf7adabd7a6e2d6f), UINT64_C(0xa5a2aa754a5ba416), + UINT64_C(0x2aca3b2d1a053f9d), UINT64_C(0x50124be52a30b6e4), + UINT64_C(0x1f423fcee22f9be0), UINT64_C(0x659a4f06d21a1299), + UINT64_C(0xeaf2de5e82448912), UINT64_C(0x902aae96b271006b), + UINT64_C(0x74523609127ad31a), UINT64_C(0x0e8a46c1224f5a63), + UINT64_C(0x81e2d7997211c1e8), UINT64_C(0xfb3aa75142244891), + UINT64_C(0xb46ad37a8a3b6595), UINT64_C(0xceb2a3b2ba0eecec), + UINT64_C(0x41da32eaea507767), UINT64_C(0x3b024222da65fe1e), + UINT64_C(0xa2722586f2d042ee), UINT64_C(0xd8aa554ec2e5cb97), + UINT64_C(0x57c2c41692bb501c), UINT64_C(0x2d1ab4dea28ed965), + UINT64_C(0x624ac0f56a91f461), UINT64_C(0x1892b03d5aa47d18), + UINT64_C(0x97fa21650afae693), UINT64_C(0xed2251ad3acf6fea), + UINT64_C(0x095ac9329ac4bc9b), UINT64_C(0x7382b9faaaf135e2), + UINT64_C(0xfcea28a2faafae69), UINT64_C(0x8632586aca9a2710), + UINT64_C(0xc9622c4102850a14), UINT64_C(0xb3ba5c8932b0836d), + UINT64_C(0x3cd2cdd162ee18e6), UINT64_C(0x460abd1952db919f), + UINT64_C(0x256b24ca6b12f26d), UINT64_C(0x5fb354025b277b14), + UINT64_C(0xd0dbc55a0b79e09f), UINT64_C(0xaa03b5923b4c69e6), + UINT64_C(0xe553c1b9f35344e2), UINT64_C(0x9f8bb171c366cd9b), + UINT64_C(0x10e3202993385610), UINT64_C(0x6a3b50e1a30ddf69), + UINT64_C(0x8e43c87e03060c18), UINT64_C(0xf49bb8b633338561), + UINT64_C(0x7bf329ee636d1eea), UINT64_C(0x012b592653589793), + UINT64_C(0x4e7b2d0d9b47ba97), UINT64_C(0x34a35dc5ab7233ee), + UINT64_C(0xbbcbcc9dfb2ca865), UINT64_C(0xc113bc55cb19211c), + UINT64_C(0x5863dbf1e3ac9dec), UINT64_C(0x22bbab39d3991495), + UINT64_C(0xadd33a6183c78f1e), UINT64_C(0xd70b4aa9b3f20667), + UINT64_C(0x985b3e827bed2b63), UINT64_C(0xe2834e4a4bd8a21a), + UINT64_C(0x6debdf121b863991), UINT64_C(0x1733afda2bb3b0e8), + UINT64_C(0xf34b37458bb86399), UINT64_C(0x8993478dbb8deae0), + UINT64_C(0x06fbd6d5ebd3716b), UINT64_C(0x7c23a61ddbe6f812), + UINT64_C(0x3373d23613f9d516), UINT64_C(0x49aba2fe23cc5c6f), + UINT64_C(0xc6c333a67392c7e4), UINT64_C(0xbc1b436e43a74e9d), + UINT64_C(0x95ac9329ac4bc9b5), UINT64_C(0xef74e3e19c7e40cc), + UINT64_C(0x601c72b9cc20db47), UINT64_C(0x1ac40271fc15523e), + UINT64_C(0x5594765a340a7f3a), UINT64_C(0x2f4c0692043ff643), + UINT64_C(0xa02497ca54616dc8), UINT64_C(0xdafce7026454e4b1), + UINT64_C(0x3e847f9dc45f37c0), UINT64_C(0x445c0f55f46abeb9), + UINT64_C(0xcb349e0da4342532), UINT64_C(0xb1eceec59401ac4b), + UINT64_C(0xfebc9aee5c1e814f), UINT64_C(0x8464ea266c2b0836), + UINT64_C(0x0b0c7b7e3c7593bd), UINT64_C(0x71d40bb60c401ac4), + UINT64_C(0xe8a46c1224f5a634), UINT64_C(0x927c1cda14c02f4d), + UINT64_C(0x1d148d82449eb4c6), UINT64_C(0x67ccfd4a74ab3dbf), + UINT64_C(0x289c8961bcb410bb), UINT64_C(0x5244f9a98c8199c2), + UINT64_C(0xdd2c68f1dcdf0249), UINT64_C(0xa7f41839ecea8b30), + UINT64_C(0x438c80a64ce15841), UINT64_C(0x3954f06e7cd4d138), + UINT64_C(0xb63c61362c8a4ab3), UINT64_C(0xcce411fe1cbfc3ca), + UINT64_C(0x83b465d5d4a0eece), UINT64_C(0xf96c151de49567b7), + UINT64_C(0x76048445b4cbfc3c), UINT64_C(0x0cdcf48d84fe7545), + UINT64_C(0x6fbd6d5ebd3716b7), UINT64_C(0x15651d968d029fce), + UINT64_C(0x9a0d8ccedd5c0445), UINT64_C(0xe0d5fc06ed698d3c), + UINT64_C(0xaf85882d2576a038), UINT64_C(0xd55df8e515432941), + UINT64_C(0x5a3569bd451db2ca), UINT64_C(0x20ed197575283bb3), + UINT64_C(0xc49581ead523e8c2), UINT64_C(0xbe4df122e51661bb), + UINT64_C(0x3125607ab548fa30), UINT64_C(0x4bfd10b2857d7349), + UINT64_C(0x04ad64994d625e4d), UINT64_C(0x7e7514517d57d734), + UINT64_C(0xf11d85092d094cbf), UINT64_C(0x8bc5f5c11d3cc5c6), + UINT64_C(0x12b5926535897936), UINT64_C(0x686de2ad05bcf04f), + UINT64_C(0xe70573f555e26bc4), UINT64_C(0x9ddd033d65d7e2bd), + UINT64_C(0xd28d7716adc8cfb9), UINT64_C(0xa85507de9dfd46c0), + UINT64_C(0x273d9686cda3dd4b), UINT64_C(0x5de5e64efd965432), + UINT64_C(0xb99d7ed15d9d8743), UINT64_C(0xc3450e196da80e3a), + UINT64_C(0x4c2d9f413df695b1), UINT64_C(0x36f5ef890dc31cc8), + UINT64_C(0x79a59ba2c5dc31cc), UINT64_C(0x037deb6af5e9b8b5), + UINT64_C(0x8c157a32a5b7233e), UINT64_C(0xf6cd0afa9582aa47), + UINT64_C(0x4ad64994d625e4da), UINT64_C(0x300e395ce6106da3), + UINT64_C(0xbf66a804b64ef628), UINT64_C(0xc5bed8cc867b7f51), + UINT64_C(0x8aeeace74e645255), UINT64_C(0xf036dc2f7e51db2c), + UINT64_C(0x7f5e4d772e0f40a7), UINT64_C(0x05863dbf1e3ac9de), + UINT64_C(0xe1fea520be311aaf), UINT64_C(0x9b26d5e88e0493d6), + UINT64_C(0x144e44b0de5a085d), UINT64_C(0x6e963478ee6f8124), + UINT64_C(0x21c640532670ac20), UINT64_C(0x5b1e309b16452559), + UINT64_C(0xd476a1c3461bbed2), UINT64_C(0xaeaed10b762e37ab), + UINT64_C(0x37deb6af5e9b8b5b), UINT64_C(0x4d06c6676eae0222), + UINT64_C(0xc26e573f3ef099a9), UINT64_C(0xb8b627f70ec510d0), + UINT64_C(0xf7e653dcc6da3dd4), UINT64_C(0x8d3e2314f6efb4ad), + UINT64_C(0x0256b24ca6b12f26), UINT64_C(0x788ec2849684a65f), + UINT64_C(0x9cf65a1b368f752e), UINT64_C(0xe62e2ad306bafc57), + UINT64_C(0x6946bb8b56e467dc), UINT64_C(0x139ecb4366d1eea5), + UINT64_C(0x5ccebf68aecec3a1), UINT64_C(0x2616cfa09efb4ad8), + UINT64_C(0xa97e5ef8cea5d153), UINT64_C(0xd3a62e30fe90582a), + UINT64_C(0xb0c7b7e3c7593bd8), UINT64_C(0xca1fc72bf76cb2a1), + UINT64_C(0x45775673a732292a), UINT64_C(0x3faf26bb9707a053), + UINT64_C(0x70ff52905f188d57), UINT64_C(0x0a2722586f2d042e), + UINT64_C(0x854fb3003f739fa5), UINT64_C(0xff97c3c80f4616dc), + UINT64_C(0x1bef5b57af4dc5ad), UINT64_C(0x61372b9f9f784cd4), + UINT64_C(0xee5fbac7cf26d75f), UINT64_C(0x9487ca0fff135e26), + UINT64_C(0xdbd7be24370c7322), UINT64_C(0xa10fceec0739fa5b), + UINT64_C(0x2e675fb4576761d0), UINT64_C(0x54bf2f7c6752e8a9), + UINT64_C(0xcdcf48d84fe75459), UINT64_C(0xb71738107fd2dd20), + UINT64_C(0x387fa9482f8c46ab), UINT64_C(0x42a7d9801fb9cfd2), + UINT64_C(0x0df7adabd7a6e2d6), UINT64_C(0x772fdd63e7936baf), + UINT64_C(0xf8474c3bb7cdf024), UINT64_C(0x829f3cf387f8795d), + UINT64_C(0x66e7a46c27f3aa2c), UINT64_C(0x1c3fd4a417c62355), + UINT64_C(0x935745fc4798b8de), UINT64_C(0xe98f353477ad31a7), + UINT64_C(0xa6df411fbfb21ca3), UINT64_C(0xdc0731d78f8795da), + UINT64_C(0x536fa08fdfd90e51), UINT64_C(0x29b7d047efec8728), +}; + +uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l) { + uint64_t j; + + for (j = 0; j < l; j++) { + uint8_t byte = s[j]; + crc = crc64_tab[(uint8_t)crc ^ byte] ^ (crc >> 8); + } + return crc; +} + +/* Test main */ +#ifdef TEST_MAIN +#include <stdio.h> +int main(void) { + printf("e9c6d914c4b8d9ca == %016llx\n", + (unsigned long long) crc64(0,(unsigned char*)"123456789",9)); + return 0; +} +#endif diff --git a/src/crc64/crc64.h b/src/crc64/crc64.h new file mode 100755 index 0000000..e031e76 --- /dev/null +++ b/src/crc64/crc64.h @@ -0,0 +1,11 @@ +#ifndef CRC64_H +#define CRC64_H + +// Header file created because of project necessity +// Source for c file: https://github.com/srned/baselib/blob/master/crc64.c + +#include <inttypes.h> + +uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l); + +#endif diff --git a/src/dir/dir.c b/src/dir/dir.c new file mode 100755 index 0000000..6c1f32d --- /dev/null +++ b/src/dir/dir.c @@ -0,0 +1,72 @@ +#include "dir.h" + + +uint64_t get_fd_size(int fd){ + struct stat st; + if(fstat(fd, &st) == 0){ + return st.st_size; + } + return 0; +} + +uint64_t get_fp_size(FILE *fp){ + if(fp != NULL){ + return get_fd_size(fileno(fp)); + } + return 0; +} + +uint64_t get_file_size(char *filename){ + struct stat st; + if(stat(filename, &st) == 0){ + return st.st_size; + } + return 0; +} + +struct str get_file_format(struct str filename){ + int i = 0; + while(filename.len-i > 0){ + if(filename.ptr[filename.len-i-1] == '.') break; + i++; + } + if(i == 0 || i == filename.len){ + return ((struct str){0}); + } + struct str fmt; + fmt.len = i; + fmt.ptr = calloc(fmt.len+1, sizeof(char)); + if(fmt.ptr == NULL) return ((struct str){0}); + memcpy(fmt.ptr, filename.ptr+filename.len-i, fmt.len); + return fmt; +} + +uint64_t getNEntries(const char *dir){ + uint64_t r = 0; + DIR *d = opendir(dir); + if(d){ + seekdir(d, (unsigned)-1); + r = telldir(d) - 2; // . and .. + closedir(d); + } + return r; +} + +char **getFiles(const char *dir){ + /*int i = 0, n = getNEntries(dir); + if(n > 0){ + char **r = calloc(n, sizeof(char*)); + DIR *d = opendir(dir); + readdir(d); readdir(d); // . and .. + struct dirent *t; + while((t = readdir(d)) != NULL){ + int l = len(t->d_name); + r[i] = calloc(l+1, sizeof(char)); + memmove(r[i++], t->d_name, l); + } + closedir(d); + return r; + }*/ + return NULL; +} + diff --git a/src/dir/dir.h b/src/dir/dir.h new file mode 100755 index 0000000..75ec02c --- /dev/null +++ b/src/dir/dir.h @@ -0,0 +1,36 @@ +#ifndef DIR_H +#define DIR_H + +#ifndef _LARGEFILE_SOURCE +#define _LARGEFILE_SOURCE +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <string.h> +#include <inttypes.h> +#include <dirent.h> +#include <sys/stat.h> +#include "str/str.h" + + +struct file { + struct str name; + bool temp; +}; + + +uint64_t get_fd_size(int fd); + +uint64_t get_fp_size(FILE *fp); + +uint64_t get_file_size(char *filename); + +struct str get_file_format(struct str filename); + +uint64_t getNEntries(const char *dir); + +char **getFiles(const char *dir); + +#endif diff --git a/src/ipc/ipc.c b/src/ipc/ipc.c new file mode 100755 index 0000000..98ffe6e --- /dev/null +++ b/src/ipc/ipc.c @@ -0,0 +1,143 @@ +#include "ipc.h" + + +ipc_sender *setup_ipc_sender(struct str addr, int backlog){ + ipc_sender *is = calloc(1, sizeof(ipc_sender)); + is->addr = dup_str(addr); + is->ssocket = socket(AF_UNIX, SOCK_STREAM, 0); + if(is->ssocket == -1){ + log_error("%s: socket: %s", __FUNCTION__, strerror(errno)); + goto error; + } + struct sockaddr_un sockaddr = { .sun_family = AF_UNIX }; + memcpy(sockaddr.sun_path, is->addr.ptr, is->addr.len); + sockaddr.sun_path[is->addr.len] = '\0'; + unlink(is->addr.ptr); + if(bind(is->ssocket, (struct sockaddr*)&sockaddr, sizeof(sockaddr)) == -1){ + log_error("%s: bind: %s", __FUNCTION__, strerror(errno)); + goto error; + } + if(listen(is->ssocket, backlog) == -1){ + log_error("%s: listen: %s", __FUNCTION__, strerror(errno)); + goto error; + } + + if(0){ +error: + destroy_ipc_sender(&is); + } + return is; +} + +void destroy_ipc_sender(ipc_sender **is){ + if(*is != NULL){ + close((*is)->ssocket); + (*is)->ssocket = -1; + unlink((*is)->addr.ptr); + free_str(&(*is)->addr); + free(*is); + *is = NULL; + } +} + +ipc_listener *setup_ipc_listener(struct str saddr){ + ipc_listener *il = calloc(1, sizeof(ipc_listener)); + il->saddr = dup_str(saddr); + il->csocket = socket(AF_UNIX, SOCK_STREAM, 0); + if(il->csocket == -1){ + log_error("%s: socket: %s", __FUNCTION__, strerror(errno)); + goto error; + } + struct sockaddr_un socksaddr = { .sun_family = AF_UNIX }; + memcpy(socksaddr.sun_path, il->saddr.ptr, il->saddr.len); + socksaddr.sun_path[il->saddr.len] = '\0'; + if(connect(il->csocket, (struct sockaddr *)&socksaddr, sizeof(socksaddr)) == -1){ + log_error("%s: connect: %s", __FUNCTION__, strerror(errno)); + goto error; + } + + if(0){ +error: + destroy_ipc_listener(&il); + } + return il; +} + +void destroy_ipc_listener(ipc_listener **il){ + if(*il != NULL){ + close((*il)->csocket); + (*il)->csocket = -1; + free_str(&(*il)->saddr); + free(*il); + *il = NULL; + } +} + +void free_ipc_message(ipc_message im){ + free_str(&im.key); + free_str(&im.val); +} + +static inline struct str ipc_message_to_str(ipc_message msg){ + struct str smsg = nstr(msg.key.len + msg.val.len + 2*sizeof(u32)); + memcpy(smsg.ptr+smsg.len, &msg.key.len, sizeof(msg.key.len)); + smsg.len += sizeof(msg.key.len); + copy_str(smsg, msg.key); + memcpy(smsg.ptr+smsg.len, &msg.val.len, sizeof(msg.val.len)); + smsg.len += sizeof(msg.val.len); + copy_str(smsg, msg.val); + return smsg; +} + +int send_ipc_message(int to, ipc_message msg){ + struct str smsg = ipc_message_to_str(msg); + if(send(to, smsg.ptr, smsg.len, 0) == -1){ + log_error("cant send message to socket %d: %s", to, strerror(errno)); + free_str(&smsg); + return 1; + } + free_str(&smsg); + char buf[2]; + if(recv(to, buf, 2, 0) == -1){ + log_error("receiving OK from listener"); + return 1; + } + if(strncmp(buf, "OK", 2) != 0){ + log_error("received '%s' from listener instead of 'OK'", buf); + return 1; + } + return 0; +} + +static inline ipc_message str_to_ipc_message(struct str smsg){ + struct ipc_message msg; + u32 l; + memcpy(&l, smsg.ptr, sizeof(l)); + smsg.ptr += sizeof(l); + msg.key = nstr(l); + msg.key.len = l; + memcpy(msg.key.ptr, smsg.ptr, l); + smsg.ptr += l; + memcpy(&l, smsg.ptr, sizeof(l)); + smsg.ptr += sizeof(l); + msg.val = nstr(l); + msg.val.len = l; + memcpy(msg.val.ptr, smsg.ptr, l); + return msg; +} + +ipc_message receive_ipc_message(ipc_listener *il){ + struct str smsg = nstr(MAX_IPC_MSG_LEN); // we are gonna have to poll btw + ipc_message msg = {0}; + smsg.len = recv(il->csocket, smsg.ptr, smsg.cap, 0); + if(smsg.len == -1){ + log_error("cant receive message from socket %d: %s", il->csocket, strerror(errno)); + }else{ + msg = str_to_ipc_message(smsg); + } + if(send(il->csocket, "OK", slen("OK"), 0) == -1){ + log_error("sending 'OK' to sender"); + } + return msg; +} + diff --git a/src/ipc/ipc.h b/src/ipc/ipc.h new file mode 100755 index 0000000..e9b5c25 --- /dev/null +++ b/src/ipc/ipc.h @@ -0,0 +1,42 @@ +#ifndef IPC_H +#define IPC_H + +#include <stdio.h> +#include <stdlib.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include "str/str.h" +#include "log/log.h" +#include "types/types.h" + + +typedef struct ipc_sender { + struct str addr; + int ssocket; +} ipc_sender; + +typedef struct ipc_listener { + struct str saddr; + int csocket; +} ipc_listener; + +#define MAX_IPC_MSG_LEN 1024 +typedef struct ipc_message { + struct str key; + struct str val; +} ipc_message; + +ipc_sender *setup_ipc_sender(struct str addr, int backlog); +void destroy_ipc_sender(ipc_sender **is); + +ipc_listener *setup_ipc_listener(struct str saddr); +void destroy_ipc_listener(ipc_listener **il); + +void free_ipc_message(ipc_message im); +int send_ipc_message(int to, ipc_message msg); +ipc_message receive_ipc_message(ipc_listener *il); + +#endif diff --git a/src/main.c b/src/main.c new file mode 100755 index 0000000..65367d6 --- /dev/null +++ b/src/main.c @@ -0,0 +1,196 @@ +#include <stdio.h> +#include <stdlib.h> +#include <sys/wait.h> +#include <time.h> +#include "str/str.h" +#include "list/list.h" +#include "net/net.h" +#include "log/log.h" +#include "ipc/ipc.h" + +#define BACKLOG 15 + +struct str ipcaddr = sstr(".ipcserver"); +struct str port; +http_server *server; +ipc_sender *sender; +struct worker { + pid_t pid; + int wsocket; +} *workers; + + +void worker_undertaker(int sig, siginfo_t *info, void *ucontext){ + if(sig == SIGCHLD && list_entry_exists(workers, info->si_pid)){ + list_remove_entry(workers, info->si_pid); + log_warn("worker %d is dead. now workers size is %d", info->si_pid, list_size(workers)); + } +} + +int init(char *argv[]){ + port = snstr(argv[1], len(argv[1])); // error check + if(port.len <= 0 || port.len > 5){ + log_error("wrong server port: '%.*s'", port.len, port.ptr); + return 1; + } + // decouple so the whole net.c doesnt get linked? + server = setup_http_server(port, BACKLOG); + if(server == NULL){ + log_error("Setting up socket server"); + return 1; + } + // configurable name? + sender = setup_ipc_sender(ipcaddr, BACKLOG); + if(sender == NULL){ + log_error("setting up ipc sender"); + return 1; + } + init_list(workers); + struct sigaction chld = { .sa_sigaction = worker_undertaker, .sa_flags = SA_SIGINFO }; + if(sigaction(SIGCHLD, &chld, NULL) == -1){ + log_error("init: SIGCHLD: %s", strerror(errno)); + return 1; + } + return 0; +} + +void deinit(void){ + destroy_http_server(&server); + destroy_ipc_sender(&sender); + list_free(workers); +} + +void print_usage(void){ + printf("server [port]\n"); +} + +void show_commands(void){ + printf( + "(case insensitive)\n" + "f: fork\n" + "s: signal\n" + "l: list\n" + "c: clear\n" + "[0-9]: turn off ssl for worker\n" + "h: help\n" + "q: quit\n" + ); +} + +int main(int argc, char *argv[]){ + + if(argc < 2){ + print_usage(); + return 1; + } + + int return_value = 0; + + if(init(argv) != 0){ + return_value = 1; + goto DEINIT; + } + log_info("Config done"); + +#ifdef SHOW_IP + system("curl -s http://ipinfo.io/ip && echo"); +#endif + +/* + sqlite3 *db = setupDatabase("src/db/db.db"); + if(db == NULL){ + fprintf(stderr, "error setting up database\n"); + return 1; + } + + if(getNEntries("archive") != getCount(db, FILE_TABLE)){ + int n = getNEntries("archive"); + char **entries = getFiles("archive"); // getFiles deprecated btw + for(int i = 0; i < n; ++i){ + insertName(db, FILE_TABLE, entries[i]); + } + printf("%d, %d\n", getNEntries("archive"), getCount(db, FILE_TABLE)); + } +*/ + + + printf("press h for help\n"); + bool end = false; + while(!end){ + char c = getchar(); + switch(c){ + case 'f': case 'F': + pid_t nw = fork(); + if(nw == 0){ + char *args[] = {"./worker.exe", ipcaddr.ptr, NULL}; + execv("./worker.exe", args); + log_error("Cannot exec worker: %s", strerror(errno)); + return 1; + } + struct worker w = { .pid = nw, .wsocket = accept(sender->ssocket, NULL, NULL) }; + list_push(workers, w); + ipc_message msg = { .key = sstr("socket"), .val = utostr(server->ssocket, 10) }; + send_ipc_message(w.wsocket, msg); + free_ipc_message(msg); + msg = (ipc_message){ .key = sstr("rewrites"), .val = sstr("urirewrites") }; + send_ipc_message(w.wsocket, msg); + break; + case 's': case 'S': + kill(0, SIGUSR1); + break; + case 'l': case 'L': + printf("|-%3d workers working for us rn-|\n", list_size(workers)); + char *faces[] = { + "(^__^)", "(·__·)", "(>__>)", "(~ _~)", "(T__T)", "(º__º)" + }; + for(int i = 0; i < list_size(workers); i++){ + int index = rand()%sizeof(faces)/sizeof(faces[0]); + printf("| %d %s\t\t\t|\n", workers[i].pid, faces[index]); + } + printf("|-------------------------------|\n"); + break; + case 'c': case 'C': + system("clear"); + break; + case 'h': case 'H': + show_commands(); + break; + case 'q': case 'Q': + while(list_size(workers) > 0){ + kill(workers[0].pid, SIGKILL); // redo this PLEASE + waitpid(workers[0].pid, NULL, 0); + } + while(wait(NULL) > 0); + close(server->ssocket); + end = true; + log_info("%d children remaining alive (lie)\n", list_size(workers)); + break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + if(list_size(workers) > c-'0'){ + log_info("signaling worker[%d] %d to turn off ssl\n", c-'0', workers[c-'0'].pid); + sigqueue(workers[c-'0'].pid, SIGRTMIN, (union sigval){.sival_int = 0}); + } + break; + } + } + +DEINIT: + deinit(); + + return return_value; +} + +// inspiration: https://www.youtube.com/watch?v=cEH_ipqHbUw (https://github.com/infraredCoding/cerveur) +// thanks to +/* + https://beej.us/guide/bgnet/ + https://dev.to/lloyds-digital/how-you-can-host-websites-from-home-4pke + https://developer.mozilla.org/en-US/docs/Web/HTTP/Messages + https://www.favicon.cc + https://www.cssscript.com/demo/animated-snowfall-effect/ + https://www.cssscript.com/demo/animated-snowfall-effect/PureSnow.js + https://www.tutorialspoint.com/http/http_requests.htm + https://codepen.io/afarrar/pen/JRaEjP +*/ + diff --git a/src/net/net.c b/src/net/net.c new file mode 100755 index 0000000..30793de --- /dev/null +++ b/src/net/net.c @@ -0,0 +1,641 @@ +#include "net.h" + + +struct str response_headers[] = { + sstr("Content-Length"), + sstr("Content-Type"), + sstr("Transfer-Encoding"), +}; + +struct { + struct str fmt; + struct str type; +} mime_types[] = { + {.fmt = sstr("avif"), .type = sstr("image/avif")}, + {.fmt = sstr("bmp"), .type = sstr("image/bmp")}, + {.fmt = sstr("css"), .type = sstr("text/css")}, + {.fmt = sstr("csv"), .type = sstr("text/csv")}, + {.fmt = sstr("gz"), .type = sstr("application/gzip")}, + {.fmt = sstr("gif"), .type = sstr("image/gif")}, + {.fmt = sstr("html"), .type = sstr("text/html")}, + {.fmt = sstr("ico"), .type = sstr("image/vnd.microsoft.icon")}, + {.fmt = sstr("jpg"), .type = sstr("image/jpeg")}, + {.fmt = sstr("jpeg"), .type = sstr("image/jpeg")}, + {.fmt = sstr("js"), .type = sstr("text/javascript")}, + {.fmt = sstr("json"), .type = sstr("application/json")}, + {.fmt = sstr("midi"), .type = sstr("audio/midi")}, + {.fmt = sstr("mp3"), .type = sstr("audio/mpeg")}, + {.fmt = sstr("mp4"), .type = sstr("video/mp4")}, + {.fmt = sstr("mpeg"), .type = sstr("video/mpeg")}, + {.fmt = sstr("png"), .type = sstr("image/png")}, + {.fmt = sstr("pdf"), .type = sstr("application/pdf")}, + {.fmt = sstr("php"), .type = sstr("application/x-httpd-php")}, + {.fmt = sstr("rar"), .type = sstr("application/vnd.rar")}, + {.fmt = sstr("tiff"), .type = sstr("image/tiff")}, + {.fmt = sstr("ts"), .type = sstr("video/mp2t")}, + {.fmt = sstr("txt"), .type = sstr("text/plain")}, + {.fmt = sstr("wav"), .type = sstr("audio/wav")}, + {.fmt = sstr("weba"), .type = sstr("audio/webm")}, + {.fmt = sstr("webm"), .type = sstr("video/webm")}, + {.fmt = sstr("webp"), .type = sstr("image/webp")}, + {.fmt = sstr("xml"), .type = sstr("application/xml")}, + {.fmt = sstr("zip"), .type = sstr("application/zip")}, + {.fmt = sstr("7z"), .type = sstr("application/x-7z-compressed")}, +}; + +LIST(struct uri_mod) uri_rewrites = NULL; + +static int pleasesslgivemetheerror(int ssl_get_error){ + char *error; + switch(ssl_get_error){ + case SSL_ERROR_NONE: error = "SSL_ERROR_NONE"; break; + case SSL_ERROR_ZERO_RETURN: error = "SSL_ERROR_ZERO_RETURN"; break; + case SSL_ERROR_WANT_READ: error = "SSL_ERROR_WANT_READ"; break; + case SSL_ERROR_WANT_WRITE: error = "SSL_ERROR_WANT_WRITE"; break; + case SSL_ERROR_WANT_CONNECT: error = "SSL_ERROR_WANT_CONNECT"; break; + case SSL_ERROR_WANT_ACCEPT: error = "SSL_ERROR_WANT_ACCEPT"; break; + case SSL_ERROR_WANT_X509_LOOKUP: error = "SSL_ERROR_WANT_X509_LOOKUP"; break; + case SSL_ERROR_WANT_ASYNC: error = "SSL_ERROR_WANT_ASYNC"; break; + case SSL_ERROR_WANT_ASYNC_JOB: error = "SSL_ERROR_WANT_ASYNC_JOB"; break; + case SSL_ERROR_WANT_CLIENT_HELLO_CB: error = "SSL_ERROR_WANT_CLIENT_HELLO_CB"; break; + case SSL_ERROR_SYSCALL: error = "SSL_ERROR_SYSCALL"; break; + case SSL_ERROR_SSL: error = "SSL_ERROR_SSL"; break; + } + int err = ERR_get_error(); + log_error("%s (%d): %s", error, err, ERR_error_string(err, NULL)); + return ssl_get_error; +} + +http_server *setup_http_server(struct str port, int backlog){ + http_server *hs = calloc(1, sizeof(http_server)); + hs->port = dup_str(port); + hs->backlog = backlog; + hs->ssocket = -1; + + int ec, val = 1; + struct addrinfo *res, hints = { .ai_family = AF_INET, .ai_socktype = SOCK_STREAM, .ai_flags = AI_PASSIVE }; + + if((ec = getaddrinfo(NULL, hs->port.ptr, &hints, &res)) != 0){ + log_error("getaddrinfo: %s", gai_strerror(ec)); + goto error; + } + if((hs->ssocket = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) == -1){ + log_error("socket: %s", strerror(errno)); + goto error; + } + + if(setsockopt(hs->ssocket, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val))){ + log_error("server: SO_REUSEADDR: %s", strerror(errno)); + goto error; + } + + if(bind(hs->ssocket, res->ai_addr, res->ai_addrlen) == -1){ + log_error("server: bind: %s", strerror(errno)); + goto error; + } + + if(listen(hs->ssocket, backlog) == -1){ + log_error("server: listen: %s", strerror(errno)); + goto error; + } + + if(0){ +error: + destroy_http_server(&hs); + } + freeaddrinfo(res); + + return hs; +} + +void destroy_http_server(http_server **hs){ + if(*hs != NULL){ + free_str(&(*hs)->port); + (*hs)->backlog = 0; + close((*hs)->ssocket); + free(*hs); + *hs = NULL; + } +} + +http_worker *setup_http_worker(int ssocket, int secure, struct str certfile, struct str keyfile){ + http_worker *hw = calloc(1, sizeof(http_worker)); + hw->ssocket = ssocket; + hw->csocket = -1; + hw->secure = secure; + hw->receive = recv; + hw->ssl_receive = SSL_read; + + SSL_library_init(); + hw->ssl_ctx = SSL_CTX_new(TLS_server_method()); + // need to compile openssl with ktls on for this (v) to work + SSL_CTX_set_options(hw->ssl_ctx, SSL_OP_ENABLE_KTLS); + if(hw->ssl_ctx == NULL){ + goto error; + } + if(SSL_CTX_use_certificate_file(hw->ssl_ctx, certfile.ptr, SSL_FILETYPE_PEM) <= 0){ + log_error("worker_cert"); + goto error; + } + if(SSL_CTX_use_PrivateKey_file(hw->ssl_ctx, keyfile.ptr, SSL_FILETYPE_PEM) <= 0 ){ + log_error("worker: key"); + goto error; + } + hw->ssl = SSL_new(hw->ssl_ctx); + SSL_set_accept_state(hw->ssl); + if(hw->ssl == NULL){ + goto error; + } + + if(0){ +error: + destroy_http_worker(&hw); + } + + return hw; +} + +void destroy_http_worker(http_worker **hw){ + if(*hw != NULL){ + (*hw)->ssocket = -1; + close((*hw)->csocket); + (*hw)->secure = 0; + SSL_free((*hw)->ssl); + SSL_CTX_free((*hw)->ssl_ctx); + (*hw)->receive = NULL; + (*hw)->ssl_receive = NULL; + free(*hw); + *hw = NULL; + } +} + +void reset_worker_ssl(http_worker *hw){ + close(hw->csocket); + SSL_free(hw->ssl); + hw->ssl = SSL_new(hw->ssl_ctx); + SSL_set_accept_state(hw->ssl); +} + +int accept_connection(http_worker *hw, char ip[INET_ADDRSTRLEN]){ + struct sockaddr_storage caddr; + int casize = sizeof(caddr); + log_info("waiting..."); + if((hw->csocket = accept(hw->ssocket, (struct sockaddr *)&caddr, (socklen_t*)&casize)) == -1){ + log_error("accept_socket() -> accept(): %s", strerror(errno)); + return -1; + } + log_info("accepted"); + if(hw->secure){ + int err = SSL_set_fd(hw->ssl, hw->csocket); + if(err != 1){ + log_error("setting fd %d", hw->csocket); + return pleasesslgivemetheerror(SSL_get_error(hw->ssl, err)); + } + if((err = SSL_accept(hw->ssl)) != 1){ + log_error("couldnt accept"); + return pleasesslgivemetheerror(SSL_get_error(hw->ssl, err)); + } + } + inet_ntop(caddr.ss_family, &(((struct sockaddr_in*)&caddr)->sin_addr), ip, INET_ADDRSTRLEN); + return 0; +} + +static inline int worker_read(http_worker *hw, struct str *buf){ + return hw->secure ? + SSL_read(hw->ssl, buf->ptr+buf->len, buf->cap-buf->len) : + recv(hw->csocket, buf->ptr+buf->len, buf->cap-buf->len, 0); +} + +int receive_request(http_worker *hw, struct str *request){ + // for some reason SSL_has_pending can return 0 but we can still read data + struct pollfd pfd[1] = { {.fd = hw->csocket, .events = POLLIN } }; + while((hw->secure && SSL_has_pending(hw->ssl)) || poll(pfd, 1, 0)){ + int new = worker_read(hw, request); + if(new < 0 || (hw->secure && new == 0)){ + int error = new; + if(hw->secure) error = pleasesslgivemetheerror(SSL_get_error(hw->ssl, new)); + else log_error("http (no s) error: %s", strerror(errno)); + return error; + } + request->len += new; + if(request->len == request->cap){ + log_info("gotta resize buffer"); + if(resize_str(request, request->cap*2) != 0){ + log_error("Not enough memory in reply str"); + return -1; + } + } + } + return 0; +} + +struct file generate_resource(struct uri resource, struct str url){ + struct file file = {0}; + /* + generate if both of these are true + 1) no file specified (aka we need index.html) + 2) theres an index.html.php + 3) index.html isnt cached (future) + */ + struct str phpfile = nstr(resource.path.len + slen(".php")); + copy_strs(phpfile, resource.path, sstr(".php")); + if(access(phpfile.ptr, F_OK) == 0){ + // we need a str_copy or something + struct str command = nstr( + slen("REQUEST_URI='") + resource.path.len + slen("' QUERY_STRING='") + resource.query.len + + slen("' URL='") + url.len + + slen("' php -c ./ ") + phpfile.len + slen(" > ") + resource.path.len + ); + copy_strs(command, + sstr("REQUEST_URI='"), resource.path, sstr("' QUERY_STRING='"), resource.query, + sstr("' URL='"), url, + sstr("' php -c ./ "), phpfile, sstr(" > "), resource.path + ); + log_warn("command: %s", command.ptr); + system(command.ptr); + free_str(&command); + } + free_str(&phpfile); + file.name = dup_str(resource.path); +/* + if(uri.query.len > 0){ + if(access(uri.path.ptr, F_OK) != 0){ + file.name.ptr = calloc(slen("localc/404/index.html")+1, sizeof(char)); + copy_str(file.name, sstr("localc/404/index.html")); + return file; + } + + struct str pid = utostr(getpid(), 10); + file.name.ptr = calloc(uri.path.len + pid.len + 1, sizeof(char)); + copy_strs(file.name, uri.path, pid); + + struct str command = {0}; + command.ptr = calloc( + slen("REQUEST_URI='") + uri.path.len + slen("' REQUEST_QUERY='") + uri.query.len + + slen("' php -c ./ ") + uri.path.len + slen(" > ") + file.name.len, + sizeof(char) + ); + copy_strs(command, + sstr("REQUEST_URI='"), uri.path, sstr("' REQUEST_QUERY='"), uri.query, + sstr("' php -c ./ "), uri.path, sstr(" > "), file.name + ); + log_warn("command: %s", command.ptr); + system(command.ptr); + file.temp = true; + + free_str(&pid); + free_str(&command); + }else{ + file.name.ptr = calloc(uri.path.len+1, sizeof(char)); + copy_strs(file.name, uri.path); + }*/ + + return file; +} + +// TODO: REVISE; return 201/204 +char *handlePOST(char *request/*struct str uri, struct str body*/){ +/* + char *resource = malloc(1), *uri = getURI(request), *params = getPOSTParams(request); + + if(isPost(uri)){ + rlen = attachBlock(&resource, rlen, HOMEFOLDER, strlen(HOMEFOLDER)); + rlen = attachBlock(&resource, rlen, uri, ulen); + if(uri[ulen-1] != '/'){ + rlen = attachBlock(&resource, rlen, "/", len("/")); + } + generatePagefile(resource, rlen, uri, ulen, params, prlen); + + rlen = attachBlock(&resource, rlen, PAGEFILE, len(PAGEFILE)); + } + resource[rlen] = '\0'; + free(uri); + free(params); + return resource; +*/ + return NULL; +} + +static uint32_t request_line_len(char *request){ + uint32_t len = 0; + while(request[len] && request[len] != '\r') len++; + return len; +} + +// why is this done like this?? +void build_http_message(char *request, int rlen, struct http_message *hm){ + char *end = request + rlen; + memset(hm, 0, sizeof(struct http_message)); + + hm->method.ptr = request; + while(request < end && *request != ' ') hm->method.len++, request++; + if(++request >= end) return; + + hm->uri.ptr = request; + while(request < end && *request != ' ') hm->uri.len++, request++; + if(++request >= end) return; + + hm->req_ver.ptr = request; + while(request < end && request[hm->req_ver.len] != '\r') hm->req_ver.len++; + request += hm->req_ver.len + 2; + if(request > end) return; + + uint32_t llen = 0; + while((llen = request_line_len(request)) != 0){ + hm->headers[hm->hlen].name.ptr = request; + while( + hm->headers[hm->hlen].name.len < llen && + request[hm->headers[hm->hlen].name.len] != ':' + ) hm->headers[hm->hlen].name.len++; + + request += hm->headers[hm->hlen].name.len + 2; + if(request > end) return; + hm->headers[hm->hlen].value.ptr = request; + hm->headers[hm->hlen].value.len = llen - hm->headers[hm->hlen].name.len - 2; + request += hm->headers[hm->hlen].value.len + 2; + hm->hlen++; + if(request > end) return; + } + request += 2; + if(request > end) return; + + hm->body.ptr = request; + while(request < end && request[hm->body.len] != '\0') hm->body.len++; +} + +// TODO: check if endianness affects this +__attribute__ ((optimize(3))) enum http_method get_http_method(struct str method){ + uint64_t m; + memmove(&m, method.ptr, sizeof(uint64_t)); + if(((m & 0x0000000000FFFFFF) - 0x0000000000544547) == 0){ + return GET; + }else if(((m & 0x00000000FFFFFFFF) - 0x0000000054534F50) == 0){ + return POST; + }else if(((m & 0x0000000000FFFFFF) - 0x0000000000545550) == 0){ + return PUT; + }else if(((m & 0x0000FFFFFFFFFFFF) - 0x00004554454C4544) == 0){ + return DELETE; + } + return GET; +} + +static uint64_t http_len(struct http_message *hm){ + uint64_t len = 0; + len += hm->method.len + hm->uri.len + hm->req_ver.len + 5 + 2; + for(int i = 0; i < hm->hlen; ++i){ + len += hm->headers[i].name.len + hm->headers[i].value.len + 4; + } + return len; +} + +/*static struct str assemble_with_body(struct http_message *hm, FILE *body, uint64_t len){ + struct str s = {0}; + s.ptr = calloc(http_len(hm) + len + 1, sizeof(char)); + copy_strs(s, hm->method, sstr(" "), hm->uri, sstr(" "), hm->req_ver, sstr("\r\n")); + + for(int i = 0; i < hm->hlen; i++){ + copy_strs(s, hm->headers[i].name, sstr(": "), hm->headers[i].value, sstr("\r\n")); + } + copy_str(s, sstr("\r\n")); + + fread(s.ptr+s.len, sizeof(char), len, body); + s.len += len; + //copy_str(s, sstr("\r\n")); + + return s; +}*/ + +static struct str http_header_to_str(struct http_message *hm){ + struct str s = {0}; + s.ptr = calloc(http_len(hm) + 1, sizeof(char)); + copy_strs(s, hm->method, sstr(" "), hm->uri, sstr(" "), hm->req_ver, sstr("\r\n")); + for(int i = 0; i < hm->hlen; i++){ + copy_strs(s, hm->headers[i].name, sstr(": "), hm->headers[i].value, sstr("\r\n")); + } + copy_str(s, sstr("\r\n")); + return s; +} + +/* + Given two strings p and u, and a uri (a struct with two strings) o + where p is a pattern, u is a url that is gonna checked for the pattern + and o is the uri output blueprint that must be returned should u follow p + + There is a tokens array where tokens found in the url can be stored (up to 9 tokens) + + In p: + <X> optionally matches a character X and stores it in the tokens array + ^ optionally matches any character and stores it in the tokens array + * optionally matches a string of character until another match is found + or the url to match ends, and stores it in the tokens array + + In o: + $1 through $9 reference the tokens in the token array (I could make it 10 tokens tbh) + Both the strings in the uri can access these tokens + Referencing a token writes it to the output uri + If theres a token before a <X>, say $3<a> that means that the character + between the less than and greater than signs will only be written + to the output if the token number 3 in the array exists (has length > 0) + All other characters get outputted normally +*/ +static struct uri uri_rewrite(struct str p, struct str u, struct uri o){ + int i = 0, j = 0, in = 0, ti = 0; + struct str tokens[9] = {0}; + for(; j < p.len; i += 1){ + if(i < u.len && p.ptr[j+in] == '<' && p.ptr[j+in+1] == u.ptr[i]){ + if(ti < 9) tokens[ti++] = (struct str){.ptr = u.ptr+i, .len = 1}; + j += 3+in, in = 0; + }else if(i < u.len && (p.ptr[j+in] == '^' || p.ptr[j+in] == u.ptr[i])){ + if(p.ptr[j] == '^' && ti < 9) tokens[ti++] = (struct str){.ptr = u.ptr+i, .len = 1}; + j += 1+in, in = 0; + }else if(i < u.len && p.ptr[j] == '*'){ + if(!in){ + if(ti < 9) tokens[ti++] = (struct str){.ptr = u.ptr+i, .len = 0}; + in = 1; + } + if(ti-in < 9) tokens[ti-in].len++; + }else{ + if(i >= u.len && p.ptr[j] == '*') j++; + else if(i >= u.len && p.ptr[j] == '<') j += 3; + else return (struct uri){0}; + } + } + if(i < u.len) return (struct uri){0}; + + struct uri r = {0}; + struct str *no = &o.path, *nr = &r.path; + for(int k = 0, rlen = 0; k < 2; k++, no = &o.query, nr = &r.query){ + for(int i = 0; i < no->len; i++, rlen++){ + if(no->ptr[i] == '$') rlen += tokens[no->ptr[++i]-'1'].len-1; + } + nr->ptr = calloc(rlen+1, sizeof(char)); + if(nr->ptr == NULL){ + if(r.path.ptr != NULL) free(r.path.ptr); + return (struct uri){0}; + } + + for(int i = 0; i < no->len; i++){ + if(no->ptr[i] == '$'){ + if(++i+1 < no->len && no->ptr[i+1] == '<'){ + if(tokens[no->ptr[i]-'1'].len > 0) i++; + else while(no->ptr[++i] != '>'); + }else{ + copy_str((*nr), tokens[no->ptr[i]-'1']); + } + }else{ + if(no->ptr[i] != '>') nr->ptr[nr->len++] = no->ptr[i]; + } + } + } + + return r; +} + +struct uri sanitize_uri(struct str uri){ + struct str suri = {.ptr = calloc(uri.len+1, sizeof(char)), .len = 0}; + if(suri.ptr == NULL) return (struct uri){0}; + int i = 0, o = 0; + while(i+o < uri.len){ + suri.ptr[i] = lowerchar(uri.ptr[i+o]); + if(i > 0 && ( + (uri.ptr[i+o] == '/' && uri.ptr[i+o-1] == '/') || + (uri.ptr[i+o] == '.' && uri.ptr[i+o-1] == '.') || + (uri.ptr[i+o] == '/' && i+o+1 == uri.len) || + uri.ptr[i+o] == '%') + ){ + ++o; + }else{ + ++i; + } + } + suri.ptr[suri.len = i] = '\0'; + + struct uri u = {0}; + for(int i = 0; i < list_size(uri_rewrites); i++){ + u = uri_rewrite(uri_rewrites[i].pattern, suri, uri_rewrites[i].output); + if(u.path.len > 0) break; + } + free_str(&suri); + if(u.path.len == 0){ + u.path = str("localc/404/index.html"); + free_str(&u.query); + } + return u; +} + +static inline int worker_write(http_worker *hw, struct str buf){ + return hw->secure ? + SSL_write(hw->ssl, buf.ptr, buf.len) : + send(hw->csocket, buf.ptr, buf.len, 0); +} + +// use sendfile(2). reason: +// "sendfile() copies data between one file descriptor and another. +// Because this copying is done within the kernel, sendfile() is +// more efficient than the combination of read(2) and write(2), +// which would require transferring data to and from user space." +void send_file(http_worker *hw, struct str filename){ + log_info("requested '%.*s' -> ", filename.len, filename.ptr); + uint64_t fsize = get_file_size(filename.ptr); + if(fsize == 0){ + // we should free it or something here idk maybe not + filename = sstr("localc/404/index.html"); + fsize = get_file_size(filename.ptr); + } + log_info("sending '%.*s'", filename.len, filename.ptr); + + enum mime_type type = TXT; + struct str fmt = get_file_format(filename); + for(int i = 0; i < sizeof(mime_types)/sizeof(mime_types[0]); i++){ + if(strncmp(fmt.ptr, mime_types[i].fmt.ptr, fmt.len) == 0){ + type = i; + break; + } + } + free_str(&fmt); + + struct http_message hm = { + .resp_ver = sstr("HTTP/1.1"), .status = sstr("200"), .reason = sstr("OK"), + .hlen = 2, .headers = { + { .name = response_headers[CONTENT_TYPE], .value = mime_types[type].type }, + { .name = response_headers[CONTENT_LENGTH], .value = utostr(fsize, 10) } + }, + .body = {0}, + }; + + struct str header = http_header_to_str(&hm); + free_str(&hm.headers[1].value); + int sent = worker_write(hw, header); + if(sent != header.len){ + if(hw->secure){ + pleasesslgivemetheerror(SSL_get_error(hw->ssl, sent)); + }else{ + log_error("send_file: %s", strerror(errno)); + } + } + free_str(&header); + + // make sure system is 64bits before compiling with O_LARGEFILE + // use fopen maybe? + int fd = open(filename.ptr, O_RDONLY | /*O_LARGEFILE |*/ O_NONBLOCK); + while(fsize > 0){ + // CYGWIN DOESNT HAVE SENDFILE OR SPLICE FOR + // SOME REASON WHEN THIS IS A REAL PROGRAM + // RUNNING ON LINUX USE SENDFILE (<sys/sendfile.h>) + // AND COMPILE OPENSSL TO USE KTLS + // sent = using_ssl ? + // SSL_sendfile(ssl, fd, 0, fsize, 0) : + // sendfile(socket, fd, NULL, fsize); + + // we're ignoring MAX_BODY_SIZE + struct str fuckcygwinineedsendfile; + fd_to_str(&fuckcygwinineedsendfile, fd, fsize); + sent = worker_write(hw, fuckcygwinineedsendfile); + free_str(&fuckcygwinineedsendfile); + + if(sent > 0){ fsize -= sent; + }else if(sent == 0){ break; + }else{ + if(hw->secure){ + pleasesslgivemetheerror(SSL_get_error(hw->ssl, sent)); + }else{ + perror("send_file (fuck cygwin btw)"); + } + } + log_info("sent %d more bytes", sent); + } + close(fd); +} + +// this shouldnt be here +int read_uri_rewrites(char *map, uint64_t size){ + uint64_t i = 0; + if(strncmp(map, "rewrites", slen("rewrites")) != 0) return 1; + i += slen("rewrites"); + while(charisspace(map[i])) i++; + if(map[i++] != '{') return 1; + list_free(uri_rewrites); + init_nlist(uri_rewrites); + while(i < size && map[i] != '}'){ + struct uri_mod um = {0}; + i += ({sread_delim_f(map+i, charisspace, false);}).len; + i += ({um.pattern = read_delim_f(map+i, charisspace, true);}).len; + i += ({sread_delim_f(map+i, charisblank, false);}).len; + i += ({um.output.path = read_delim_f(map+i, charisspace, true);}).len; + i += ({sread_delim_f(map+i, charisblank, false);}).len; + i += ({um.output.query = read_delim_f(map+i, charisspace, true);}).len; + i += ({sread_delim(map+i, '\n');}).len + 1; + list_push(uri_rewrites, um); + } + return 0; +} + +void free_uri_rewrites(void){ + for(uint32_t i = 0; i < list_size(uri_rewrites); i++){ + free_str(&uri_rewrites[i].pattern); + free_str(&uri_rewrites[i].output.path); + free_str(&uri_rewrites[i].output.query); + } + list_free(uri_rewrites); +} + diff --git a/src/net/net.h b/src/net/net.h new file mode 100755 index 0000000..d7459da --- /dev/null +++ b/src/net/net.h @@ -0,0 +1,144 @@ +#ifndef NET_H +#define NET_H + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +//#include <sys/socket.h> +#include <netdb.h> +#include <arpa/inet.h> +//#include <netinet/in.h> +#include <ifaddrs.h> +#include <unistd.h> +#include <signal.h> +#include <stdbool.h> +#include <inttypes.h> +#include <ctype.h> +#include <time.h> +#include "str/str.h" +#include "list/list.h" +#include "dir/dir.h" +#include "log/log.h" +#include <openssl/ssl.h> +#include <openssl/err.h> +#include <poll.h> +#include <fcntl.h> +#ifdef __linux__ +#include <sys/sendfile.h> +#endif + + +enum http_method { + GET, + POST, + PUT, + DELETE +}; + +enum response_header { + CONTENT_LENGTH, + CONTENT_TYPE, + TRANSFER_ENCODING, +}; + +enum mime_type { + AVIF, BMP, CSS, CSV, GZ, GIF, HTML, + ICO, JPG, JPEG, JS, JSON, MIDI, MP3, + MP4, MPEG, PNG, PDF, PHP, RAR, TIFF, TS, + TXT, WAV, WEBA, WEBM, WEBP, XML, ZIP, + _7Z, +}; + + +struct uri { + struct str path; + struct str query; +}; + +struct uri_mod { + struct str pattern; + struct uri output; +}; + +struct header { + struct str name; + struct str value; +}; + +#define MAX_HEADERS 16 + + +struct http_message { + union { + struct str method; + struct str resp_ver; + }; + union { + struct str uri; + struct str status; + }; + union { + struct str req_ver; + struct str reason; + }; + int hlen; + struct header headers[MAX_HEADERS]; + struct str body; +}; + +typedef struct http_server { + struct str port; + int backlog; + int ssocket; +} http_server; + +typedef ssize_t (*recv_func)(int,void*,size_t,int); +typedef int (*ssl_recv_func)(SSL*,void*,int); +typedef struct http_worker { + int ssocket; + int csocket; + int secure; + SSL_CTX *ssl_ctx; + SSL *ssl; + recv_func receive; + ssl_recv_func ssl_receive; +} http_worker; + +#define MAX_RESPONSE_SIZE 0x0FFFFFFF +#define MAX_BODY_SIZE (MAX_RESPONSE_SIZE - 0x0FFF) + +#define insert_header(hm, h) \ + (hm).headers[(hm).hlen++] = (h) + + +http_server *setup_http_server(struct str port, int backlog); + +void destroy_http_server(http_server **hs); + +http_worker *setup_http_worker(int ssocket, int secure, struct str certfile, struct str keyfile); + +void destroy_http_worker(http_worker **hw); + +void reset_worker_ssl(http_worker *hw); + +int accept_connection(http_worker *hw, char ip[INET_ADDRSTRLEN]); + +int receive_request(http_worker *hw, struct str *request); + +struct file generate_resource(struct uri resource, struct str url); + +char *handlePOST(char *request); + +void build_http_message(char *request, int len, struct http_message *hm); + +enum http_method get_http_method(struct str method); + +struct uri sanitize_uri(struct str uri); + +void send_file(http_worker *hw, struct str filename); + +int read_uri_rewrites(char *map, uint64_t size); + +void free_uri_rewrites(void); + +#endif diff --git a/src/util/util.h b/src/util/util.h new file mode 100755 index 0000000..6f447e6 --- /dev/null +++ b/src/util/util.h @@ -0,0 +1,13 @@ +#ifndef UTIL_H +#define UTIL_H + +#define ARRAY_LEN(first, ...) \ + (sizeof(((typeof(first) []){first, __VA_ARGS__}))/(sizeof(typeof(first)))) + +#define bool_equal(a,b) ({ \ + typeof(a) _a = (a); \ + typeof(b) _b = (b); \ + ((_a) ? (_b) : (!(_b))); \ +}) + +#endif diff --git a/src/worker.c b/src/worker.c new file mode 100755 index 0000000..0b4cb07 --- /dev/null +++ b/src/worker.c @@ -0,0 +1,154 @@ +#include <stdio.h> +#include <stdlib.h> +#include <sys/mman.h> +#include "str/str.h" +#include "ipc/ipc.h" +#include "net/net.h" + + +ipc_listener *listener; +http_worker *worker; +struct str rewritesfile; + + +int init(char *argv[]){ + // replace signals with unix sockets + // reinit + // finish + // toggle ssl + //log_set_level(LOG_DEBUG, 0); + //log_set_level(LOG_INFO, 0); + //log_set_level(LOG_WARN, 0); + + struct str saddr = str(argv[1]); + listener = setup_ipc_listener(saddr); + free_str(&saddr); + if(listener == NULL){ + log_error("cant set up ipc listener on worker %d", getpid()); + return 1; + } + + // check key for value maybe idk + ipc_message msg = receive_ipc_message(listener); + int ssocket = strtou(msg.val); + free_ipc_message(msg); // v configurable certificate locations? + worker = setup_http_worker(ssocket, 1, sstr("ssl/cert.pem"), sstr("ssl/key.pem")); + if(worker == NULL){ + log_error("setting up http worker"); + return 1; + } + // this is disgusting and should be done elsewhere + msg = receive_ipc_message(listener); // check for value + rewritesfile = dup_str(msg.val); + free_ipc_message(msg); + int fsize = get_file_size(rewritesfile.ptr); + int fd = open(rewritesfile.ptr, O_RDONLY | O_NONBLOCK); + char *rewrites = mmap(NULL, fsize, PROT_READ, MAP_SHARED, fd, 0); + if(rewrites == (void*)-1){ + log_error("cant mmap rewrites: %s", strerror(errno)); + return 1; + } + if(read_uri_rewrites(rewrites, fsize) != 0){ + log_error("init: read_uri_rewrites: %s", strerror(errno)); + return 1; + } + munmap(rewritesfile.ptr, fsize); + + return 0; +} + +void deinit(void){ + destroy_ipc_listener(&listener); + destroy_http_worker(&worker); + free_str(&rewritesfile); + free_uri_rewrites(); +} + + +int main(int argc, char **argv){ + + int return_value = 0; + + if(init(argv) != 0){ + return_value = 1; + goto DEINIT; + } + log_info("init'd"); + + bool end = false; + struct str request = {.cap = 8192, .len = 0, .ptr = alloca(8192)}; + while(!end){ + char cip[INET_ADDRSTRLEN] = {0}; + return_value = accept_connection(worker, cip); + switch(return_value){ + case -1: // couldnt accept, do something ig + continue; + case SSL_ERROR_SSL: + reset_worker_ssl(worker); + log_info("continuing\n"); + continue; + } + log_info("socket %d accepted with ip %s", worker->csocket, cip); + return_value = receive_request(worker, &request); + switch(return_value){ + case -1: // couldnt accept, do something ig + continue; + case SSL_ERROR_SSL: + reset_worker_ssl(worker); + log_info("continuing\n"); + continue; + } + + printf("'%.*s'\n", request.len, request.ptr); + + struct http_message hm = {0}; + build_http_message(request.ptr, request.len, &hm); + log_info("uri before: %.*s", hm.uri.len, hm.uri.ptr); + struct uri suri = sanitize_uri(hm.uri); + log_info("uri after: %.*s", suri.path.len, suri.path.ptr); + enum http_method method = get_http_method(hm.method); + + switch(method){ + case GET: + struct file resource = generate_resource(suri, hm.uri); + send_file(worker, resource.name); + //if(resource.temp == true) remove(resource.name.ptr); + free_str(&resource.name); + break; + case POST: + //handlePOST(request); + send(worker->csocket, "HTTP/1.1 201 Created\r\n\r\n", len("HTTP/1.1 201 Created\r\n\r\n"), 0); + break; + case PUT: + break; + case DELETE: + break; + } + free_str(&suri.path); + free_str(&suri.query); + request.len = 0; + + SSL_clear(worker->ssl); + close(worker->csocket); + + //SSL_shutdown(config.ssl); // look into SSL_clear() + } + +DEINIT: + log_info("dieing :("); + deinit(); + + return return_value; +} + +/* +struct timespec t_start, t_end; + +#define start_timing() clock_gettime(CLOCK_MONOTONIC, &t_start) +#define end_timing() clock_gettime(CLOCK_MONOTONIC, &t_end) + +double get_timing(void){ + uint64_t diff = (t_end.tv_sec*1000000000 + t_end.tv_nsec) - (t_start.tv_sec*1000000000 + t_start.tv_nsec); + return diff/1000000000.0; +} +*/