Added global variables in sitefiles to allow for https support

This commit is contained in:
Nate Choe
2022-01-29 13:21:35 -06:00
parent db645b0bf5
commit c46ed581c3
18 changed files with 430 additions and 58 deletions

View File

@@ -32,8 +32,8 @@
#include <responses.h>
#include <connections.h>
int newConnection(int fd, Connection *ret) {
ret->fd = fd;
int newConnection(Stream *stream, Connection *ret) {
ret->stream = stream;
ret->progress = RECEIVE_REQUEST;
ret->currLineAlloc = 30;
@@ -66,7 +66,7 @@ void resetConnection(Connection *conn) {
}
void freeConnection(Connection *conn) {
shutdown(conn->fd, SHUT_RDWR);
freeStream(conn->stream);
free(conn->currLine);
free(conn->path);
free(conn->fields);
@@ -198,7 +198,7 @@ static int processChar(Connection *conn, char c, Sitefile *site) {
int updateConnection(Connection *conn, Sitefile *site) {
char buff[300];
for (;;) {
ssize_t received = read(conn->fd, buff, sizeof(buff));
ssize_t received = recvStream(conn->stream, buff, sizeof(buff));
if (received < 0)
return errno != EAGAIN;
if (received == 0)

View File

@@ -18,6 +18,7 @@
#ifndef _HAVE_CONNECTIONS
#define _HAVE_CONNECTIONS
#include <runner.h>
#include <sockets.h>
#include <sitefile.h>
typedef enum {
@@ -33,7 +34,7 @@ typedef struct {
} Field;
typedef struct Connection {
int fd;
Stream *stream;
ConnectionSteps progress;
RequestType type;
@@ -60,7 +61,7 @@ typedef struct Connection {
//Ephemeral fields: Things which are freed and reallocated after each new
//request, path, body
int newConnection(int fd, Connection *ret);
int newConnection(Stream *stream, Connection *ret);
//returns non-zero on error. creates a new connection bound to fd
void resetConnection(Connection *conn);
void freeConnection(Connection *conn);

View File

@@ -36,8 +36,11 @@ typedef struct {
typedef struct {
int size;
SiteCommand *content;
SocketType type;
char *key;
char *cert;
} Sitefile;
Sitefile *parseFile(char *path);
Sitefile *parseSitefile(char *path);
void freeSitefile(Sitefile *site);
#endif

56
src/include/sockets.h Normal file
View File

@@ -0,0 +1,56 @@
/*
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 <https://www.gnu.org/licenses/>.
*/
#ifndef _HAVE_SOCKETS
#define _HAVE_SOCKETS
#include <stddef.h>
#include <netinet/in.h>
#include <gnutls/gnutls.h>
#include <util.h>
typedef struct {
SocketType type;
int fd;
struct sockaddr_in addr;
socklen_t addrlen;
gnutls_certificate_credentials_t creds;
gnutls_priority_t priority;
} Listener;
typedef struct {
SocketType type;
int fd;
gnutls_session_t session;
} Stream;
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)
Stream *acceptStream(Listener *listener, int flags);
//returns 1 on error, accepts fcntl flags
void freeListener(Listener *listener);
void freeStream(Stream *stream);
ssize_t sendStream(Stream *stream, 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.
#endif

View File

@@ -17,6 +17,11 @@
*/
#ifndef _HAVE_UTIL
#define _HAVE_UTIL
typedef enum {
TCP,
TLS,
} SocketType;
typedef enum {
GET,
POST,
@@ -30,6 +35,8 @@ typedef enum {
//INVALID in HTTP/1.1.
} RequestType;
int initLogging(char *path);
int createLog(char *msg);
int istrcmp(char *s1, char *s2);
//case insensitive strcmp
RequestType getType(char *str);

View File

@@ -20,25 +20,25 @@
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <netinet/in.h>
#include <util.h>
#include <runner.h>
#include <sockets.h>
#include <sitefile.h>
FILE *logs;
int main(int argc, char **argv) {
char *logout = "/var/log/swebs.log";
char *sitefile = NULL;
int processes = 8;
uint16_t port = htons(80);
uint16_t port = 443;
int backlog = 100;
for (;;) {
int c = getopt(argc, argv, "o:j:s:c:p:b:h");
int c = getopt(argc, argv, "o:j:s:p:b:c:hl");
if (c == -1)
break;
switch (c) {
@@ -52,11 +52,26 @@ int main(int argc, char **argv) {
sitefile = optarg;
break;
case 'p':
port = htons(atoi(optarg));
port = atoi(optarg);
break;
case 'b':
backlog = atoi(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"
);
exit(EXIT_SUCCESS);
case 'h':
printf(
"Usage: swebs [options]\n"
@@ -65,39 +80,44 @@ int main(int argc, char **argv) {
" -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"
" -l Show some legal details\n"
" -h Show this help message\n"
);
exit(EXIT_SUCCESS);
case '?':
fprintf(stderr, "-h for help\n");
exit(EXIT_FAILURE);
}
}
int fd = socket(AF_INET, SOCK_STREAM, 0);
assert(fd >= 0);
int opt = 1;
assert(setsockopt(fd, SOL_SOCKET,
SO_REUSEPORT,
&opt, sizeof(opt)) >= 0);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = port;
socklen_t addrlen = sizeof(addr);
assert(bind(fd, (struct sockaddr *) &addr, addrlen) >= 0);
assert(listen(fd, backlog) >= 0);
if (sitefile == NULL) {
fprintf(stderr, "No sitefile configured\n");
exit(EXIT_FAILURE);
}
Sitefile *site = parseFile(sitefile);
Sitefile *site = parseSitefile(sitefile);
if (site == NULL) {
fprintf(stderr, "Invalid sitefile %s\n", sitefile);
exit(EXIT_FAILURE);
}
logs = fopen(logout, "a");
if (logs == NULL) {
Listener *listener;
switch (site->type) {
case TCP: default:
listener = createListener(TCP, port, backlog);
break;
case TLS:
initTLS();
listener = createListener(TLS, port, backlog,
site->key, site->cert);
break;
}
if (listener == NULL) {
fprintf(stderr, "Failed to create socket\n");
exit(EXIT_FAILURE);
}
if (initLogging(logout)) {
fprintf(stderr, "Couldn't open logs file %s\n", logout);
exit(EXIT_FAILURE);
}
@@ -122,16 +142,15 @@ int main(int argc, char **argv) {
(void*(*)(void*)) runServer, args);
}
createLog("swebs started");
for (;;) {
fsync(fd);
//TODO: Find out why this works
int newfd = accept(fd, (struct sockaddr *) &addr,
&addrlen);
if (newfd < 0)
exit(EXIT_FAILURE);
int flags = fcntl(newfd, F_GETFL);
if (fcntl(newfd, F_SETFL, flags | O_NONBLOCK))
exit(EXIT_FAILURE);
Stream *stream = acceptStream(listener, O_NONBLOCK);
if (stream == NULL) {
createLog("Accepting a stream failed");
continue;
}
int lowestThread = 0;
int lowestCount = pending[0];
for (int i = 1; i < processes - 1; i++) {
@@ -140,8 +159,8 @@ int main(int argc, char **argv) {
lowestCount = pending[i];
}
}
if (write(notify[lowestThread][1], &newfd, sizeof(newfd))
< sizeof(newfd))
if (write(notify[lowestThread][1], &stream, sizeof(&stream))
< sizeof(&stream))
exit(EXIT_FAILURE);
}
}

View File

@@ -86,7 +86,6 @@ static void readResponse(Connection *conn, char *path) {
if (sendBinaryResponse(conn, "200 OK", data, len) < len)
goto error;
free(data);
fsync(conn->fd);
return;
error:
sendErrorResponse(conn, ERROR_500);

View File

@@ -21,7 +21,7 @@
#include <stdarg.h>
#include <string.h>
#include <limits.h>
#include <unistd.h>
#include <sys/stat.h>
#include <responseutil.h>
@@ -40,7 +40,7 @@ static int sendConnection(Connection *conn, char *format, ...) {
va_start(ap, format);
vsprintf(data, format, ap);
if (write(conn->fd, data, len) < len) {
if (sendStream(conn->stream, data, len) < len) {
free(data);
return 1;
}
@@ -83,5 +83,5 @@ int sendBinaryResponse(Connection *conn, char *status,
, status, len)
)
return 1;
return write(conn->fd, data, len) < len;
return sendStream(conn->stream, data, len) < len;
}

View File

@@ -23,9 +23,7 @@
#include <assert.h>
#include <poll.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/socket.h>
#include <runner.h>
#include <sitefile.h>
@@ -74,13 +72,13 @@ void *runServer(RunnerArgs *args) {
exit(EXIT_FAILURE);
connections = newconns;
}
int newfd;
if (read(notify, &newfd, sizeof(newfd)) < sizeof(newfd))
Stream *stream;
if (read(notify, &stream, sizeof(stream)) < sizeof(stream))
exit(EXIT_FAILURE);
fds[connCount].fd = newfd;
fds[connCount].fd = stream->fd;
fds[connCount].events = POLLIN;
if (newConnection(newfd, connections + connCount))
if (newConnection(stream, connections + connCount))
exit(EXIT_FAILURE);
connCount++;
pending[id]++;

View File

@@ -165,13 +165,16 @@ static char *copyString(char *str) {
return ret;
}
Sitefile *parseFile(char *path) {
Sitefile *parseSitefile(char *path) {
FILE *file = fopen(path, "r");
if (file == NULL)
return NULL;
Sitefile *ret = malloc(sizeof(Sitefile));
if (ret == NULL)
return NULL;
ret->type = TCP;
ret->key = NULL;
ret->cert = NULL;
int allocatedLength = 50;
ret->size = 0;
ret->content = malloc(allocatedLength * sizeof(SiteCommand));
@@ -219,6 +222,25 @@ Sitefile *parseFile(char *path) {
setValue:
continue;
}
else if (strcmp(argv[0], "define") == 0) {
if (argc < 3)
goto error;
if (strcmp(argv[1], "transport") == 0) {
if (strcmp(argv[2], "TCP") == 0)
ret->type = TCP;
else if (strcmp(argv[2], "TLS") == 0)
ret->type = TLS;
else
goto error;
}
else if (strcmp(argv[1], "key") == 0)
ret->key = copyString(argv[2]);
else if (strcmp(argv[1], "cert") == 0)
ret->cert = copyString(argv[2]);
else
goto error;
continue;
}
if (ret->size >= allocatedLength) {
allocatedLength *= 2;
SiteCommand *newcontent = realloc(ret->content,

178
src/sockets.c Normal file
View File

@@ -0,0 +1,178 @@
/*
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 <https://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdint.h>
#include <assert.h>
#include <fcntl.h>
#include <unistd.h>
#include <netinet/in.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <sockets.h>
int initTLS() {
assert(gnutls_global_init() >= 0);
return 0;
}
Listener *createListener(SocketType type, uint16_t port, int backlog, ...) {
Listener *ret = malloc(sizeof(Listener));
if (ret == NULL)
return NULL;
ret->type = type;
ret->fd = socket(AF_INET, SOCK_STREAM, 0);
if (ret->fd < 0) {
free(ret);
return NULL;
}
int opt = 1;
if (setsockopt(ret->fd, SOL_SOCKET,
SO_REUSEPORT,
&opt, sizeof(opt)) < 0) {
goto error;
}
ret->addr.sin_family = AF_INET;
ret->addr.sin_addr.s_addr = INADDR_ANY;
ret->addr.sin_port = htons(port);
ret->addrlen = sizeof(ret->addr);
if (bind(ret->fd, (struct sockaddr *) &ret->addr, ret->addrlen) < 0)
goto error;
if (listen(ret->fd, backlog) < 0)
goto error;
va_list ap;
va_start(ap, backlog);
switch (type) {
case TCP: default:
break;
case TLS: {
char *keyfile = va_arg(ap, char *);
char *certfile = va_arg(ap, char *);
if (gnutls_certificate_allocate_credentials(&ret->creds)
< 0)
goto error;
if (gnutls_certificate_set_x509_key_file(ret->creds,
certfile, keyfile,
GNUTLS_X509_FMT_PEM) < 0)
goto error;
if (gnutls_priority_init(&ret->priority,
NULL, NULL) < 0)
goto error;
#if GNUTLS_VERSION_NUMBER >= 0x030506
gnutls_certificate_set_known_dh_params(ret->creds,
GNUTLS_SEC_PARAM_MEDIUM);
#endif
break;
}
}
va_end(ap);
return ret;
error:
close(ret->fd);
free(ret);
return NULL;
}
Stream *acceptStream(Listener *listener, int flags) {
Stream *ret = malloc(sizeof(Stream));
if (ret == NULL)
return NULL;
ret->type = listener->type;
ret->fd = accept(listener->fd, (struct sockaddr *) &listener->addr,
&listener->addrlen);
if (ret->fd < 0) {
free(ret);
return NULL;
}
switch (listener->type) {
case TCP: default:
break;
case TLS:
if (gnutls_init(&ret->session, GNUTLS_SERVER) < 0)
goto error;
if (gnutls_priority_set(ret->session,
listener->priority) < 0)
goto error;
if (gnutls_credentials_set(ret->session,
GNUTLS_CRD_CERTIFICATE,
listener->creds) < 0)
goto error;
gnutls_certificate_server_set_request(ret->session,
GNUTLS_CERT_IGNORE);
gnutls_handshake_set_timeout(ret->session,
GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
gnutls_transport_set_int(ret->session, ret->fd);
if (gnutls_handshake(ret->session) < 0)
goto error;
break;
}
{
int flags = fcntl(ret->fd, F_GETFD);
fcntl(ret->fd, F_SETFD, flags | O_NONBLOCK);
}
return ret;
error:
close(ret->fd);
free(ret);
return NULL;
}
void freeListener(Listener *listener) {
if (listener->type == TLS) {
gnutls_certificate_free_credentials(listener->creds);
gnutls_priority_deinit(listener->priority);
}
close(listener->fd);
free(listener);
}
void freeStream(Stream *stream) {
if (stream->type == TLS) {
gnutls_bye(stream->session, GNUTLS_SHUT_RDWR);
gnutls_deinit(stream->session);
}
close(stream->fd);
free(stream);
}
ssize_t sendStream(Stream *stream, void *data, size_t len) {
switch (stream->type) {
case TCP:
return write(stream->fd, data, len);
case TLS:
return gnutls_record_send(stream->session, data, len);
default:
return -1;
}
}
ssize_t recvStream(Stream *stream, void *data, size_t len) {
switch (stream->type) {
case TCP:
return read(stream->fd, data, len);
case TLS:
return gnutls_record_recv(stream->session, data, len);
default:
return -1;
}
}

View File

@@ -15,12 +15,39 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <time.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdint.h>
#include <util.h>
static FILE *logs;
int initLogging(char *path) {
logs = fopen(path, "a");
return logs == NULL;
}
int createLog(char *msg) {
time_t currenttime;
time(&currenttime);
struct tm *timeinfo = gmtime(&currenttime);
if (timeinfo == NULL)
return 1;
fprintf(logs, "[%d-%02d-%02dT%02d:%02d:%02dZ] %s\n",
timeinfo->tm_year + 1900,
timeinfo->tm_mon + 1,
timeinfo->tm_mday,
timeinfo->tm_hour,
timeinfo->tm_min,
timeinfo->tm_sec,
msg);
fflush(logs);
return 0;
}
int istrcmp(char *s1, char *s2) {
for (int i = 0;; i++) {
char c1 = tolower(s1[i]);