/* swebs - a simple web server Copyright (C) 2022 Nate Choe This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include #include #if DYNAMIC_LINKED_PAGES #include #endif /* * This if isn't technically necessary, but it generates warnings, which is * good. * */ typedef enum { SUCCESS, LINE_END, FILE_END, ERROR } ReturnCode; /* this isn't ideal, but it's necessary to avoid namespace collisions. */ static void freeTokens(int argc, char **argv) { int i; for (i = 0; i < argc; i++) free(argv[i]); free(argv); } static ReturnCode getToken(FILE *file, char **ret) { typedef enum { QUOTED, NONQUOTED } TokenType; TokenType type; size_t allocatedLen = 50; size_t len; for (;;) { int c = fgetc(file); if (c == '\n') return LINE_END; if (c == EOF) return FILE_END; if (c == '#') { while (c != '\n') c = fgetc(file); return LINE_END; } if (!isspace(c)) { if (c == '"') type = QUOTED; else { type = NONQUOTED; ungetc(c, file); } break; } } *ret = malloc(allocatedLen); for (len = 0;; len++) { int c; if (len >= allocatedLen) { char *newret; allocatedLen *= 2; newret = realloc(*ret, allocatedLen); if (newret == NULL) goto error; *ret = newret; } c = fgetc(file); switch (type) { case QUOTED: if (c == '"') goto gotToken; break; case NONQUOTED: if (isspace(c)) { ungetc(c, file); goto gotToken; } break; } switch (c) { case '\\': c = fgetc(file); if (c == EOF) goto error; break; case EOF: if (type == NONQUOTED) goto gotToken; goto error; } (*ret)[len] = c; } gotToken: (*ret)[len] = '\0'; return SUCCESS; error: free(*ret); return ERROR; } static ReturnCode getCommand(FILE *file, int *argcret, char ***argvret) { /* THIS FUNCTION WILL NOT RETURN LINE_END */ int argc; char **argv; int allocatedTokens; if (feof(file)) return FILE_END; argc = 0; allocatedTokens = 5; argv = malloc(allocatedTokens * sizeof(*argv)); for (;;) { ReturnCode code; if (argc >= allocatedTokens) { char **newargv; allocatedTokens *= 2; newargv = realloc(argv, allocatedTokens * sizeof(char *)); if (newargv == NULL) goto error; argv = newargv; } code = getToken(file, argv + argc); switch (code) { case ERROR: goto error; case LINE_END: if (argc == 0) continue; /* We allow empty lines */ /* fallthrough */ case FILE_END: if (argc == 0) { free(argv); return FILE_END; } *argcret = argc; *argvret = argv; return SUCCESS; case SUCCESS: argc++; break; } } error: freeTokens(argc, argv); return ERROR; } Sitefile *parseSitefile(char *path) { FILE *file; RequestType respondto = GET; const int cflags = REG_EXTENDED | REG_ICASE; char *host = NULL; int argc; char **argv; Sitefile *ret; unsigned short currport; currport = 80; file = fopen(path, "r"); if (file == NULL) return NULL; ret = xmalloc(sizeof *ret); ret->size = 0; ret->alloc = 50; ret->content = xmalloc(ret->alloc * sizeof *ret->content); ret->portcount = 0; ret->portalloc = 5; ret->ports = xmalloc(ret->portalloc * sizeof *ret->ports); #if DYNAMIC_LINKED_PAGES ret->getResponse = NULL; #endif for (;;) { ReturnCode status = getCommand(file, &argc, &argv); switch (status) { int i; case FILE_END: for (i = 0; i < ret->portcount; ++i) { Port *port = ret->ports + i; if (port->type == TLS && (port->key == NULL || port->cert == NULL)) { fprintf(stderr, "Port %hu declared as TLS without proper TLS files\n", port->num); goto nterror; } } fclose(file); return ret; case ERROR: case LINE_END: goto nterror; case SUCCESS: break; } if (strcmp(argv[0], "set") == 0) { if (argc < 3) goto error; if (strcmp(argv[1], "respondto") == 0) { respondto = getType(argv[2]); if (respondto == INVALID) goto error; } else if (strcmp(argv[1], "host") == 0) host = xstrdup(argv[2]); else if (strcmp(argv[1], "port") == 0) currport = atoi(argv[2]); else goto error; continue; } else if (strcmp(argv[0], "define") == 0) { if (argc < 3) goto error; else if (strcmp(argv[1], "library") == 0) { #if DYNAMIC_LINKED_PAGES ret->getResponse = loadGetResponse(argv[2]); #else fputs( "This version of swebs has no dynamic page support\n", stderr); exit(EXIT_FAILURE); #endif } else goto error; continue; } else if (strcmp(argv[0], "declare") == 0) { Port newport; int i; if (argc < 3) { fputs( "Usage: declare [transport] [port]\n", stderr); goto error; } newport.num = atoi(argv[2]); for (i = 0; i < ret->portcount; ++i) { if (ret->ports[i].num == newport.num) { fprintf(stderr, "Port %hu declared multiple times\n", newport.num); goto error; } } if (strcmp(argv[1], "TCP") == 0) newport.type = TCP; else if (strcmp(argv[1], "TLS") == 0) newport.type = TLS; else { fprintf(stderr, "Invalid transport %s\n", argv[1]); goto error; } newport.timeout = 2000; newport.key = newport.cert = NULL; if (ret->portcount >= ret->portalloc) { ret->portalloc *= 2; ret->ports = xrealloc(ret->ports, ret->portalloc * sizeof *ret->ports); } memcpy(ret->ports + ret->portcount, &newport, sizeof newport); ++ret->portcount; continue; } #define PORT_ATTRIBUTE(name, func) \ else if (strcmp(argv[0], #name) == 0) { \ int i; \ unsigned short port; \ if (argc < 3) { \ fputs("Usage: " #name " [" #name "] [port]\n", \ stderr); \ goto error; \ } \ port = atoi(argv[2]); \ for (i = 0; i < ret->portcount; ++i) \ if (ret->ports[i].num == port) \ ret->ports[i].name = func(argv[1]); \ continue; \ } PORT_ATTRIBUTE(key, xstrdup) PORT_ATTRIBUTE(cert, xstrdup) PORT_ATTRIBUTE(timeout, atoi) #undef PORT_ATTRIBUTE if (ret->size >= ret->alloc) { SiteCommand *newcontent; ret->alloc *= 2; newcontent = realloc(ret->content, ret->alloc * sizeof *newcontent); if (newcontent == NULL) goto error; ret->content = newcontent; } if (regcomp(&ret->content[ret->size].path, argv[1], cflags)) goto error; if (strcmp(argv[0], "read") == 0) { if (argc < 3) goto error; ret->content[ret->size].arg = xstrdup(argv[2]); if (ret->content[ret->size].arg == NULL) goto error; ret->content[ret->size].command = READ; } else if (strcmp(argv[0], "throw") == 0) { if (argc < 3) goto error; ret->content[ret->size].arg = getCode(atoi(argv[2])); if (ret->content[ret->size].arg == NULL) goto error; ret->content[ret->size].command = THROW; } else if (strcmp(argv[0], "linked") == 0) { #if DYNAMIC_LINKED_PAGES ret->content[ret->size].command = LINKED; #else fputs( "This version of swebs doesn't have linked page support", stderr); goto error; #endif } else { fprintf(stderr, "Unknown sitefile command %s", argv[0]); goto error; } freeTokens(argc, argv); ret->content[ret->size].respondto = respondto; if (host == NULL) regcomp(&ret->content[ret->size].host, ".*", cflags); else regcomp(&ret->content[ret->size].host, host, cflags); ret->content[ret->size].port = currport; ret->size++; } error: freeTokens(argc, argv); nterror: freeSitefile(ret); return NULL; } void freeSitefile(Sitefile *site) { long i; for (i = 0; i < site->size; i++) { regfree(&site->content[i].path); regfree(&site->content[i].host); free(site->content[i].arg); } free(site->content); free(site); }