Reorganized responseutil.c and made the entire program ANSI and POSIX compliant

This commit is contained in:
Nate Choe
2022-02-13 22:54:19 -06:00
parent b91bb8614d
commit 2105f00965
17 changed files with 395 additions and 294 deletions

View File

@@ -1,7 +1,7 @@
SRC = $(wildcard src/*.c)
OBJ = $(subst .c,.o,$(subst src,work,$(SRC)))
LIBS = -pthread -pie -lrt $(shell pkg-config --libs gnutls)
CFLAGS := -O2 -pipe -Wall -Wpedantic -Werror
CFLAGS := -O2 -pipe -Wall -Wpedantic -Werror -ansi -D_POSIX_C_SOURCE=200809L
CFLAGS += -Isrc/include -fpie $(shell pkg-config --cflags gnutls)
INSTALLDIR := /usr/sbin
OUT = swebs

View File

@@ -1,3 +1,3 @@
#!/bin/sh
../build/swebs -B -P swebs.pid -s sitefile -o logs -p 8000 -b 100
../build/swebs -P swebs.pid -s sitefile -o logs -p 8000 -b 100 -j2

View File

@@ -33,6 +33,8 @@
#include <connections.h>
int newConnection(Stream *stream, Connection *ret) {
struct timespec currentTime;
ret->stream = stream;
ret->progress = RECEIVE_REQUEST;
@@ -51,10 +53,11 @@ int newConnection(Stream *stream, Connection *ret) {
ret->path = NULL;
ret->body = NULL;
//pointers to things that are allocated within functions should be
//initialized to NULL so that free() doens't fail.
/*
* pointers to things that are allocated within functions should be
* initialized to NULL so that free() doens't fail.
* */
struct timespec currentTime;
if (clock_gettime(CLOCK_MONOTONIC, &currentTime) < 0) {
free(ret->currLine);
return 1;
@@ -81,8 +84,15 @@ void freeConnection(Connection *conn) {
}
static int processRequest(Connection *conn) {
char *line = conn->currLine;
for (int i = 0;; i++) {
char *line;
long i;
line = conn->currLine;
/*
* line is not necessarily always conn->currentLine. It's the beginning
* of the currently parsing thing.
* */
for (i = 0;; i++) {
if (line[i] == ' ') {
line[i] = '\0';
conn->type = getType(line);
@@ -95,7 +105,7 @@ static int processRequest(Connection *conn) {
return 1;
}
}
for (int i = 0;; i++) {
for (i = 0;; i++) {
if (line[i] == ' ') {
line[i] = '\0';
conn->path = malloc(i + 1);
@@ -118,10 +128,14 @@ static int processRequest(Connection *conn) {
}
static int processField(Connection *conn) {
long i;
char *line, *split, *field, *value;
size_t linelen;
Field *newfields;
if (conn->currLineLen == 0) {
conn->progress = RECEIVE_BODY;
conn->bodylen = 0;
for (size_t i = 0; i < conn->fieldCount; i++) {
for (i = 0; i < conn->fieldCount; i++) {
if (strcmp(conn->fields[i].field,
"Content-Length") == 0)
conn->bodylen = atol(conn->fields[i].value);
@@ -135,26 +149,26 @@ static int processField(Connection *conn) {
if (conn->fieldCount >= conn->allocatedFields) {
conn->allocatedFields *= 2;
Field *newfields = realloc(conn->fields, conn->allocatedFields *
newfields = realloc(conn->fields, conn->allocatedFields *
sizeof(char *[2]));
if (newfields == NULL)
return 1;
conn->fields = newfields;
}
char *line = conn->currLine;
char *split = strstr(line, ": ");
size_t linelen = conn->currLineLen;
line = conn->currLine;
split = strstr(line, ": ");
linelen = conn->currLineLen;
if (split == NULL)
return 1;
char *field = malloc(split - line + 1);
field = malloc(split - line + 1);
memcpy(field, line, split - line);
field[split - line] = '\0';
linelen -= split - line + 2;
line += split - line + 2;
char *value = malloc(linelen + 1);
value = malloc(linelen + 1);
memcpy(value, line, linelen + 1);
conn->fields[conn->fieldCount].field = field;
@@ -168,8 +182,9 @@ static int processField(Connection *conn) {
static int processChar(Connection *conn, char c, Sitefile *site) {
if (conn->progress < RECEIVE_BODY) {
if (conn->currLineLen >= conn->currLineAlloc) {
char *newline;
conn->currLineAlloc *= 2;
char *newline = realloc(conn->currLine,
newline = realloc(conn->currLine,
conn->currLineAlloc);
assert(newline != NULL);
conn->currLine = newline;
@@ -203,26 +218,29 @@ static int processChar(Connection *conn, char c, Sitefile *site) {
}
static long diff(struct timespec *t1, struct timespec *t2) {
/* returns the difference in times in milliseconds */
return (t2->tv_sec - t1->tv_sec) * 1000 +
(t2->tv_nsec - t1->tv_nsec) / 1000000;
}
int updateConnection(Connection *conn, Sitefile *site) {
char buff[300];
for (;;) {
char buff[300];
ssize_t received;
unsigned long i;
struct timespec currentTime;
if (clock_gettime(CLOCK_MONOTONIC, &currentTime) < 0)
return 1;
if (site->timeout > 0 &&
diff(&conn->lastdata, &currentTime) > site->timeout)
return 1;
ssize_t received = recvStream(conn->stream, buff, sizeof(buff));
received = recvStream(conn->stream, buff, sizeof(buff));
if (received < 0)
return errno != EAGAIN;
if (received == 0)
return 0;
memcpy(&conn->lastdata, &currentTime, sizeof(struct timespec));
for (unsigned long i = 0; i < received; i++) {
for (i = 0; i < received; i++) {
if (processChar(conn, buff[i], site))
return 1;
}

View File

@@ -15,8 +15,9 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef _HAVE_CONNECTIONS
#define _HAVE_CONNECTIONS
#ifndef HAVE_CONNECTIONS
#define HAVE_CONNECTIONS
#include <runner.h>
#include <sockets.h>
#include <sitefile.h>
@@ -24,7 +25,7 @@
typedef enum {
RECEIVE_REQUEST,
RECEIVE_HEADER,
RECEIVE_BODY,
RECEIVE_BODY
} ConnectionSteps;
typedef struct {
@@ -37,38 +38,42 @@ typedef struct Connection {
ConnectionSteps progress;
struct timespec lastdata;
//the last time that data was received from this connection.
/* the last time that data was received from this connection. */
RequestType type;
char *path;
//ephemeral
/* ephemeral */
Field *fields;
//pointer to array of 2 pointers, persistent
/* pointer to array of 2 pointers, persistent */
size_t fieldCount;
size_t allocatedFields;
char *body;
//ephemeral
/* ephemeral */
size_t bodylen;
size_t receivedBody;
char *currLine;
//persistent
/* persistent */
size_t currLineAlloc;
size_t currLineLen;
} Connection;
//The 2 types of fields:
//Persistent fields: Things which aren't freed after a reset, currLine, fields
//Ephemeral fields: Things which are freed and reallocated after each new
//request, path, body
/*
* The 2 types of fields:
* Persistent fields: Things which aren't freed after a reset, currLine, fields
* Ephemeral fields: Things which are freed and reallocated after each new
* request, path, body
* */
int newConnection(Stream *stream, Connection *ret);
//returns non-zero on error. creates a new connection bound to fd
/* returns non-zero on error. */
void resetConnection(Connection *conn);
void freeConnection(Connection *conn);
int updateConnection(Connection *conn, Sitefile *site);
//returns non-zero on error.
//Generating a new connection and repeatedly calling updateConnection will
//handle everything.
/*
* returns non-zero on error.
* Generating a new connection and repeatedly calling updateConnection will
* handle everything.
* */
#endif

View File

@@ -15,11 +15,11 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef _HAVE_RESPONSES
#define _HAVE_RESPONSES
#ifndef HAVE_RESPONSES
#define HAVE_RESPONSES
#include <sitefile.h>
#include <connections.h>
int sendResponse(Connection *conn, Sitefile *site);
//returns 1 on error, sets conn->progress to SEND_RESPONSE and sets conn->fd
/* returns 1 on error, sets conn->progress to SEND_RESPONSE */
#endif

View File

@@ -15,8 +15,8 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef _HAVE_RESPONSE_UTIL
#define _HAVE_RESPONSE_UTIL
#ifndef HAVE_RESPONSE_UTIL
#define HAVE_RESPONSE_UTIL
#include <connections.h>
#define CODE_200 "200 OK"
@@ -26,10 +26,12 @@
#define ERROR_500 "500 Internal Server Error"
char *getCode(int code);
int sendStringResponse(Connection *conn, const char *status, char *str);
int sendBinaryResponse(Connection *conn, const char *status,
int sendStringResponse(Stream *stream, const char *status, char *str);
int sendBinaryResponse(Stream *stream, const char *status,
void *data, size_t len);
int sendErrorResponse(Connection *conn, const char *error);
//sendErrorResponse(conn, ERROR_404);
int sendHeader(Connection *conn, const char *status, size_t len);
int sendErrorResponse(Stream *stream, const char *error);
/* sendErrorResponse(conn, ERROR_404); */
int sendHeader(Stream *stream, const char *status, size_t len);
int sendSeekableFile(Stream *stream, const char *status, int fd);
int sendPipe(Stream *stream, const char *status, int fd);
#endif

View File

@@ -15,8 +15,8 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef _HAVE_RUNNER
#define _HAVE_RUNNER
#ifndef HAVE_RUNNER
#define HAVE_RUNNER
#include <sys/socket.h>
#include <sitefile.h>
@@ -25,16 +25,18 @@
typedef struct {
Sitefile *site;
int *pending;
//pending[thread id] = the number of connections being handled by that
// thread
/*
* pending[thread id] = the number of connections being handled by that
* thread
* */
int notify;
/*
* When this runner should accept a connection, notify will contain an
* int ready to be read.
*/
* */
int id;
} RunnerArgs;
//my least favourite anti pattern
/* my least favourite anti pattern */
void *runServer(RunnerArgs *args);
#endif

View File

@@ -15,8 +15,8 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef _HAVE_SITEFILE
#define _HAVE_SITEFILE
#ifndef HAVE_SITEFILE
#define HAVE_SITEFILE
#include <regex.h>
#include <util.h>
@@ -24,7 +24,7 @@
typedef enum {
READ,
EXEC,
THROW,
THROW
} Command;
typedef struct {

View File

@@ -15,8 +15,8 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef _HAVE_SOCKETS
#define _HAVE_SOCKETS
#ifndef HAVE_SOCKETS
#define HAVE_SOCKETS
#include <stddef.h>
#include <netinet/in.h>
@@ -41,16 +41,18 @@ typedef struct {
int initTLS();
Listener *createListener(SocketType type, uint16_t port, int backlog, ...);
//extra arguments depend on type (similar to fcntl):
//tcp: (void)
//tls: (char *keyfile, char *certfile, char *ocspfile)
/*
* extra arguments depend on type (similar to fcntl):
* tcp: (void)
* tls: (char *keyfile, char *certfile, char *ocspfile)
* */
Stream *acceptStream(Listener *listener, int flags);
//returns 1 on error, accepts fcntl flags
/* returns 1 on error, accepts fcntl flags */
void freeListener(Listener *listener);
void freeStream(Stream *stream);
ssize_t sendStream(Stream *stream, const void *data, size_t len);
ssize_t recvStream(Stream *stream, void *data, size_t len);
//return value is the same as the read and write syscalls.
/* return value is the same as the read and write syscalls. */
#endif

View File

@@ -15,11 +15,11 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef _HAVE_UTIL
#define _HAVE_UTIL
#ifndef HAVE_UTIL
#define HAVE_UTIL
typedef enum {
TCP,
TLS,
TLS
} SocketType;
typedef enum {
@@ -30,14 +30,16 @@ typedef enum {
DELETE,
PATCH,
OPTIONS,
INVALID,
//this indicates an invalid type of request, there is no request called
//INVALID in HTTP/1.1.
INVALID
/*
* this indicates an invalid type of request, there is no request called
* INVALID in HTTP/1.1.
* */
} RequestType;
int initLogging(char *path);
int createLog(char *msg);
int istrcmp(char *s1, char *s2);
//case insensitive strcmp
/* case insensitive strcmp */
RequestType getType(char *str);
#endif

View File

@@ -21,7 +21,7 @@
#include <string.h>
#include <assert.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdarg.h>
#include <pwd.h>
#include <fcntl.h>
@@ -36,14 +36,55 @@
#include <sockets.h>
#include <sitefile.h>
static void daemonize(char *pidfile) {
pid_t pid;
FILE *pidout;
pid = fork();
switch (pid) {
case -1:
exit(EXIT_FAILURE);
case 0:
break;
default:
pidout = fopen(pidfile, "w");
fprintf(pidout, "%d\n", getpid());
fclose(pidout);
exit(EXIT_SUCCESS);
}
if (setsid() < 0)
exit(EXIT_FAILURE);
}
static void printLongMessage(char *first, ...) {
va_list ap;
va_start(ap, first);
puts(first);
for (;;) {
char *nextarg = va_arg(ap, char *);
if (nextarg == NULL)
break;
puts(nextarg);
}
va_end(ap);
}
int main(int argc, char **argv) {
char *logout = "/var/log/swebs.log";
char *sitefile = NULL;
int processes = sysconf(_SC_NPROCESSORS_ONLN) + 1;
uint16_t port = 443;
int backlog = 100;
bool shouldDaemonize = false;
char shouldDaemonize = 0;
char *pidfile = "/run/swebs.pid";
Sitefile *site;
Listener *listener;
int *pending, (*notify)[2];
pthread_t *threads;
int i;
for (;;) {
int c = getopt(argc, argv, "o:j:s:p:b:c:BP:hl");
if (c == -1)
@@ -65,39 +106,41 @@ int main(int argc, char **argv) {
backlog = atoi(optarg);
break;
case 'B':
shouldDaemonize = true;
shouldDaemonize = 1;
break;
case 'P':
pidfile = optarg;
break;
case 'l':
printf(
"swebs Copyright (C) 2022 Nate Choe\n"
"This is free software, and you are welcome to redistribute under certain\n"
"conditions, but comes with ABSOLUTELY NO WARRANTY. For more details see the\n"
"GNU General Public License Version 3\n"
"\n"
"This program dynamically links with:\n"
" gnutls (gnutls.org)\n"
"\n"
"For any complaints, email me at natechoe9@gmail.com\n"
"I'm a programmer not a lawyer, so there's a good chance I accidentally\n"
"violated the LGPL.\n"
printLongMessage(
"swebs Copyright (C) 2022 Nate Choe",
"This is free software, and you are welcome to redistribute under certain",
"conditions, but comes with ABSOLUTELY NO WARRANTY. For more details see the",
"GNU General Public License Version 3\n",
"This program dynamically links with:",
" gnutls (gnutls.org)\n",
"For any complaints, email me at natechoe9@gmail.com",
"I'm a programmer not a lawyer, so there's a good chance I accidentally",
"violated the LGPL.",
NULL
);
exit(EXIT_SUCCESS);
case 'h':
printf(
"Usage: swebs [options]\n"
" -o [out] Set the log file (default: /var/log/swebs.log)\n"
" -j [cores] Use that many cores (default: $(nproc)+1)\n"
" -s [site file] Use that site file (required)\n"
" -p [port] Set the port (default: 443)\n"
" -b [backlog] Set the socket backlog (default: 100)\n"
" -B Run swebs in the background and daemonize\n"
" -P [pidfile] Specify PID file if daemonizing\n"
" (defualt: /run/swebs.pid)\n"
" -l Show some legal details\n"
" -h Show this help message\n"
printLongMessage(
"Usage: swebs [options]",
" -o [out] Set the log file (default: /var/log/swebs.log)",
" -j [cores] Use that many cores (default: $(nproc)+1)",
" -s [site file] Use that site file (required)",
" -p [port] Set the port (default: 443)",
" -b [backlog] Set the socket backlog (default: 100)",
" -B Run swebs in the background and daemonize",
" -P [pidfile] Specify PID file if daemonizing",
" (defualt: /run/swebs.pid)",
" -l Show some legal details",
" -h Show this help message",
NULL
);
exit(EXIT_SUCCESS);
case '?':
@@ -111,13 +154,12 @@ int main(int argc, char **argv) {
fprintf(stderr, "No sitefile configured\n");
exit(EXIT_FAILURE);
}
Sitefile *site = parseSitefile(sitefile);
site = parseSitefile(sitefile);
if (site == NULL) {
fprintf(stderr, "Invalid sitefile %s\n", sitefile);
exit(EXIT_FAILURE);
}
Listener *listener;
switch (site->type) {
case TCP: default:
listener = createListener(TCP, port, backlog);
@@ -133,13 +175,8 @@ int main(int argc, char **argv) {
exit(EXIT_FAILURE);
}
if (shouldDaemonize) {
if (daemon(1, 0) < 0)
exit(EXIT_FAILURE);
FILE *pid = fopen(pidfile, "w");
fprintf(pid, "%d\n", getpid());
fclose(pid);
}
if (shouldDaemonize)
daemonize(pidfile);
if (initLogging(logout)) {
fprintf(stderr, "Couldn't open logs file %s\n", logout);
@@ -147,13 +184,14 @@ int main(int argc, char **argv) {
}
{
struct passwd *swebs = getpwnam("swebs");
struct passwd *swebs, *root;
swebs = getpwnam("swebs");
if (swebs == NULL)
createLog("Couldn't find swebs user");
else
if (seteuid(swebs->pw_uid))
createLog("seteuid() failed");
struct passwd *root = getpwnam("root");
root = getpwnam("root");
if (root == NULL) {
createLog("Couldn't find root user, quitting");
exit(EXIT_FAILURE);
@@ -164,18 +202,18 @@ int main(int argc, char **argv) {
}
}
int *pending = calloc(processes - 1, sizeof(int));
int (*notify)[2] = malloc(sizeof(int[2]) * (processes - 1));
pthread_t *threads = malloc(sizeof(pthread_t) * (processes - 1));
pending = calloc(processes - 1, sizeof(int));
notify = malloc(sizeof(int[2]) * (processes - 1));
threads = malloc(sizeof(pthread_t) * (processes - 1));
if (threads == NULL)
exit(EXIT_FAILURE);
for (int i = 0; i < processes - 1; i++) {
if (pipe(notify[i]))
exit(EXIT_FAILURE);
for (i = 0; i < processes - 1; i++) {
RunnerArgs *args = malloc(sizeof(RunnerArgs));
if (args == NULL)
exit(EXIT_FAILURE);
if (pipe(notify[i]))
exit(EXIT_FAILURE);
args->site = site;
args->pending = pending;
args->notify = notify[i][0];
@@ -188,21 +226,19 @@ int main(int argc, char **argv) {
for (;;) {
Stream *stream = acceptStream(listener, O_NONBLOCK);
int lowestThread;
if (stream == NULL) {
createLog("Accepting a stream failed");
continue;
}
int lowestThread = 0;
int lowestCount = pending[0];
for (int i = 1; i < processes - 1; i++) {
if (pending[i] < lowestCount) {
lowestThread = 0;
for (i = 1; i < processes - 1; i++)
if (pending[i] < pending[lowestThread])
lowestThread = i;
lowestCount = pending[i];
}
}
if (write(notify[lowestThread][1], &stream, sizeof(&stream))
< sizeof(&stream))
exit(EXIT_FAILURE);
}
}

View File

@@ -30,71 +30,59 @@
#include <responses.h>
#include <responseutil.h>
static int resilientSend(Stream *stream, const void *buff, size_t len) {
//Will either write len bytes, or return 1 for error.
const char *data = (const char *) buff;
//using character pointers to do pointer arithmetic
while (len > 0) {
ssize_t sent = sendStream(stream, data, len);
if (sent < 0)
return 1;
len -= sent;
data += sent;
}
return 0;
}
static int readResponse(Connection *conn, char *path) {
int fd = 1;
int fd = -1;
struct stat statbuf;
if (stat(path, &statbuf)) {
sendErrorResponse(conn, ERROR_404);
sendErrorResponse(conn->stream, ERROR_404);
return 1;
}
if (S_ISDIR(statbuf.st_mode)) {
long reqPathLen = strlen(conn->path);
long pathLen = strlen(path);
char *assembledPath = malloc(reqPathLen + pathLen + 1);
char requestPath[PATH_MAX], responsePath[PATH_MAX];
size_t responsePathLen;
struct stat requestBuff;
if (assembledPath == NULL)
goto error;
memcpy(assembledPath, path, pathLen);
memcpy(assembledPath + pathLen, conn->path, reqPathLen + 1);
char requestPath[PATH_MAX];
if (realpath(assembledPath, requestPath) == NULL) {
if (errno == ENOENT) {
free(assembledPath);
sendErrorResponse(conn, ERROR_404);
sendErrorResponse(conn->stream, ERROR_404);
return 1;
}
free(assembledPath);
goto error;
}
char responsePath[PATH_MAX];
if (realpath(path, responsePath) == NULL) {
free(assembledPath);
goto error;
}
size_t responsePathLen = strlen(responsePath);
responsePathLen = strlen(responsePath);
if (memcmp(requestPath, responsePath, responsePathLen)) {
free(assembledPath);
goto forbidden;
}
//in theory an attacker could just request
// /blog/../../../../site/privatekey.pem
//so we make sure that the filepath is actually within the path
//specified by the page.
/*
* in theory an attacker could just request
* /blog/../../../../site/privatekey.pem
* so we make sure that the filepath is actually within the path
* specified by the page.
* */
struct stat requestbuf;
if (stat(requestPath, &requestbuf)) {
if (stat(requestPath, &requestBuff)) {
free(assembledPath);
sendErrorResponse(conn, ERROR_404);
sendErrorResponse(conn->stream, ERROR_404);
return 1;
}
if (S_ISDIR(requestbuf.st_mode)) {
if (S_ISDIR(requestBuff.st_mode)) {
free(assembledPath);
sendErrorResponse(conn, ERROR_400);
sendErrorResponse(conn->stream, ERROR_400);
return 1;
}
@@ -105,53 +93,38 @@ static int readResponse(Connection *conn, char *path) {
fd = open(path, O_RDONLY);
if (fd < 0)
goto forbidden;
off_t len = lseek(fd, 0, SEEK_END);
if (len < 0)
goto error;
if (lseek(fd, 0, SEEK_SET) < 0) {
sendErrorResponse(conn, ERROR_500);
close(fd);
return 1;
}
sendHeader(conn, CODE_200, len);
while (len > 0) {
char buff[1024];
size_t readLen = read(fd, buff, sizeof(buff));
if (resilientSend(conn->stream, buff, readLen))
goto error;
len -= readLen;
}
return 0;
return sendSeekableFile(conn->stream, CODE_200, fd);
error:
sendErrorResponse(conn, ERROR_500);
sendErrorResponse(conn->stream, ERROR_500);
return 1;
forbidden:
sendErrorResponse(conn, ERROR_403);
sendErrorResponse(conn->stream, ERROR_403);
return 1;
}
static int execResponse(Connection *conn, char *path) {
int output[2];
int status;
pid_t pid;
if (pipe(output))
goto error;
pid_t pid = fork();
pid = fork();
if (pid < 0)
goto error;
if (pid == 0) {
char **args;
int i;
close(1);
close(output[0]);
dup2(output[1], 1);
char **args =
malloc((conn->fieldCount*2 + 2) * sizeof(char *));
args = malloc((conn->fieldCount*2 + 2) * sizeof(char *));
if (args == NULL) {
close(output[1]);
exit(EXIT_FAILURE);
}
args[0] = conn->path;
for (int i = 0; i < conn->fieldCount; i++) {
for (i = 0; i < conn->fieldCount; i++) {
args[i * 2 + 1] = conn->fields[i].field;
args[i * 2 + 2] = conn->fields[i].value;
}
@@ -165,46 +138,17 @@ static int execResponse(Connection *conn, char *path) {
free(args);
exit(EXIT_SUCCESS);
}
size_t allocResponse = 1024;
size_t responseLen = 0;
char *response = malloc(allocResponse);
close(output[1]);
for (;;) {
if (responseLen == allocResponse) {
allocResponse *= 2;
char *newresponse = realloc(response, allocResponse);
if (newresponse == NULL)
goto looperror;
response = newresponse;
}
ssize_t len = read(output[0],
response + responseLen,
allocResponse - responseLen);
if (len < 0)
goto looperror;
if (len == 0)
break;
responseLen += len;
continue;
looperror:
close(output[0]);
int status;
waitpid(pid, &status, 0);
free(response);
goto error;
status = sendPipe(conn->stream, CODE_200, output[0]);
{
int exitcode;
waitpid(pid, &exitcode, 0);
}
close(output[0]);
int status;
waitpid(pid, &status, 0);
sendHeader(conn, CODE_200, responseLen);
if (resilientSend(conn->stream, response, responseLen)) {
free(response);
return 1;
}
free(response);
return 0;
return status;
error:
sendErrorResponse(conn, ERROR_500);
sendErrorResponse(conn->stream, ERROR_500);
return 1;
}
@@ -217,17 +161,18 @@ static int fullmatch(regex_t *regex, char *str) {
int sendResponse(Connection *conn, Sitefile *site) {
char *host = NULL;
for (int i = 0; i < conn->fieldCount; i++) {
int i;
for (i = 0; i < conn->fieldCount; i++) {
if (strcmp(conn->fields[i].field, "Host") == 0) {
host = conn->fields[i].value;
break;
}
}
if (host == NULL) {
sendErrorResponse(conn, ERROR_400);
sendErrorResponse(conn->stream, ERROR_400);
return 1;
}
for (int i = 0; i < site->size; i++) {
for (i = 0; i < site->size; i++) {
if (site->content[i].respondto != conn->type)
continue;
if (fullmatch(&site->content[i].host, host))
@@ -243,17 +188,18 @@ int sendResponse(Connection *conn, Sitefile *site) {
site->content[i].arg);
break;
case THROW:
sendErrorResponse(conn,
sendErrorResponse(conn->stream,
site->content[i].arg);
break;
default:
sendErrorResponse(conn, ERROR_500);
sendErrorResponse(conn->stream,
ERROR_500);
return 1;
}
resetConnection(conn);
return 0;
}
}
sendErrorResponse(conn, ERROR_404);
sendErrorResponse(conn->stream, ERROR_404);
return 1;
}

View File

@@ -22,17 +22,35 @@
#include <string.h>
#include <limits.h>
#include <unistd.h>
#include <sys/stat.h>
#include <responseutil.h>
#define CONST_FIELDS "Server: swebs/0.1\r\n"
static int sendConnection(Connection *conn, char *format, ...) {
static int resilientSend(Stream *stream, void *data, size_t len) {
char *buffer = (char *) data;
size_t left = len;
while (left) {
ssize_t sent = sendStream(stream, buffer, left);
if (sent < 0)
return 1;
if (sent == 0)
break;
buffer += sent;
left -= sent;
}
return left != 0;
}
static int sendStreamValist(Stream *stream, char *format, ...) {
va_list ap;
int len;
char *data;
va_start(ap, format);
int len = vsnprintf(NULL, 0, format, ap);
char *data = malloc(len + 1);
len = vsnprintf(NULL, 0, format, ap);
data = malloc(len + 1);
if (data == NULL)
return 1;
@@ -40,7 +58,7 @@ static int sendConnection(Connection *conn, char *format, ...) {
va_start(ap, format);
vsprintf(data, format, ap);
if (sendStream(conn->stream, data, len) < len) {
if (resilientSend(stream, data, len) < len) {
free(data);
return 1;
}
@@ -65,8 +83,8 @@ char *getCode(int code) {
}
}
int sendStringResponse(Connection *conn, const char *status, char *str) {
return sendConnection(conn,
int sendStringResponse(Stream *stream, const char *status, char *str) {
return sendStreamValist(stream,
"HTTP/1.1 %s\r\n"
CONST_FIELDS
"Content-Length: %lu\r\n"
@@ -76,38 +94,89 @@ int sendStringResponse(Connection *conn, const char *status, char *str) {
);
}
int sendErrorResponse(Connection *conn, const char *error) {
int sendErrorResponse(Stream *stream, const char *error) {
const char *template =
"<meta charset=utf-8>"
"<h1 text-align=center>"
"%s"
"</h1>";
int ret;
int len = snprintf(NULL, 0, template, error);
char *response = malloc(len + 1);
sprintf(response, template, error);
int ret = sendStringResponse(conn, error, response);
ret = sendStringResponse(stream, error, response);
free(response);
return ret;
}
int sendBinaryResponse(Connection *conn, const char *status,
int sendBinaryResponse(Stream *stream, const char *status,
void *data, size_t len) {
if (sendConnection(conn,
"HTTP/1.1 %s\r\n"
CONST_FIELDS
"Content-Length: %lu\r\n"
"\r\n"
, status, len)
)
if (sendHeader(stream, status, len))
return 1;
return sendStream(conn->stream, data, len) < len;
return resilientSend(stream, data, len);
}
int sendHeader(Connection *conn, const char *status, size_t len) {
return (sendConnection(conn,
int sendHeader(Stream *stream, const char *status, size_t len) {
return (sendStreamValist(stream,
"HTTP/1.1 %s\r\n"
CONST_FIELDS
"Content-Length: %lu\r\n"
"\r\n"
, status, len));
}
int sendSeekableFile(Stream *stream, const char *status, int fd) {
off_t len;
size_t totalSent = 0;
len = lseek(fd, 0, SEEK_END);
lseek(fd, 0, SEEK_SET);
sendHeader(stream, status, len);
for (;;) {
char buffer[1024];
ssize_t inBuffer = read(fd, buffer, sizeof(buffer));
if (inBuffer < 0)
return 1;
if (inBuffer == 0)
return totalSent != len;
if (resilientSend(stream, buffer, inBuffer))
return 1;;
}
}
int sendPipe(Stream *stream, const char *status, int fd) {
size_t allocResponse = 1024;
size_t responseLen = 0;
char *response = malloc(allocResponse);
for (;;) {
ssize_t len;
if (responseLen >= allocResponse) {
char *newresponse;
allocResponse *= 2;
newresponse = realloc(response, allocResponse);
if (newresponse == NULL)
goto error;
response = newresponse;
}
len = read(fd,
response + responseLen,
allocResponse - responseLen);
if (len < 0)
goto error;
else if (len == 0)
break;
responseLen += len;
}
close(fd);
sendHeader(stream, CODE_200, responseLen);
if (resilientSend(stream, response, responseLen)) {
free(response);
return 1;
}
free(response);
return 0;
error:
close(fd);
free(response);
sendErrorResponse(stream, ERROR_500);
return 1;
}

View File

@@ -15,7 +15,6 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
@@ -39,19 +38,18 @@ void *runServer(RunnerArgs *args) {
int allocConns = 100;
struct pollfd *fds = malloc(sizeof(struct pollfd) * allocConns);
Connection *connections = malloc(sizeof(Connection) * allocConns);
int connCount = 1;
/* connections are 1 indexed because fds[0] is the notify fd. */
assert(fds != NULL);
assert(connections != NULL);
fds[0].fd = notify;
fds[0].events = POLLIN;
int connCount = 1;
//connections are 1 indexed, this is because fds[0] is the notify fd.
for (;;) {
poll(fds, connCount, -1);
int i;
poll(fds, connCount, site->timeout);
for (int i = 1; i < connCount; i++) {
if (fds[i].revents & POLLRDHUP)
goto remove;
for (i = 1; i < connCount; i++) {
if (updateConnection(connections + i, site))
goto remove;
continue;
@@ -65,27 +63,30 @@ remove:
}
if (fds[0].revents & POLLIN) {
Stream *newstream;
if (connCount >= allocConns) {
struct pollfd *newfds;
Connection *newconns;
allocConns *= 2;
struct pollfd *newfds = realloc(fds,
newfds = realloc(fds,
sizeof(struct pollfd) * allocConns);
if (newfds == NULL)
exit(EXIT_FAILURE);
fds = newfds;
Connection *newconns = realloc(connections,
newconns = realloc(connections,
sizeof(Connection) * allocConns);
if (newconns == NULL)
exit(EXIT_FAILURE);
connections = newconns;
}
Stream *stream;
if (read(notify, &stream, sizeof(stream)) < sizeof(stream))
if (read(notify, &newstream, sizeof(newstream))
< sizeof(newstream))
exit(EXIT_FAILURE);
fds[connCount].fd = stream->fd;
fds[connCount].events = POLLIN | POLLRDHUP;
fds[connCount].fd = newstream->fd;
fds[connCount].events = POLLIN;
if (newConnection(stream, connections + connCount))
if (newConnection(newstream, connections + connCount))
exit(EXIT_FAILURE);
connCount++;
pending[id]++;

View File

@@ -28,12 +28,13 @@ typedef enum {
SUCCESS,
LINE_END,
FILE_END,
ERROR,
ERROR
} ReturnCode;
//this isn't ideal, but it's necessary to avoid namespace collisions.
/* this isn't ideal, but it's necessary to avoid namespace collisions. */
static void freeTokens(int argc, char **argv) {
for (int i = 0; i < argc; i++)
int i;
for (i = 0; i < argc; i++)
free(argv[i]);
free(argv);
}
@@ -41,9 +42,11 @@ static void freeTokens(int argc, char **argv) {
static ReturnCode getToken(FILE *file, char **ret) {
typedef enum {
QUOTED,
NONQUOTED,
NONQUOTED
} TokenType;
TokenType type;
size_t allocatedLen = 50;
size_t len;
for (;;) {
int c = fgetc(file);
@@ -67,19 +70,19 @@ static ReturnCode getToken(FILE *file, char **ret) {
}
}
long allocatedLen = 30;
long len;
*ret = malloc(allocatedLen);
for (len = 0;; len++) {
int c;
if (len >= allocatedLen) {
char *newret;
allocatedLen *= 2;
char *newret = realloc(*ret, allocatedLen);
newret = realloc(*ret, allocatedLen);
if (newret == NULL)
goto error;
*ret = newret;
}
int c = fgetc(file);
c = fgetc(file);
switch (type) {
case QUOTED:
if (c == '"')
@@ -114,22 +117,27 @@ error:
}
static ReturnCode getCommand(FILE *file, int *argcret, char ***argvret) {
//THIS FUNCTION WILL NOT RETURN LINE_END
/* THIS FUNCTION WILL NOT RETURN LINE_END */
int argc;
char **argv;
int allocatedTokens;
if (feof(file))
return FILE_END;
int argc = 0;
int allocatedTokens = 5;
char **argv = malloc(allocatedTokens * sizeof(char *));
argc = 0;
allocatedTokens = 5;
argv = malloc(allocatedTokens * sizeof(*argv));
for (;;) {
ReturnCode code;
if (argc >= allocatedTokens) {
char **newargv;
allocatedTokens *= 2;
char **newargv = realloc(argv,
newargv = realloc(argv,
allocatedTokens * sizeof(char *));
if (newargv == NULL)
goto error;
argv = newargv;
}
ReturnCode code = getToken(file, argv + argc);
code = getToken(file, argv + argc);
switch (code) {
case ERROR:
@@ -137,8 +145,8 @@ static ReturnCode getCommand(FILE *file, int *argcret, char ***argvret) {
case LINE_END:
if (argc == 0)
continue;
//We allow empty lines
//fallthrough
/* We allow empty lines */
/* fallthrough */
case FILE_END:
if (argc == 0) {
free(argv);
@@ -159,28 +167,29 @@ error:
Sitefile *parseSitefile(char *path) {
FILE *file = fopen(path, "r");
RequestType respondto = GET;
const int cflags = REG_EXTENDED | REG_ICASE;
char *host = NULL;
int argc;
char **argv;
int allocatedLength = 50;
Sitefile *ret;
if (file == NULL)
return NULL;
Sitefile *ret = malloc(sizeof(Sitefile));
ret = malloc(sizeof(Sitefile));
if (ret == NULL)
return NULL;
ret->type = TCP;
ret->key = NULL;
ret->cert = NULL;
ret->timeout = 0;
int allocatedLength = 50;
ret->size = 0;
ret->content = malloc(allocatedLength * sizeof(SiteCommand));
if (ret->content == NULL) {
free(ret);
return NULL;
}
RequestType respondto = GET;
const int cflags = REG_EXTENDED | REG_ICASE;
char *host = NULL;
int lasthostset = 0;
int argc;
char **argv;
for (;;) {
ReturnCode status = getCommand(file, &argc, &argv);
switch (status) {
@@ -200,12 +209,8 @@ Sitefile *parseSitefile(char *path) {
if (respondto == INVALID)
goto error;
}
else if (strcmp(argv[1], "host") == 0) {
if (ret->size == lasthostset)
free(host);
else if (strcmp(argv[1], "host") == 0)
host = strdup(argv[2]);
lasthostset = ret->size;
}
else
goto error;
continue;
@@ -232,8 +237,9 @@ Sitefile *parseSitefile(char *path) {
continue;
}
if (ret->size >= allocatedLength) {
SiteCommand *newcontent;
allocatedLength *= 2;
SiteCommand *newcontent = realloc(ret->content,
newcontent = realloc(ret->content,
allocatedLength * sizeof(SiteCommand));
if (newcontent == NULL)
goto error;
@@ -282,7 +288,8 @@ nterror:
}
void freeSitefile(Sitefile *site) {
for (long i = 0; i < site->size; i++) {
long i;
for (i = 0; i < site->size; i++) {
regfree(&site->content[i].path);
regfree(&site->content[i].host);
free(site->content[i].arg);

View File

@@ -23,6 +23,7 @@
#include <fcntl.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <gnutls/gnutls.h>
@@ -35,6 +36,7 @@ int initTLS() {
Listener *createListener(SocketType type, uint16_t port, int backlog, ...) {
Listener *ret = malloc(sizeof(Listener));
va_list ap;
if (ret == NULL)
return NULL;
ret->type = type;
@@ -43,11 +45,13 @@ Listener *createListener(SocketType type, uint16_t port, int backlog, ...) {
free(ret);
return NULL;
}
int opt = 1;
if (setsockopt(ret->fd, SOL_SOCKET,
SO_REUSEPORT,
&opt, sizeof(opt)) < 0) {
goto error;
{
int opt = 1;
if (setsockopt(ret->fd, SOL_SOCKET, SO_REUSEADDR,
&opt, sizeof(opt))) {
free(ret);
return NULL;
}
}
ret->addr.sin_family = AF_INET;
ret->addr.sin_addr.s_addr = INADDR_ANY;
@@ -58,7 +62,6 @@ Listener *createListener(SocketType type, uint16_t port, int backlog, ...) {
if (listen(ret->fd, backlog) < 0)
goto error;
va_list ap;
va_start(ap, backlog);
switch (type) {
case TCP: default:
@@ -104,8 +107,10 @@ Stream *acceptStream(Listener *listener, int flags) {
return NULL;
}
int oldflags = fcntl(ret->fd, F_GETFL);
fcntl(ret->fd, F_SETFL, oldflags | flags);
{
int oldflags = fcntl(ret->fd, F_GETFL);
fcntl(ret->fd, F_SETFL, oldflags | flags);
}
switch (listener->type) {
case TCP: default:

View File

@@ -32,8 +32,9 @@ int initLogging(char *path) {
int createLog(char *msg) {
time_t currenttime;
struct tm *timeinfo;
time(&currenttime);
struct tm *timeinfo = gmtime(&currenttime);
timeinfo = gmtime(&currenttime);
if (timeinfo == NULL)
return 1;
fprintf(logs, "[%d-%02d-%02dT%02d:%02d:%02dZ] %s\n",
@@ -49,7 +50,8 @@ int createLog(char *msg) {
}
int istrcmp(char *s1, char *s2) {
for (int i = 0;; i++) {
int i;
for (i = 0;; i++) {
char c1 = tolower(s1[i]);
char c2 = tolower(s2[i]);
if (c1 != c2)
@@ -60,10 +62,12 @@ int istrcmp(char *s1, char *s2) {
}
RequestType getType(char *str) {
uint64_t type;
int i;
if (strlen(str) >= 8)
return INVALID;
uint64_t type = 0;
for (int i = 0; str[i]; i++) {
type = 0;
for (i = 0; str[i]; i++) {
type <<= 8;
type |= str[i];
}
@@ -85,6 +89,8 @@ RequestType getType(char *str) {
default:
return INVALID;
}
//This would actually be far nicer in HolyC of all languages. I feel
//like the context immediately following each magic number is enough.
/*
* This would actually be far nicer in HolyC of all languages. I feel
* like the context immediately following each magic number is enough.
* */
}