├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── config.c ├── config.h ├── docroot ├── index.gmi ├── spec.txt └── test.jpg ├── example.conf ├── file.c ├── file.h ├── gemini.c ├── gemini.h ├── geminid-chroot.service ├── geminid.service ├── lexurl.l ├── log.c ├── log.h ├── main.c ├── mime.c ├── mime.h ├── parseurl.c ├── tls.c ├── tls.h ├── url.c ├── url.h ├── util.c ├── util.h ├── vhost.c └── vhost.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | 54 | cert.pem 55 | key.pem 56 | docroot/ 57 | Makefile.local 58 | magic.mgc 59 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020, J. von Rotz 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020, 2021, 2022 J. von Rotz 2 | # 3 | # Redistribution and use in source and binary forms, with or without 4 | # modification, are permitted provided that the following conditions are met: 5 | # 6 | # 1. Redistributions of source code must retain the above copyright notice, 7 | # this list of conditions and the following disclaimer. 8 | # 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # 3. Neither the name of the copyright holder nor the names of its 14 | # contributors may be used to endorse or promote products derived 15 | # from this software without specific prior written permission. 16 | # 17 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 20 | # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 21 | # OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 | # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 25 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | # 29 | 30 | CC=/usr/bin/cc 31 | LEX=/usr/bin/lex 32 | LDFLAGS=-lconfig -lmagic -lssl -lcrypto 33 | CFLAGS=-Wall -Wextra -pedantic 34 | DEBUGFLAGS=-g -DGEMINID_DEBUG 35 | TLSFLAGS= # Use -DTLS_USE_V1_2_METHOD for older versions of OpenSSL, you can use Makefile.local for that 36 | OBJ=main.o tls.o gemini.o util.o mime.o file.o log.o url.o lexurl.o config.o vhost.o 37 | PREFIX=/usr/local 38 | CONFDIR=/etc/geminid 39 | DATADIR=/srv/geminid 40 | LOGDIR=/var/log/geminid 41 | RUNUSER=gemini 42 | RUNGROUP=gemini 43 | INCDIRS= 44 | LIBDIRS= 45 | 46 | -include Makefile.local 47 | 48 | all: geminid 49 | 50 | geminid: $(OBJ) 51 | $(CC) $(CFLAGS) $(LIBDIRS) $(DEBUGFLAGS) -o geminid $(LDFLAGS) $(OBJ) 52 | 53 | parseurl: parseurl.c url.o lexurl.o 54 | $(CC) $(CFLAGS) $(LIBDIRS) $(DEBUGFLAGS) -o parseurl url.o lexurl.o parseurl.c 55 | 56 | tls.o: tls.c 57 | $(CC) $(CFLAGS) $(INCDIRS) $(DEBUGFLAGS) $(TLSFLAGS) -c tls.c 58 | 59 | %.o: %.c 60 | $(CC) $(CFLAGS) $(INCDIRS) $(DEBUGFLAGS) -o $@ -c $< 61 | 62 | lexurl.c: lexurl.l 63 | $(LEX) -o lexurl.c lexurl.l 64 | 65 | cert: 66 | openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes # nodes: No DES, do not prompt for pass 67 | 68 | clean: 69 | rm -f *.o *.core lexurl.c geminid parseurl 70 | 71 | veryclean: clean 72 | rm -f *.pem *.key *.mgc 73 | 74 | install: 75 | install -d -g $(RUNGROUP) -o $(RUNUSER) -m 0755 $(DATADIR) $(LOGDIR) $(CONFDIR) 76 | install -d -g $(RUNGROUP) -o $(RUNUSER) -m 0755 $(CONFDIR)/certs $(CONFDIR)/keys 77 | install -g $(RUNGROUP) -o $(RUNUSER) -m 0744 geminid $(PREFIX)/bin 78 | install -g $(RUNGROUP) -o $(RUNUSER) -m 0644 example.conf $(CONFDIR) 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # geminid 2 | A Gemini Server in C. Please note that this is not production-ready code. 3 | The current state is a result of a few hours of hacking, barely able to even 4 | serve content. But the goal is to have a compliant gemini server written in C. 5 | 6 | ## Prerequisites 7 | A Unix-like or POSIX-compliant OS is required. OpenSSL 1.1.1 is recommended. If you build with an earlier version, I assume it is one which at least supports TLS 1.2. You need to define `TLS_USE_V1_2_METHOD` if you want to use TLS 1.2. If you want to use even older versions, you need to modify the source code in tls.c to use the appropriate version-specific method. OpenSSL 3.0 has been tested successfully. 8 | 9 | I've added a more sophisticated URL parser which is based on lex/flex, so you need that, too. As of 2020-05-20 there is a new configuration file introduced which requires libconfig as a dependency. 10 | 11 | ## Building 12 | Edit Makefile and gemini.h to your needs, do `make geminid`. There is a test program for the URL parser, which can be built with `make parseurl`. If you have non-standard include or library paths, you can use `INCDIRS` and `LIBDIRS`, respectively. You are advised to include local modifications in a separate file Makefile.local, which will be included if it exists. 13 | 14 | So, if you are on BSD, you might want to do 15 | ``` 16 | cat > Makefile.local < Makefile.local <`: Path to the configuration file 45 | - `-t`: Test and print configuration 46 | 47 | ## Complaining 48 | To vent your anger, you may reach me at jr at vrtz dot ch. 49 | 50 | You can find a demonstration of it running at gemini://gemini.uxq.ch/ and some more information on how I run it at gemini://gemini.uxq.ch/running.gmi; However, please be aware that this machine will go offline on 2022-05-30, since I'm switching jobs and therefore will not have access to a machine with gratis internet feed. 51 | -------------------------------------------------------------------------------- /config.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2020 J. von Rotz 2 | * 3 | * Redistribution and use in source and binary forms, with or without 4 | * modification, are permitted provided that the following conditions are met: 5 | * 6 | * 1. Redistributions of source code must retain the above copyright notice, 7 | * this list of conditions and the following disclaimer. 8 | * 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * 13 | * 3. Neither the name of the copyright holder nor the names of its 14 | * contributors may be used to endorse or promote products derived 15 | * from this software without specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 20 | * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 21 | * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 25 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | #include 31 | #include 32 | #ifndef __linux__ 33 | #include 34 | #endif /* ! __linux__ */ 35 | #include 36 | #include "config.h" 37 | #include 38 | 39 | GLOBALCONF *new_globalconf(config_t *cfg) { 40 | config_setting_t *setting; 41 | GLOBALCONF *global; 42 | 43 | global = malloc(sizeof(*global)); 44 | 45 | if(global == NULL) { 46 | perror("malloc"); 47 | return NULL; 48 | } 49 | 50 | *global = (GLOBALCONF){ 51 | .serverroot = "/srv/geminid", 52 | .logdir = "/var/log/geminid", 53 | .port = 1965, 54 | .loglocaltime = true, 55 | .logtimeformat = "[%d/%b/%Y:%H:%M:%S %z]", 56 | .ipv6_enable = true, 57 | }; 58 | 59 | setting = config_lookup(cfg, "global"); 60 | if (setting != NULL) { 61 | int aux; 62 | 63 | (void)config_setting_lookup_string(setting, "serverroot", &global->serverroot); 64 | (void)config_setting_lookup_string(setting, "logdir", &global->logdir); 65 | 66 | if (config_setting_lookup_int(setting, "port", &aux)) { 67 | if (aux < 0 || aux > SHRT_MAX) 68 | fprintf(stderr, "global.port: Invalid port number %d\n", aux); 69 | else 70 | global->port = aux; 71 | } 72 | 73 | if (config_setting_lookup_bool(setting, "loglocaltime", &aux)) 74 | global->loglocaltime = !!aux; 75 | 76 | (void)config_setting_lookup_string(setting, "logtimeformat", &global->logtimeformat); 77 | 78 | if (config_setting_lookup_bool(setting, "ipv6_enable", &aux)) 79 | global->ipv6_enable = !!aux; 80 | } 81 | 82 | return global; 83 | } 84 | 85 | VHOSTLIST *new_vhostlist(config_t *cfg) { 86 | config_setting_t *setting; 87 | VHOSTLIST *vhostlist; 88 | VHOSTCONF *firstvhost; 89 | unsigned int i; 90 | 91 | vhostlist = calloc(1, sizeof(*vhostlist)); 92 | setting = config_lookup(cfg, "vhost"); 93 | if(setting != NULL) { 94 | vhostlist->count = config_setting_length(setting); 95 | vhostlist->vhost = calloc(vhostlist->count, sizeof(VHOSTCONF)); 96 | firstvhost = vhostlist->vhost; 97 | 98 | for(i = 0; i < vhostlist->count; ++i) { 99 | config_setting_t *vhost = config_setting_get_elem(setting, i); 100 | 101 | if(!(config_setting_lookup_string(vhost, "name", &(vhostlist->vhost->name)) 102 | && config_setting_lookup_string(vhost, "docroot", &(vhostlist->vhost->docroot)) 103 | && config_setting_lookup_string(vhost, "accesslog", &(vhostlist->vhost->accesslog)) 104 | && config_setting_lookup_string(vhost, "errorlog", &(vhostlist->vhost->errorlog)) 105 | && config_setting_lookup_string(vhost, "cert", &(vhostlist->vhost->cert)) 106 | && config_setting_lookup_string(vhost, "key", &(vhostlist->vhost->key)) 107 | && config_setting_lookup_string(vhost, "index", &(vhostlist->vhost->index)))) { 108 | fprintf(stderr, "Failed parsing config\n"); 109 | return NULL; 110 | } 111 | 112 | vhostlist->vhost++; 113 | } 114 | vhostlist->vhost = firstvhost; 115 | } else { 116 | fprintf(stderr, "no vhost definitions found.\n"); 117 | free(vhostlist); 118 | return NULL; 119 | } 120 | 121 | 122 | return vhostlist; 123 | } 124 | 125 | int init_geminid_config(const char *configpath, config_t *cfg, GLOBALCONF **global, VHOSTLIST **vhostlist) { 126 | config_init(cfg); 127 | if(! config_read_file(cfg, configpath)) { 128 | fprintf(stderr, "%s:%d - %s\n", config_error_file(cfg), 129 | config_error_line(cfg), config_error_text(cfg)); 130 | return -1; 131 | } 132 | 133 | *global = new_globalconf(cfg); 134 | *vhostlist = new_vhostlist(cfg); 135 | 136 | if(*global != NULL && *vhostlist != NULL) 137 | return 0; 138 | 139 | free(*global); 140 | free(*vhostlist); 141 | return -1; 142 | } 143 | 144 | 145 | int testprintconfig(const char *configpath) { 146 | GLOBALCONF *global; 147 | VHOSTLIST *vhostlist; 148 | config_t cfg; 149 | 150 | if (init_geminid_config(configpath, &cfg, &global, &vhostlist) < 0) { 151 | config_destroy(&cfg); 152 | exit(EXIT_FAILURE); 153 | } 154 | 155 | fputs("Global config:\n-------------\n", stderr); 156 | fprintf(stderr, "serverroot: %s\n", global->serverroot); 157 | fprintf(stderr, "logdir: %s\n", global->logdir); 158 | fprintf(stderr, "logtimeformat: %s\n", global->logtimeformat); 159 | fprintf(stderr, "loglocaltime: %s\n", global->loglocaltime ? "true" : "false"); 160 | fprintf(stderr, "ipv6_enable: %s\n", global->ipv6_enable ? "true" : "false"); 161 | fprintf(stderr, "port: %hu\n", global->port); 162 | fputc('\n', stderr); 163 | 164 | for(unsigned i = 0; i < vhostlist->count; i++) { 165 | const VHOSTCONF *vhost; 166 | 167 | vhost = &vhostlist->vhost[i]; 168 | 169 | fprintf(stderr, "vHost %u:\n------\n", i); 170 | fprintf(stderr, "name: %s\n", vhost->name); 171 | fprintf(stderr, "docroot: %s\n", vhost->docroot); 172 | fprintf(stderr, "accesslog: %s\n", vhost->accesslog); 173 | fprintf(stderr, "errorlog: %s\n", vhost->errorlog); 174 | fprintf(stderr, "cert: %s\n", vhost->cert); 175 | fprintf(stderr, "key: %s\n", vhost->key); 176 | fprintf(stderr, "index: %s\n", vhost->index); 177 | fputc('\n', stderr); 178 | } 179 | 180 | free(global); 181 | free(vhostlist->vhost); 182 | free(vhostlist); 183 | config_destroy(&cfg); 184 | return(EXIT_SUCCESS); 185 | } 186 | -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2020 J. von Rotz 2 | * 3 | * Redistribution and use in source and binary forms, with or without 4 | * modification, are permitted provided that the following conditions are met: 5 | * 6 | * 1. Redistributions of source code must retain the above copyright notice, 7 | * this list of conditions and the following disclaimer. 8 | * 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * 13 | * 3. Neither the name of the copyright holder nor the names of its 14 | * contributors may be used to endorse or promote products derived 15 | * from this software without specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 20 | * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 21 | * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 25 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | #include 31 | #include 32 | 33 | typedef struct { 34 | const char *serverroot; 35 | const char *logdir; 36 | const char *logtimeformat; 37 | bool loglocaltime; 38 | bool ipv6_enable; 39 | unsigned short port; 40 | } GLOBALCONF; 41 | 42 | typedef struct { 43 | const char *name; 44 | const char *docroot; 45 | const char *accesslog; 46 | const char *errorlog; 47 | const char *cert; 48 | const char *key; 49 | const char *index; 50 | } VHOSTCONF; 51 | 52 | typedef struct { 53 | unsigned int count; 54 | VHOSTCONF *vhost; 55 | } VHOSTLIST; 56 | 57 | int init_geminid_config(const char *configpath, config_t *cfg, GLOBALCONF **global, VHOSTLIST **vhostlist); 58 | int testprintconfig(const char *configpath); 59 | -------------------------------------------------------------------------------- /docroot/index.gmi: -------------------------------------------------------------------------------- 1 | # It works! 2 | 3 | In an Apache spirit, I pronounce this gemini server running. 4 | 5 | 6 | ## Stuff to browse 7 | => spec.txt Specification hosted on this machine 8 | => gemini://gemini.circumlunar.space Cradle of Gemini 9 | => test.jpg Have a picture! 10 | 11 | ## What's left to say 12 | Have a lot of fun. 13 | -------------------------------------------------------------------------------- /docroot/spec.txt: -------------------------------------------------------------------------------- 1 | ----------------------------- 2 | Project Gemini 3 | "Speculative specification" 4 | v0.11.0, March 1st 2020 5 | ----------------------------- 6 | 7 | This is an increasingly less rough sketch of an actual spec for 8 | Project Gemini. Although not finalised yet, further changes to the 9 | specification are likely to be relatively small. You can write code 10 | to this pseudo-specification and be confident that it probably won't 11 | become totally non-functional due to massive changes next week, but 12 | you are still urged to keep an eye on ongoing development of the 13 | protocol and make changes as required. 14 | 15 | This is provided mostly so that people can quickly get up to speed on 16 | what I'm thinking without having to read lots and lots of old phlog 17 | posts and keep notes. 18 | 19 | Feedback on any part of this is extremely welcome, please email 20 | solderpunk@sdf.org. 21 | 22 | ----------------------------- 23 | 24 | 1. Overview 25 | 26 | Gemini is a client-server protocol featuring request-response 27 | transactions, broadly similar to gopher or HTTP. Connections are 28 | closed at the end of a single transaction and cannot be reused. When 29 | Gemini is served over TCP/IP, servers should listen on port 1965 (the 30 | first manned Gemini mission, Gemini 3, flew in March '65). This is an 31 | unprivileged port, so it's very easy to run a server as a "nobody" 32 | user, even if e.g. the server is written in Go and so can't drop 33 | privileges in the traditional fashion. 34 | 35 | 1.1 Gemini transactions 36 | 37 | There is one kind of Gemini transaction, roughly equivalent to a 38 | gopher request or a HTTP "GET" request. Transactions happen as 39 | follows: 40 | 41 | C: Opens connection 42 | S: Accepts connection 43 | C/S: Complete TLS handshake (see 1.4) 44 | C: Validates server certificate (see 1.4.2) 45 | C: Sends request (one CRLF terminated line) (see 1.2) 46 | S: Sends response header (one CRFL terminated line), closes connection 47 | under non-success conditions (see 1.3.1, 1.3.2) 48 | S: Sends response body (text or binary data) (see 1.3.3) 49 | S: Closes connection 50 | C: Handles response (see 1.3.4) 51 | 52 | 1.2 Gemini requests 53 | 54 | Gemini requests are a single CRLF-terminated line with the following 55 | structure: 56 | 57 | 58 | 59 | is a UTF-8 encoded absolute URL, of maximum length 1024 bytes. 60 | If the scheme of the URL is not specified, a scheme of gemini:// is 61 | implied. 62 | 63 | Sending an absolute URL instead of only a path or selector is 64 | effectively equivalent to building in a HTTP "Host" header. It 65 | permits virtual hosting of multiple Gemini domains on the same IP 66 | address. It also allows servers to optionally act as proxies. 67 | Including schemes other than gemini:// in requests allows servers to 68 | optionally act as protocol-translating gateways to e.g. fetch gopher 69 | resources over Gemini. Proxying is optional and the vast majority of 70 | servers are expected to only respond to requests for resources at 71 | their own domain(s). 72 | 73 | 1.3 Responses 74 | 75 | Gemini response consist of a single CRLF-terminated header line, 76 | optionally followed by a response body. 77 | 78 | 1.3.1 Response headers 79 | 80 | Gemini response headers look like this: 81 | 82 | 83 | 84 | is a two-digit numeric status code, as described below in 85 | 1.3.2 and in Appendix 1. 86 | 87 | is any non-zero number of consecutive spaces or tabs. 88 | 89 | is a UTF-8 encoded string of maximum length 1024, whose meaning 90 | is dependent. 91 | 92 | If does not belong to the "SUCCESS" range of codes, then the 93 | server MUST close the connection after sending the header and MUST NOT 94 | send a response body. 95 | 96 | If a server sends a which is not a two-digit number or a 97 | which exceeds 1024, the client SHOULD close the connection and 98 | disregard the response header, informing the user of an error. 99 | 100 | 1.3.2 Status codes 101 | 102 | Gemini uses two-digit numeric status codes. Related status codes share 103 | the same first digit. Importantly, the first digit of Gemini status 104 | codes do not group codes into vague categories like "client error" and 105 | "server error" as per HTTP. Instead, the first digit alone provides 106 | enough information for a client to determine how to handle the 107 | response. By design, it is possible to write a simple but feature 108 | complete client which only looks at the first digit. The second digit 109 | provides more fine-grained information, for unambiguous server logging, 110 | to allow writing comfier interactive clients which provide a slightly 111 | more streamlined user interface, and to allow writing more robust and 112 | intelligent automated clients like content aggregators, search engine 113 | crawlers, etc. 114 | 115 | The first digit of a response code unambiguously places the response 116 | into one of six categories, which define the semantics of the 117 | line. 118 | 119 | 1 INPUT 120 | 121 | The requested resource accepts a line of textual user input. 122 | The line is a prompt which should be displayed to the 123 | user. The same resource should then be requested again with 124 | the user's input included as a query component. Queries are 125 | included in requests as per the usual generic URL definition 126 | in RFC3986, i.e. separated from the path by a ?. There is no 127 | response body. 128 | 129 | 2 SUCCESS 130 | 131 | The request was handled successfully and a response body will 132 | follow the response header. The line is a MIME media 133 | type which applies to the response body. 134 | 135 | 3 REDIRECT 136 | 137 | The server is redirecting the client to a new location for the 138 | requested resource. There is no response body. The header 139 | text is a new URL for the requested resource. The URL may be 140 | absolute or relative. The redirect should be considered 141 | temporary, i.e. clients should continue to request the 142 | resource at the original address and should not performance 143 | convenience actions like automatically updating bookmarks. 144 | There is no response body. 145 | 146 | 4 TEMPORARY FAILURE 147 | 148 | The request has failed. There is no response body. The 149 | nature of the failure is temporary, i.e. an identical request 150 | MAY succeed in the future. The contents of may provide 151 | additional information on the failure, and should be displayed 152 | to human users. 153 | 154 | 5 PERMANENT FAILURE 155 | 156 | The request has failed. There is no response body. The 157 | nature of the failure is permanent, i.e. identical future 158 | requests will reliably fail for the same reason. The contents 159 | of may provide additional information on the failure, 160 | and should be displayed to human users. Automatic clients 161 | such as aggregators or indexing crawlers should should not 162 | repeat this request. 163 | 164 | 6 CLIENT CERTIFICATE REQUIRED 165 | 166 | The requested resource requires client-certificate 167 | authentication to access. If the request was made without a 168 | certificate, it should be repeated with one. If the request 169 | was made with a certificate, the server did not accept it and 170 | the request should be repeated with a different certificate. 171 | The contents of may provide additional information on 172 | certificate requirements or the reason a certificate was 173 | rejected. 174 | 175 | Note that for basic interactive clients for human use, errors 4 and 5 176 | may be effectively handled identically, by simply displaying the 177 | contents of under a heading of "ERROR". The 178 | temporary/permanent error distinction is primarily relevant to 179 | well-behaving automated clients. Basic clients may also choose not to 180 | support client-certificate authentication, in which case only four 181 | distinct status handling routines are required (for statuses beginning 182 | with 1, 2, 3 or a combined 4-or-5). 183 | 184 | The full two-digit system is detailed in Appendix 1. Note that for 185 | each of the six valid first digits, a code with a second digit of zero 186 | corresponds is a generic status of that kind with no special 187 | semantics. This means that basic servers without any advanced 188 | functionality need only be able to return codes of 10, 20, 30, 40 or 189 | 50. 190 | 191 | The Gemini status code system has been carefully designed so that the 192 | increased power (and correspondingly increased complexity) of the 193 | second digits is entirely "opt-in" on the part of both servers and 194 | clients. 195 | 196 | 1.3.3 Response bodies 197 | 198 | Response bodies are just raw content, text or binary, ala gopher. 199 | There is no support for compression, chunking or any other kind of 200 | content or transfer encoding. The server closes the connection after 201 | the final byte, there is no "end of response" signal like gopher's 202 | lonely dot. 203 | 204 | Response bodies only accompany responses whose header indicates a 205 | SUCCESS status (i.e. a status code whose first digit is 2). For such 206 | responses, is a MIME media type as defined in RFC 2046. 207 | 208 | If a MIME type begins with "text/" and no charset is explicitly given, 209 | the charset should be assumed to be UTF-8. Compliant clients MUST 210 | support UTF-8-encoded text/* responses. Clients MAY optionally 211 | support other encodings. Clients receiving a response in a charset 212 | they cannot decode SHOULD gracefully inform the user what happened 213 | instead of displaying garbage. 214 | 215 | If is an empty string, the MIME type MUST default to 216 | "text/gemini; charset=utf-8". 217 | 218 | 1.3.4 Response body handling 219 | 220 | Response handling by clients should be informed by the provided MIME 221 | type information. Gemini defines one MIME type of its own 222 | (text/gemini) whose handling is discussed below in 1.3.5. In all 223 | other cases, clients should do "something sensible" based on the MIME 224 | type. Minimalistic clients might adopt a strategy of printing all 225 | other text/* responses to the screen without formatting and saving 226 | all non-text responses to the disk. Clients for unix systems may 227 | consult /etc/mailcap to find installed programs for handling non-text 228 | types. 229 | 230 | 1.3.5 text/gemini responses 231 | 232 | 1.3.5.1 Overview 233 | 234 | In the same sense that HTML is the "native" response format of HTTP 235 | and plain text is the native response format of gopher, Gemini defines 236 | its own native response format - though of course, thanks to the 237 | inclusion of a MIME type in the response header Gemini can be used to 238 | serve plain text, rich text, HTML, Markdown, LaTeX, etc. 239 | 240 | Response bodies of type "text/gemini" are a kind of lightweight 241 | hypertext format, which takes inspiration from gophermaps and from 242 | Markdown. The format permits richer typographic possibilities than 243 | the plain text of Gopher, but remains extremely easy to parse. The 244 | format is line-oriented, and a satisfactory rendering can be achieved 245 | with a single pass of a document, processing each line independently. 246 | As per gopher, links can only be displayed one per line, encouraging 247 | neat, list-like structure. 248 | 249 | Similar to how the two-digit Gemini status codes were designed so that 250 | simple clients can function correctly while ignoring the second digit, 251 | the text/gemini format has been designed so that simple clients can 252 | ignore the more advanced features and still remain very usable. 253 | 254 | 1.3.5.2 Line-orientation 255 | 256 | As mentioned, the text/gemini format is line-oriented. Each line of a 257 | text/gemini document has a single "line type". It is possible to 258 | unambiguously determine a line's type purely by inspecting its first 259 | three characters. A line's type determines the manner in which it 260 | should be presented to the user. Any details of presentation or 261 | rendering associated with a particular line type are strictly limited 262 | in scope to that individual line. 263 | 264 | There are 6 different line types in total. However, a fully 265 | functional and specification compliant Gemini client need only 266 | recognise and handle 4 of them - these are the "core line types", 267 | (see 1.3.5.3). Advanced clients can also handle the additional 268 | "advanced line types" (see 1.3.5.4). Simple clients can treat all 269 | advanced line types as one of the core line types and still offer an 270 | adequate user experience. 271 | 272 | 1.3.5.3 Core line types 273 | 274 | The four core line types are: 275 | 276 | 1.3.5.3.1 Text lines 277 | 278 | Text lines are the most fundamenal line type - any line which does 279 | not match the definition of another line type defined below defaults 280 | to being a text line. The majority of lines in a typical text/gemini 281 | document will be text lines. 282 | 283 | Text lines should be presented to the user, after being wrapped to 284 | the appropriate width for the client's viewport (see below). Text 285 | lines may be presented to the user in a visually pleasing manner 286 | for general reading, the precise meaning of which is at the 287 | client's discretion. For example, variable width fonts may be used, 288 | spacing may be normalised, with spaces between sentences being made 289 | wider than spacing between words, and other such typographical 290 | niceties may be applied. Clients may permit users to customise the 291 | appearance of text lines by altering the font, font size, text and 292 | background colour, etc. Authors should not expect to exercise any 293 | control over the precise rendering of their text lines, only of their 294 | actual textual content. Content such as ASCII art, computer source 295 | code, etc. which may appear incorrectly when treated as such should 296 | be enclosed beween preformatting toggle lines (see 1.3.5.3.3). 297 | 298 | Blank lines are instances of text lines and have no special meaning. 299 | They should be rendered individually as vertical blank space each 300 | time they occur. In this way they are analogous to
tags in HTML. 301 | Consecutive blank lines should NOT be collapsed into a fewer blank 302 | lines. Note also that consecutive non-blank text lines do not form 303 | any kind of coherent unit or block such as a "paragraph": all text 304 | lines are independent entities. 305 | 306 | Text lines which are longer than can fit on a client's display device 307 | SHOULD be "wrapped" to fit, i.e. long lines should be split (ideally 308 | at whitespace or at hyphens) into multiple consecutive lines of a 309 | device-appropriate width. This wrapping is applied to each line of 310 | text independently. Multiple consecutive lines which are shorter 311 | than the client's display device MUST NOT be combined into fewer, 312 | longer lines. 313 | 314 | In order to take full advantage of this method of text formatting, 315 | authors of text/gemini content SHOULD avoid hard-wrapping to a 316 | pecific fixed width, in contrast to the convention in Gopherspace 317 | where text is typically wrapped at 80 characters or fewer. Instead, 318 | text which should be displayed as a contiguous block should be written 319 | as a single long line. Most text editors can be configured to 320 | "soft-wrap", i.e. to write this kind of file while displaying the long 321 | lines wrapped to fit the author's display device. 322 | 323 | Authors who insist on hard-wrapping their content MUST be aware that 324 | the content will display neatly on clients whose display device is as 325 | wide as the hard-wrapped length or wider, but will appear with 326 | irregular line widths on narrower clients. 327 | 328 | 1.3.5.3.2 Link lines 329 | 330 | Lines beginning with the two characters "=>" are link lines, which 331 | have the following syntax: 332 | 333 | =>[][] 334 | 335 | where: 336 | 337 | * is any non-zero number of consecutive spaces or 338 | tabs 339 | * Square brackets indicate that the enclosed content is 340 | optional. 341 | * is a URL, which may be absolute or relative. If the URL does 342 | not include a scheme, a scheme of gemini:// is implied. 343 | 344 | All the following examples are valid link lines: 345 | 346 | => gemini://example.org/ 347 | => gemini://example.org/ An example link 348 | => gemini://example.org/foo Another example link at the same host 349 | =>gemini://example.org/bar Yet another example link at the same host 350 | => foo/bar/baz.txt A relative link 351 | => gopher://example.org:70/1 A gopher link 352 | 353 | Note that link URLs may have schemes other than gemini://. This means 354 | that Gemini documents can simply and elegantly link to documents 355 | hosted via other protocols, unlike gophermaps which can only link to 356 | non-gopher content via a non-standard adaptation of the `h` item-type. 357 | 358 | Clients can present links to users in whatever fashion the client 359 | author wishes. 360 | 361 | 1.3.5.3.3 Preformatting toggle lines 362 | 363 | Any line whose first three characters are "```" (i.e. three 364 | consecutive back ticks with no leading whitespace) are preformatted 365 | toggle lines. These lines should NOT be included in the rendered 366 | output shown to the user. Instead, these lines toggle the parser 367 | between preformatted mode being "on" or "off". Preformatted mode 368 | should be "off" at the beginning of a document. The current status 369 | of preformatted mode is the only internal state a parser is required 370 | to maintain. When preformatted mode is "on", the usual rules for 371 | identifying line types are suspended, and all lines should be 372 | identified as preformatted text lines (see 1.3.5.3.4). 373 | 374 | Preformatting toggle lines can be thought of as analogous to
 and
375 | 
tags in HTML. 376 | 377 | 1.3.5.3.4 Preformatted text lines 378 | 379 | Preformatted text lines should be presented to the user in a "neutral", 380 | monowidth font without any alteration to whitespace or stylistic 381 | enhancements. Graphical clients should use scrolling mechanisms to 382 | present preformatted text lines which are longer than the client 383 | viewport, in preference to wrapping. In displaying preformatted text 384 | lines, clients should keep in mind applications like ASCII art and 385 | computer source code: in particular, source code in langugaes with 386 | significant whitespace (e.g. Python) should be able to be copied and 387 | pasted from the client into a file and interpreted/compiled without 388 | any problems arising from the client's manner of displaying them. 389 | 390 | 1.3.5.4 Advanced line types 391 | 392 | The following advanced line types MAY be recognised by advanced 393 | clients. Simple clients may treat them all as text lines as per 394 | 1.3.5.3.1 without any loss of essential function. 395 | 396 | 1.3.5.4.1 Heading lines 397 | 398 | Lines beginning with "#" are heading lines. Heading lines consist of 399 | one, two or three consecutive "#" characters, followed by optional 400 | whitespace, followed by heading text. The number of # characters 401 | indicates the "level" of header; #, ## and ### can be thought of as 402 | analogous to

,

and

in HTML. 403 | 404 | Heading text should be presented to the user, and clients MAY use 405 | special formatting, e.g. a larger or bold font, to indicate its 406 | status as a header (simple clients may simply print the line, 407 | including its leading #s, without any styling at all). However, the 408 | main motivation for the definition of heading lines is not stylistic 409 | but to provide a machine-readable representation of the internal 410 | structure of the document. Advanced clients can use this information 411 | to, e.g. display an automatically generated and hierarchically 412 | formatted "table of contents" for a long document in a side-pane, 413 | allowing users to easily jump to specific sections without excessive 414 | scrolling. CMS-style tools automatically generating menus or 415 | Atom/RSS feeds for a directory of text/gemini files can use first 416 | heading in the file as a human-friendly title. 417 | 418 | 1.3.5.4.2 Unordered list items 419 | 420 | Lines beginning with a * are unordered list items. This line type 421 | exists purely for stylistic reasons. The * may be replaced in 422 | advanced clients by a bullet symbol. Any text after the * character 423 | should be presented to the user as if it were a text line, i.e. 424 | wrapped to fit the viewport and formatted "nicely". Advanced clients 425 | can take the space of the bullet symbol into account when wrapping 426 | long list items to ensure that all lines of text corresponding to 427 | the item are offset an equal distance from the left of the screen. 428 | 429 | 1.4 TLS 430 | 431 | 1.4.1 Version requirements 432 | 433 | Use of TLS for Gemini transactions is mandatory. 434 | 435 | Servers MUST use TLS version 1.2 or higher and SHOULD use TLS version 436 | 1.3 or higher. Clients MAY refuse to connect to servers using TLS 437 | version 1.2 or lower. 438 | 439 | 1.4.2 Server certificate validation 440 | 441 | Clients can validate TLS connections however they like (including not 442 | at all) but the strongly RECOMMENDED approach is to implement a 443 | lightweight "TOFU" certificate-pinning system which treats self-signed 444 | certificates as first- class citizens. This greatly reduces TLS 445 | overhead on the network (only one cert needs to be sent, not a whole 446 | chain) and lowers the barrier to entry for setting up a Gemini site 447 | (no need to pay a CA or setup a Let's Encrypt cron job, just make a 448 | cert and go). 449 | 450 | TOFU stands for "Trust On First Use" and is public-key security model 451 | similar to that used by OpenSSH. The first time a Gemini client 452 | connects to a server, it accepts whatever certificate it is presented. 453 | That certificate's fingerprint and expiry date are saved in a 454 | persistent database (like the .known_hosts file for SSH), associated 455 | with the server's hostname. On all subsequent connections to that 456 | hostname, the received certificate's fingerprint is computed and 457 | compared to the one in the database. If the certificate is not the 458 | one previously received, but the previous certificate's expiry date 459 | has not passed, the user is shown a warning, analogous to the one web 460 | browser users are shown when receiving a certificate without a 461 | signature chain leading to a trusted CA. 462 | 463 | This model is by no means perfect, but it is not awful and is vastly 464 | superior to just accepting self-signed certificates unconditionally. 465 | 466 | 1.4.3 Transient client certificate sessions 467 | 468 | Self-signed client certificates can optionally be used by Gemini 469 | clients to permit servers to recognise subsequent requests from the 470 | same client as belonging to a single "session". This facilitates 471 | maintaining state in server-side applications. The functionality is 472 | very similar to HTTP cookies, but with important differences. 473 | 474 | Whereas HTTP cookies are originally created by a webserver and given 475 | to a client via a response header, client certificates are created by 476 | the client and given to the server as part of the TLS handshake: 477 | Client certificates are fundamentally a client-centric means of 478 | identification. Further, whereas HTTP cookies can be "resurrected" by 479 | webservers after a client deletes them if the server recognises the 480 | client by means of browser finger-printing or some other tracking 481 | technology (leading to unkillable "super cookies"), if a client 482 | deletes a client certificate and also the accompanying private key 483 | (which the server has never seen), then the session ID can never be 484 | recreated. Thus clients not only need to opt in to a certificate 485 | session, but once they have done so they retain a guaranteed ability 486 | to opt out of it at any point and the server cannot defeat this 487 | ability. 488 | 489 | Gemini requests typically will be made without a client certificate 490 | being sent to the server. If a requested resource is part of a 491 | server-side application which requires persistent state, a Gemini 492 | server can return a status code of 61 (see Appendix 1 below) to 493 | request that the client repeat the request with a "transient 494 | certificate" to initiate a client certificate section. 495 | 496 | Interactive clients for human users MUST inform users that such a 497 | session has been requested and require the user to approve generation 498 | of such a certificate. Transient certificates MUST NOT be generated 499 | automatically. 500 | 501 | Transient certificates are limited in scope to a particular domain. 502 | Transient certificates MUST NOT be reused across different domains. 503 | 504 | Transient certificates MUST be permanently deleted when the matching 505 | server issues a response with a status code of 21 (see Appendix 1 506 | below). 507 | 508 | Transient certificates MUST be permanently deleted when the client 509 | process terminates. 510 | 511 | Transient certificates SHOULD be permanently deleted after not having 512 | been used for more than 24 hours. 513 | 514 | Appendix 1. Full two digit status codes 515 | 516 | 10 INPUT 517 | 518 | As per definition of single-digit code 1 in 1.3.2. 519 | 520 | 20 SUCCESS 521 | 522 | As per definition of single-digit code 2 in 1.3.2. 523 | 524 | 21 SUCCESS - END OF CLIENT CERTIFICATE SESSION 525 | 526 | The request was handled successfully and a response body will 527 | follow the response header. The line is a MIME media 528 | type which applies to the response body. In addition, the 529 | server is signalling the end of a transient client certificate 530 | session which was previously initiated with a status 61 531 | response. The client should immediately and permanently 532 | delete the certificate and accompanying private key which was 533 | used in this request. 534 | 535 | 30 REDIRECT - TEMPORARY 536 | 537 | As per definition of single-digit code 3 in 1.3.2. 538 | 539 | 31 REDIRECT - PERMANENT 540 | 541 | The requested resource should be consistently requested from 542 | the new URL provided in future. Tools like search engine 543 | indexers or content aggregators should update their 544 | configurations to avoid requesting the old URL, and end-user 545 | clients may automatically update bookmarks, etc. Note that 546 | clients which only pay attention to the initial digit of 547 | status codes will treat this as a temporary redirect. They 548 | will still end up at the right place, they just won't be able 549 | to make use of the knowledge that this redirect is permanent, 550 | so they'll pay a small performance penalty by having to follow 551 | the redirect each time. 552 | 553 | 40 TEMPORARY FAILURE 554 | 555 | As per definition of single-digit code 4 in 1.3.2. 556 | 557 | 41 SERVER UNAVAILABLE 558 | 559 | The server is unavailable due to overload or maintenance. 560 | (cf HTTP 503) 561 | 562 | 42 CGI ERROR 563 | 564 | A CGI process, or similar system for generating dynamic 565 | content, died unexpectedly or timed out. 566 | 567 | 43 PROXY ERROR 568 | 569 | A proxy request failed because the server was unable to 570 | successfully complete a transaction with the remote host. 571 | (cf HTTP 502, 504) 572 | 573 | 44 SLOW DOWN 574 | 575 | Rate limiting is in effect. is an integer number of 576 | seconds which the client must wait before another request is 577 | made to this server. 578 | (cf HTTP 429) 579 | 580 | 50 PERMANENT FAILURE 581 | 582 | As per definition of single-digit code 5 in 1.3.2. 583 | 584 | 51 NOT FOUND 585 | 586 | The requested resource could not be found but may be available 587 | in the future. 588 | (cf HTTP 404) 589 | (struggling to remember this important status code? Easy: 590 | you can't find things hidden at Area 51!) 591 | 592 | 52 GONE 593 | 594 | The resource requested is no longer available and will not be 595 | available again. Search engines and similar tools should 596 | remove this resource from their indices. Content aggregators 597 | should stop requesting the resource and convey to their human 598 | users that the subscribed resource is gone. 599 | (cf HTTP 410) 600 | 601 | 53 PROXY REQUEST REFUSED 602 | 603 | The request was for a resource at a domain not served by the 604 | server and the server does not accept proxy requests. 605 | 606 | 59 BAD REQUEST 607 | 608 | The server was unable to parse the client's request, 609 | presumably due to a malformed request. 610 | (cf HTTP 400) 611 | 612 | 60 CLIENT CERTIFICATE REQUIRED 613 | 614 | As per definition of single-digit code 6 in 1.3.2. 615 | 616 | 61 TRANSIENT CERTIFICATE REQUESTED 617 | 618 | The server is requesting the initiation of a transient client 619 | certificate session, as described in 1.4.3. The client should 620 | ask the user if they want to accept this and, if so, generate 621 | a disposable key/cert pair and re-request the resource using it. 622 | The key/cert pair should be destroyed when the client quits, 623 | or some reasonable time after it was last used (24 hours? 624 | Less?) 625 | 626 | 62 AUTHORISED CERTIFICATE REQUIRED 627 | 628 | This resource is protected and a client certificate which the 629 | server accepts as valid must be used - a disposable key/cert 630 | generated on the fly in response to this status is not 631 | appropriate as the server will do something like compare the 632 | certificate fingerprint against a white-list of allowed 633 | certificates. The client should ask the user if they want to 634 | use a pre-existing certificate from a stored "key chain". 635 | 636 | 63 CERTIFICATE NOT ACCEPTED 637 | 638 | The supplied client certificate is not valid for accessing the 639 | requested resource. 640 | 641 | 64 FUTURE CERTIFICATE REJECTED 642 | 643 | The supplied client certificate was not accepted because its 644 | validity start date is in the future. 645 | 646 | 65 EXPIRED CERTIFICATE REJECTED 647 | 648 | The supplied client certificate was not accepted because its 649 | expiry date has passed. 650 | 651 | -------------------------------------------------------------------------------- /docroot/test.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jovoro/geminid/eec25221eadfa47b09de5bd0dc15e90b1263d43a/docroot/test.jpg -------------------------------------------------------------------------------- /example.conf: -------------------------------------------------------------------------------- 1 | // geminid configuration file 2 | 3 | // Global configuration 4 | global = { 5 | serverroot = "/srv/geminid"; // Root directory of the vhost docroots 6 | logdir = "/var/log/geminid"; // Where to put logfiles 7 | loglocaltime = true; // Use local time for timestamps in logs 8 | logtimeformat = "[%d/%b/%Y:%H:%M:%S %z]"; //time format for timestamps in logs 9 | port = 1965; // TCP port to listen on 10 | ipv6_enable = true; // Use IPv6 11 | }; 12 | 13 | // vHost definitions 14 | vhost = ( 15 | { 16 | name = "gemini.uxq.ch"; // Hostname 17 | docroot = "gemini.uxq.ch"; // document root, relative to serverroot 18 | accesslog = "gemini.uxq.ch_access.log"; // Name of access log file, relative to logdir 19 | errorlog = "gemini.uxq.ch_error.log"; // Name of error log file, relative to logdir 20 | cert = "/etc/geminid/certs/gemini.uxq.ch.pem"; // Path to SSL public key 21 | key = "/etc/geminid/keys/gemini.uxq.ch.pem"; // Path to SSL private key 22 | index = "index.gmi"; // Filename of default document to load when directory is requested 23 | }, 24 | 25 | { 26 | name = "gemini.uxw.ch"; // Hostname 27 | docroot = "gemini.uxw.ch"; // document root, relative to serverroot 28 | accesslog = "gemini.uxw.ch_access.log"; // Name of access log file, relative to logdir 29 | errorlog = "gemini.uxw.ch_error.log"; // Name of error log file, relative to logdir 30 | cert = "/etc/geminid/certs/gemini.uxw.ch.pem"; // Path to SSL public key 31 | key = "/etc/geminid/keys/gemini.uxw.ch.pem"; // Path to SSL private key 32 | index = "index.gmi"; // Filename of default document to load when directory is requested 33 | } 34 | ); 35 | 36 | -------------------------------------------------------------------------------- /file.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, J. von Rotz 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 11 | * 2. Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * 3. Neither the name of the copyright holder nor the names of its 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include "gemini.h" 38 | #include "file.h" 39 | #include "url.h" 40 | 41 | int read_file_meta(char *path, char *buffer) { 42 | int i; 43 | int pathlen; 44 | int pathpos; 45 | char tmpbuf[MAXBUF]; 46 | magic_t magic; 47 | const char *magicstr; 48 | 49 | /* Assume text/gemini for *.gmi files */ 50 | i = 0; 51 | pathlen = strlen(path); 52 | pathpos = pathlen-4; 53 | while(i < pathlen) { 54 | tmpbuf[i] = path[pathpos+i]; 55 | i++; 56 | } 57 | tmpbuf[i] = '\0'; 58 | 59 | if(strncmp(tmpbuf, ".gmi", 4) != 0) { 60 | magic = magic_open(MAGIC_MIME_TYPE); 61 | magic_load(magic, NULL); 62 | magicstr = magic_file(magic, path); 63 | 64 | if(magicstr == NULL) 65 | strncpy(buffer, "text/gemini; charset=utf-8", 27); 66 | else 67 | strncpy(buffer, magicstr, MAXBUF); 68 | 69 | magic_close(magic); 70 | } else { 71 | strncpy(buffer, "text/gemini", 12); 72 | } 73 | return strlen(buffer); 74 | } 75 | 76 | int read_file(char *path, void *buffer) { 77 | FILE *fh; 78 | long fsize; 79 | size_t result; 80 | 81 | fh = fopen(path, "r"); 82 | if(fh == NULL) 83 | return -1; 84 | 85 | fseek(fh, 0, SEEK_END); 86 | fsize = ftell(fh); 87 | fseek(fh, 0, SEEK_SET); 88 | if(buffer == NULL) 89 | return -1; 90 | 91 | result = fread(buffer, sizeof(char), fsize, fh); 92 | fclose(fh); 93 | 94 | return result; 95 | } 96 | 97 | int read_directory(char *path, char *document_root, char *requesturl, char **buffer) { 98 | DIR *dp; 99 | struct stat statbuf; 100 | struct dirent *ep; 101 | int i; 102 | int outsiz; 103 | int linelen; 104 | char localpath[MAXBUF]; 105 | char tmpbuf[MAXBUF]; 106 | char urlbuf[MAXBUF]; 107 | unsigned char *table; 108 | void *np; 109 | char *bufloc; 110 | 111 | snprintf(localpath, MAXBUF, "%s/%s", document_root, path); 112 | 113 | dp = opendir(localpath); 114 | if(dp == NULL) 115 | return -1; 116 | 117 | 118 | outsiz = 26 + strlen(path); 119 | *buffer = calloc(outsiz, 1); 120 | if(!*buffer) 121 | return -1; 122 | 123 | bufloc = *buffer; 124 | table = new_url_encoder_table(); 125 | if(!table) 126 | return -1; 127 | 128 | strncat(*buffer, "# Directory listing of ", 24); 129 | strncat(*buffer, path, strlen(path)); 130 | strncat(*buffer, "\r\n", 3); 131 | 132 | while ((ep = readdir(dp)) != NULL) { 133 | if(strncmp(ep->d_name, ".", 1) == 0 || strncmp(ep->d_name, "..", 2) == 0) 134 | continue; 135 | 136 | i = snprintf(tmpbuf, MAXBUF, "%s/%s", localpath, ep->d_name); 137 | if(i < 0) { 138 | return -1; 139 | } 140 | 141 | if((i = stat(tmpbuf, &statbuf)) == 0) { 142 | tmpbuf[0] = 0; 143 | linelen = 0; 144 | strncat(tmpbuf, requesturl, strlen(requesturl)); 145 | strncat(tmpbuf, ep->d_name, strlen(ep->d_name)); 146 | if(S_ISDIR(statbuf.st_mode)) { 147 | strncat(tmpbuf, "/", 2); 148 | linelen += 1; /* For adding slash at the end of d_name to indicate directory */ 149 | } 150 | 151 | i = url_encode(table, (unsigned char *) tmpbuf, urlbuf, 4096); 152 | linelen += 3 + i + 1 + strlen(ep->d_name) + 2; 153 | outsiz += linelen; 154 | if((np = realloc(bufloc, outsiz))) { 155 | *buffer = np; 156 | bufloc = np; 157 | } else { 158 | free(table); 159 | return -1; 160 | } 161 | strncat(*buffer, "=> ", 4); 162 | strncat(*buffer, urlbuf, i); 163 | strncat(*buffer, " ", 2); 164 | strncat(*buffer, ep->d_name, strlen(ep->d_name)); 165 | if(S_ISDIR(statbuf.st_mode)) { 166 | strncat(*buffer, "/", 2); 167 | } 168 | strncat(*buffer, "\r\n", 3); 169 | } 170 | } 171 | bufloc[outsiz-1] = 0; 172 | closedir(dp); 173 | free(table); 174 | 175 | return outsiz; 176 | } 177 | -------------------------------------------------------------------------------- /file.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, J. von Rotz 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 11 | * 2. Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * 3. Neither the name of the copyright holder nor the names of its 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | * 30 | */ 31 | 32 | int read_file_meta(char *path, char *buffer); 33 | int read_file(char *path, void *buffer); 34 | int read_directory(); 35 | 36 | -------------------------------------------------------------------------------- /gemini.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2020 J. von Rotz 2 | * 3 | * Redistribution and use in source and binary forms, with or without 4 | * modification, are permitted provided that the following conditions are met: 5 | * 6 | * 1. Redistributions of source code must retain the above copyright notice, 7 | * this list of conditions and the following disclaimer. 8 | * 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * 13 | * 3. Neither the name of the copyright holder nor the names of its 14 | * contributors may be used to endorse or promote products derived 15 | * from this software without specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 20 | * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 21 | * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 25 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #ifdef __linux__ 36 | #include 37 | #else 38 | #include 39 | #endif /* __linux__ */ 40 | #include "gemini.h" 41 | 42 | #include 43 | 44 | #include "util.h" 45 | #include "file.h" 46 | #include "log.h" 47 | #include "url.h" 48 | 49 | int read_request(SSL *ssl, char *buffer) { 50 | int i; 51 | i = SSL_read(ssl, buffer, MAXREQSIZ+1); 52 | if(i > 1024) 53 | return -1; 54 | else 55 | return i; 56 | } 57 | 58 | int write_gemini_response(SSL *ssl, int status_major, int status_minor, char *meta, int metalen, char *buffer, int buflen) { 59 | char *header; 60 | int headerlen = MAXSTATUSSIZ+MAXWHITESIZ+metalen+1; 61 | header = malloc(headerlen); 62 | if(header == NULL) 63 | return -1; 64 | 65 | snprintf(header, headerlen, "%d%d %s\r\n", status_major, status_minor, meta); 66 | SSL_write(ssl, header, strlen(header)); 67 | SSL_write(ssl, buffer, buflen); 68 | free(header); 69 | return 1; 70 | } 71 | 72 | int parse_request(char *buffer, int reqlen, URL *urlp) { 73 | int i; 74 | unsigned char tmpbuf[MAXBUF]; 75 | 76 | *(buffer+reqlen) = '\0'; 77 | trim(buffer); 78 | i = lexurl(urlp, buffer); 79 | 80 | if(i < 0) { 81 | fprintf(stderr, "Error: Failed to parse URL: %s\n", buffer); 82 | return -1; 83 | } 84 | 85 | if(strcmp(urlp->userinfo, "") != 0) 86 | return -2; 87 | 88 | 89 | if(strncmp(urlp->scheme, "", MAX_URL_SCHEME) == 0) 90 | strncpy(urlp->scheme, "gemini://", MAX_URL_SCHEME); 91 | 92 | i = url_decode(tmpbuf, urlp->path, MAXBUF); 93 | if(i < 0) 94 | return -1; 95 | 96 | memcpy(urlp->path, tmpbuf, MAX_URL_PATH); 97 | urlp->path[MAX_URL_PATH] = 0; 98 | 99 | return 1; 100 | } 101 | 102 | int handle_request(SSL *ssl, char *document_root, char *default_document, FILE *access_log, FILE *error_log) { 103 | int reqlen = 0; 104 | int reslen = 0; 105 | int mimelen = 0; 106 | int i; 107 | char reqbuf[MAXBUF]; 108 | char *resbuf; 109 | char *pathbuf; 110 | char host[MAXBUF]; 111 | char path[MAXBUF]; 112 | char defdocpath[MAXBUF]; 113 | char localpath[MAXBUF]; 114 | char mime[MAXBUF]; 115 | struct stat statbuf; 116 | struct stat defdocstatbuf; 117 | URL requrl; 118 | 119 | memset(reqbuf, 0, MAXBUF); 120 | memset(host, 0, MAXBUF); 121 | memset(path, 0, MAXBUF); 122 | memset(localpath, 0, MAXBUF); 123 | 124 | reqlen = read_request(ssl, reqbuf); 125 | if(reqlen < 0) { 126 | write_gemini_response(ssl, STATUS_PERMFAIL, 9, "Request too long", 16, "", 0); 127 | log_access(access_log, &(LOG_ACCESS_ENTRY){ 128 | .request = reqbuf, 129 | .status.major = STATUS_PERMFAIL, 130 | .status.minor = 9, 131 | }); 132 | log_error(error_log, "Error: Request too long\n"); 133 | return -1; 134 | } 135 | 136 | i = parse_request(reqbuf, reqlen, &requrl); 137 | if(i < 0) { 138 | write_gemini_response(ssl, STATUS_PERMFAIL, 9, "Bad request", 11, "", 0); 139 | log_access(access_log, &(LOG_ACCESS_ENTRY){ 140 | .request = reqbuf, 141 | .status.major = STATUS_PERMFAIL, 142 | .status.minor = 9, 143 | }); 144 | if(i == -2) 145 | log_error(error_log, "Error: Userinfo detected in URL request\n"); 146 | else 147 | log_error(error_log, "Error: Could not parse request %s\n", reqbuf); 148 | 149 | return -1; 150 | } 151 | 152 | i = build_request_string(reqbuf, MAXBUF, &requrl); 153 | if(i < 0) { 154 | write_gemini_response(ssl, STATUS_TEMPFAIL, 1, "Processing Error", 16, "", 0); 155 | log_access(access_log, &(LOG_ACCESS_ENTRY){ 156 | .request = reqbuf, 157 | .status.major = STATUS_TEMPFAIL, 158 | .status.minor = 1 159 | }); 160 | log_error(error_log, "Error: Could not handle request for %s\n", reqbuf); 161 | return -1; 162 | } 163 | 164 | strncpy(host, requrl.host, MAXBUF); 165 | strncpy(path, requrl.path, MAXBUF); 166 | 167 | i = snprintf(localpath, MAXBUF, "%s/%s", document_root, path); 168 | if(i < 0) { 169 | write_gemini_response(ssl, STATUS_TEMPFAIL, 1, "Processing Error", 16, "", 0); 170 | log_access(access_log, &(LOG_ACCESS_ENTRY) { 171 | .request = reqbuf, 172 | .status.major = STATUS_TEMPFAIL, 173 | .status.minor = 9 174 | }); 175 | log_error(error_log, "Error: Could not handle request for %s\n", reqbuf); 176 | return -1; 177 | } 178 | 179 | pathbuf = realpath(localpath, NULL); 180 | 181 | if(pathbuf == NULL) { 182 | write_gemini_response(ssl, STATUS_PERMFAIL, 1, "File not found", 14, "", 0); 183 | log_access(access_log, &(LOG_ACCESS_ENTRY){ 184 | .request = reqbuf, 185 | .host = host, 186 | .path = path, 187 | .status.major = STATUS_PERMFAIL, 188 | .status.minor = 1, 189 | }); 190 | log_error(error_log, "Error: Could not get realpath for %s\n", reqbuf); 191 | return -1; 192 | } 193 | 194 | if(strncmp(document_root, pathbuf, strlen(document_root)) != 0) { 195 | memcpy(localpath, document_root, strlen(document_root)+1); 196 | localpath[strlen(document_root)+1] = 0; 197 | strcat(localpath, "/"); 198 | 199 | requrl.path[0] = path[0] = '/'; 200 | requrl.path[1] = path[1] = 0; 201 | i = build_request_string(reqbuf, MAXBUF, &requrl); 202 | if(i < 0) { 203 | write_gemini_response(ssl, STATUS_TEMPFAIL, 1, "Processing Error", 16, "", 0); 204 | log_access(access_log, &(LOG_ACCESS_ENTRY){ 205 | .request = reqbuf, 206 | .status.major = STATUS_TEMPFAIL, 207 | .status.minor = 1, 208 | }); 209 | log_error(error_log, "Error: Could not handle request for %s\n", reqbuf); 210 | return -1; 211 | } 212 | } 213 | 214 | free(pathbuf); 215 | 216 | if(access(localpath, R_OK) != -1) { 217 | /* Local path is readable; Find out, if file or directory */ 218 | /* FIXME: What happens if it is neither of both..? Hm... */ 219 | if((i = stat(localpath, &statbuf)) != 0) { 220 | write_gemini_response(ssl, STATUS_TEMPFAIL, 1, "I/O Error", 9, "", 0); 221 | log_access(access_log, &(LOG_ACCESS_ENTRY){ 222 | .request = reqbuf, 223 | .host = host, 224 | .path = path, 225 | .status.major = STATUS_TEMPFAIL, 226 | .status.minor = 1 227 | }); 228 | return -1; 229 | } 230 | if(S_ISDIR(statbuf.st_mode)) { 231 | /* path is directory */ 232 | 233 | if(path[strlen(path)-1] != '/') { 234 | /* redirect to correct location with trailing slash (status 31) */ 235 | i = snprintf(mime, MAXBUF, "%s/", reqbuf); 236 | if(i < 0) { 237 | write_gemini_response(ssl, STATUS_TEMPFAIL, 1, "Processing Error", 16, "", 0); 238 | log_access(access_log, &(LOG_ACCESS_ENTRY) { 239 | .request = reqbuf, 240 | .status.major = STATUS_TEMPFAIL, 241 | .status.minor = 9 242 | }); 243 | log_error(error_log, "Error: Could not handle request for %s\n", reqbuf); 244 | return -1; 245 | } 246 | 247 | write_gemini_response(ssl, STATUS_REDIRECT, 1, mime, strlen(mime), "", 0); 248 | log_access(access_log, &(LOG_ACCESS_ENTRY){ 249 | .request = reqbuf, 250 | .host = host, 251 | .path = path, 252 | .status.major = STATUS_REDIRECT, 253 | .status.minor = 1 254 | }); 255 | return 0; 256 | } 257 | 258 | i = snprintf(defdocpath, MAXBUF, "%s/%s", localpath, default_document); 259 | if(i < 0) { 260 | write_gemini_response(ssl, STATUS_TEMPFAIL, 1, "Processing Error", 16, "", 0); 261 | log_access(access_log, &(LOG_ACCESS_ENTRY) { 262 | .request = reqbuf, 263 | .status.major = STATUS_TEMPFAIL, 264 | .status.minor = 9 265 | }); 266 | log_error(error_log, "Error: Could not handle request for %s\n", reqbuf); 267 | return -1; 268 | } 269 | 270 | if(access(defdocpath, R_OK) != -1) { 271 | if((i = stat(defdocpath, &defdocstatbuf)) == 0) { 272 | /* We have a default document, read it */ 273 | resbuf = malloc(defdocstatbuf.st_size + 1); 274 | mimelen = read_file_meta(defdocpath, mime); 275 | reslen = read_file(defdocpath, resbuf); 276 | } else { 277 | write_gemini_response(ssl, STATUS_TEMPFAIL, 1, "I/O Error", 9, "", 0); 278 | log_access(access_log, &(LOG_ACCESS_ENTRY){ 279 | .request = reqbuf, 280 | .host = host, 281 | .path = path, 282 | .status.major = STATUS_TEMPFAIL, 283 | .status.minor = 1, 284 | }); 285 | return -1; 286 | } 287 | } else { 288 | /* No default document, list directory */ 289 | strncpy(mime, "text/gemini", 12); 290 | mimelen = 12; 291 | reslen = read_directory(path, document_root, reqbuf, &resbuf); /* document_root will be prepended again */ 292 | reslen--; /* Dont write terminating NULL byte */ 293 | if(reslen < 0) { 294 | write_gemini_response(ssl, STATUS_TEMPFAIL, 1, "I/O Error", 9, "", 0); 295 | log_access(access_log, &(LOG_ACCESS_ENTRY){ 296 | .request = reqbuf, 297 | .host = host, 298 | .path = path, 299 | .status.major = STATUS_TEMPFAIL, 300 | .status.minor = 1, 301 | }); 302 | return -1; 303 | } 304 | } 305 | } else { 306 | /* path is file, maybe... */ 307 | resbuf = malloc(statbuf.st_size + 1); 308 | mimelen = read_file_meta(localpath, mime); 309 | reslen = read_file(localpath, resbuf); 310 | } 311 | if(reslen < 1) { 312 | write_gemini_response(ssl, STATUS_TEMPFAIL, 1, "I/O Error", 9, "", 0); 313 | log_access(access_log, &(LOG_ACCESS_ENTRY){ 314 | .request = reqbuf, 315 | .host = host, 316 | .path = path, 317 | .status.major = STATUS_TEMPFAIL, 318 | .status.minor = 1, 319 | }); 320 | return -1; 321 | } 322 | write_gemini_response(ssl, STATUS_SUCCESS, 0, mime, mimelen, resbuf, reslen); 323 | log_access(access_log, &(LOG_ACCESS_ENTRY){ 324 | .request = reqbuf, 325 | .host = host, 326 | .path = path, 327 | .status.major = STATUS_SUCCESS, 328 | .response_length = reslen, 329 | }); 330 | free(resbuf); 331 | } else { 332 | /* Local path is not readable. Currently, this simply means, that the file 333 | * does not exist and we send the according response. But in the future 334 | * this could mean more than that. 335 | */ 336 | write_gemini_response(ssl, STATUS_PERMFAIL, 1, "File not found", 14, "", 0); 337 | log_access(access_log, &(LOG_ACCESS_ENTRY){ 338 | .request = reqbuf, 339 | .host = host, 340 | .path = path, 341 | .status.major = STATUS_PERMFAIL, 342 | .status.minor = 1, 343 | }); 344 | } 345 | 346 | return 1; 347 | } 348 | 349 | -------------------------------------------------------------------------------- /gemini.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2020 J. von Rotz 2 | * 3 | * Redistribution and use in source and binary forms, with or without 4 | * modification, are permitted provided that the following conditions are met: 5 | * 6 | * 1. Redistributions of source code must retain the above copyright notice, 7 | * this list of conditions and the following disclaimer. 8 | * 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * 13 | * 3. Neither the name of the copyright holder nor the names of its 14 | * contributors may be used to endorse or promote products derived 15 | * from this software without specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 20 | * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 21 | * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 25 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | 31 | #define MAXREQSIZ 1024 /* gemini spec 1.2: UTF-8 encoded URL of max. 1024 _bytes_ */ 32 | #define MAXRESSIZ 4096 33 | #define MAXHOST 1024 34 | #define MAXPATH 1024 35 | #define MAXBUF 4096 36 | #define MAXMETASIZ 1025 37 | #define MAXSTATUSSIZ 3 38 | #define MAXWHITESIZ 11 39 | #define DEFAULT_DOCUMENT "index.gmi" 40 | #define DOCUMENT_ROOT "docroot" 41 | #define LISTEN_PORT 1965 42 | 43 | 44 | int handle_request(); 45 | 46 | enum { REQ_GEMINI, REQ_GOPHER }; 47 | 48 | enum { STATUS_DUMMY, STATUS_INPUT, STATUS_SUCCESS, STATUS_REDIRECT, STATUS_TEMPFAIL, STATUS_PERMFAIL, STATUS_CERTREQ }; 49 | 50 | extern int listen_port; 51 | extern char default_document[MAXBUF]; 52 | -------------------------------------------------------------------------------- /geminid-chroot.service: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020, 2021, 2022 J. von Rotz 2 | # 3 | # This is a sample service description for geminid when run in a chroot. 4 | # Just create the necessary directories and copy the geminid binary and 5 | # the required libraries there, for example: 6 | # 7 | # mkdir -p /users/gemini/{bin,lib64,etc/geminid/{keys,certs},var/log/geminid} 8 | # cp /usr/local/bin/geminid /users/gemini/bin 9 | # cp /etc/geminid/geminid.conf /users/gemini/etc/geminid 10 | # cp /etc/geminid/keys/* /users/gemini/etc/geminid/keys 11 | # cp /etc/geminid/certs/* /users/gemini/etc/geminid/certs 12 | # cp /lib64/ld-linux-x86-64.so.2 /users/gemini/lib64 13 | # cp /lib64/ld-2.31.so /users/gemini/lib64 14 | # ...etc., repeat for all required libraries. Use the ldd command on the geminid binary to find out which libraries to copy. 15 | 16 | [Unit] 17 | Description=Geminid, a gemini server written in C 18 | After=network.target 19 | Before= 20 | 21 | [Service] 22 | Type=simple 23 | User=gemini 24 | Group=gemini 25 | RootDirectory=/users/gemini 26 | ExecStart=/bin/geminid -c /etc/geminid/geminid.conf 27 | 28 | [Install] 29 | WantedBy=multi-user.target 30 | -------------------------------------------------------------------------------- /geminid.service: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020, 2021, 2022 J. von Rotz 2 | # 3 | # This is a sample service description for geminid. 4 | # It is assumed that the default install targets from the Makefile are used. 5 | 6 | [Unit] 7 | Description=Geminid, a gemini server written in C 8 | After=network.target 9 | Before= 10 | 11 | [Service] 12 | Type=simple 13 | User=gemini 14 | Group=gemini 15 | ExecStart=/usr/local/bin/geminid -c /etc/geminid/geminid.conf 16 | 17 | [Install] 18 | WantedBy=multi-user.target 19 | -------------------------------------------------------------------------------- /lexurl.l: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2020 J. von Rotz 2 | * 3 | * Redistribution and use in source and binary forms, with or without 4 | * modification, are permitted provided that the following conditions are met: 5 | * 6 | * 1. Redistributions of source code must retain the above copyright notice, 7 | * this list of conditions and the following disclaimer. 8 | * 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * 13 | * 3. Neither the name of the copyright holder nor the names of its 14 | * contributors may be used to endorse or promote products derived 15 | * from this software without specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 20 | * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 21 | * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 25 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | %option noyywrap 31 | %option noinput 32 | %option nounput 33 | 34 | %{ 35 | #ifdef __linux__ 36 | #include 37 | #else 38 | #include 39 | #endif /* __linux__ */ 40 | #include "url.h" 41 | 42 | #define YYSTYPE URL 43 | URL yylval; 44 | %} 45 | 46 | %% 47 | 48 | [a-z]{1,}:\/\/ { strncpy(yylval.scheme, yytext, MAX_URL_SCHEME-1); } 49 | [a-z0-9]{1,}(:[a-z0-9]*)?@ { strncpy(yylval.userinfo, yytext, MAX_URL_USERINFO-1); } 50 | [a-zA-Z0-9.-]{2,} { strncpy(yylval.host, yytext, MAX_URL_HOST-1); } 51 | :[0-9]{1,5} { strncpy(yylval.port, yytext, MAX_URL_PORT-1); } 52 | \/[^?]* { strncpy(yylval.path, yytext, MAX_URL_PATH-1); } 53 | \?[a-zA-Z0-9=&]* { strncpy(yylval.query, yytext, MAX_URL_QUERY-1); } 54 | #[a-zA-Z0-9]+ { strncpy(yylval.fragment, yytext, MAX_URL_FRAGMENT-1); } 55 | 56 | %% 57 | 58 | int lexurl(URL *url, char *buf) { 59 | if(url == NULL) 60 | return -1; 61 | 62 | yy_scan_string(buf); 63 | yylex(); 64 | *url = yylval; 65 | 66 | return 1; 67 | } 68 | 69 | URL *alexurl(char *buf) { 70 | URL *url; 71 | url = calloc(1, sizeof(*url)); 72 | 73 | if(url == NULL) 74 | return NULL; 75 | 76 | yy_scan_string(buf); 77 | yylex(); 78 | *url = yylval; 79 | 80 | return url; 81 | } 82 | -------------------------------------------------------------------------------- /log.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2020 J. von Rotz 2 | * 3 | * Redistribution and use in source and binary forms, with or without 4 | * modification, are permitted provided that the following conditions are met: 5 | * 6 | * 1. Redistributions of source code must retain the above copyright notice, 7 | * this list of conditions and the following disclaimer. 8 | * 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * 13 | * 3. Neither the name of the copyright holder nor the names of its 14 | * contributors may be used to endorse or promote products derived 15 | * from this software without specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 20 | * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 21 | * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 25 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #include "log.h" 36 | 37 | static struct { 38 | const char *time_format; 39 | struct tm * (*broken_down)(const time_t *, struct tm *); 40 | } g_logconfig = { 41 | .time_format = "[%d/%b/%Y:%H:%M:%S %z]", 42 | .broken_down = gmtime_r, 43 | }; 44 | 45 | int log_setup(const LOGCONFIG *logconfig) { 46 | g_logconfig.time_format = logconfig->time_format; 47 | g_logconfig.broken_down = logconfig->use_local_time 48 | ? localtime_r 49 | : gmtime_r 50 | ; 51 | return 0; 52 | } 53 | 54 | static const char *current_time(char *buffer, size_t buflen) { 55 | struct tm tm; 56 | time_t t; 57 | 58 | if (time(&t) == -1) { 59 | perror("time"); 60 | return "???"; 61 | } 62 | 63 | if (g_logconfig.broken_down(&t, &tm) == NULL) { 64 | perror("gmtime_r / localtime_r"); 65 | return "???"; 66 | } 67 | 68 | if (strftime(buffer, buflen, g_logconfig.time_format, &tm) == 0) { 69 | fprintf(stderr, "stftime failed to format time"); 70 | return "???"; 71 | } 72 | 73 | return buffer; 74 | } 75 | 76 | void log_access(FILE *lf, const LOG_ACCESS_ENTRY *entry) { 77 | char timebuf[32]; 78 | 79 | // NOTE 2021-03-29 dacav@fastmail.com: 80 | // The last two fields are used to be the expansion of two 81 | // Ttring variables named `cc_issuer` and `cc_subject`. All 82 | // invocations of `log_access` in the original code passed the "-" 83 | // string for both. 84 | fprintf(lf, "%s %s %s %s %d%d %ld - -\n", 85 | current_time(timebuf, sizeof(timebuf)), 86 | entry->request ? entry->request : "-", 87 | entry->host ? entry->host : "", 88 | entry->path ? entry->path : "", 89 | entry->status.major, 90 | entry->status.minor, 91 | entry->response_length 92 | ); 93 | } 94 | 95 | void log_error(FILE *lf, const char *format, ...) { 96 | char logbuf[4096]; 97 | char timebuf[32]; 98 | va_list ap; 99 | 100 | va_start(ap, format); 101 | vsnprintf(logbuf, sizeof(logbuf), format, ap); 102 | va_end(ap); 103 | 104 | fprintf(lf, "%s %s\n", current_time(timebuf, sizeof(timebuf)), logbuf); 105 | } 106 | -------------------------------------------------------------------------------- /log.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2020 J. von Rotz 2 | * 3 | * Redistribution and use in source and binary forms, with or without 4 | * modification, are permitted provided that the following conditions are met: 5 | * 6 | * 1. Redistributions of source code must retain the above copyright notice, 7 | * this list of conditions and the following disclaimer. 8 | * 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * 13 | * 3. Neither the name of the copyright holder nor the names of its 14 | * contributors may be used to endorse or promote products derived 15 | * from this software without specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 20 | * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 21 | * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 25 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | #include 31 | 32 | typedef struct { 33 | bool use_local_time; 34 | const char *time_format; 35 | } LOGCONFIG; 36 | 37 | int log_setup(const LOGCONFIG *); 38 | 39 | typedef struct { 40 | const char *request; 41 | const char *host; 42 | const char *path; 43 | struct { 44 | int major, minor; 45 | } status; 46 | long response_length; 47 | } LOG_ACCESS_ENTRY; 48 | 49 | void log_access(FILE *lf, const LOG_ACCESS_ENTRY *); 50 | void log_error(FILE *lf, const char *format, ...) __attribute__((format(printf, 2, 3))); 51 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2020 J. von Rotz 2 | * 3 | * Redistribution and use in source and binary forms, with or without 4 | * modification, are permitted provided that the following conditions are met: 5 | * 6 | * 1. Redistributions of source code must retain the above copyright notice, 7 | * this list of conditions and the following disclaimer. 8 | * 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * 13 | * 3. Neither the name of the copyright holder nor the names of its 14 | * contributors may be used to endorse or promote products derived 15 | * from this software without specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 20 | * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 21 | * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 25 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include "tls.h" 43 | #include "gemini.h" 44 | #include "log.h" 45 | #include "config.h" 46 | #include "vhost.h" 47 | 48 | static volatile int keepRunning = 1; 49 | VHOST *vhost; 50 | unsigned int vhostcount = 0; 51 | 52 | void initWorker(int client, const char *server_root) { 53 | char document_root[MAXBUF]; 54 | SSL *ssl; 55 | VHOST *select_vhost; 56 | 57 | SSL_CTX_set_tlsext_servername_callback(vhost->ctx, sni_cb); 58 | ssl = SSL_new(vhost->ctx); /* Use first vhost as default context */ 59 | SSL_set_fd(ssl, client); 60 | 61 | if (SSL_accept(ssl) <= 0) { 62 | ERR_print_errors_fp(stderr); 63 | } else { 64 | select_vhost = get_current_vhost(); 65 | if(select_vhost == NULL) { 66 | fprintf(stderr, "Cannot get current vhost\n"); 67 | return; 68 | } 69 | 70 | snprintf(document_root, MAXBUF-1, "%s/%s", server_root, select_vhost->docroot); 71 | handle_request(ssl, document_root, select_vhost->defaultdocument, select_vhost->accesslog, select_vhost->errorlog); 72 | } 73 | 74 | SSL_shutdown(ssl); 75 | SSL_free(ssl); 76 | close(client); 77 | } 78 | 79 | void usage(char *progname) { 80 | fprintf(stderr, "Usage: %s -c config [-t]\n\n", progname); 81 | fprintf(stderr, "\t-c config\n\t\tPath to configuration file\n\n"); 82 | fprintf(stderr, "\t-t\n\t\tTest and print configuration\n\n"); 83 | exit(EXIT_FAILURE); 84 | } 85 | 86 | /* Currently not needed */ 87 | /* 88 | void intHandler(int signal) { 89 | keepRunning = 0; 90 | } 91 | */ 92 | 93 | int main(int argc, char **argv) { 94 | int sock; 95 | int pid; 96 | int client; 97 | int opt; 98 | uint len; 99 | struct sockaddr_in addr; 100 | struct sockaddr_in6 addr6; 101 | const char *configpath = NULL; 102 | int run_tests = 0; 103 | char accesslog_path[MAXBUF]; 104 | char errorlog_path[MAXBUF]; 105 | GLOBALCONF *global; 106 | VHOSTLIST *vhostlist; 107 | VHOSTCONF *vhcp; 108 | VHOST *vhp; 109 | VHOST *tempvhp; 110 | config_t cfg; 111 | unsigned int i; 112 | int j; 113 | 114 | while((opt = getopt(argc, argv, "c:t")) != -1) { 115 | switch(opt) { 116 | case 'c': 117 | configpath = optarg; 118 | break; 119 | 120 | case 't': 121 | run_tests = 1; 122 | break; 123 | 124 | default: 125 | usage(argv[0]); 126 | } 127 | } 128 | 129 | if(configpath == NULL || strcmp(configpath, "") == 0) 130 | usage(argv[0]); 131 | 132 | if (run_tests) { 133 | testprintconfig(configpath); 134 | exit(EXIT_SUCCESS); 135 | } 136 | 137 | /* signal(SIGINT, intHandler); */ 138 | 139 | /* Prepare configuration */ 140 | if (init_geminid_config(configpath, &cfg, &global, &vhostlist) < 0) { 141 | config_destroy(&cfg); 142 | fprintf(stderr, "Cannot parse config.\n"); 143 | exit(EXIT_FAILURE); 144 | } 145 | 146 | if(global == NULL) { 147 | config_destroy(&cfg); 148 | fprintf(stderr, "Cannot parse config.\n"); 149 | exit(EXIT_FAILURE); 150 | } 151 | 152 | if(vhostlist->count < 1) { 153 | config_destroy(&cfg); 154 | fprintf(stderr, "No vhosts defined.\n"); 155 | exit(EXIT_FAILURE); 156 | } 157 | 158 | init_openssl(); 159 | 160 | /* Global configuration */ 161 | if (log_setup(&(LOGCONFIG){.use_local_time = global->loglocaltime, .time_format = global->logtimeformat})) { 162 | fprintf(stderr, "Error setting up logging system\n"); 163 | exit(EXIT_FAILURE); 164 | } 165 | 166 | /* Configuration per virtual host */ 167 | vhostcount = vhostlist->count; 168 | vhost = calloc(vhostcount, sizeof(VHOST)); 169 | if(vhost == NULL) { 170 | fprintf(stderr, "Error allocating vhost struct\n"); 171 | exit(EXIT_FAILURE); 172 | } 173 | 174 | vhcp = vhostlist->vhost; 175 | vhp = vhost; 176 | for(i=0; i < vhostcount; i++) { 177 | snprintf(accesslog_path, MAXBUF-1, "%s/%s", global->logdir, vhcp->accesslog); 178 | snprintf(errorlog_path, MAXBUF-1, "%s/%s", global->logdir, vhcp->errorlog); 179 | tempvhp = create_vhost(vhcp->name, vhcp->docroot, vhcp->index, accesslog_path, errorlog_path, vhcp->cert, vhcp->key); 180 | if(tempvhp == NULL) { 181 | fprintf(stderr, "Error allocating vhost struct\n"); 182 | exit(EXIT_FAILURE); 183 | } 184 | *vhp = *tempvhp; 185 | free(tempvhp); 186 | 187 | /* Print configuration settings */ 188 | fprintf(stderr, "serverroot: %s\nlogdir: %s\nhostname: %s\naccess_log_path: %s\nerror_log_path: %s\ndefault_document: %s\nlisten_port: %d\ndocument_root: %s\npublic key: %s\nprivate key: %s\n\n\n", global->serverroot, global->logdir, vhcp->name, accesslog_path, errorlog_path, vhcp->index, global->port, vhcp->docroot, vhcp->cert, vhcp->key); 189 | vhcp++; 190 | vhp++; 191 | } 192 | j = set_current_vhost(vhost); 193 | if(j < 0) { 194 | fprintf(stderr, "Cannot set current (default) vhost\n"); 195 | exit(EXIT_FAILURE); 196 | } 197 | 198 | if(global->ipv6_enable) 199 | sock = create_socket6(global->port); 200 | else 201 | sock = create_socket(global->port); 202 | 203 | setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &(int){ 1 }, sizeof(int)); 204 | 205 | /* Auto-reap zombies for now. 206 | * Maybe refine that with a real signal handler later 207 | * to get exit codes from children. 208 | */ 209 | signal(SIGCHLD, SIG_IGN); 210 | 211 | while(keepRunning) { 212 | if(global->ipv6_enable) { 213 | len = sizeof(addr6); 214 | client = accept(sock, (struct sockaddr*)&addr6, &len); 215 | } else { 216 | len = sizeof(addr); 217 | client = accept(sock, (struct sockaddr*)&addr, &len); 218 | } 219 | if (client < 0) { 220 | perror("Unable to accept"); 221 | continue; 222 | } 223 | if((pid = fork()) == 0) { 224 | // Child 225 | close(sock); 226 | initWorker(client, global->serverroot); 227 | exit(0); 228 | } else if (pid > 0) { 229 | // Parent 230 | close(client); 231 | } else { 232 | // Failed 233 | perror("Unable to fork"); 234 | } 235 | } 236 | 237 | destroy_vhost(vhost, vhostcount); 238 | close(sock); 239 | cleanup_openssl(); 240 | 241 | free(global); 242 | free(vhostlist); 243 | config_destroy(&cfg); 244 | } 245 | 246 | -------------------------------------------------------------------------------- /mime.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, J. von Rotz 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 11 | * 2. Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * 3. Neither the name of the copyright holder nor the names of its 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | * 30 | */ 31 | 32 | #include 33 | #include 34 | #include 35 | #include "mime.h" 36 | 37 | int guess_mime_type(char *path, const char *mime) { 38 | magic_t magic; 39 | 40 | #ifdef GEMINID_DEBUG 41 | fprintf(stderr, "Guessing magic for file %s\n", path); 42 | #endif /* GEMINID_DEBUG */ 43 | 44 | magic = magic_open(MAGIC_MIME_TYPE); 45 | magic_load(magic, NULL); 46 | magic_compile(magic, NULL); 47 | mime = magic_file(magic, path); 48 | 49 | if(mime == NULL) 50 | return 0; 51 | 52 | #ifdef GEMINID_DEBUG 53 | fprintf(stderr, "%s is %s\n", path, mime); 54 | #endif /* GEMINID_DEBUG */ 55 | 56 | return strlen(mime); 57 | } 58 | -------------------------------------------------------------------------------- /mime.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, J. von Rotz 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 11 | * 2. Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * 3. Neither the name of the copyright holder nor the names of its 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | * 30 | */ 31 | 32 | int guess_mime_type(char *path, const char *mime); 33 | -------------------------------------------------------------------------------- /parseurl.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2020 J. von Rotz 2 | * 3 | * Redistribution and use in source and binary forms, with or without 4 | * modification, are permitted provided that the following conditions are met: 5 | * 6 | * 1. Redistributions of source code must retain the above copyright notice, 7 | * this list of conditions and the following disclaimer. 8 | * 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * 13 | * 3. Neither the name of the copyright holder nor the names of its 14 | * contributors may be used to endorse or promote products derived 15 | * from this software without specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 20 | * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 21 | * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 25 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | #include 31 | #include 32 | #include 33 | #ifndef __linux__ 34 | #include 35 | #endif /* ! __linux__ */ 36 | #include "url.h" 37 | 38 | int main(int argc, char **argv) { 39 | URL *test; 40 | 41 | if(argc < 2) { 42 | fprintf(stderr, "Usage: %s \n", argv[0]); 43 | exit(EXIT_FAILURE); 44 | } 45 | 46 | printf("input: %s\n", argv[1]); 47 | test = alexurl(argv[1]); 48 | 49 | printf("scheme: %s\n", test->scheme); 50 | printf("userinfo: %s\n", test->userinfo); 51 | printf("host: %s\n", test->host); 52 | printf("port: %s\n", test->port); 53 | printf("path: %s\n", test->path); 54 | printf("query: %s\n", test->query); 55 | printf("fragment: %s\n", test->fragment); 56 | 57 | free(test); 58 | exit(EXIT_SUCCESS); 59 | } 60 | 61 | 62 | -------------------------------------------------------------------------------- /tls.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2020 J. von Rotz 2 | * 3 | * Redistribution and use in source and binary forms, with or without 4 | * modification, are permitted provided that the following conditions are met: 5 | * 6 | * 1. Redistributions of source code must retain the above copyright notice, 7 | * this list of conditions and the following disclaimer. 8 | * 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * 13 | * 3. Neither the name of the copyright holder nor the names of its 14 | * contributors may be used to endorse or promote products derived 15 | * from this software without specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 20 | * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 21 | * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 25 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include "tls.h" 39 | #include "gemini.h" 40 | #include "vhost.h" 41 | extern VHOST *vhost; 42 | extern unsigned int vhostcount; 43 | 44 | int create_socket(int port) { 45 | int s; 46 | struct sockaddr_in addr; 47 | 48 | addr.sin_family = AF_INET; 49 | addr.sin_port = htons(port); 50 | addr.sin_addr.s_addr = htonl(INADDR_ANY); 51 | 52 | s = socket(AF_INET, SOCK_STREAM, 0); 53 | if (s < 0) { 54 | perror("Unable to create socket"); 55 | exit(EXIT_FAILURE); 56 | } 57 | 58 | if (bind(s, (struct sockaddr*)&addr, sizeof(addr)) < 0) { 59 | perror("Unable to bind"); 60 | exit(EXIT_FAILURE); 61 | } 62 | 63 | if (listen(s, 1) < 0) { 64 | perror("Unable to listen"); 65 | exit(EXIT_FAILURE); 66 | } 67 | 68 | return s; 69 | } 70 | 71 | int create_socket6(int port) { 72 | int s; 73 | struct sockaddr_in6 addr; 74 | 75 | addr.sin6_family = AF_INET6; 76 | addr.sin6_port = htons(port); 77 | addr.sin6_addr = in6addr_any; 78 | 79 | s = socket(AF_INET6, SOCK_STREAM, 0); 80 | if (s < 0) { 81 | perror("Unable to create socket"); 82 | exit(EXIT_FAILURE); 83 | } 84 | 85 | if (bind(s, (struct sockaddr*)&addr, sizeof(addr)) < 0) { 86 | perror("Unable to bind"); 87 | exit(EXIT_FAILURE); 88 | } 89 | 90 | if (listen(s, 1) < 0) { 91 | perror("Unable to listen"); 92 | exit(EXIT_FAILURE); 93 | } 94 | 95 | return s; 96 | } 97 | 98 | void init_openssl() { 99 | SSL_load_error_strings(); 100 | OpenSSL_add_ssl_algorithms(); 101 | } 102 | 103 | void cleanup_openssl() { 104 | EVP_cleanup(); 105 | } 106 | 107 | SSL_CTX *create_context() { 108 | const SSL_METHOD *method; 109 | SSL_CTX *ctx; 110 | #ifdef TLS_USE_V1_2_METHOD 111 | method = TLSv1_2_server_method(); 112 | #else 113 | method = TLS_server_method(); 114 | #endif /*TLS_USE_V1_2_METHOD*/ 115 | 116 | ctx = SSL_CTX_new(method); 117 | if (!ctx) { 118 | perror("Unable to create SSL context"); 119 | ERR_print_errors_fp(stderr); 120 | exit(EXIT_FAILURE); 121 | } 122 | 123 | return ctx; 124 | } 125 | 126 | int sni_cb(SSL *ssl, int *ad, void *arg) { 127 | const char *servername; 128 | unsigned int i; 129 | int vr; 130 | SSL_CTX *r; 131 | 132 | /* Fix complaints about unused parameters */ 133 | (void)(ad); 134 | (void)(arg); 135 | 136 | if(ssl == NULL) 137 | return SSL_TLSEXT_ERR_NOACK; 138 | 139 | servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); 140 | 141 | if (!servername || servername[0] == '\0') 142 | return SSL_TLSEXT_ERR_NOACK; 143 | 144 | for(i=0; i < vhostcount; i++) { 145 | if(strcmp((vhost+i)->hostname, servername) == 0) { 146 | r = SSL_set_SSL_CTX(ssl, (vhost+i)->ctx); 147 | if(r == NULL) 148 | SSL_TLSEXT_ERR_NOACK; 149 | 150 | vr = set_current_vhost((vhost+i)); 151 | if(vr != 0) 152 | SSL_TLSEXT_ERR_NOACK; 153 | 154 | return SSL_TLSEXT_ERR_OK; 155 | } 156 | } 157 | 158 | return SSL_TLSEXT_ERR_NOACK; 159 | } 160 | 161 | void configure_context(SSL_CTX *ctx, const char *cert_public_path, const char *cert_private_path) { 162 | SSL_CTX_set_ecdh_auto(ctx, 1); 163 | 164 | if (SSL_CTX_use_certificate_chain_file(ctx, cert_public_path) <= 0) { 165 | ERR_print_errors_fp(stderr); 166 | exit(EXIT_FAILURE); 167 | } 168 | 169 | if (SSL_CTX_use_PrivateKey_file(ctx, cert_private_path, SSL_FILETYPE_PEM) <= 0 ) { 170 | ERR_print_errors_fp(stderr); 171 | exit(EXIT_FAILURE); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /tls.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2020 J. von Rotz 2 | * 3 | * Redistribution and use in source and binary forms, with or without 4 | * modification, are permitted provided that the following conditions are met: 5 | * 6 | * 1. Redistributions of source code must retain the above copyright notice, 7 | * this list of conditions and the following disclaimer. 8 | * 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. 11 | * 12 | * 3. Neither the name of the copyright holder nor the names of its 13 | * contributors may be used to endorse or promote products derived 14 | * from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 18 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 19 | * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 20 | * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | */ 28 | 29 | int create_socket(int port); 30 | int create_socket6(int port); 31 | void init_openssl(); 32 | void cleanup_openssl(); 33 | SSL_CTX *create_context(); 34 | void configure_context(SSL_CTX *ctx, const char *cert_public_path, const char *cert_private_path); 35 | int sni_cb(SSL *ssl, int *ad, void *arg); 36 | extern unsigned int vhostcount; 37 | -------------------------------------------------------------------------------- /url.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2020 J. von Rotz 2 | * 3 | * Redistribution and use in source and binary forms, with or without 4 | * modification, are permitted provided that the following conditions are met: 5 | * 6 | * 1. Redistributions of source code must retain the above copyright notice, 7 | * this list of conditions and the following disclaimer. 8 | * 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * 13 | * 3. Neither the name of the copyright holder nor the names of its 14 | * contributors may be used to endorse or promote products derived 15 | * from this software without specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 20 | * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 21 | * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 25 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include "url.h" 35 | 36 | int build_request_string(char *buf, int bufsiz, URL *url) { 37 | if(url == NULL) 38 | return -1; 39 | 40 | bufsiz--; /* Save one for \0 */ 41 | if(bufsiz < 1) 42 | return -1; 43 | 44 | return snprintf(buf, bufsiz, "%s%s%s%s%s%s", url->scheme, url->host, url->port, url->path, url->query, url->fragment); 45 | } 46 | 47 | /* URL encoding function, stolen from 48 | * https://stackoverflow.com/questions/5842471/c-url-encoding 49 | */ 50 | 51 | unsigned char *new_url_encoder_table() { 52 | int i; 53 | unsigned char *table; 54 | 55 | table = malloc(256); 56 | if(!table) 57 | return NULL; 58 | 59 | for(i = 0; i < 256; i++) { 60 | table[i] = isalnum(i) || i == '~' || i == '*' || i == '-' || i == '.' || i == '_' || i == '/' || i == ':' ? i : 0; 61 | } 62 | 63 | return table; 64 | } 65 | 66 | int url_encode(unsigned char *table, unsigned char *inbuf, char *outbuf, int outbufsiz) { 67 | int outlen = 0; 68 | 69 | for (; *inbuf; inbuf++) { 70 | if(outlen + 3 > outbufsiz) 71 | return outbufsiz; 72 | 73 | if(table[*inbuf]) 74 | outlen += sprintf(outbuf, "%c", table[*inbuf]); 75 | else 76 | outlen += sprintf(outbuf, "%%%02X", *inbuf); 77 | 78 | while(*++outbuf); 79 | } 80 | 81 | return outlen; 82 | } 83 | 84 | /* URL decoding function, stolen from 85 | * https://stackoverflow.com/questions/2673207/c-c-url-decode-library 86 | */ 87 | int url_decode(unsigned char *dst, const char *src, int bufsiz) { 88 | char a, b; 89 | int outlen = 0; 90 | while (*src) { 91 | if(outlen > bufsiz) 92 | return -1; 93 | 94 | if ((*src == '%') && 95 | ((a = src[1]) && (b = src[2])) && 96 | (isxdigit(a) && isxdigit(b))) { 97 | if (a >= 'a') 98 | a -= 'a'-'A'; 99 | if (a >= 'A') 100 | a -= ('A' - 10); 101 | else 102 | a -= '0'; 103 | if (b >= 'a') 104 | b -= 'a'-'A'; 105 | if (b >= 'A') 106 | b -= ('A' - 10); 107 | else 108 | b -= '0'; 109 | *dst = 16*a+b; 110 | src+=3; 111 | dst++; 112 | } else { 113 | *dst = *src; 114 | dst++; 115 | src++; 116 | } 117 | outlen++; 118 | } 119 | 120 | *dst++ = '\0'; 121 | return outlen; 122 | } 123 | -------------------------------------------------------------------------------- /url.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2020 J. von Rotz 2 | * 3 | * Redistribution and use in source and binary forms, with or without 4 | * modification, are permitted provided that the following conditions are met: 5 | * 6 | * 1. Redistributions of source code must retain the above copyright notice, 7 | * this list of conditions and the following disclaimer. 8 | * 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * 13 | * 3. Neither the name of the copyright holder nor the names of its 14 | * contributors may be used to endorse or promote products derived 15 | * from this software without specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 20 | * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 21 | * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 25 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | #define MAX_URL_SCHEME 15 31 | #define MAX_URL_USERINFO 31 32 | #define MAX_URL_HOST 511 33 | #define MAX_URL_PORT 7 34 | #define MAX_URL_PATH 1023 35 | #define MAX_URL_QUERY 1023 36 | #define MAX_URL_FRAGMENT 127 37 | 38 | typedef struct { 39 | char scheme[MAX_URL_SCHEME+1]; 40 | char userinfo[MAX_URL_USERINFO+1]; 41 | char host[MAX_URL_HOST+1]; 42 | char port[MAX_URL_PORT+1]; 43 | char path[MAX_URL_PATH+1]; 44 | char query[MAX_URL_QUERY+1]; 45 | char fragment[MAX_URL_FRAGMENT+1]; 46 | } URL; 47 | 48 | int lexurl(URL *url, char *buf); 49 | URL *alexurl(char *buf); 50 | int build_request_string(char *buf, int bufsiz, URL *url); 51 | unsigned char *new_url_encoder_table(); 52 | int url_encode(unsigned char *table, unsigned char *inbuf, char *outbuf, int outbufsiz); 53 | int url_decode(unsigned char *dst, const char *src, int bufsiz); 54 | -------------------------------------------------------------------------------- /util.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2020 J. von Rotz 2 | * 3 | * Redistribution and use in source and binary forms, with or without 4 | * modification, are permitted provided that the following conditions are met: 5 | * 6 | * 1. Redistributions of source code must retain the above copyright notice, 7 | * this list of conditions and the following disclaimer. 8 | * 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * 13 | * 3. Neither the name of the copyright holder nor the names of its 14 | * contributors may be used to endorse or promote products derived 15 | * from this software without specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 20 | * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 21 | * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 25 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #ifdef __linux__ 37 | #include 38 | #else 39 | #include 40 | #endif /* __linux__ */ 41 | #include "util.h" 42 | 43 | /* Stolen from https://stackoverflow.com/questions/122616/how-do-i-trim-leading-trailing-whitespace-in-a-standard-way */ 44 | 45 | char *trim(char *str) { 46 | size_t len = 0; 47 | char *frontp = str; 48 | char *endp = NULL; 49 | 50 | if( str == NULL ) { return NULL; } 51 | if( str[0] == '\0' ) { return str; } 52 | 53 | len = strlen(str); 54 | endp = str + len; 55 | 56 | /* Move the front and back pointers to address the first non-whitespace 57 | * characters from each end. 58 | */ 59 | while( isspace((unsigned char) *frontp) ) { ++frontp; } 60 | if( endp != frontp ) 61 | { 62 | while( isspace((unsigned char) *(--endp)) && endp != frontp ) {} 63 | } 64 | 65 | if( frontp != str && endp == frontp ) 66 | *str = '\0'; 67 | else if( str + len - 1 != endp ) 68 | *(endp + 1) = '\0'; 69 | 70 | /* Shift the string so that it starts at str so that if it's dynamically 71 | * allocated, we can still free it on the returned pointer. Note the reuse 72 | * of endp to mean the front of the string buffer now. 73 | */ 74 | endp = str; 75 | if( frontp != str ) 76 | { 77 | while( *frontp ) { *endp++ = *frontp++; } 78 | *endp = '\0'; 79 | } 80 | 81 | return str; 82 | } 83 | /* FIXME: This function seems to be never used */ 84 | char *substring(char *s, int pos, int length) { 85 | char *sp; 86 | int c; 87 | 88 | sp = malloc(length+1); 89 | 90 | if(sp == NULL) { 91 | printf("Unable to allocate memory.\n"); 92 | exit(1); 93 | } 94 | 95 | for (c = 0 ; c < length ; c++) { 96 | *(sp+c) = *(sp+pos-1); 97 | s++; 98 | } 99 | 100 | *(sp+c) = '\0'; 101 | return sp; 102 | } 103 | -------------------------------------------------------------------------------- /util.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2020 J. von Rotz 2 | * 3 | * Redistribution and use in source and binary forms, with or without 4 | * modification, are permitted provided that the following conditions are met: 5 | * 6 | * 1. Redistributions of source code must retain the above copyright notice, 7 | * this list of conditions and the following disclaimer. 8 | * 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * 13 | * 3. Neither the name of the copyright holder nor the names of its 14 | * contributors may be used to endorse or promote products derived 15 | * from this software without specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 20 | * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 21 | * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 25 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | char *trim(char *str); 31 | char *substring(char *s, int pos, int length); 32 | -------------------------------------------------------------------------------- /vhost.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2020 J. von Rotz 2 | * 3 | * Redistribution and use in source and binary forms, with or without 4 | * modification, are permitted provided that the following conditions are met: 5 | * 6 | * 1. Redistributions of source code must retain the above copyright notice, 7 | * this list of conditions and the following disclaimer. 8 | * 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * 13 | * 3. Neither the name of the copyright holder nor the names of its 14 | * contributors may be used to endorse or promote products derived 15 | * from this software without specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 20 | * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 21 | * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 25 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include "tls.h" 36 | #include "vhost.h" 37 | 38 | VHOST *current_vhost = NULL; 39 | 40 | VHOST *create_vhost(const char *hostname, const char *docroot, const char *defaultdocument, const char *accesslog_path, const char *errorlog_path, const char *cert_public_path, const char *cert_private_path) { 41 | VHOST *vhost; 42 | 43 | vhost = calloc(1, sizeof(VHOST)); 44 | vhost->hostname = calloc(strlen(hostname)+1, sizeof(char)); 45 | if(vhost->hostname == NULL) 46 | return NULL; 47 | 48 | strncpy(vhost->hostname, hostname, strlen(hostname)+1); 49 | 50 | vhost->docroot = calloc(strlen(docroot)+1, sizeof(char)); 51 | if(vhost->docroot == NULL) 52 | return NULL; 53 | 54 | strncpy(vhost->docroot, docroot, strlen(docroot)+1); 55 | 56 | vhost->defaultdocument = calloc(strlen(defaultdocument)+1, sizeof(char)); 57 | if(vhost->defaultdocument == NULL) 58 | return NULL; 59 | 60 | strncpy(vhost->defaultdocument, defaultdocument, strlen(defaultdocument)+1); 61 | 62 | vhost->accesslog = fopen(accesslog_path, "a"); 63 | if(vhost->accesslog == NULL) { 64 | fprintf(stderr, "Cannot open access log: %s\n", accesslog_path); 65 | exit(EXIT_FAILURE); 66 | } 67 | 68 | 69 | vhost->errorlog = fopen(errorlog_path, "a"); 70 | if(vhost->errorlog == NULL) { 71 | fprintf(stderr, "Cannot open error log: %s\n", errorlog_path); 72 | exit(EXIT_FAILURE); 73 | } 74 | 75 | vhost->ctx = create_context(); 76 | if(vhost->ctx == NULL) { 77 | fprintf(stderr, "Error creating SSL context\n"); 78 | } 79 | 80 | configure_context(vhost->ctx, cert_public_path, cert_private_path); 81 | return vhost; 82 | } 83 | 84 | void destroy_vhost(VHOST *vhost, unsigned int count) { 85 | unsigned int i; 86 | for(i=0; i < count; i++) { 87 | free((vhost+i)->hostname); 88 | free((vhost+i)->docroot); 89 | free((vhost+i)->defaultdocument); 90 | fclose((vhost+i)->accesslog); 91 | fclose((vhost+i)->errorlog); 92 | SSL_CTX_free((vhost+i)->ctx); 93 | } 94 | free(vhost); 95 | } 96 | 97 | int set_current_vhost(VHOST *vhost) { 98 | current_vhost = vhost; 99 | if(current_vhost == NULL) 100 | return -1; 101 | 102 | return 0; 103 | } 104 | 105 | VHOST *get_current_vhost() { 106 | return current_vhost; 107 | } 108 | -------------------------------------------------------------------------------- /vhost.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2020 J. von Rotz 2 | * 3 | * Redistribution and use in source and binary forms, with or without 4 | * modification, are permitted provided that the following conditions are met: 5 | * 6 | * 1. Redistributions of source code must retain the above copyright notice, 7 | * this list of conditions and the following disclaimer. 8 | * 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * 13 | * 3. Neither the name of the copyright holder nor the names of its 14 | * contributors may be used to endorse or promote products derived 15 | * from this software without specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 20 | * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 21 | * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 25 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | typedef struct { 31 | char *hostname; 32 | char *docroot; 33 | char *defaultdocument; 34 | SSL_CTX *ctx; 35 | FILE *accesslog; 36 | FILE *errorlog; 37 | } VHOST; 38 | 39 | VHOST *create_vhost(const char *hostname, const char *docroot, const char *defaultdocument, const char *accesslog_path, const char *errorlog_path, const char *cert_public_path, const char *cert_private_path); 40 | void destroy_vhost(VHOST *vhost, unsigned int count); 41 | int set_current_vhost(VHOST *vhost); 42 | VHOST *get_current_vhost(); 43 | --------------------------------------------------------------------------------