Compare commits

...

10 Commits

Author SHA1 Message Date
Nate Choe
47f09c3ce2 Change default Accept to */* to conform to RFC 9112 2024-01-25 22:54:20 -06:00
Nate Choe
dce50642a0 Merge build and run in dockerfile 2023-09-28 01:33:10 -05:00
Nate Choe
8c6b4b9f76 Proper connection timeouts 2022-08-26 06:06:07 -05:00
Nate Choe
3cb29c6b76 Fixed sendKnownPipeValist() 2022-08-16 15:55:07 -05:00
Nate Choe
df9484283e Fixed tokenization (again) 2022-07-31 00:32:22 -05:00
Nate Choe
92eda6f326 Rewrote connection list code 2022-07-30 20:33:52 -05:00
Nate Choe
06c6faed73 Change HTML tag in example site 2022-07-28 14:12:36 -05:00
Nate Choe
4630000b9f Rewrote sitefile parsing 2022-07-28 14:11:45 -05:00
Nate Choe
52df580ccb Fixed tokenization 2022-07-25 05:08:33 -05:00
Nate Choe
36d2fefa3b Rewrote tokenization code 2022-07-25 04:08:43 -05:00
12 changed files with 529 additions and 426 deletions

View File

@@ -1,16 +1,8 @@
FROM debian:stable-slim AS build FROM debian:stable-slim AS build
RUN apt-get update -y && apt-get upgrade -y && apt-get install -y libgnutls28-dev libgnutls30 gcc make pkg-config RUN apt-get update -y && apt-get upgrade -y && apt-get install -y libgnutls28-dev libgnutls30 gcc make pkg-config
COPY . /swebs COPY . /swebs
WORKDIR /swebs WORKDIR /swebs
RUN make RUN make && make install
FROM debian:stable-slim AS run
RUN apt-get update -y && apt-get upgrade -y && apt-get install -y libgnutls28-dev libgnutls30
COPY --from=build /swebs/build/swebs /usr/sbin/swebs
RUN mkdir /usr/include/swebs
COPY --from=build /swebs/src/swebs /usr/include/swebs/
RUN useradd -M swebs
ENTRYPOINT [ "swebs", "-s", "/site/sitefile" ] ENTRYPOINT [ "swebs", "-s", "/site/sitefile" ]

View File

@@ -2,7 +2,7 @@ SRC = $(wildcard src/*.c)
OBJ = $(subst .c,.o,$(subst src,work,$(SRC))) OBJ = $(subst .c,.o,$(subst src,work,$(SRC)))
LIBS = gnutls LIBS = gnutls
LDFLAGS = -pie -lrt -ldl $(shell pkg-config --libs $(LIBS)) LDFLAGS = -pie -lrt -ldl $(shell pkg-config --libs $(LIBS))
CFLAGS := -O2 -pipe -Wall -Wpedantic -Wshadow -ansi -D_XOPEN_SOURCE=500 CFLAGS := -O2 -pipe -Wall -Wpedantic -Wshadow -ansi -D_XOPEN_SOURCE=500 -ggdb
CFLAGS += -Isrc/ -fpie -D_POSIX_C_SOURCE=200809L $(shell pkg-config --cflags $(LIBS)) CFLAGS += -Isrc/ -fpie -D_POSIX_C_SOURCE=200809L $(shell pkg-config --cflags $(LIBS))
INSTALLDIR := /usr/sbin INSTALLDIR := /usr/sbin
HEADERDIR := /usr/include/ HEADERDIR := /usr/include/

View File

@@ -9,6 +9,15 @@ sitefiles consist of commands, which are of the form
sitefiles also allow comments with # sitefiles also allow comments with #
Actions and arguments are made of tokens. A token is some text surrounded by
whitespace, where backslashes are always escaped literally.
For example:
`token` -> `"token"`
`token\ with\ escapes` -> 'token with escapes'
`token\\with\\backslashes` -> 'token\with\backslashes'
# Part 2: Commands # Part 2: Commands
* ```set [variable] [value]``` - sets some local variable for the following * ```set [variable] [value]``` - sets some local variable for the following

View File

@@ -1 +1 @@
<p1>I have nothing to blog about</p1> <p>I have nothing to blog about</p>

View File

@@ -6,8 +6,8 @@ key domain.key 8001
cert domain.crt 8001 cert domain.crt 8001
# Port 8001 can have TLS, but it's self signed and very bad. # Port 8001 can have TLS, but it's self signed and very bad.
timeout 200000 8000 timeout 20000 8000
timeout 200000 8001 timeout 20000 8001
# Set these values for the ports # Set these values for the ports
set port 8000 set port 8000
@@ -22,7 +22,7 @@ set type text/html
# The following pages are html # The following pages are html
read / site/index.html read / site/index.html
# The path / should be read from site/index.html # The path / should be read from site/index.html
read /hello site/hello.html read /hello site/hello.html
# The path /hello should be read from site/hello.html # The path /hello should be read from site/hello.html
throw /blog/forbidden 403 throw /blog/forbidden 403

View File

@@ -49,8 +49,9 @@ static struct sockaddr_un addr;
/* We want to be able to handle a signal at any time, so some global variables /* We want to be able to handle a signal at any time, so some global variables
* are needed. */ * are needed. */
static const int signals[] = { static const int signals[] = {
SIGPIPE, SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGABRT, SIGBUS, SIGFPE, SIGPIPE, SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGABRT, SIGBUS,
SIGKILL, SIGSEGV, SIGTERM, SIGTTIN, SIGTTOU, SIGURG, SIGXCPU, SIGXFSZ, SIGFPE, SIGKILL, SIGSEGV, SIGTERM, SIGTTIN, SIGTTOU, SIGURG, SIGXCPU,
SIGXFSZ,
}; };
static void exitClean(int signal) { static void exitClean(int signal) {
@@ -196,6 +197,7 @@ int main(int argc, char **argv) {
if (pending[j] < pending[lowestproc]) if (pending[j] < pending[lowestproc])
lowestproc = j; lowestproc = j;
sendFd(fd, runners[lowestproc].fd, &i, sizeof i); sendFd(fd, runners[lowestproc].fd, &i, sizeof i);
close(fd);
} }
} }
} }

View File

@@ -307,9 +307,42 @@ static int wasasked(char *request, char *type) {
return 0; return 0;
} }
static int sendCertainResponse(Connection *conn, Sitefile *site, int index) {
int ret;
ret = 0;
switch (site->content[index].command) {
case READ:
ret = readResponse(conn, site->content + index);
break;
case THROW:
ret = sendErrorResponse(conn->stream, site->content[index].arg);
break;
case LINKED:
#if DYNAMIC_LINKED_PAGES
if (!site->getResponse) {
sendErrorResponse(conn->stream, ERROR_500);
ret = 1;
}
else
ret = linkedResponse(conn, site->getResponse,
site->content[index].contenttype);
#else
/* Unreachable state (if a linked response was in the sitefile,
* the parse would've thrown an error) */
ret = sendErrorResponse(conn->stream, ERROR_500);
#endif
break;
default:
sendErrorResponse(conn->stream, ERROR_500);
return 1;
}
resetConnection(conn);
return ret;
}
int sendResponse(Connection *conn, Sitefile *site) { int sendResponse(Connection *conn, Sitefile *site) {
char *host = NULL; char *host = NULL;
char *accept = NULL; char *accept = "*/*";
int i; int i;
for (i = 0; i < conn->fieldCount; i++) { for (i = 0; i < conn->fieldCount; i++) {
if (strcmp(conn->fields[i].field, "Host") == 0) if (strcmp(conn->fields[i].field, "Host") == 0)
@@ -317,7 +350,7 @@ int sendResponse(Connection *conn, Sitefile *site) {
else if (strcmp(conn->fields[i].field, "Accept") == 0) else if (strcmp(conn->fields[i].field, "Accept") == 0)
accept = conn->fields[i].value; accept = conn->fields[i].value;
} }
if (host == NULL || accept == NULL) { if (host == NULL) {
sendErrorResponse(conn->stream, ERROR_400); sendErrorResponse(conn->stream, ERROR_400);
return 1; return 1;
} }
@@ -337,41 +370,8 @@ int sendResponse(Connection *conn, Sitefile *site) {
continue; continue;
} }
foundport: foundport:
if (fullmatch(&site->content[i].path, conn->path.data) == 0) { if (fullmatch(&site->content[i].path, conn->path.data) == 0)
switch (site->content[i].command) { return sendCertainResponse(conn, site, i);
case READ:
if (readResponse(conn,
site->content + i))
return 1;
break;
case THROW:
if (sendErrorResponse(conn->stream,
site->content[i].arg))
return 1;
break;
case LINKED:
#if DYNAMIC_LINKED_PAGES
if (!site->getResponse)
sendErrorResponse(conn->stream,
ERROR_500);
else if (linkedResponse(conn,
site->getResponse,
site->content[i].contenttype))
return 1;
#else
/* Unreachable state (filtered by startup) */
sendErrorResponse(conn->stream,
ERROR_500);
#endif
break;
default:
sendErrorResponse(conn->stream,
ERROR_500);
return 1;
}
resetConnection(conn);
return 0;
}
} }
sendErrorResponse(conn->stream, ERROR_404); sendErrorResponse(conn->stream, ERROR_404);
return 1; return 1;

View File

@@ -128,6 +128,8 @@ int sendErrorResponse(Stream *stream, const char *error) {
int ret; int ret;
int len = snprintf(NULL, 0, template, error); int len = snprintf(NULL, 0, template, error);
char *response = malloc(len + 1); char *response = malloc(len + 1);
if (response == NULL)
return 1;
sprintf(response, template, error); sprintf(response, template, error);
ret = sendStringResponse(stream, error, response, ret = sendStringResponse(stream, error, response,
"Content-Type: text/html\r\n", NULL); "Content-Type: text/html\r\n", NULL);
@@ -155,6 +157,7 @@ static int sendKnownPipeValist(Stream *stream, const char *status,
return totalSent != len; return totalSent != len;
if (resilientSend(stream, buffer, inBuffer)) if (resilientSend(stream, buffer, inBuffer))
return 1; return 1;
totalSent += inBuffer;
} }
} }
@@ -185,6 +188,8 @@ int sendPipe(Stream *stream, const char *status, int fd, ...) {
size_t responseLen = 0; size_t responseLen = 0;
char *response = malloc(allocResponse); char *response = malloc(allocResponse);
va_list ap; va_list ap;
if (response == NULL)
goto error;
for (;;) { for (;;) {
ssize_t len; ssize_t len;
if (responseLen >= allocResponse) { if (responseLen >= allocResponse) {

View File

@@ -31,19 +31,39 @@
#include <swebs/sitefile.h> #include <swebs/sitefile.h>
#include <swebs/connections.h> #include <swebs/connections.h>
void runServer(int connfd, Sitefile *site, volatile int *pending, int id) { typedef struct {
int allocConns = 100;
struct pollfd *fds; struct pollfd *fds;
Connection *connections; Connection *conns;
int connCount; int len;
int alloc;
} ConnList;
static int createConnList(ConnList *list);
static int addConnList(ConnList *list, struct pollfd *fd, Connection *conn);
static void removeConnList(ConnList *list, int ind);
static void pollConnList(ConnList *list);
static void freeConnList(ConnList *list);
void runServer(int connfd, Sitefile *site, volatile int *pending, int id) {
Context **contexts; Context **contexts;
int i; int i;
ConnList conns;
connCount = 1; if (createConnList(&conns))
fds = xmalloc(allocConns * sizeof *fds); return;
connections = xmalloc(allocConns * sizeof *connections);
fds[0].fd = connfd; {
fds[0].events = POLLIN; struct pollfd newfd;
Connection newconn;
newfd.fd = connfd;
newfd.events = POLLIN;
if (addConnList(&conns, &newfd, &newconn)) {
freeConnList(&conns);
return;
}
}
/* connections are 1 indexed because fds[0] is the notify fd. I hate /* connections are 1 indexed because fds[0] is the notify fd. I hate
* that poll() forces us to do these hacks. */ * that poll() forces us to do these hacks. */
@@ -78,94 +98,115 @@ void runServer(int connfd, Sitefile *site, volatile int *pending, int id) {
createErrorLog("seteuid() failed", errno); createErrorLog("seteuid() failed", errno);
root = getpwnam("root"); root = getpwnam("root");
if (root != NULL) { if (root != NULL) {
/* I don't know why this if statement could be false but we have it /* I don't know why this if statement could be false but we have
* just in case. */ * it just in case. */
if (geteuid() == root->pw_uid) if (geteuid() == root->pw_uid)
createLog("swebs probably should not be run as root"); createLog("swebs probably should not be run as root");
} }
} }
for (;;) { for (;;) {
poll(fds, connCount, -1); pollConnList(&conns);
createFormatLog("poll() finished with %d connections", connCount); createFormatLog("poll() finished with %d connections",
conns.len);
for (i = 1; i < connCount; i++) { for (i = 1; i < conns.len; i++) {
if (fds[i].revents & POLLIN) { if (conns.fds[i].revents & POLLIN) {
createFormatLog("Connection %d has data", i); createFormatLog("Connection %d has data", i);
if (updateConnection(connections + i, site)) if (updateConnection(conns.conns + i, site)) {
goto remove; freeConnection(conns.conns + i);
} removeConnList(&conns, i);
continue; --i;
remove: }
{
int remove, replace;
remove = i;
replace = connCount - 1;
freeConnection(connections + remove);
memcpy(fds + remove, fds + replace,
sizeof(struct pollfd));
memcpy(connections + remove,
connections + replace,
sizeof(struct pollfd));
--pending[id];
--i;
--connCount;
} }
} }
if (fds[0].revents & POLLIN) { if (conns.fds[0].revents & POLLIN) {
Stream *newstream; Stream *newstream;
int newfd; Connection newconn;
int portind; int portind;
struct pollfd newfd;
createLog("Main fd has data"); createLog("Main fd has data");
newfd = recvFd(connfd, &portind, sizeof portind); newfd.fd = recvFd(connfd, &portind, sizeof portind);
if (newfd < 0) { if (newfd.fd < 0) {
createLog("Message received that included an invalid fd, quitting"); createLog("Message received that included an invalid fd, quitting");
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
newfd.events = POLLIN;
newstream = createStream(contexts[portind], O_NONBLOCK, newfd); newstream = createStream(contexts[portind],
O_NONBLOCK, newfd.fd);
if (newstream == NULL) { if (newstream == NULL) {
createLog( createLog(
"Stream couldn't be created from file descriptor"); "Stream couldn't be created from file descriptor");
shutdown(newfd, SHUT_RDWR); shutdown(newfd.fd, SHUT_RDWR);
close(newfd); close(newfd.fd);
continue; continue;
} }
if (connCount >= allocConns) { if (newConnection(newstream, &newconn, portind)) {
struct pollfd *newfds;
Connection *newconns;
allocConns *= 2;
newfds = realloc(fds,
sizeof(struct pollfd) * allocConns);
if (newfds == NULL) {
allocConns /= 2;
continue;
}
fds = newfds;
newconns = realloc(connections,
sizeof(Connection) * allocConns);
if (newconns == NULL) {
allocConns /= 2;
continue;
}
connections = newconns;
}
if (newConnection(newstream, connections + connCount, portind)) {
createLog("Couldn't initialize connection from stream"); createLog("Couldn't initialize connection from stream");
continue; continue;
} }
fds[connCount].fd = newfd;
fds[connCount].events = POLLIN; if (addConnList(&conns, &newfd, &newconn)) {
connCount++; freeConnection(&newconn);
continue;
}
pending[id]++; pending[id]++;
} }
} }
} }
static int createConnList(ConnList *list) {
list->alloc = 100;
list->fds = xmalloc(list->alloc * sizeof *list->fds);
list->conns = xmalloc(list->alloc * sizeof *list->conns);
list->len = 0;
return 0;
}
static int addConnList(ConnList *list, struct pollfd *fd, Connection *conn) {
if (list->len >= list->alloc) {
int newalloc;
struct pollfd *newfds;
Connection *newconns;
newalloc = list->alloc * 2;
newfds = realloc(list->fds, newalloc * sizeof *list->fds);
if (newfds == NULL)
return 1;
newconns = realloc(list->conns, newalloc * sizeof *list->conns);
if (newconns == NULL)
return 1;
list->alloc = newalloc;
list->fds = newfds;
list->conns = newconns;
}
memcpy(list->fds + list->len, fd, sizeof *fd);
memcpy(list->conns + list->len, conn, sizeof *conn);
++list->len;
return 0;
}
static void removeConnList(ConnList *list, int ind) {
const int replace = list->len - 1;
memcpy(list->fds + ind, list->fds + replace, sizeof *list->fds);
memcpy(list->conns + ind, list->conns + replace, sizeof *list->conns);
--list->len;
}
static void pollConnList(ConnList *list) {
poll(list->fds, list->len, -1);
}
static void freeConnList(ConnList *list) {
int i;
for (i = 0; i < list->len; ++i)
freeConnection(list->conns + i);
free(list->fds);
free(list->conns);
}

View File

@@ -33,145 +33,119 @@
* good. * good.
* */ * */
#define CFLAGS (REG_EXTENDED | REG_ICASE)
typedef enum { typedef enum {
SUCCESS, ARG,
LINE_END, LINE_END,
FILE_END, FILE_END,
ERROR TOKEN_ERROR
} ReturnCode; } TokenType;
/* this isn't ideal, but it's necessary to avoid namespace collisions. */
static void freeTokens(int argc, char **argv) { typedef struct {
TokenType type;
char *data;
} Token;
typedef enum {
NORMAL,
PAST_END,
COMMAND_ERROR
} CommandType;
static void freecommand(int argc, char **argv) {
int i; int i;
for (i = 0; i < argc; i++) for (i = 0; i < argc; i++)
free(argv[i]); free(argv[i]);
free(argv); free(argv);
} }
static ReturnCode getToken(FILE *file, char **ret) { static void gettoken(FILE *file, Token *ret) {
typedef enum { int c;
QUOTED, char *data;
NONQUOTED
} TokenType;
TokenType type;
size_t allocatedLen = 50;
size_t len; size_t len;
size_t alloc;
for (;;) { for (;;) {
int c = fgetc(file); c = fgetc(file);
if (c == '\n') switch (c) {
return LINE_END; case '#':
if (c == EOF) while (c != '\n' && c != EOF)
return FILE_END;
if (c == '#') {
while (c != '\n')
c = fgetc(file); c = fgetc(file);
return LINE_END; case '\n':
} ret->type = LINE_END;
if (!isspace(c)) { return;
if (c == '"') case EOF:
type = QUOTED; ret->type = FILE_END;
else { return;
type = NONQUOTED; case ' ': case '\t':
ungetc(c, file); continue;
}
break;
} }
ret->type = ARG;
ungetc(c, file);
break;
} }
*ret = malloc(allocatedLen); alloc = 20;
data = xmalloc(alloc);
for (len = 0;; len++) { for (len = 0;; ++len) {
int c; if (len >= alloc) {
if (len >= allocatedLen) { alloc *= 2;
char *newret; data = xrealloc(data, alloc);
allocatedLen *= 2;
newret = realloc(*ret, allocatedLen);
if (newret == NULL)
goto error;
*ret = newret;
} }
c = fgetc(file); c = fgetc(file);
switch (type) { if (isspace(c) || c == EOF) {
case QUOTED: ungetc(c, file);
if (c == '"') data[len] = '\0';
goto gotToken; ret->type = ARG;
break; ret->data = data;
case NONQUOTED: return;
if (isspace(c)) {
ungetc(c, file);
goto gotToken;
}
break;
} }
switch (c) { switch (c) {
case '\\': case '\\':
c = fgetc(file); c = fgetc(file);
if (c == EOF) if (c == EOF) {
goto error; ret->type = TOKEN_ERROR;
break; return;
case EOF: }
if (type == NONQUOTED) default:
goto gotToken; data[len] = c;
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) { static CommandType getcommand(FILE *file, int *argcret, char ***argvret) {
/* THIS FUNCTION WILL NOT RETURN LINE_END */ int argc, argalloc;
int argc;
char **argv; char **argv;
int allocatedTokens; argalloc = 5;
if (feof(file)) argv = xmalloc(argalloc * sizeof *argv);
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) { for (argc = 0;; ++argc) {
case ERROR: Token token;
goto error; if (argc >= argalloc) {
case LINE_END: argalloc *= 2;
if (argc == 0) argv = xrealloc(argv, argalloc * sizeof *argv);
continue; }
/* We allow empty lines */ gettoken(file, &token);
/* fallthrough */ switch (token.type) {
case FILE_END: case FILE_END:
if (argc == 0) { if (argc == 0)
free(argv); return PAST_END;
return FILE_END; goto gotcommand;
} case LINE_END:
*argcret = argc; if (argc == 0)
*argvret = argv; return getcommand(file, argcret, argvret);
return SUCCESS; goto gotcommand;
case SUCCESS: gotcommand:
argc++; *argcret = argc;
break; *argvret = argv;
return NORMAL;
case ARG:
argv[argc] = token.data;
break;
case TOKEN_ERROR:
return COMMAND_ERROR;
} }
} }
error:
freeTokens(argc, argv);
return ERROR;
} }
static char *getport(char *data, unsigned short *ret) { static char *getport(char *data, unsigned short *ret) {
@@ -213,27 +187,223 @@ static int getports(unsigned short **ports, int *portcount, char *data) {
} }
} }
Sitefile *parseSitefile(char *path) { typedef struct {
FILE *file; RequestType respondto;
RequestType respondto = GET; char *host;
const int cflags = REG_EXTENDED | REG_ICASE;
char *host = NULL;
int argc;
char **argv;
Sitefile *ret;
unsigned short *ports; unsigned short *ports;
int portcount; int portcount;
char *contenttype; char *contenttype;
} LocalVars;
typedef enum {
DATA_CHANGE,
SITE_SPEC,
COMMAND_RET_ERROR
} CommandReturn;
static CommandReturn localvar(LocalVars *vars, Sitefile *sitefile,
int argc, char **argv) {
if (argc < 3)
return COMMAND_RET_ERROR;
if (strcmp(argv[1], "respondto") == 0) {
if ((vars->respondto = getType(argv[2])) == INVALID)
return COMMAND_RET_ERROR;
return DATA_CHANGE;
}
if (strcmp(argv[1], "host") == 0) {
free(vars->host);
vars->host = xstrdup(argv[2]);
return DATA_CHANGE;
}
else if (strcmp(argv[1], "port") == 0) {
free(vars->ports);
if (getports(&vars->ports, &vars->portcount, argv[2])) {
fprintf(stderr, "Invalid port list %s\n", argv[2]);
return COMMAND_RET_ERROR;
}
return DATA_CHANGE;
}
else if (strcmp(argv[1], "type") == 0) {
free(vars->contenttype);
vars->contenttype = strdup(argv[2]);
return DATA_CHANGE;
}
return COMMAND_RET_ERROR;
}
static CommandReturn globalvar(LocalVars *vars, Sitefile *sitefile,
int argc, char **argv) {
if (argc < 3)
return COMMAND_RET_ERROR;
if (strcmp(argv[1], "library") == 0) {
#if DYNAMIC_LINKED_PAGES
sitefile->getResponse = loadGetResponse(argv[2]);
return DATA_CHANGE;
#else
fputs("This version of swebs has no dynamic page support\n",
stderr);
return COMMAND_RET_ERROR;
#endif
}
return COMMAND_RET_ERROR;
}
static CommandReturn declareport(LocalVars *vars, Sitefile *sitefile,
int argc, char **argv) {
Port newport;
int i;
if (argc < 3) {
fputs("Usage: declare [transport] [port]\n", stderr);
return COMMAND_RET_ERROR;
}
newport.num = atoi(argv[2]);
for (i = 0; i < sitefile->portcount; ++i) {
if (sitefile->ports[i].num == newport.num) {
fprintf(stderr, "Port %hu declared multiple times\n",
newport.num);
return COMMAND_RET_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]);
return COMMAND_RET_ERROR;
}
newport.timeout = 2000;
newport.key = newport.cert = NULL;
if (sitefile->portcount >= sitefile->portalloc) {
sitefile->portalloc *= 2;
sitefile->ports = xrealloc(sitefile->ports,
sitefile->portalloc * sizeof *sitefile->ports);
}
memcpy(sitefile->ports + sitefile->portcount, &newport,
sizeof newport);
++sitefile->portcount;
return DATA_CHANGE;
}
static CommandReturn portvar(LocalVars *vars, Sitefile *sitefile,
int argc, char **argv) {
#define PORT_ATTRIBUTE(name, func) \
if (strcmp(argv[0], #name) == 0) { \
int i; \
unsigned short port; \
if (argc < 3) { \
fputs("Usage: " #name " [" #name "] [port]\n", \
stderr); \
return COMMAND_RET_ERROR; \
} \
port = atoi(argv[2]); \
for (i = 0; i < sitefile->portcount; ++i) \
if (sitefile->ports[i].num == port) \
sitefile->ports[i].name = func(argv[1]); \
return DATA_CHANGE; \
}
PORT_ATTRIBUTE(key, xstrdup)
PORT_ATTRIBUTE(cert, xstrdup)
PORT_ATTRIBUTE(timeout, atoi)
#undef PORT_ATTRIBUTE
return COMMAND_RET_ERROR;
}
static int expandsitefile(Sitefile *sitefile, char *regex) {
if (sitefile->size >= sitefile->alloc) {
SiteCommand *newcontent;
sitefile->alloc *= 2;
newcontent = xrealloc(sitefile->content, sitefile->alloc *
sizeof *newcontent);
sitefile->content = newcontent;
}
return regcomp(&sitefile->content[sitefile->size].path, regex, CFLAGS);
}
static char *getcodestring(const char *str) {
return getCode(atoi(str));
}
static CommandReturn defsitespec(LocalVars *vars, Sitefile *sitefile,
int argc, char **argv) {
const struct {
char *command;
char *(*getarg)(const char *);
Command type;
} sitespecs[] = {
{"read", strdup, READ},
{"throw", getcodestring, THROW},
};
int i;
if (argc < 3)
return COMMAND_RET_ERROR;
expandsitefile(sitefile, argv[1]);
for (i = 0; i < LEN(sitespecs); ++i) {
if (strcmp(argv[0], sitespecs[i].command) == 0) {
sitefile->content[sitefile->size].arg =
sitespecs[i].getarg(argv[2]);
if (sitefile->content[sitefile->size].arg == NULL)
return COMMAND_RET_ERROR;
sitefile->content[sitefile->size].command =
sitespecs[i].type;
return SITE_SPEC;
}
}
return COMMAND_RET_ERROR;
}
static CommandReturn linkedsitespec(LocalVars *vars, Sitefile *sitefile,
int argc, char **argv) {
#if DYNAMIC_LINKED_PAGES
if (argc < 2)
return COMMAND_RET_ERROR;
expandsitefile(sitefile, argv[1]);
sitefile->content[sitefile->size].command = LINKED;
return SITE_SPEC;
#else
fputs("This version of swebs doesn't have linked page support", stderr);
return COMMAND_RET_ERROR;
#endif
}
Sitefile *parseSitefile(char *path) {
FILE *file;
int argc;
char **argv;
Sitefile *ret;
const struct {
char *name;
CommandReturn (*updatesitefile)(LocalVars *vars,
Sitefile *sitefile,
int argc, char **argv);
} commandspec[] = {
{"set", localvar},
{"define", globalvar},
{"read", defsitespec},
{"throw", defsitespec},
{"linked", linkedsitespec},
{"declare", declareport},
{"key", portvar},
{"cert", portvar},
{"timeout", portvar},
};
LocalVars vars;
file = fopen(path, "r"); file = fopen(path, "r");
if (file == NULL) if (file == NULL)
return NULL; return NULL;
vars.respondto = GET;
vars.host = xstrdup(".*");
vars.ports = xmalloc(sizeof *vars.ports);
vars.ports[0] = 80;
vars.portcount = 1;
vars.contenttype = xstrdup("text/html");
ret = xmalloc(sizeof *ret); ret = xmalloc(sizeof *ret);
ports = malloc(sizeof *ports);
ports[0] = 80;
portcount = 1;
ret->size = 0; ret->size = 0;
ret->alloc = 50; ret->alloc = 50;
ret->content = xmalloc(ret->alloc * sizeof *ret->content); ret->content = xmalloc(ret->alloc * sizeof *ret->content);
@@ -244,206 +414,88 @@ Sitefile *parseSitefile(char *path) {
ret->getResponse = NULL; ret->getResponse = NULL;
#endif #endif
contenttype = xstrdup("text/html");
for (;;) { for (;;) {
ReturnCode status = getCommand(file, &argc, &argv); int i;
switch (status) { CommandType commandtype;
int i; nextcommand:
case FILE_END: commandtype = getcommand(file, &argc, &argv);
free(ports); switch (commandtype) {
for (i = 0; i < ret->portcount; ++i) { case PAST_END:
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) {
free(host);
host = xstrdup(argv[2]);
}
else if (strcmp(argv[1], "port") == 0) {
free(ports);
if (getports(&ports, &portcount, argv[2])) {
fprintf(stderr, "Invalid port list %s\n",
argv[2]);
goto error;
}
}
else if (strcmp(argv[1], "type") == 0) {
free(contenttype);
contenttype = strdup(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) { for (i = 0; i < ret->portcount; ++i) {
if (ret->ports[i].num == newport.num) { Port *port = ret->ports + i;
if (port->type == TLS &&
(port->key == NULL ||
port->cert == NULL)) {
fprintf(stderr, fprintf(stderr,
"Port %hu declared multiple times\n", newport.num); "Port %hu declared without proper TLS files\n", port->num);
goto error; goto nterror;
} }
} }
free(vars.ports);
if (strcmp(argv[1], "TCP") == 0) free(vars.contenttype);
newport.type = TCP; free(vars.host);
else if (strcmp(argv[1], "TLS") == 0) fclose(file);
newport.type = TLS; return ret;
else { case COMMAND_ERROR:
fprintf(stderr, "Invalid transport %s\n", goto nterror;
argv[1]); case NORMAL:
goto error; break;
}
for (i = 0; i < LEN(commandspec); ++i) {
if (strcmp(argv[0], commandspec[i].name) == 0) {
switch (commandspec[i].updatesitefile(&vars,
ret, argc, argv)) {
case DATA_CHANGE:
goto nextcommand;
case SITE_SPEC:
goto newsitespec;
case COMMAND_RET_ERROR:
goto error;
}
break;
} }
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;
} }
fprintf(stderr, "Unknown sitefile command %s", argv[0]);
goto error;
newsitespec:
freecommand(argc, argv);
ret->content[ret->size].respondto = vars.respondto;
regcomp(&ret->content[ret->size].host, vars.host, CFLAGS);
if (regcomp(&ret->content[ret->size].path, argv[1], ret->content[ret->size].ports = xmalloc(vars.portcount *
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].ports = xmalloc(portcount *
sizeof *ret->content[ret->size].ports); sizeof *ret->content[ret->size].ports);
memcpy(ret->content[ret->size].ports, ports, portcount * sizeof *ports); memcpy(ret->content[ret->size].ports, vars.ports,
ret->content[ret->size].portcount = portcount; vars.portcount * sizeof *vars.ports);
ret->content[ret->size].portcount = vars.portcount;
ret->content[ret->size].contenttype = xstrdup(contenttype); ret->content[ret->size].contenttype = xstrdup(vars.contenttype);
ret->size++; ++ret->size;
} }
error: error:
freeTokens(argc, argv); freecommand(argc, argv);
nterror: nterror:
free(vars.ports);
free(vars.contenttype);
free(vars.host);
freeSitefile(ret); freeSitefile(ret);
return NULL; return NULL;
} }
void freeSitefile(Sitefile *site) { void freeSitefile(Sitefile *site) {
long i; long i;
for (i = 0; i < site->size; i++) { for (i = 0; i < site->size; ++i) {
regfree(&site->content[i].path); regfree(&site->content[i].path);
regfree(&site->content[i].host); regfree(&site->content[i].host);
free(site->content[i].arg); free(site->content[i].arg);
free(site->content[i].ports);
free(site->content[i].contenttype);
} }
free(site->content); free(site->content);
for (i = 0; i < site->portcount; ++i) {
free(site->ports[i].key);
free(site->ports[i].cert);
}
free(site->ports);
free(site); free(site);
} }

View File

@@ -144,18 +144,18 @@ Stream *createStream(Context *context, int flags, int fd) {
break; break;
case TLS: case TLS:
if (gnutls_init(&ret->session, GNUTLS_SERVER) < 0) { if (gnutls_init(&ret->session, GNUTLS_SERVER) < 0) {
createErrorLog("gnutls_init() failed", errno); createLog("gnutls_init() failed");
goto error; goto error;
} }
if (gnutls_priority_set(ret->session, if (gnutls_priority_set(ret->session,
context->priority) < 0) { context->priority) < 0) {
createErrorLog("gnutls_priority_set() failed", errno); createLog("gnutls_priority_set() failed");
goto error; goto error;
} }
if (gnutls_credentials_set(ret->session, if (gnutls_credentials_set(ret->session,
GNUTLS_CRD_CERTIFICATE, GNUTLS_CRD_CERTIFICATE,
context->creds) < 0) { context->creds) < 0) {
createErrorLog("gnutls_credentials_set() failed", errno); createLog("gnutls_credentials_set() failed");
goto error; goto error;
} }
gnutls_certificate_server_set_request(ret->session, gnutls_certificate_server_set_request(ret->session,
@@ -164,7 +164,7 @@ Stream *createStream(Context *context, int flags, int fd) {
GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT); GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
gnutls_transport_set_int(ret->session, ret->fd); gnutls_transport_set_int(ret->session, ret->fd);
if (gnutls_handshake(ret->session) < 0) { if (gnutls_handshake(ret->session) < 0) {
createErrorLog("gnutls_handshake() failed", errno); createLog("gnutls_handshake() failed");
goto error; goto error;
} }
break; break;

View File

@@ -22,6 +22,8 @@
int initLogging(char *path); int initLogging(char *path);
#define LEN(arr) (sizeof (arr) / sizeof (*arr))
int smalloc(size_t size); int smalloc(size_t size);
/* returns an id passed to saddr, or -1 on error */ /* returns an id passed to saddr, or -1 on error */
void *saddr(int id); void *saddr(int id);