💾 Archived View for gemini.rmf-dev.com › repo › Vaati › gmi_proxy › files › 26d24d4b741a640bf150f496… captured on 2023-05-24 at 18:44:44. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2023-04-19)
-=-=-=-=-=-=-
0 /* See LICENSE file for copyright and license details. */
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <errno.h>
5 #include <unistd.h>
6 #include <stdint.h>
7 #include <fcntl.h>
8 #include <time.h>
9 #include <pthread.h>
10 #include "strlcpy.h"
11 #include "util.h"
12 #include "server.h"
13 #include "conf.h"
14 #include "log.h"
15 #include "murmur3.h"
16
17 #define MAX_RETRY 2048
18 #define DEFAULT_CERT_DIR "./clients"
19
20 struct conf conf = {0};
21 struct server *servers = NULL;
22 int hashtable_seed = 0;
23 uint32_t hashtable_size = 0;
24 uint32_t hashtable_bits = 0;
25
26 enum {
27 READING_NONE,
28 READING_LEFT,
29 READING_RIGHT,
30 READING_COMMENT
31 };
32
33 enum {
34 SP_NAME,
35 SP_CERTIFICATE,
36 SP_KEY,
37 SP_RELAY_TO,
38 SP_PLAIN,
39 SP_FORWARD_IP,
40 SP_CLIENT_CERTIFICATES
41 };
42
43 enum {
44 P_CLIENT_CERTIFICATES_FOLDER,
45 P_THREADS,
46 P_LOG
47 };
48
49 enum {
50 VALUE_INVALID,
51 VALUE_STRING,
52 VALUE_BOOL,
53 VALUE_INT
54 };
55
56 const char *server_parameters[] = {
57 "name",
58 "certificate",
59 "key",
60 "relay-to",
61 "plain",
62 "forward-ip",
63 "client-certificates"
64 };
65
66
67 const char *parameters[] = {
68 "certificates-directory",
69 "threads",
70 "log"
71 };
72
73 struct server_node {
74 struct server server;
75 struct server_node *next;
76 };
77
78 int conf_parse_value(const char *value,
79 char* string, size_t len, bool *b, int *i) {
80 if (string && *value == '"') {
81 char *end = string + len;
82 value++;
83 while (*value && string < end) {
84 if (*value == '"') {
85 if (value[1]) return VALUE_INVALID;
86 *string = '\0';
87 return VALUE_STRING;
88 }
89 *string++ = *value++;
90 }
91 return VALUE_INVALID;
92 }
93 if (b) {
94 if (!strcmp(value, "true") || !strcmp(value, "True")) {
95 *b = true;
96 return VALUE_BOOL;
97 }
98 if (!strcmp(value, "false") || !strcmp(value, "False")) {
99 *b = false;
100 return VALUE_BOOL;
101 }
102 }
103 if (i) {
104 int v;
105 if (!strcmp(value, "0")) {
106 *i = 0;
107 return VALUE_INT;
108 }
109 v = atoi(value);
110 if (!v)
111 return VALUE_INVALID;
112 *i = v;
113 return VALUE_INT;
114 }
115 return VALUE_INVALID;
116 }
117
118
119 int conf_parse(const char *word) {
120
121 size_t i;
122 int type;
123 int integer;
124 bool found, boolean;
125 char string[WORD_SIZE], *value;
126
127 value = strchr(word, ':');
128 if (!value) return -1;
129 *value = '\0';
130 value++;
131
132 found = false;
133 for (i = 0; i < SIZEOF(parameters); i++) {
134 if (!strcmp(parameters[i], word)) {
135 found = true;
136 break;
137 }
138 }
139 if (!found) return -1;
140
141 type = conf_parse_value(value, string, sizeof(string),
142 &boolean, &integer);
143
144 switch (i) {
145 case P_CLIENT_CERTIFICATES_FOLDER:
146 case P_LOG:
147 if (type != VALUE_STRING) return -1;
148 #ifdef DEBUG
149 log_debug("%s = %s", word, string);
150 #endif
151 break;
152 case P_THREADS:
153 if (type != VALUE_INT) return -1;
154 #ifdef DEBUG
155 log_debug("%s = %d", word, integer);
156 #endif
157 break;
158 default:
159 return -1;
160 }
161
162 switch (i) {
163 case P_LOG:
164 {
165 int fd = open(string, O_WRONLY | O_CREAT | O_APPEND,
166 0600);
167 if (fd < 0) {
168 log_error("unable to open %s: %s",
169 string, strerror(errno));
170 return -2;
171 }
172 close(fileno(stdout));
173 if (dup(fd) == -1) {
174 log_error("dub: %s", strerror(errno));
175 return -2;
176 }
177 close(fd);
178 }
179 break;
180 case P_CLIENT_CERTIFICATES_FOLDER:
181 {
182 int fd = open(string, O_DIRECTORY);
183 if (fd < 0) {
184 log_error("directory not found: %s", string);
185 return -2;
186 }
187 close(fd);
188 }
189 STRCPY(conf.clients, string);
190 break;
191 case P_THREADS:
192 conf.threads = integer;
193 break;
194 default:
195 return -1;
196 }
197
198 return 0;
199 }
200
201 int conf_parse_server(struct server *server, const char *word) {
202
203 size_t i;
204 int type;
205 bool found, boolean;
206 char string[WORD_SIZE], *value;
207
208 value = strchr(word, ':');
209 if (!value) return -1;
210 *value = '\0';
211 value++;
212
213 found = false;
214 for (i = 0; i < SIZEOF(server_parameters); i++) {
215 if (!strcmp(server_parameters[i], word)) {
216 found = true;
217 break;
218 }
219 }
220 if (!found) return -1;
221
222 type = conf_parse_value(value, string, sizeof(string), &boolean, NULL);
223
224 switch (i) {
225 case SP_NAME:
226 case SP_CERTIFICATE:
227 case SP_KEY:
228 case SP_RELAY_TO:
229 if (type != VALUE_STRING) return -1;
230 #ifdef DEBUG
231 log_debug("%s = %s", word, string);
232 #endif
233 break;
234 case SP_PLAIN:
235 case SP_FORWARD_IP:
236 case SP_CLIENT_CERTIFICATES:
237 if (type != VALUE_BOOL) return -1;
238 #ifdef DEBUG
239 log_debug("%s = %s", word, boolean ? "true" : "false");
240 #endif
241 break;
242 default:
243 return -1;
244 }
245
246 switch (i) {
247 case SP_NAME:
248 STRCPY(server->name, string);
249 break;
250 case SP_CERTIFICATE:
251 STRCPY(server->certificate, string);
252 break;
253 case SP_KEY:
254 STRCPY(server->key, string);
255 break;
256 case SP_RELAY_TO:
257 STRCPY(server->relayto, string);
258 {
259 char *ptr = strchr(server->relayto, ':');
260 if (ptr) {
261 *ptr++ = '\0';
262 if (atoi(ptr) == 0) return -1;
263 STRCPY(server->relayto_port, ptr);
264 } else {
265 STRCPY(server->relayto_port, "1965");
266 }
267 }
268 break;
269 case SP_PLAIN:
270 server->plain = boolean;
271 break;
272 case SP_FORWARD_IP:
273 server->forward = boolean;
274 break;
275 case SP_CLIENT_CERTIFICATES:
276 server->client = boolean;
277 break;
278 default:
279 return -1;
280 }
281
282 return 0;
283 }
284
285 int conf_load(const char *path) {
286
287 size_t length, line, count;
288 FILE *f;
289 char *data, *ptr, *end;
290 bool in, failure, string, reading;
291 struct server_node *nodes = NULL;
292 struct server server = {0};
293 char word[WORD_SIZE] = {0};
294 char *word_ptr = word, *word_end = word + sizeof(word);
295
296 STRCPY(conf.clients, DEFAULT_CERT_DIR);
297
298 f = fopen(path, "r");
299 if (!f) {
300 log_error("Failed to open %s: %s", path, strerror(errno));
301 return -1;
302 }
303
304 fseek(f, 0, SEEK_END);
305 length = ftell(f);
306 fseek(f, 0, SEEK_SET);
307
308 data = malloc(length);
309 if (!data) {
310 log_error("Memory allocation failure: %s", strerror(errno));
311 fclose(f);
312 return -1;
313 }
314
315 if (fread(data, 1, length, f) != length) {
316 log_error("Failed to read file: %s", strerror(errno));
317 fclose(f);
318 free(data);
319 return -1;
320 }
321
322 fclose(f);
323
324 ptr = data;
325 end = data + length;
326 reading = string = failure = in = false;
327 line = 1;
328 count = 0;
329 while (ptr < end) {
330 switch (*ptr) {
331 case '\n':
332 if (string) {
333 failure = true;
334 break;
335 }
336 if (word_ptr > word) {
337 int ret;
338 *word_ptr = '\0';
339 word_ptr = word;
340 ret = in ? conf_parse_server(&server, word) :
341 conf_parse(word);
342 if (ret) {
343 failure = ret == -1 ? true : ret;
344 break;
345 }
346 }
347 reading = READING_NONE;
348 line++;
349 /* fallthrough */
350 case ' ':
351 case '\t':
352 if (string) goto in_string;
353 break;
354 case '{':
355 if (reading == READING_COMMENT) break;
356 if (in) {
357 failure = true;
358 break;
359 }
360 in = true;
361 break;
362 case '}':
363 if (reading == READING_COMMENT) break;
364 if (!in || word != word_ptr) {
365 missing:
366 failure = true;
367 break;
368 }
369 if (!server.certificate[0]) {
370 log_error("missing certificate parameter");
371 goto missing;
372 }
373 if (!server.key[0]) {
374 log_error("missing key parameter");
375 goto missing;
376 }
377 if (!server.relayto[0]) {
378 log_error("missing relay-to parameter");
379 goto missing;
380 }
381 if (!server.name[0]) {
382 log_error("missing name parameter");
383 goto missing;
384 }
385 in = false;
386 {
387 struct server_node *node =
388 malloc(sizeof(struct server_node));
389 if (!node) {
390 log_error("memory allocation failure");
391 exit(-1);
392 }
393 node->server = server;
394 node->next = nodes;
395 nodes = node;
396 memset(&server, 0, sizeof(server));
397 count++;
398 }
399 break;
400 default:
401 if (reading == READING_COMMENT ||
402 (*ptr == '#' && !string)) {
403 reading = READING_COMMENT;
404 string = false;
405 break;
406 }
407 if (*ptr == '"') {
408 if (reading == READING_LEFT) {
409 failure = true;
410 break;
411 }
412 string = !string;
413 }
414 in_string:
415 if (word_ptr == word)
416 reading = READING_LEFT;
417 if (!string && *ptr == ':') reading = READING_RIGHT;
418 *word_ptr = *ptr;
419 word_ptr++;
420 if (word_ptr >= word_end) {
421 log_error("line is too long");
422 failure = true;
423 break;
424 }
425 break;
426 }
427 if (failure) break;
428 ptr++;
429 }
430
431 free(data);
432
433 if (failure) {
434 if (failure == true)
435 log_error("invalid syntax: line %ld", line);
436 return -1;
437 }
438
439 if (in) {
440 log_error("invalid syntax: non-closing '{'");
441 return -1;
442 }
443
444 {
445
446 struct server_node *node;
447 uint32_t size = count;
448 int seed, try;
449
450 /* find the nearest power of 2 so we can use & instead of % */
451 size--;
452 size |= size >> 1;
453 size |= size >> 2;
454 size |= size >> 4;
455 size |= size >> 8;
456 size |= size >> 16;
457 size++;
458
459 servers = malloc(sizeof(server) * (size + 1));
460 if (!servers) {
461 log_error("memory allocation failure");
462 return -1;
463 }
464
465 try = 0;
466 retry:
467 node = nodes;
468 seed = rand();
469 memset(servers, 0, size * sizeof(server));
470 while (node) {
471 size_t len = strnlen(node->server.name,
472 sizeof(node->server.name));
473 uint32_t hash = murmur3(node->server.name, len, seed)
474 & (size - 1);
475 if (servers[hash].exist) {
476 if (!strcmp(node->server.name,
477 servers[hash].name)) {
478 log_error("multiple instances of %s",
479 node->server.name);
480 return -1;
481 }
482 try++;
483 if (try < MAX_RETRY) goto retry;
484 log_warning("resizing hash table");
485 size *= 2;
486 servers = realloc(servers,
487 sizeof(server) * (size + 1));
488 if (!servers) {
489 log_error("memory allocation failure");
490 return -1;
491 }
492 try = 0;
493 goto retry;
494 }
495 servers[hash] = node->server;
496 servers[hash].exist = true;
497 node = node->next;
498 }
499 servers[size].exist = END_OF_ARRAY;
500 hashtable_seed = seed;
501 hashtable_size = size;
502 hashtable_bits = size - 1;
503
504 #ifdef DEBUG
505 log_debug("hash table of size %d created in %d %s", size,
506 try + 1, try ? "tries" : "try");
507 #endif
508 }
509
510 while (nodes) {
511 struct server_node *next = nodes->next;
512 free(nodes);
513 nodes = next;
514 }
515
516 if (!count) {
517 log_error("The configuration file does "
518 "not contain any virtual servers");
519 return -1;
520 }
521
522 log_info("%d virtual servers loaded", count);
523
524 return 0;
525 }
526
527 struct server *conf_get(const char *name) {
528 unsigned int hash = murmur3(name, strlen(name), hashtable_seed);
529 struct server *server = &servers[hash & (hashtable_bits)];
530 #ifndef SKIP_VERIFICATION
531 /* verification could be removed if we would fetch the domain name
532 * from the certificate instead of having it as a field in the
533 * configuration file */
534 const char *ptr = server->name;
535 const char *end = server->name + sizeof(server->name);
536 while (*ptr) {
537 if (*(ptr++) != *(name++))
538 return NULL;
539 if (ptr >= end)
540 return NULL;
541 }
542 #endif
543 return &servers[hash & (hashtable_bits)];
544 }
545
546 void conf_unload() {
547 free(servers);
548 }
549