114 Commits
v3.0 ... v4.0

Author SHA1 Message Date
4cc7b1c18a Bugfix for charset in Content-Type 2020-12-28 21:04:39 +01:00
6fa62aeb4d Range Responses 2020-12-28 19:11:56 +01:00
3663a6064b Added TODO for Reverse DNS 2020-12-28 16:40:48 +01:00
ed233f8b5e Bugifx for closed connections 2020-12-28 16:07:39 +01:00
6e8875d528 Bugfix query 2020-12-28 15:57:23 +01:00
640f3abc1f Flush after print 2020-12-28 15:22:02 +01:00
7d8f065ec9 Add compile-debian target 2020-12-28 14:57:09 +01:00
dd44ffee95 Update Makefile 2020-12-28 14:53:03 +01:00
c0962b90a6 Bugfix create /var/necronda-server/ 2020-12-28 14:01:16 +01:00
161952441d Shared mem bugfix 3 2020-12-28 13:55:38 +01:00
09b2118863 Shared mem bugfix 2 2020-12-28 13:53:18 +01:00
b64828b01a Shared mem debug 2020-12-28 13:48:37 +01:00
8a640acd24 Remove shared mem on error 2020-12-28 13:37:30 +01:00
609fd34ab7 Define value only if not defined 2020-12-28 13:32:24 +01:00
7cb4f40d22 Process FastCGI header fields 2020-12-28 13:18:38 +01:00
156c7d6621 normal printing in fastcgi.c 2020-12-28 09:23:00 +01:00
f39e1a6cb6 Code Cleanup 2020-12-27 23:58:59 +01:00
c708633197 FastCGI stdin implementation 2020-12-27 23:57:14 +01:00
b7f574e2ed Last-Modified for php scripts 2020-12-27 22:42:01 +01:00
be4475c336 Bugfix for FastCGI compression 2020-12-27 20:27:25 +01:00
c6fbf25192 Bugfix for FastCGI compression 2020-12-27 20:15:49 +01:00
b6ecb68b1a FastCGI compression 2020-12-27 20:04:15 +01:00
492e6a94cb Remove debugging file messages 2020-12-27 19:39:00 +01:00
1733ad3c1d Always lowering both http field names in search 2020-12-27 19:36:48 +01:00
885ec2226f FastCGI working buggy 2020-12-27 18:33:50 +01:00
1429961e44 Add fastcgi 2020-12-26 21:13:18 +01:00
d88c70c978 Compression of xml files 2020-12-24 23:53:14 +01:00
bfe08623c2 Bugfix for receive in http.c 2020-12-24 23:52:58 +01:00
2ed8451db1 File compression working 2020-12-23 21:19:40 +01:00
495381ab3f Possibility to accept If-Modified-Since field 2020-12-23 18:57:37 +01:00
9d1594dc01 Using SEEK_END and SEEK_SET 2020-12-23 18:33:34 +01:00
463568182d Check for illegal characters in http header 2020-12-23 18:29:38 +01:00
fbd9a2667f Bugfix cache file 2020-12-23 18:21:03 +01:00
d39f69d665 Support for compressed files in client.c 2020-12-22 22:40:08 +01:00
c6f364d6e8 Add ETag calculation 2020-12-22 22:31:58 +01:00
8cccf48826 Add geoip TODOs 2020-12-22 20:32:00 +01:00
7e2c9bea6d Fix --geoip usage 2020-12-22 20:23:01 +01:00
562cfb04e6 Remove parent stds and added --geoip 2020-12-22 20:22:07 +01:00
14bcc47db2 Handle child process exit codes 2020-12-22 20:01:59 +01:00
815fa32d14 Fix for free() 2020-12-22 18:43:11 +01:00
31fa9195a1 Bugfix 2020-12-20 17:01:38 +01:00
caa09c3f05 Bugfix 2020-12-20 16:55:18 +01:00
00abf345b9 Added basic file transfer 2020-12-20 16:47:40 +01:00
f0338209f5 Added magic file type detection 2020-12-19 22:09:03 +01:00
f6ac55adaf Added cache.c/.h 2020-12-19 17:38:56 +01:00
5e9c98d67f Added HEAD method 2020-12-19 17:13:03 +01:00
ba57509461 Bugfix for redirection 2020-12-19 13:17:30 +01:00
4e079e094f Bugfix location redirect 2020-12-19 13:06:51 +01:00
a4901f4c23 Changed port numbers 2020-12-19 13:03:32 +01:00
f5a42b8c28 Redirection on insecure connection 2020-12-19 13:03:03 +01:00
4f33e1d809 Changed abort goto 2020-12-18 23:08:28 +01:00
c7902ea5b5 Added dir mode in client 2020-12-18 22:39:18 +01:00
50f5438654 Proper url encoding for location field 2020-12-18 22:20:32 +01:00
a5c1516468 Added uri parsing 2020-12-18 22:06:41 +01:00
db224c7452 Renamed http not strict 2020-12-16 18:44:43 +01:00
812d5ab384 Renamed http strict and not strict 2020-12-16 18:43:53 +01:00
1be4929c6c Refacotred graceful terminating 2020-12-16 18:38:23 +01:00
80ed6dea3e Added http header strict and not strict mode 2020-12-16 18:35:11 +01:00
14b4f71af7 Refactored html error document 2020-12-15 22:13:57 +01:00
a6789a7f3e Added favicon 2020-12-15 21:55:02 +01:00
7c2e3f2f9b Changed sleep time from 1 sec to 50 ms 2020-12-15 19:59:20 +01:00
93973c6e77 Refactored http default error document 2020-12-15 19:53:21 +01:00
97b04173ee Fixed typo in src/http.h 2020-12-13 21:00:18 +01:00
8002ab3ed7 Moved http.c and http.h from src/net to src 2020-12-13 20:55:27 +01:00
35b924eea5 Bugfix of error document 2020-12-13 20:33:46 +01:00
3dbe356b87 Bugfix of error document 2020-12-13 19:57:15 +01:00
84c3e87a08 Bugfix of error document 2020-12-13 19:29:55 +01:00
51531ff78e Updated http error document 2020-12-13 19:08:15 +01:00
e0ecc35d0c Refactored http header parsing error handling 2020-12-13 19:02:06 +01:00
c2de0ce439 Added http error messages 2020-12-13 18:50:08 +01:00
2bcfa97e1b Added url_encode and url_decode 2020-12-13 16:05:55 +01:00
7c5a57b3d8 Renamed webroot to webroot_base 2020-12-13 15:34:39 +01:00
99328996fa Using const char in http 2020-12-13 15:33:46 +01:00
8249b7ed89 Used strcpy 2020-12-13 15:17:56 +01:00
db94388e69 Added struct uri 2020-12-13 14:12:53 +01:00
a5919807a3 Restructured includes 2020-12-13 14:12:53 +01:00
cc53b43c8a Added uri.h and uri.c 2020-12-13 14:12:53 +01:00
1ea0c95ebb Improved argument parsing 2020-12-13 13:40:53 +01:00
9f3d8cc0c0 Added argument parsing 2020-12-13 13:05:49 +01:00
8b9b32b9af Refactored client.c 2020-12-12 22:09:42 +01:00
81b5a569db Fix in http_receive_request 2020-12-12 22:02:49 +01:00
dcdf91d5bf Refactored client.c 2020-12-12 21:47:15 +01:00
2d6277dc23 Implemented http responses 2020-12-12 21:41:21 +01:00
8335a20e32 Fixed keep alive 2020-12-12 17:53:19 +01:00
8ac29b3815 Fixing format_duration 2020-12-12 17:14:59 +01:00
687d918677 Parsing HTTP header 2020-12-12 17:13:36 +01:00
b4a4f12e9c Changed termination message 2020-12-12 16:06:04 +01:00
38448401d0 Updated format_duration 2020-12-12 15:31:26 +01:00
352742d26c Renamed timeout to client_timeout 2020-12-12 11:26:13 +01:00
e71786716e Updated sigterm handling 2020-12-11 23:51:23 +01:00
bce6f64c6e Refactored format_duration 2020-12-11 23:41:06 +01:00
43bf946cce Added Keep-Alive header 2020-12-11 23:19:36 +01:00
49f1a5f429 Added Client timeout 2020-12-11 23:16:30 +01:00
3ba1848e40 Updated makefile 2020-12-11 23:07:12 +01:00
f7fc4fa801 Http keep alive 2020-12-11 23:03:54 +01:00
e116b940b2 Set timeout for client 2020-12-11 20:34:12 +01:00
37f3e4eae1 client request handler 2020-12-11 20:28:48 +01:00
bbbde82a46 Removed BIOs 2020-12-11 17:08:26 +01:00
77852d9626 Added ready status message 2020-12-11 17:01:53 +01:00
13fb362d12 Implemented BIOs 2020-12-11 16:38:17 +01:00
0bf8c0ccc3 Implemented basic SSL 2020-12-11 16:25:26 +01:00
f98ffc7077 Child process management 2020-12-10 22:45:45 +01:00
e6760cf665 Process management 2020-12-10 22:24:06 +01:00
ae430c340c Updated Makefile 2020-12-10 21:46:30 +01:00
42b8aee4bd Simple echo client 2020-12-10 20:55:33 +01:00
24ce5d8cd6 Implemented Hello World 2020-12-09 22:45:35 +01:00
00382a71e2 Renamed functions in client 2020-12-09 20:04:27 +01:00
feaadf31bf Make run.sh executable 2020-12-09 19:37:28 +01:00
a16c66c7e7 Update run.sh 2020-12-09 19:36:17 +01:00
7500476dc5 Added readme 2020-12-09 19:30:15 +01:00
a417efdece Updated Makefile 2020-12-09 19:23:55 +01:00
d104a43d1b Refactored for version 4 2020-12-09 19:21:57 +01:00
e68e0239c3 Passing Country Code to PHP 2020-12-05 16:48:38 +01:00
f148578154 Adding micros start to php-cgi env 2020-12-05 12:40:49 +01:00
39 changed files with 2940 additions and 3111 deletions

1
.gitignore vendored
View File

@ -6,3 +6,4 @@
!.gitignore
!CppNet
!CppNet/**
!README.md

View File

@ -2,21 +2,18 @@
packages:
@echo "Installing packages..."
sudo apt-get install g++ libmagic-dev libssl-dev php-cgi
sudo apt-get install gcc libmagic-dev libssl-dev php-fpm
@echo "Finished downloading!"
update:
@echo "Updating imported git repos..."
cd CppNet
git pull
cd ..
@echo "Finished updating!"
compile:
@echo "Compiling..."
@mkdir -p bin
g++ src/necronda-server.cpp -o bin/necronda-server -std=c++17 -fPIC -pthread -lz -lmagic -lssl -ldl -lcrypto
@echo "Finished compiling!"
gcc src/necronda-server.c -o bin/necronda-server -std=c11 -lssl -lcrypto -lmagic -lz
install: | packages update compile
compile-debian:
@mkdir -p bin
gcc src/necronda-server.c -o bin/necronda-server -std=c11 -lssl -lcrypto -lmagic -lz \
-D MAGIC_FILE="\"/usr/share/file/magic.mgc\"" \
-D PHP_FPM_SOCKET="\"/var/run/php/php7.3-fpm.sock\""
install: | packages compile
@echo "Finished!"

2
README.md Normal file
View File

@ -0,0 +1,2 @@
# Necronda web server

9
run.sh Normal file → Executable file
View File

@ -1,7 +1,6 @@
#!/bin/bash
echo "-- Building and starting Necronda Server..."
make update && make compile && \
echo -e "-- Successfully finished compiling!\n" && \
sleep 0.0625 && \
echo -e "-- Starting Server...\n" && \
authbind ./bin/necronda-server
make compile && \
echo "-- Successfully finished compiling!" && \
echo "-- Starting Server..." && \
./bin/necronda-server $@

View File

@ -1,196 +0,0 @@
#include "URI.h"
#include "necronda-server.h"
#include <utility>
#include <sys/stat.h>
using namespace std;
string getExtension(string path) {
long pos = path.find_last_of('.');
if (pos == string::npos) {
return "";
}
return path.substr(pos + 1, path.length() - pos);
}
string getFilename(string path) {
long pos = path.find_last_of('/');
if (pos == string::npos) {
return "";
}
return path.substr(pos + 1, path.length() - pos);
}
bool isDirectory(string path) {
struct stat statbuf;
return stat(path.c_str(), &statbuf) == 0 && S_ISDIR(statbuf.st_mode) != 0;
}
bool isFile(string path) {
struct stat statbuf;
return stat(path.c_str(), &statbuf) == 0 && S_ISDIR(statbuf.st_mode) == 0;
}
bool fileExists(string path) {
struct stat statbuf;
return stat(path.c_str(), &statbuf) == 0;
}
URI::URI() = default;
URI::URI(string webroot, string reqpath) {
unsigned long pos = reqpath.find('?');
if (pos != string::npos) {
queryinit = true;
query = reqpath.substr(pos + 1, reqpath.length() - pos);
reqpath.erase(pos, reqpath.length() - pos);
} else {
query = "";
queryinit = false;
}
if (webroot.length() >= 1 && webroot[webroot.length() - 1] == '/') {
webroot.erase(webroot.length() - 1);
}
reqpath = url_decode(reqpath);
if (reqpath.find("/../") != string::npos) {
throw (char *) "Invalid path";
}
if (reqpath[0] != '/') {
reqpath = '/' + reqpath;
}
this->webroot = webroot;
this->reqpath = reqpath;
info = "";
relpath = reqpath;
while ((!fileExists(webroot + relpath) || (isDirectory(webroot + relpath) && !fileExists(webroot + relpath + "/index.php")))
&& (!fileExists(webroot + relpath + ".php") || (isDirectory(webroot + relpath + ".php") && !fileExists(webroot + relpath + ".php/index.php")))
&& (!fileExists(webroot + relpath + ".html") || (isDirectory(webroot + relpath + ".html") && !fileExists(webroot + relpath + ".html/index.php")))) {
long slash = relpath.find_last_of('/');
if (slash == string::npos || relpath == "/") {
break;
}
info = relpath.substr(slash) + info;
relpath.erase(slash);
}
if (!info.empty() && isDirectory(webroot + relpath)) {
relpath.append("/");
}
string abs = relpath;
if (fileExists(webroot + abs)) {
string ext = getExtension(abs);
if (ext == "php" || ext == "html") {
abs.erase(abs.length() - ext.length() - 1, abs.length());
}
}
string fname = getFilename(abs);
if (fname == "index") {
abs.erase(abs.length() - fname.length() - 1, abs.length());
}
this->filepath = webroot + relpath;
if (isDirectory(webroot + abs)) {
if (abs[abs.length() - 1] != '/') {
abs += "/";
}
this->relpath = abs;
abs += "index";
if (fileExists(webroot + abs + ".php")) {
this->filepath = webroot + abs + ".php";
} else if (fileExists(webroot + abs + ".html")) {
this->filepath = webroot + abs + ".html";
}
} else {
if (abs[abs.length() - 1] == '/') {
abs.erase(abs.length() - 1, abs.length() - 1);
}
this->relpath = abs;
if (fileExists(webroot + abs + ".php")) {
this->filepath = webroot + abs + ".php";
} else if (fileExists(webroot + abs + ".html")) {
this->filepath = webroot + abs + ".html";
}
}
if (isStatic() && !info.empty()) {
if (relpath[relpath.length() - 1] == '/') {
relpath.erase(relpath.length() - 1);
}
newpath = relpath + info;
filepath = "";
} else if (relpath != reqpath) {
if (!info.empty() && relpath[relpath.length() - 1] == '/') {
info.erase(0,1);
}
newpath = relpath + info;
} else {
newpath = "";
}
}
string URI::getWebRoot() {
return webroot;
}
string URI::getRelativePath() {
return relpath;
}
string URI::getAbsolutePath() {
return webroot + relpath;
}
string URI::getFilePath() {
return filepath;
}
string URI::getRelativeFilePath() {
string str = getFilePath();
long len = getWebRoot().length();
return str.substr(len, str.length() - len);
}
string URI::getNewPath() {
if (isStatic()) {
if (hasQuery()) {
return getRelativePath();
}
}
if (!newpath.empty() && newpath != reqpath) {
return url_encode(newpath) + (queryinit? "?" + query : "");
} else {
return "";
}
}
FILE *URI::openFile() {
return fopen64(getFilePath().c_str(), "rb");
}
string URI::getFilePathInfo() {
return info; //getAbsolutePath().erase(getFilePath().length(), getAbsolutePath().length());
}
string URI::getFileType() {
return getMimeType(getFilePath());
}
bool URI::isStatic() {
return getExtension(filepath) != "php";
}
string URI::getQuery() {
return query;
}
bool URI::hasQuery() {
return queryinit;
}

View File

@ -1,51 +0,0 @@
#include <iostream>
#ifndef NECRONDA_PATH
#define NECRONDA_PATH
using namespace std;
class URI {
private:
string webroot;
string reqpath;
string relpath;
string query;
string info;
string filepath;
string newpath;
bool queryinit{};
public:
URI();
URI(string webroot, string reqpath);
string getWebRoot();
string getRelativePath();
string getAbsolutePath();
string getFilePath();
string getRelativeFilePath();
string getNewPath();
FILE *openFile();
string getFilePathInfo();
string getFileType();
bool isStatic();
string getQuery();
bool hasQuery();
};
#endif

341
src/cache.c Normal file
View File

@ -0,0 +1,341 @@
/**
* Necronda Web Server
* File cache implementation
* src/cache.c
* Lorenz Stechauner, 2020-12-19
*/
#include <zlib.h>
#include "cache.h"
#include "uri.h"
int magic_init() {
magic = magic_open(MAGIC_MIME);
if (magic == NULL) {
fprintf(stderr, ERR_STR "Unable to open magic cookie: %s" CLR_STR "\n", strerror(errno));
fflush(stderr);
return -1;
}
if (magic_load(magic, MAGIC_FILE) != 0) {
fprintf(stderr, ERR_STR "Unable to load magic cookie: %s" CLR_STR "\n", magic_error(magic));
fflush(stderr);
return -2;
}
return 0;
}
void cache_process_term() {
cache_continue = 0;
}
int cache_process() {
signal(SIGINT, cache_process_term);
signal(SIGTERM, cache_process_term);
int shm_id = shmget(SHM_KEY, FILE_CACHE_SIZE * sizeof(cache_entry), 0);
if (shm_id < 0) {
fprintf(stderr, ERR_STR "Unable to create shared memory: %s" CLR_STR "\n", strerror(errno));
fflush(stderr);
return -1;
}
shmdt(cache);
void *shm_rw = shmat(shm_id, NULL, 0);
if (shm_rw == (void *) -1) {
fprintf(stderr, ERR_STR "Unable to attach shared memory (rw): %s" CLR_STR "\n", strerror(errno));
fflush(stderr);
return -2;
}
cache = shm_rw;
if (mkdir("/var/necronda-server/", 0755) < 0) {
if (errno != EEXIST) {
fprintf(stderr, ERR_STR "Unable to create directory '/var/necronda-server/': %s" CLR_STR "\n", strerror(errno));
fflush(stderr);
return -3;
}
}
FILE *cache_file = fopen("/var/necronda-server/cache", "rb");
if (cache_file != NULL) {
fread(cache, sizeof(cache_entry), FILE_CACHE_SIZE, cache_file);
fclose(cache_file);
}
for (int i = 0; i < FILE_CACHE_SIZE; i++) {
cache[i].is_updating = 0;
}
FILE *file;
char buf[16384];
char comp_buf[16384];
char filename_comp[256];
unsigned long read;
int compress;
SHA_CTX ctx;
unsigned char hash[SHA_DIGEST_LENGTH];
while (cache_continue) {
for (int i = 0; i < FILE_CACHE_SIZE; i++) {
if (cache[i].filename[0] != 0 && cache[i].meta.etag[0] == 0 && !cache[i].is_updating) {
cache[i].is_updating = 1;
SHA1_Init(&ctx);
file = fopen(cache[i].filename, "rb");
compress = strncmp(cache[i].meta.type, "text/", 5) == 0 ||
(strncmp(cache[i].meta.type, "application/", 12) == 0 &&
strstr(cache[i].meta.type, "+xml") != NULL);
int level = NECRONDA_ZLIB_LEVEL;
z_stream strm;
FILE *comp_file = NULL;
if (compress) {
sprintf(buf, "%.*s/.necronda-server", cache[i].webroot_len, cache[i].filename);
mkdir(buf, 0755);
sprintf(buf, "%.*s/.necronda-server/cache", cache[i].webroot_len, cache[i].filename);
mkdir(buf, 0700);
char *rel_path = cache[i].filename + cache[i].webroot_len + 1;
for (int j = 0; j < strlen(rel_path); j++) {
char ch = rel_path[j];
if (ch == '/') {
ch = '_';
}
buf[j] = ch;
}
buf[strlen(rel_path)] = 0;
sprintf(filename_comp, "%.*s/.necronda-server/cache/%s.z", cache[i].webroot_len, cache[i].filename, buf);
comp_file = fopen(filename_comp, "wb");
if (comp_file == NULL) {
compress = 0;
fprintf(stderr, ERR_STR "Unable to open cache file: %s" CLR_STR "\n", strerror(errno));
fflush(stderr);
} else {
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
if (deflateInit(&strm, level) != Z_OK) {
fprintf(stderr, ERR_STR "Unable to init deflate: %s" CLR_STR "\n", strerror(errno));
fflush(stderr);
compress = 0;
fclose(comp_file);
}
}
}
while ((read = fread(buf, 1, sizeof(buf), file)) > 0) {
SHA1_Update(&ctx, buf, read);
if (compress) {
strm.avail_in = read;
strm.next_in = (unsigned char *) buf;
do {
strm.avail_out = sizeof(comp_buf);
strm.next_out = (unsigned char *) comp_buf;
deflate(&strm, feof(file) ? Z_FINISH : Z_NO_FLUSH);
fwrite(comp_buf, 1, sizeof(comp_buf) - strm.avail_out, comp_file);
strm.avail_in = 0;
} while (strm.avail_out == 0);
}
}
if (compress) {
deflateEnd(&strm);
fclose(comp_file);
strcpy(cache[i].meta.filename_comp, filename_comp);
} else {
memset(cache[i].meta.filename_comp, 0, sizeof(cache[i].meta.filename_comp));
}
SHA1_Final(hash, &ctx);
memset(cache[i].meta.etag, 0, sizeof(cache[i].meta.etag));
for (int j = 0; j < SHA_DIGEST_LENGTH; j++) {
sprintf(cache[i].meta.etag + j * 2, "%02x", hash[j]);
}
fclose(file);
cache[i].is_updating = 0;
}
}
cache_file = fopen("/var/necronda-server/cache", "wb");
fwrite(cache, sizeof(cache_entry), FILE_CACHE_SIZE , cache_file);
fclose(cache_file);
sleep(1);
}
return 0;
}
int cache_init() {
if (magic_init() != 0) {
return -1;
}
int shm_id = shmget(SHM_KEY, FILE_CACHE_SIZE * sizeof(cache_entry), IPC_CREAT | IPC_EXCL | 0600);
if (shm_id < 0) {
fprintf(stderr, ERR_STR "Unable to create shared memory: %s" CLR_STR "\n", strerror(errno));
fflush(stderr);
return -2;
}
void *shm = shmat(shm_id, NULL, SHM_RDONLY);
if (shm == (void *) -1) {
fprintf(stderr, ERR_STR "Unable to attach shared memory (ro): %s" CLR_STR "\n", strerror(errno));
fflush(stderr);
return -3;
}
cache = shm;
void *shm_rw = shmat(shm_id, NULL, 0);
if (shm_rw == (void *) -1) {
fprintf(stderr, ERR_STR "Unable to attach shared memory (rw): %s" CLR_STR "\n", strerror(errno));
fflush(stderr);
return -4;
}
cache = shm_rw;
memset(cache, 0, FILE_CACHE_SIZE * sizeof(cache_entry));
shmdt(shm_rw);
cache = shm;
pid_t pid = fork();
if (pid == 0) {
// child
if (cache_process() == 0) {
return 1;
} else {
return -6;
}
} else if (pid > 0) {
// parent
fprintf(stderr, "Started child process with PID %i as cache-updater\n", pid);
fflush(stderr);
children[0] = pid;
} else {
fprintf(stderr, ERR_STR "Unable to create child process: %s" CLR_STR "\n", strerror(errno));
fflush(stderr);
return -5;
}
return 0;
}
int cache_unload() {
int shm_id = shmget(SHM_KEY, 0, 0);
if (shm_id < 0) {
fprintf(stderr, ERR_STR "Unable to create shared memory: %s" CLR_STR "\n", strerror(errno));
fflush(stderr);
} else if (shmctl(shm_id, IPC_RMID, NULL) < 0) {
fprintf(stderr, ERR_STR "Unable to configure shared memory: %s" CLR_STR "\n", strerror(errno));
fflush(stderr);
}
shmdt(cache);
return 0;
}
int cache_update_entry(int entry_num, const char *filename, const char *webroot) {
void *cache_ro = cache;
int shm_id = shmget(SHM_KEY, 0, 0);
void *shm_rw = shmat(shm_id, NULL, 0);
if (shm_rw == (void *) -1) {
print(ERR_STR "Unable to attach shared memory (rw): %s" CLR_STR, strerror(errno));
return -1;
}
cache = shm_rw;
struct stat statbuf;
stat(filename, &statbuf);
memcpy(&cache[entry_num].meta.stat, &statbuf, sizeof(statbuf));
cache[entry_num].webroot_len = (unsigned char) strlen(webroot);
strcpy(cache[entry_num].filename, filename);
magic_setflags(magic, MAGIC_MIME_TYPE);
const char *type = magic_file(magic, filename);
char type_new[24];
sprintf(type_new, "%s", type);
if (strcmp(type, "text/plain") == 0) {
if (strncmp(filename + strlen(filename) - 4, ".css", 4) == 0) {
sprintf(type_new, "text/css");
} else if (strcmp(filename + strlen(filename) - 3, ".js") == 0) {
sprintf(type_new, "text/javascript");
}
}
strcpy(cache[entry_num].meta.type, type_new);
magic_setflags(magic, MAGIC_MIME_ENCODING);
strcpy(cache[entry_num].meta.charset, magic_file(magic, filename));
memset(cache[entry_num].meta.etag, 0, sizeof(cache[entry_num].meta.etag));
memset(cache[entry_num].meta.filename_comp, 0, sizeof(cache[entry_num].meta.filename_comp));
cache[entry_num].is_updating = 0;
shmdt(shm_rw);
cache = cache_ro;
return 0;
}
int cache_filename_comp_invalid(const char *filename) {
void *cache_ro = cache;
int shm_id = shmget(SHM_KEY, 0, 0);
void *shm_rw = shmat(shm_id, NULL, 0);
if (shm_rw == (void *) -1) {
print(ERR_STR "Unable to attach shared memory (rw): %s" CLR_STR, strerror(errno));
return -1;
}
cache = shm_rw;
int i;
for (i = 0; i < FILE_CACHE_SIZE; i++) {
if (cache[i].filename[0] != 0 && strlen(cache[i].filename) == strlen(filename) &&
strcmp(cache[i].filename, filename) == 0) {
if (cache[i].is_updating) {
return 0;
} else {
break;
}
}
}
memset(cache[i].meta.etag, 0, sizeof(cache[i].meta.etag));
memset(cache[i].meta.filename_comp, 0, sizeof(cache[i].meta.filename_comp));
cache[i].is_updating = 0;
shmdt(shm_rw);
cache = cache_ro;
return 0;
}
int uri_cache_init(http_uri *uri) {
if (uri->filename == NULL) {
return 0;
}
int i;
for (i = 0; i < FILE_CACHE_SIZE; i++) {
if (cache[i].filename[0] != 0 && strlen(cache[i].filename) == strlen(uri->filename) &&
strcmp(cache[i].filename, uri->filename) == 0) {
uri->meta = &cache[i].meta;
if (cache[i].is_updating) {
return 0;
} else {
break;
}
}
}
if (uri->meta == NULL) {
for (i = 0; i < FILE_CACHE_SIZE; i++) {
if (cache[i].filename[0] == 0) {
if (cache_update_entry(i, uri->filename, uri->webroot) != 0) {
return -1;
}
uri->meta = &cache[i].meta;
break;
}
}
} else {
struct stat statbuf;
stat(uri->filename, &statbuf);
if (memcmp(&uri->meta->stat.st_mtime, &statbuf.st_mtime, sizeof(statbuf.st_mtime)) != 0) {
if (cache_update_entry(i, uri->filename, uri->webroot) != 0) {
return -1;
}
}
}
return 0;
}

45
src/cache.h Normal file
View File

@ -0,0 +1,45 @@
/**
* Necronda Web Server
* File cache implementation (header file)
* src/cache.h
* Lorenz Stechauner, 2020-12-19
*/
#ifndef NECRONDA_SERVER_CACHE_H
#define NECRONDA_SERVER_CACHE_H
#include <magic.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include "uri.h"
magic_t magic;
typedef struct {
char filename[256];
unsigned char webroot_len;
unsigned char is_updating:1;
meta_data meta;
} cache_entry;
cache_entry *cache;
int cache_continue = 1;
int magic_init();
void cache_process_term();
int cache_process();
int cache_init();
int cache_unload();
int cache_update_entry(int entry_num, const char *filename, const char *webroot);
int cache_filename_comp_invalid(const char *filename);
int uri_cache_init(http_uri *uri);
#endif //NECRONDA_SERVER_CACHE_H

532
src/client.c Normal file
View File

@ -0,0 +1,532 @@
/**
* Necronda Web Server
* Client connection and request handlers
* src/client.c
* Lorenz Stechauner, 2020-12-03
*/
#include "necronda-server.h"
#include "utils.h"
#include "uri.h"
#include "http.h"
#include "fastcgi.h"
int server_keep_alive = 1;
char *client_addr_str, *client_addr_str_ptr, *server_addr_str, *server_addr_str_ptr,
*log_client_prefix, *log_conn_prefix, *log_req_prefix,
*client_host_str;
struct timeval client_timeout = {.tv_sec = CLIENT_TIMEOUT, .tv_usec = 0};
char *get_webroot(const char *http_host) {
char *webroot = malloc(strlen(webroot_base) + strlen(http_host) + 1);
unsigned long len = strlen(webroot_base);
while (webroot_base[len - 1] == '/') len--;
long pos = strchr(http_host, ':') - http_host;
sprintf(webroot, "%.*s/%.*s", (int) len, webroot_base, (int) (pos < 0 ? strlen(http_host) : pos), http_host);
return webroot;
}
void client_terminate() {
server_keep_alive = 0;
}
int client_websocket_handler() {
// TODO implement client_websocket_handler
return 0;
}
int client_request_handler(sock *client, unsigned long client_num, unsigned int req_num) {
struct timespec begin, end;
int ret, client_keep_alive, dir_mode;
char buf0[1024], buf1[1024];
char msg_buf[4096], msg_pre_buf[4096], err_msg[256];
char buffer[CHUNK_SIZE];
err_msg[0] = 0;
char *host, *hdr_connection, *webroot;
long content_length = 0;
FILE *file = NULL;
msg_buf[0] = 0;
int accept_if_modified_since = 0;
int use_fastcgi = 0;
fastcgi_conn php_fpm = {.socket = 0, .req_id = 0};
http_res res;
sprintf(res.version, "1.1");
res.status = http_get_status(501);
res.hdr.field_num = 0;
http_add_header_field(&res.hdr, "Date", http_get_date(buf0, sizeof(buf0)));
http_add_header_field(&res.hdr, "Server", SERVER_STR);
clock_gettime(CLOCK_MONOTONIC, &begin);
fd_set socket_fds;
FD_ZERO(&socket_fds);
FD_SET(client->socket, &socket_fds);
client_timeout.tv_sec = CLIENT_TIMEOUT;
client_timeout.tv_usec = 0;
ret = select(client->socket + 1, &socket_fds, NULL, NULL, &client_timeout);
if (ret <= 0) {
if (errno != 0) {
return 1;
}
client_keep_alive = 0;
res.status = http_get_status(408);
goto respond;
}
clock_gettime(CLOCK_MONOTONIC, &begin);
http_req req;
ret = http_receive_request(client, &req);
if (ret != 0) {
client_keep_alive = 0;
if (ret < 0) {
goto abort;
} else if (ret == 1) {
sprintf(err_msg, "Unable to parse header: Invalid header format.");
} else if (ret == 2) {
sprintf(err_msg, "Unable to parse header: Invalid method.");
} else if (ret == 3) {
sprintf(err_msg, "Unable to parse header: Invalid version.");
} else if (ret == 4) {
sprintf(err_msg, "Unable to parse header: Header contains illegal characters.");
} else if (ret == 5) {
sprintf(err_msg, "Unable to parse header: End of header not found.");
}
res.status = http_get_status(400);
goto respond;
}
hdr_connection = http_get_header_field(&req.hdr, "Connection");
client_keep_alive = hdr_connection != NULL && strncmp(hdr_connection, "keep-alive", 10) == 0;
host = http_get_header_field(&req.hdr, "Host");
if (host == NULL || strchr(host, '/') != NULL) {
res.status = http_get_status(400);
sprintf(err_msg, "The client provided no or an invalid Host header field.");
goto respond;
}
sprintf(log_req_prefix, "[%s%24s%s]%s ", BLD_STR, host, CLR_STR, log_client_prefix);
log_prefix = log_req_prefix;
print(BLD_STR "%s %s" CLR_STR, req.method, req.uri);
webroot = get_webroot(host);
if (webroot == NULL) {
res.status = http_get_status(307);
sprintf(buf0, "https://%s%s", NECRONDA_DEFAULT, req.uri);
http_add_header_field(&req.hdr, "Location", buf0);
goto respond;
}
dir_mode = URI_DIR_MODE_FORBIDDEN;
http_uri uri;
ret = uri_init(&uri, webroot, req.uri, dir_mode);
if (ret != 0) {
if (ret == 1) {
sprintf(err_msg, "Invalid URI: has to start with slash.");
} else if (ret == 2) {
sprintf(err_msg, "Invalid URI: contains relative path change (/../).");
}
res.status = http_get_status(400);
goto respond;
}
ssize_t size = sizeof(buf0);
url_decode(req.uri, buf0, &size);
int change_proto = strncmp(uri.uri, "/.well-known/", 13) != 0 && !client->enc;
if (strcmp(uri.uri, buf0) != 0 || change_proto) {
res.status = http_get_status(308);
size = sizeof(buf0);
encode_url(uri.uri, buf0, &size);
if (change_proto) {
sprintf(buf1, "https://%s%s", host, buf0);
http_add_header_field(&res.hdr, "Location", buf1);
} else {
http_add_header_field(&res.hdr, "Location", buf0);
}
goto respond;
}
if (uri.filename == NULL && (int) uri.is_static && (int) uri.is_dir && strlen(uri.pathinfo) == 0) {
res.status = http_get_status(403);
sprintf(err_msg, "It is not allowed to list the contents of this directory.");
goto respond;
} else if (uri.filename == NULL && (int) !uri.is_static && (int) uri.is_dir && strlen(uri.pathinfo) == 0) {
// TODO list directory contents
res.status = http_get_status(501);
sprintf(err_msg, "Listing contents of an directory is currently not implemented.");
goto respond;
} else if (uri.filename == NULL || (strlen(uri.pathinfo) > 0 && (int) uri.is_static)) {
res.status = http_get_status(404);
goto respond;
}
if (uri.is_static) {
res.status = http_get_status(200);
http_add_header_field(&res.hdr, "Allow", "GET, HEAD");
http_add_header_field(&res.hdr, "Accept-Ranges", "bytes");
if (strncmp(req.method, "GET", 3) != 0 && strncmp(req.method, "HEAD", 4) != 0) {
res.status = http_get_status(405);
goto respond;
}
ret = uri_cache_init(&uri);
if (ret != 0) {
res.status = http_get_status(500);
sprintf(err_msg, "Unable to communicate with internal file cache.");
goto respond;
}
char *last_modified = http_format_date(uri.meta->stat.st_mtime, buf0, sizeof(buf0));
http_add_header_field(&res.hdr, "Last-Modified", last_modified);
sprintf(buf1, "%s; charset=%s", uri.meta->type, uri.meta->charset);
http_add_header_field(&res.hdr, "Content-Type", buf1);
if (uri.meta->etag[0] != 0) {
http_add_header_field(&res.hdr, "ETag", uri.meta->etag);
}
if (strncmp(uri.meta->type, "text/", 5) == 0) {
http_add_header_field(&res.hdr, "Cache-Control", "public, max-age=3600");
} else {
http_add_header_field(&res.hdr, "Cache-Control", "public, max-age=86400");
}
char *if_modified_since = http_get_header_field(&req.hdr, "If-Modified-Since");
char *if_none_match = http_get_header_field(&req.hdr, "If-None-Match");
if ((if_none_match != NULL && strstr(if_none_match, uri.meta->etag) == NULL) || (accept_if_modified_since &&
if_modified_since != NULL && strncmp(if_modified_since, last_modified, strlen(last_modified)) == 0)) {
res.status = http_get_status(304);
goto respond;
}
char *range = http_get_header_field(&req.hdr, "Range");
if (range != NULL) {
if (strlen(range) <= 6 || strncmp(range, "bytes=", 6) != 0) {
res.status = http_get_status(416);
http_remove_header_field(&res.hdr, "Content-Type", HTTP_REMOVE_ALL);
http_remove_header_field(&res.hdr, "Last-Modified", HTTP_REMOVE_ALL);
http_remove_header_field(&res.hdr, "ETag", HTTP_REMOVE_ALL);
http_remove_header_field(&res.hdr, "Cache-Control", HTTP_REMOVE_ALL);
goto respond;
}
range += 6;
char *ptr = strchr(range, '-');
if (ptr == NULL) {
res.status = http_get_status(416);
goto respond;
}
file = fopen(uri.filename, "rb");
fseek(file, 0, SEEK_END);
unsigned long file_len = ftell(file);
fseek(file, 0, SEEK_SET);
if (file_len == 0) {
content_length = 0;
goto respond;
}
long num1 = 0;
long num2 = (long) file_len - 1;
if (ptr != range) num1 = (long) strtoul(range, NULL, 10);
if (ptr[1] != 0) num2 = (long) strtoul(ptr + 1, NULL, 10);
if (num1 >= file_len || num2 >= file_len || num1 > num2) {
res.status = http_get_status(416);
goto respond;
}
sprintf(buf0, "bytes %li-%li/%li", num1, num2, file_len);
http_add_header_field(&res.hdr, "Content-Range", buf0);
res.status = http_get_status(206);
fseek(file, num1, SEEK_SET);
content_length = num2 - num1 + 1;
goto respond;
}
char *accept_encoding = http_get_header_field(&req.hdr, "Accept-Encoding");
if (uri.meta->filename_comp[0] != 0 && accept_encoding != NULL && strstr(accept_encoding, "deflate") != NULL) {
file = fopen(uri.meta->filename_comp, "rb");
if (file == NULL) {
cache_filename_comp_invalid(uri.filename);
goto not_compressed;
}
http_add_header_field(&res.hdr, "Content-Encoding", "deflate");
} else {
not_compressed:
file = fopen(uri.filename, "rb");
}
fseek(file, 0, SEEK_END);
content_length = ftell(file);
fseek(file, 0, SEEK_SET);
} else {
struct stat statbuf;
stat(uri.filename, &statbuf);
char *last_modified = http_format_date(statbuf.st_mtime, buf0, sizeof(buf0));
http_add_header_field(&res.hdr, "Last-Modified", last_modified);
res.status = http_get_status(200);
if (fastcgi_init(&php_fpm, client_num, req_num, client, &req, &uri) != 0) {
res.status = http_get_status(502);
sprintf(err_msg, "Unable to communicate with PHP-FPM.");
goto respond;
}
if (strncmp(req.method, "POST", 4) == 0 || strncmp(req.method, "PUT", 3) == 0) {
char *client_content_length = http_get_header_field(&req.hdr, "Content-Length");
unsigned long client_content_len = 0;
if (client_content_length == NULL) {
goto fastcgi_end;
}
client_content_len = strtoul(client_content_length, NULL, 10);
ret = fastcgi_receive(&php_fpm, client, client_content_len);
if (ret != 0) {
if (ret < 0) {
goto abort;
} else {
sprintf(err_msg, "Unable to communicate with PHP-FPM.");
}
res.status = http_get_status(502);
goto respond;
}
}
fastcgi_end:
fastcgi_close_stdin(&php_fpm);
ret = fastcgi_header(&php_fpm, &res, err_msg);
if (ret != 0) {
if (ret < 0) {
goto abort;
} else {
sprintf(err_msg, "Unable to communicate with PHP-FPM.");
}
res.status = http_get_status(502);
goto respond;
}
char *status = http_get_header_field(&res.hdr, "Status");
if (status != NULL) {
res.status = http_get_status(strtoul(status, NULL, 10));
http_remove_header_field(&res.hdr, "Status", HTTP_REMOVE_ALL);
if (res.status == NULL){
res.status = http_get_status(500);
sprintf(err_msg, "The status code was set to an invalid or unknown value.");
goto respond;
}
}
char *accept_encoding = http_get_header_field(&req.hdr, "Accept-Encoding");
if (accept_encoding != NULL && strstr(accept_encoding, "deflate") != NULL) {
http_add_header_field(&res.hdr, "Content-Encoding", "deflate");
}
content_length = -1;
use_fastcgi = 1;
if (http_get_header_field(&res.hdr, "Content-Length") == NULL) {
http_add_header_field(&res.hdr, "Transfer-Encoding", "chunked");
}
}
respond:
if (http_get_header_field(&res.hdr, "Accept-Ranges") == NULL) {
http_add_header_field(&res.hdr, "Accept-Ranges", "none");
}
if (!use_fastcgi && file == NULL && res.status->code >= 400 && res.status->code < 600) {
http_error_msg *http_msg = http_get_error_msg(res.status->code);
sprintf(msg_pre_buf, http_error_document, res.status->code, res.status->msg,
http_msg != NULL ? http_msg->err_msg : "", err_msg[0] != 0 ? err_msg : "");
content_length = sprintf(msg_buf, http_default_document, res.status->code, res.status->msg,
msg_pre_buf, res.status->code >= 300 && res.status->code < 400 ? "info" : "error",
http_error_icon, "#C00000");
http_add_header_field(&res.hdr, "Content-Type", "text/html; charset=UTF-8");
}
if (content_length >= 0) {
sprintf(buf0, "%li", content_length);
http_add_header_field(&res.hdr, "Content-Length", buf0);
} else if (http_get_header_field(&res.hdr, "Transfer-Encoding") == NULL) {
server_keep_alive = 0;
}
if (server_keep_alive && client_keep_alive) {
http_add_header_field(&res.hdr, "Connection", "keep-alive");
sprintf(buf0, "timeout=%i, max=%i", CLIENT_TIMEOUT, REQ_PER_CONNECTION);
http_add_header_field(&res.hdr, "Keep-Alive", buf0);
} else {
http_add_header_field(&res.hdr, "Connection", "close");
}
http_send_response(client, &res);
clock_gettime(CLOCK_MONOTONIC, &end);
char *location = http_get_header_field(&res.hdr, "Location");
unsigned long micros = (end.tv_nsec - begin.tv_nsec) / 1000 + (end.tv_sec - begin.tv_sec) * 1000000;
print("%s%03i %s%s%s (%s)%s", http_get_status_color(res.status), res.status->code, res.status->msg,
location != NULL ? " -> " : "", location != NULL ? location : "", format_duration(micros, buf0), CLR_STR);
if (strncmp(req.method, "HEAD", 4) != 0) {
unsigned long snd_len = 0;
unsigned long len = 0;
if (msg_buf[0] != 0) {
while (snd_len < content_length) {
if (client->enc) {
ret = SSL_write(client->ssl, msg_buf, (int) (content_length - snd_len));
if (ret <= 0) {
print(ERR_STR "Unable to send: %s" CLR_STR, ssl_get_error(client->ssl, ret));
}
} else {
ret = send(client->socket, msg_buf, content_length - snd_len, 0);
if (ret <= 0) {
print(ERR_STR "Unable to send: %s" CLR_STR, strerror(errno));
}
}
if (ret <= 0) {
break;
}
snd_len += ret;
}
} else if (file != NULL) {
while (snd_len < content_length) {
len = fread(&buffer, 1, CHUNK_SIZE, file);
if (snd_len + len > content_length) {
len = content_length - snd_len;
}
if (client->enc) {
ret = SSL_write(client->ssl, buffer, (int) len);
if (ret <= 0) {
print(ERR_STR "Unable to send: %s" CLR_STR, ssl_get_error(client->ssl, ret));
}
} else {
ret = send(client->socket, buffer, len, 0);
if (ret <= 0) {
print(ERR_STR "Unable to send: %s" CLR_STR, strerror(errno));
}
}
if (ret <= 0) {
break;
}
snd_len += ret;
}
} else if (use_fastcgi) {
char *transfer_encoding = http_get_header_field(&res.hdr, "Transfer-Encoding");
int chunked = transfer_encoding != NULL && strncmp(transfer_encoding, "chunked", 7) == 0;
char *content_encoding = http_get_header_field(&res.hdr, "Content-Encoding");
int comp = content_encoding != NULL && strncmp(content_encoding, "deflate", 7) == 0;
int flags = (chunked ? FASTCGI_CHUNKED : 0) | (comp ? FASTCGI_COMPRESS : 0);
fastcgi_send(&php_fpm, client, flags);
}
}
clock_gettime(CLOCK_MONOTONIC, &end);
micros = (end.tv_nsec - begin.tv_nsec) / 1000 + (end.tv_sec - begin.tv_sec) * 1000000;
print("Transfer complete: %s", format_duration(micros, buf0));
uri_free(&uri);
abort:
if (php_fpm.socket != 0) close(php_fpm.socket);
http_free_req(&req);
http_free_res(&res);
return !client_keep_alive;
}
int client_connection_handler(sock *client, unsigned long client_num) {
struct timespec begin, end;
int ret, req_num;
char buf[16];
clock_gettime(CLOCK_MONOTONIC, &begin);
// TODO get geoip data for ip address
// TODO Reverse DNS request
client_host_str = client_addr_str;
print("Connection accepted from %s (%s) [%s]", client_addr_str, client_host_str, "N/A");
client_timeout.tv_sec = CLIENT_TIMEOUT;
client_timeout.tv_usec = 0;
if (setsockopt(client->socket, SOL_SOCKET, SO_RCVTIMEO, &client_timeout, sizeof(client_timeout)) < 0)
goto set_timeout_err;
if (setsockopt(client->socket, SOL_SOCKET, SO_SNDTIMEO, &client_timeout, sizeof(client_timeout)) < 0) {
set_timeout_err:
print(ERR_STR "Unable to set timeout for socket: %s" CLR_STR, strerror(errno));
return 1;
}
if (client->enc) {
client->ssl = SSL_new(client->ctx);
SSL_set_fd(client->ssl, client->socket);
SSL_set_accept_state(client->ssl);
ret = SSL_accept(client->ssl);
if (ret <= 0) {
print(ERR_STR "Unable to perform handshake: %s" CLR_STR, ssl_get_error(client->ssl, ret));
goto close;
}
}
req_num = 0;
ret = 0;
while (ret == 0 && server_keep_alive && req_num < REQ_PER_CONNECTION) {
ret = client_request_handler(client, client_num, req_num++);
log_prefix = log_conn_prefix;
}
close:
if (client->enc) {
SSL_shutdown(client->ssl);
SSL_free(client->ssl);
}
shutdown(client->socket, SHUT_RDWR);
close(client->socket);
clock_gettime(CLOCK_MONOTONIC, &end);
unsigned long micros = (end.tv_nsec - begin.tv_nsec) / 1000 + (end.tv_sec - begin.tv_sec) * 1000000;
print("Connection closed (%s)", format_duration(micros, buf));
return 0;
}
int client_handler(sock *client, unsigned long client_num, struct sockaddr_in6 *client_addr) {
int ret;
struct sockaddr_in6 *server_addr;
struct sockaddr_storage server_addr_storage;
char *color_table[] = {"\x1B[31m", "\x1B[32m", "\x1B[33m", "\x1B[34m", "\x1B[35m", "\x1B[36m"};
signal(SIGINT, client_terminate);
signal(SIGTERM, client_terminate);
client_addr_str_ptr = malloc(INET6_ADDRSTRLEN);
inet_ntop(client_addr->sin6_family, (void *) &client_addr->sin6_addr, client_addr_str_ptr, INET6_ADDRSTRLEN);
if (strncmp(client_addr_str_ptr, "::ffff:", 7) == 0) {
client_addr_str = client_addr_str_ptr + 7;
} else {
client_addr_str = client_addr_str_ptr;
}
socklen_t len = sizeof(server_addr_storage);
getsockname(client->socket, (struct sockaddr *) &server_addr_storage, &len);
server_addr = (struct sockaddr_in6 *) &server_addr_storage;
server_addr_str_ptr = malloc(INET6_ADDRSTRLEN);
inet_ntop(server_addr->sin6_family, (void *) &server_addr->sin6_addr, server_addr_str_ptr, INET6_ADDRSTRLEN);
if (strncmp(server_addr_str_ptr, "::ffff:", 7) == 0) {
server_addr_str = server_addr_str_ptr + 7;
} else {
server_addr_str = server_addr_str_ptr;
}
log_req_prefix = malloc(256);
log_client_prefix = malloc(256);
sprintf(log_client_prefix, "[%s%4i%s]%s[%*s][%5i]%s", (int) client->enc ? HTTPS_STR : HTTP_STR,
ntohs(server_addr->sin6_port), CLR_STR, color_table[client_num % 6], INET_ADDRSTRLEN, client_addr_str,
ntohs(client_addr->sin6_port), CLR_STR);
log_conn_prefix = malloc(256);
sprintf(log_conn_prefix, "[%24s]%s ", server_addr_str, log_client_prefix);
log_prefix = log_conn_prefix;
print("Started child process with PID %i", getpid());
ret = client_connection_handler(client, client_num);
free(client_addr_str_ptr);
free(server_addr_str_ptr);
free(log_conn_prefix);
free(log_req_prefix);
free(log_client_prefix);
return ret;
}

View File

@ -1,673 +0,0 @@
#include <utility>
#include <utility>
/**
* Necronda Web Server 3.0
* client.cpp - Client and Connection handler
* Lorenz Stechauner, 2018-05-16
*/
#include <string>
#include <iostream>
#include <zlib.h>
#include <cassert>
#include <fstream>
#include <openssl/md5.h>
#include <cstring>
#include <fcntl.h>
#include <sstream>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include "network/Socket.h"
#include "network/http/HttpRequest.h"
#include "network/http/HttpConnection.h"
#include "necronda-server.h"
#include "network/http/HttpStatusCode.h"
#include "URI.h"
#include "procopen.h"
#include "network/Address.h"
typedef struct {
string host;
string cc;
string country;
string prov;
string provname;
string city;
string timezone;
string localdate;
} IpAddressInfo;
void log_to_file(const char *prefix, const string &str, string host) {
//FILE *file = fopen((getWebRoot(std::move(host)) + ".access.log").c_str(), "a");
//fprintf(file, "%s%s\r\n", prefix, str.c_str());
//fflush(file);
//fclose(file);
}
void log_error_to_file(const char *prefix, const string &str, string host) {
log_to_file(prefix, "\x1B[1;31m" + str + "\x1B[0m", std::move(host));
}
/**
* Writes log messages to the console
* @param prefix The connection prefix
* @param str The string to be written
*/
void log(const char *prefix, const string &str) {
printf("%s%s\r\n", prefix, str.c_str());
flush(cout);
}
void log_error(const char *prefix, const string &str) {
log(prefix, "\x1B[1;31m" + str + "\x1B[0m");
}
void php_error_handler(const char *prefix, FILE *stderr) {
string line;
while (!(line = read_line(stderr)).empty()) {
log_error(prefix, line);
}
fclose(stderr);
}
IpAddressInfo get_ip_address_info(Address* addr) {
FILE *name = popen(("/opt/ipinfo/ipinfo.py " + addr->toString()).c_str(), "r");
char hostbuffer[1024];
memset(hostbuffer, 0, 1024);
size_t size = fread(hostbuffer, 1, 1024, name);
istringstream buffer(hostbuffer);
string line;
IpAddressInfo info;
int num = 0;
while (std::getline(buffer, line)) {
switch (num) {
case 0: info.host = line; break;
case 1: info.cc = line; break;
case 2: info.country = line; break;
case 3: info.prov = line; break;
case 4: info.provname = line; break;
case 5: info.city = line; break;
case 6: info.timezone = line; break;
case 7: info.localdate = line; break;
}
num++;
}
return info;
}
string get_os_info(int fd) {
struct tcp_info ti;
socklen_t tisize = sizeof(ti);
getsockopt(fd, IPPROTO_TCP, TCP_INFO, &ti, &tisize);
int ttl;
socklen_t ttlsize = sizeof(ttl);
getsockopt(fd, IPPROTO_IP, IP_TTL, &ttl, &ttlsize);
return "win_size=" + to_string(ti.tcpi_rcv_space) + ", ttl=" + to_string(ttl);
}
string getETag(string filename) {
ifstream etags = ifstream("/var/necronda/ETags");
ifstream a = ifstream();
string line;
int index = 0;
int i = 0;
string timestamp = getTimestamp(filename);
long size = getFileSize(filename);
while (getline(etags, line)) {
i++;
if (line == filename) {
index = i;
break;
}
long p1 = line.find(':');
if (p1 == string::npos) continue;
long p2 = line.find(':', (unsigned) p1 + 1);
if (p2 == string::npos) continue;
long p3 = line.find(':', (unsigned) p2 + 1);
if (p3 == string::npos) continue;
string FILENAME = line.substr(0, (unsigned) p1);
string HASH = line.substr((unsigned) p1 + 1, (unsigned) (p2 - p1));
string TIMESTAMP = line.substr((unsigned) p2 + 1, (unsigned) (p3 - p2));
long SIZE = strtol(line.substr((unsigned) p3 + 1, line.length() - p3).c_str(), nullptr, 10);
if (FILENAME == filename) {
index = i;
if (timestamp != TIMESTAMP || size != SIZE) {
break;
} else {
etags.close();
return HASH;
}
}
}
etags.close();
MD5_CTX mdContext;
MD5_Init(&mdContext);
size_t bytes;
char buffer[4096];
FILE *file = fopen(filename.c_str(), "rb");
if (file == nullptr) {
throw (char *) "Invalid file";
}
while ((bytes = fread(buffer, 1, 4096, file)) != 0) {
MD5_Update(&mdContext, buffer, bytes);
}
fclose(file);
unsigned char md[16];
MD5_Final(md, &mdContext);
char md5buff[32];
for (int i = 0; i < 16; i++) {
sprintf(md5buff + i * 2, "%02x", md[i]);
}
string md5 = string(md5buff);
if (index == 0) {
char buff[256];
sprintf(buff, "%s:%s:%s:%ld\n", filename.c_str(), md5.c_str(), timestamp.c_str(), size);
FILE *f = fopen("/var/necronda/ETags", "a");
if (f == nullptr) {
throw (char *) strerror(errno);
}
fseek(f, 0, SEEK_END);
fwrite(buff, 1, strlen(buff), f);
fflush(f);
fclose(f);
} else {
}
return md5;
}
#include <iostream>
#include <wait.h>
#include <thread>
long getPosition(std::string str, char c, int occurence) {
int tempOccur = 0;
int num = 0;
for (auto it : str) {
num++;
if (it == c) {
if (++tempOccur == occurence) {
return num;
}
}
}
return -1;
}
int websocket_handler(Socket *socket, stds *pipes) {
fd_set readfd;
int maxfd = (socket->getFd() > pipes->stdout->_fileno) ? socket->getFd() : pipes->stdout->_fileno;
FD_ZERO(&readfd);
FD_SET(socket->getFd(), &readfd);
FD_SET(pipes->stdout->_fileno, &readfd);
/*while (true) {
int ret = ::select(maxfd + 1, &readfd, nullptr, nullptr, nullptr);
if (ret < 0) {
throw (char *) strerror(errno);
}
int c = fgetc(pipes->stdout);
if (c == -1) {
long rec = socket->receive(pipes->stdin);
} else {
ungetc(c, pipes->stdout);
socket->send(pipes->stdout);
}
}*/
}
/**
* Handles (keep-alive) HTTP connections
* @param prefix The connection prefix
* @param socket The socket
* @param id The client ID
* @param num The Connection Number in the client
* @return Should the server wait for another header?
*/
bool connection_handler(const char *preprefix, const char *col1, const char *col2, Socket *socket, long id, long num, IpAddressInfo *info) {
bool error = false;
char buffer[1024];
char *prefix = (char *) preprefix;
HttpConnection req;
try {
req = HttpConnection(socket);
} catch (char *msg) {
try {
if (msg == "Malformed header") {
log(prefix, "Unable to parse header: Malformed header");
socket->send("HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n");
return false;
} else if (msg == "timeout") {
log(prefix, "Timeout!");
socket->send("HTTP/1.1 408 Request Timeout\r\nConnection: close\r\n\r\n");
return false;
} else {
log(prefix, (string) "Unable to receive from socket: " + msg);
return false;
}
} catch (char *msg2) {
return false;
}
}
try {
bool noRedirect, redir, invalidMethod, etag, compress, wantsWebsocket, websocket = false;
URI path;
pid_t childpid;
FILE *file;
int statuscode;
string hash, type, host;
thread *t;
long pos;
stds pipes;
if (req.isExistingField("Connection") && req.getField("Connection") == "keep-alive") {
req.setField("Connection", "keep-alive");
req.setField("Keep-Alive", "timeout=3600, max=100");
} else {
req.setField("Connection", "close");
error = true;
}
host = "";
if (!req.isExistingField("Host")) {
req.respond(400);
goto respond;
}
host = req.getField("Host");
pos = host.find(':');
if (pos != string::npos) {
host.erase(pos, host.length() - pos);
}
/*
FILE *name = popen(("dig @8.8.8.8 +time=1 -x " + socket->getPeerAddress()->toString() +
" | grep -oP \"^[^;].*\\t\\K([^ ]*)\\w\"").c_str(), "r");
char hostbuffer[1024];
memset(hostbuffer, 0, 1024);
size_t size = fread(hostbuffer, 1, 1024, name);
hostbuffer[size - 1] = 0; // remove \n
if (size <= 1) {
sprintf(hostbuffer, "%s", socket->getPeerAddress()->toString().c_str());
}
*/
sprintf(buffer, "[\x1B[1m%s\x1B[0m][%i]%s[%s][%i]%s ", host.c_str(), socket->getSocketPort(), col1,
info->host.c_str(), socket->getPeerPort(), col2);
prefix = buffer;
log(prefix, "\x1B[1m" + req.getMethod() + " " + req.getPath() + "\x1B[0m");
log_to_file(prefix, "\x1B[1m" + req.getMethod() + " " + req.getPath() + "\x1B[0m", host);
noRedirect = req.getPath().find("/.well-known/") == 0 || (req.getPath().find("/files/") == 0);
redir = true;
if (!noRedirect) {
if (getWebRoot(host).empty()) {
req.redirect(303, "https://www.necronda.net" + req.getPath());
} else if (socket->getSocketPort() != 443) {
req.redirect(302, "https://" + host + req.getPath());
} else {
redir = false;
}
} else {
redir = false;
}
path = URI(getWebRoot(host), req.getPath());
childpid = 0;
if (redir) {
goto respond;
} else if (!path.getNewPath().empty() && req.getMethod() != "POST") {
req.redirect(303, path.getNewPath());
goto respond;
}
file = path.openFile();
if (file == nullptr) {
req.setField("Cache-Control", "public, max-age=60");
req.respond(404);
goto respond;
}
type = path.getFileType();
if (type.find("inode/") == 0 || (path.getRelativeFilePath().find("/.") != string::npos && !noRedirect)) {
req.respond(403);
goto respond;
}
req.setField("Content-Type", type);
req.setField("Last-Modified", getHttpDate(path.getFilePath()));
invalidMethod = false;
etag = false;
if (path.isStatic()) {
hash = getETag(path.getFilePath());
req.setField("ETag", hash);
req.setField("Accept-Ranges", "bytes");
if (type.find("text/") == 0) {
req.setField("Cache-Control", "public, max-age=3600");
} else {
req.setField("Cache-Control", "public, max-age=86400");
}
req.setField("Allow", "GET");
if (req.getMethod() != "GET") {
invalidMethod = true;
}
if (req.isExistingField("If-None-Match") && req.getField("If-None-Match") == hash) {
etag = true;
}
} else {
req.setField("Accept-Ranges", "none");
req.setField("Cache-Control", "private, no-cache");
req.setField("Allow", "GET, POST, PUT");
if (req.getMethod() != "GET" && req.getMethod() != "POST" && req.getMethod() != "PUT") {
invalidMethod = true;
}
}
if (invalidMethod) {
req.respond(405);
goto respond;
} else if (etag) {
req.respond(304);
goto respond;
}
statuscode = 0;
if (!path.isStatic()) {
string cmd = (string) "env -i" +
" REDIRECT_STATUS=" + cli_encode("CGI") +
" DOCUMENT_ROOT=" + cli_encode(getWebRoot(host)) +
" " + req.cgiExport() +
(req.isExistingField("Content-Length") ? " CONTENT_LENGTH=" +
cli_encode(req.getField(
"Content-Length"))
: "") +
(req.isExistingField("Content-Type") ? " CONTENT_TYPE=" + cli_encode(
req.getField("Content-Type")) : "") +
((socket->isSecured()) ? " HTTPS=on" : "") +
" PATH_INFO=" + cli_encode(path.getFilePathInfo()) +
" PATH_TRANSLATED=" + cli_encode(path.getAbsolutePath()) +
" QUERY_STRING=" + cli_encode(path.getQuery()) +
" REMOTE_ADDR=" + cli_encode(socket->getPeerAddress()->toString()) +
" REMOTE_HOST=" + cli_encode(info->host) +
" REMOTE_PORT=" + cli_encode(to_string(socket->getPeerPort())) +
" REQUEST_METHOD=" + cli_encode(req.getMethod()) +
" REQUEST_URI=" + cli_encode(req.getPath()) +
" SCRIPT_FILENAME=" + cli_encode(path.getFilePath()) +
" SCRIPT_NAME=" + cli_encode(path.getRelativePath()) +
" SERVER_ADMIN=" + cli_encode("lorenz.stechauner@gmail.com") +
" SERVER_NAME=" + cli_encode(host) +
" SERVER_PORT=" + cli_encode(to_string(socket->getSocketPort())) +
" SERVER_SOFTWARE=" + cli_encode("Necronda 3.0") +
" SERVER_PROTOCOL=" + cli_encode("HTTP/1.1") +
" GATEWAY_INTERFACE=" + cli_encode("CGI/1.1") +
" /usr/bin/php-cgi";
pipes = procopen(cmd.c_str());
childpid = pipes.pid;
long len = req.isExistingField("Content-Length")
? strtol(req.getField("Content-Length").c_str(), nullptr, 10)
: ((req.getMethod() == "POST" || req.getMethod() == "PUT") ? -1 : 0);
socket->receive(pipes.stdin, len);
wantsWebsocket = req.getMethod() == "GET" &&
req.isExistingResponseField("Connection") && req.getField("Connection") == "Upgrade" &&
req.isExistingResponseField("Upgrade") && req.getField("Upgrade") == "websocket";
if (!wantsWebsocket) {
// Close only if no Websocket upgrade is possible
fclose(pipes.stdin);
}
t = new thread(php_error_handler, prefix, pipes.stderr);
string line;
while (!(line = read_line(pipes.stdout)).empty()) {
pos = line.find(':');
string index = line.substr(0, pos);
string data = line.substr(pos + 1, line.length() - pos);
while (index[0] == ' ') index.erase(index.begin() + 0);
while (index[index.length() - 1] == ' ') index.erase(index.end() - 1);
while (data[0] == ' ') data.erase(data.begin() + 0);
while (data[data.length() - 1] == ' ') data.erase(data.end() - 1);
if (index == "Status") {
statuscode = (int) strtol(data.substr(0, 3).c_str(), nullptr, 10);
} else {
if (index == "Location" && statuscode == 0) {
statuscode = 303;
} else if (index == "Content-Type") {
type = data;
}
req.setField(index, data);
}
}
websocket = statuscode == 101 &&
req.isExistingResponseField("Connection") && req.getResponseField("Connection") == "Upgrade" &&
req.isExistingResponseField("Upgrade") && req.getResponseField("Upgrade") == "websocket";
fclose(file);
file = pipes.stdout;
if (websocket) {
log(prefix, "Upgrade to WebSocket!");
req.respond(statuscode);
goto respond;
} else {
int c = fgetc(pipes.stdout);
if (c == -1) {
// No Data -> Error
req.respond((statuscode == 0) ? 500 : statuscode);
goto respond;
} else {
ungetc(c, pipes.stdout);
}
}
}
statuscode = (statuscode == 0) ? 200 : statuscode;
compress = (type.find("text/") == 0 ||
(type.find("application/") == 0 && type.find("+xml") != string::npos) ||
type == "application/json" ||
type == "application/javascript") &&
req.isExistingField("Accept-Encoding") &&
req.getField("Accept-Encoding").find("deflate") != string::npos;
if (compress) {
req.setField("Accept-Ranges", "none");
}
if (compress && req.isExistingField("Range")) {
req.respond(416);
} else if (req.isExistingField("Range")) {
string range = req.getField("Range");
if (range.find("bytes=") != 0 || !path.isStatic()) {
req.respond(416);
} else {
fseek(file, 0L, SEEK_END);
long len = ftell(file);
fseek(file, 0L, SEEK_SET);
long p = range.find('-');
if (p == string::npos) {
req.respond(416);
} else {
string part1 = range.substr(6, (unsigned long) (p - 6));
string part2 = range.substr((unsigned long) (p + 1),
range.length() - p - 1);
long num1 = stol(part1, nullptr, 10);
long num2 = len - 1;
if (!part2.empty()) {
num2 = stol(part2, nullptr, 10);
}
if (num1 < 0 || num1 >= len || num2 < 0 || num2 >= len) {
req.respond(416);
} else {
req.setField("Content-Range",
(string) "bytes " + to_string(num1) + "-" +
to_string(num2) +
"/" + to_string(len));
req.respond(206, file, compress, num1, num2);
}
}
}
} else {
req.respond(statuscode, file, compress);
}
fclose(file);
if (childpid > 0) {
waitpid(childpid, nullptr, 0);
}
respond:
HttpStatusCode status = req.getStatusCode();
int code = status.code;
string color;
string comment;
if ((code >= 200 && code < 300) || code == 304 || code == 101) {
color = "\x1B[1;32m"; // Success (Cached, Switching Protocols): Green
} else if (code >= 100 && code < 200) {
color = "\x1B[1;93m"; // Continue: Yellow
} else if (code >= 300 && code < 400) {
color = "\x1B[1;93m"; // Redirect: Yellow
comment = " -> " +
(req.isExistingResponseField("Location") ? req.getResponseField("Location") : "<invalid>");
} else if (code >= 400 && code < 500) {
color = "\x1B[1;31m"; // Client Error: Red
//comment = " -> " + req.getPath();
} else if (code >= 500 & code < 600) {
color = "\x1B[1;31m"; // Server Error: Red
//comment = " -> " + req.getPath();
}
string msg = color + to_string(status.code) + " " + status.message + comment + " (" + formatTime(req.getDuration()) + ")\x1B[0m";
log(prefix, msg);
if (!host.empty()) {
log_to_file(prefix, msg, host);
}
if (websocket) {
websocket_handler(socket, &pipes);
log(prefix, "\x1B[1mClosing WebSocket (" + formatTime(req.getDuration()) + ")\x1B[0m");
}
} catch (char *msg) {
HttpStatusCode status = req.getStatusCode();
log(prefix, to_string(status.code) + " " + status.message + " (" + formatTime(req.getDuration()) + ")");
try {
if (strncmp(msg, "timeout", strlen(msg)) == 0) {
log(prefix, "Timeout!");
req.setField("Connection", "close");
req.respond(408);
error = true;
} else if (strncmp(msg, "Invalid path", strlen(msg)) == 0) {
log(prefix, "Timeout!");
req.setField("Connection", "close");
req.respond(400);
} else {
log(prefix, (string) "Unable to receive from socket: " + msg);
error = true;
}
} catch (char *msg2) {
}
}
return !error;
}
/**
* Handles HTTP clients
* @param socket The socket
* @param id The client ID
*/
void client_handler(Socket *socket, long id, bool ssl) {
const char *prefix;
char const *col1;
char const *col2 = "\x1B[0m";
IpAddressInfo info = get_ip_address_info(socket->getPeerAddress());
auto os = get_os_info(socket->getFd());
{
auto group = (int) (id % 6);
if (group == 0) {
col1 = "\x1B[0;31m"; // Red
} else if (group == 1) {
col1 = "\x1B[0;32m"; // Green
} else if (group == 2) {
col1 = "\x1B[0;34m"; // Blue
} else if (group == 3) {
col1 = "\x1B[0;33m"; // Yellow
} else if (group == 4) {
col1 = "\x1B[0;35m"; // Magenta
} else {
col1 = "\x1B[0;36m"; // Cyan
}
string *a = new string("[" + socket->getSocketAddress()->toString() + "][" +
to_string(socket->getSocketPort()) + "]" + col1 +
"[" + info.host + "][" + to_string(socket->getPeerPort()) +
"]" + col2 + " ");
prefix = a->c_str();
}
log(prefix, "Connection established");
log(prefix, string("Host: ") + info.host + " (" + socket->getPeerAddress()->toString() + ")");
log(prefix, string("OS: ") + os);
log(prefix, string("Location: ") + info.cc + "/" + info.country + ", " + info.prov + "/" + info.provname + ", " + info.city);
log(prefix, string("Local Date: ") + info.localdate + " (" + info.timezone + ")");
bool err = false;
try {
socket->setReceiveTimeout(3600000);
socket->setSendTimeout(36000000);
} catch (char *msg) {
log(prefix, (string) "Unable to set timeout on socket: " + msg);
err = true;
}
try {
if (ssl) {
//socket->sslHandshake("/home/lorenz/Documents/Projects/Necronda-Server/necronda-server-3.0/privkey.pem",
// "/home/lorenz/Documents/Projects/Necronda-Server/necronda-server-3.0/fullchain.pem");
socket->sslHandshake("/cert/necronda.net/privkey.pem",
"/cert/necronda.net/fullchain.pem");
}
} catch (char *msg) {
log(prefix, (string) "Unable to perform handshake: " + msg);
err = true;
}
long reqnum = 0;
if (!err) {
while (connection_handler(prefix, col1, col2, socket, id, ++reqnum, &info));
reqnum--;
}
log(prefix,
"Connection terminated (#:" + to_string(reqnum) + ", R: " + formatSize(socket->getBytesReceived()) + ", S: " +
formatSize(socket->getBytesSent()) + ", T: " + formatTime(socket->getDuration()) + ")");
socket->close();
}

547
src/fastcgi.c Normal file
View File

@ -0,0 +1,547 @@
/**
* Necronda Web Server
* FastCGI interface implementation
* src/fastcgi.c
* Lorenz Stechauner, 2020-12-26
*/
#include "fastcgi.h"
#include "necronda-server.h"
#include <sys/un.h>
char *fastcgi_add_param(char *buf, const char *key, const char *value) {
char *ptr = buf;
unsigned long key_len = strlen(key);
unsigned long val_len = strlen(value);
if (key_len <= 127) {
ptr[0] = (char) (key_len & 0x7F);
ptr++;
} else {
ptr[0] = (char) (0x80 | (key_len >> 24));
ptr[1] = (char) ((key_len >> 16) & 0xFF);
ptr[2] = (char) ((key_len >> 8) & 0xFF);
ptr[3] = (char) (key_len & 0xFF);
ptr += 4;
}
if (val_len <= 127) {
ptr[0] = (char) (val_len & 0x7F);
ptr++;
} else {
ptr[0] = (char) (0x80 | (val_len >> 24));
ptr[1] = (char) ((val_len >> 16) & 0xFF);
ptr[2] = (char) ((val_len >> 8) & 0xFF);
ptr[3] = (char) (val_len & 0xFF);
ptr += 4;
}
memcpy(ptr, key, key_len);
ptr += key_len;
memcpy(ptr, value, val_len);
ptr += val_len;
return ptr;
}
int fastcgi_init(fastcgi_conn *conn, unsigned int client_num, unsigned int req_num, const sock *client,
const http_req *req, const http_uri *uri) {
unsigned short req_id = (client_num & 0xFFF) << 4;
if (client_num == 0) {
req_id |= (req_num + 1) & 0xF;
} else {
req_id |= req_num & 0xF;
}
conn->req_id = req_id;
conn->out_buf = NULL;
conn->out_off = 0;
int php_fpm = socket(AF_UNIX, SOCK_STREAM, 0);
if (php_fpm < 0) {
print(ERR_STR "Unable to create unix socket: %s" CLR_STR, strerror(errno));
return -1;
}
conn->socket = php_fpm;
struct sockaddr_un php_fpm_addr = {AF_UNIX, PHP_FPM_SOCKET};
if (connect(conn->socket, (struct sockaddr *) &php_fpm_addr, sizeof(php_fpm_addr)) < 0) {
print(ERR_STR "Unable to connect to unix socket of PHP-FPM: %s" CLR_STR, strerror(errno));
return -1;
}
FCGI_Header header = {
.version = FCGI_VERSION_1,
.requestIdB1 = req_id >> 8,
.requestIdB0 = req_id & 0xFF,
.paddingLength = 0,
.reserved = 0
};
header.type = FCGI_BEGIN_REQUEST;
header.contentLengthB1 = 0;
header.contentLengthB0 = sizeof(FCGI_BeginRequestBody);
FCGI_BeginRequestRecord begin = {
header,
{.roleB1 = (FCGI_RESPONDER >> 8) & 0xFF, .roleB0 = FCGI_RESPONDER & 0xFF, .flags = 0}
};
if (send(conn->socket, &begin, sizeof(begin), 0) != sizeof(begin)) {
print(ERR_STR "Unable to send to PHP-FPM: %s" CLR_STR, strerror(errno));
return -2;
}
char param_buf[4096];
char buf0[256];
char *param_ptr = param_buf + sizeof(header);
param_ptr = fastcgi_add_param(param_ptr, "REDIRECT_STATUS", "CGI");
param_ptr = fastcgi_add_param(param_ptr, "DOCUMENT_ROOT", uri->webroot);
param_ptr = fastcgi_add_param(param_ptr, "GATEWAY_INTERFACE", "CGI/1.1");
param_ptr = fastcgi_add_param(param_ptr, "SERVER_SOFTWARE", SERVER_STR);
param_ptr = fastcgi_add_param(param_ptr, "SERVER_PROTOCOL", "HTTP/1.1");
param_ptr = fastcgi_add_param(param_ptr, "SERVER_NAME", http_get_header_field(&req->hdr, "Host"));
if (client->enc) {
param_ptr = fastcgi_add_param(param_ptr, "HTTPS", "on");
}
struct sockaddr_storage addr_storage;
struct sockaddr_in6 *addr;
socklen_t len = sizeof(addr_storage);
getsockname(client->socket, (struct sockaddr *) &addr_storage, &len);
addr = (struct sockaddr_in6 *) &addr_storage;
sprintf(buf0, "%i", addr->sin6_port);
param_ptr = fastcgi_add_param(param_ptr, "SERVER_PORT", buf0);
len = sizeof(addr_storage);
getpeername(client->socket, (struct sockaddr *) &addr_storage, &len);
addr = (struct sockaddr_in6 *) &addr_storage;
sprintf(buf0, "%i", addr->sin6_port);
param_ptr = fastcgi_add_param(param_ptr, "REMOTE_PORT", buf0);
char addr_str[INET6_ADDRSTRLEN];
char *addr_ptr;
inet_ntop(addr->sin6_family, (void *) &addr->sin6_addr, addr_str, INET6_ADDRSTRLEN);
if (strncmp(addr_str, "::ffff:", 7) == 0) {
addr_ptr = addr_str + 7;
} else {
addr_ptr = addr_str;
}
param_ptr = fastcgi_add_param(param_ptr, "REMOTE_ADDR", addr_ptr);
param_ptr = fastcgi_add_param(param_ptr, "REMOTE_HOST", addr_ptr);
//param_ptr = fastcgi_add_param(param_ptr, "REMOTE_IDENT", "");
//param_ptr = fastcgi_add_param(param_ptr, "REMOTE_USER", "");
param_ptr = fastcgi_add_param(param_ptr, "REQUEST_METHOD", req->method);
param_ptr = fastcgi_add_param(param_ptr, "REQUEST_URI", req->uri);
param_ptr = fastcgi_add_param(param_ptr, "SCRIPT_NAME", uri->filename + strlen(uri->webroot));
param_ptr = fastcgi_add_param(param_ptr, "SCRIPT_FILENAME", uri->filename);
//param_ptr = fastcgi_add_param(param_ptr, "PATH_TRANSLATED", uri->filename);
param_ptr = fastcgi_add_param(param_ptr, "QUERY_STRING", uri->query != NULL ? uri->query : "");
if (uri->pathinfo != NULL && strlen(uri->pathinfo) > 0) {
sprintf(buf0, "/%s", uri->pathinfo);
} else {
sprintf(buf0, "");
}
param_ptr = fastcgi_add_param(param_ptr, "PATH_INFO", buf0);
//param_ptr = fastcgi_add_param(param_ptr, "AUTH_TYPE", "");
char *content_length = http_get_header_field(&req->hdr, "Content-Length");
param_ptr = fastcgi_add_param(param_ptr, "CONTENT_LENGTH", content_length != NULL ? content_length : "");
char *content_type = http_get_header_field(&req->hdr, "Content-Type");
param_ptr = fastcgi_add_param(param_ptr, "CONTENT_TYPE", content_type != NULL ? content_type : "");
for (int i = 0; i < req->hdr.field_num; i++) {
char *ptr = buf0;
ptr += sprintf(ptr, "HTTP_");
for (int j = 0; j < strlen(req->hdr.fields[i][0]); j++, ptr++) {
char ch = req->hdr.fields[i][0][j];
if ((ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9')) {
ch = ch;
} else if (ch >= 'a' && ch <= 'z') {
ch &= 0x5F;
} else {
ch = '_';
}
ptr[0] = ch;
ptr[1] = 0;
}
param_ptr = fastcgi_add_param(param_ptr, buf0, req->hdr.fields[i][1]);
}
unsigned short param_len = param_ptr - param_buf - sizeof(header);
header.type = FCGI_PARAMS;
header.contentLengthB1 = param_len >> 8;
header.contentLengthB0 = param_len & 0xFF;
memcpy(param_buf, &header, sizeof(header));
if (send(conn->socket, param_buf, param_len + sizeof(header), 0) != param_len + sizeof(header)) {
print(ERR_STR "Unable to send to PHP-FPM: %s" CLR_STR, strerror(errno));
return -2;
}
header.type = FCGI_PARAMS;
header.contentLengthB1 = 0;
header.contentLengthB0 = 0;
if (send(conn->socket, &header, sizeof(header), 0) != sizeof(header)) {
print(ERR_STR "Unable to send to PHP-FPM: %s" CLR_STR, strerror(errno));
return -2;
}
return 0;
}
int fastcgi_close_stdin(fastcgi_conn *conn) {
FCGI_Header header = {
.version = FCGI_VERSION_1,
.type = FCGI_STDIN,
.requestIdB1 = conn->req_id >> 8,
.requestIdB0 = conn->req_id & 0xFF,
.contentLengthB1 = 0,
.contentLengthB0 = 0,
.paddingLength = 0,
.reserved = 0
};
if (send(conn->socket, &header, sizeof(header), 0) != sizeof(header)) {
print(ERR_STR "Unable to send to PHP-FPM: %s" CLR_STR, strerror(errno));
return -2;
}
return 0;
}
int fastcgi_php_error(char *msg, int msg_len, char *err_msg) {
char *msg_str = malloc(msg_len + 1);
char *ptr0 = msg_str;
strncpy(msg_str, msg, msg_len);
char *ptr1 = NULL;
int len;
int err = 0;
while (1) {
ptr1 = strstr(ptr0, "PHP message: ");
if (ptr1 == NULL) {
len = (int) (msg_len - (ptr0 - msg_str));
} else {
len = (int) (ptr1 - ptr0);
}
if (len == 0) {
goto next;
}
int msg_type = 0;
int msg_pre_len = 0;
if (len >= 14 && strncmp(ptr0, "PHP Warning: ", 14) == 0) {
msg_type = 1;
msg_pre_len = 14;
} else if (len >= 18 && strncmp(ptr0, "PHP Fatal error: ", 18) == 0) {
msg_type = 2;
msg_pre_len = 18;
} else if (len >= 18 && strncmp(ptr0, "PHP Parse error: ", 18) == 0) {
msg_type = 2;
msg_pre_len = 18;
} else if (len >= 18 && strncmp(ptr0, "PHP Notice: ", 13) == 0) {
msg_type = 1;
msg_pre_len = 13;
}
char *ptr2 = ptr0;
char *ptr3;
int len2;
while (ptr2 - ptr0 < len) {
ptr3 = strchr(ptr2, '\n');
len2 = (int) (len - (ptr2 - ptr0));
if (ptr3 != NULL && (ptr3 - ptr2) < len2) {
len2 = (int) (ptr3 - ptr2);
}
print("%s%.*s%s", msg_type == 1 ? WRN_STR : msg_type == 2 ? ERR_STR: "", len2, ptr2, msg_type != 0 ? CLR_STR : "");
if (msg_type == 2 && ptr2 == ptr0) {
sprintf(err_msg, "%.*s", len2, ptr2);
err = 1;
}
if (ptr3 == NULL) {
break;
}
ptr2 = ptr3 + 1;
}
next:
if (ptr1 == NULL) {
break;
}
ptr0 = ptr1 + 13;
}
return err;
}
int fastcgi_header(fastcgi_conn *conn, http_res *res, char *err_msg) {
FCGI_Header header;
char *content;
unsigned short content_len, req_id;
int ret;
int err = 0;
while (1) {
ret = recv(conn->socket, &header, sizeof(header), 0);
if (ret < 0) {
res->status = http_get_status(502);
sprintf(err_msg, "Unable to communicate with PHP-FPM.");
print(ERR_STR "Unable to receive from PHP-FPM: %s" CLR_STR, strerror(errno));
return -1;
} else if (ret != sizeof(header)) {
res->status = http_get_status(502);
sprintf(err_msg, "Unable to communicate with PHP-FPM.");
print(ERR_STR "Unable to receive from PHP-FPM" CLR_STR);
return -1;
}
req_id = (header.requestIdB1 << 8) | header.requestIdB0;
content_len = (header.contentLengthB1 << 8) | header.contentLengthB0;
content = malloc(content_len + header.paddingLength);
ret = recv(conn->socket, content, content_len + header.paddingLength, 0);
if (ret < 0) {
res->status = http_get_status(502);
sprintf(err_msg, "Unable to communicate with PHP-FPM.");
print(ERR_STR "Unable to receive from PHP-FPM: %s" CLR_STR, strerror(errno));
free(content);
return -1;
} else if (ret != (content_len + header.paddingLength)) {
res->status = http_get_status(502);
sprintf(err_msg, "Unable to communicate with PHP-FPM.");
print(ERR_STR "Unable to receive from PHP-FPM" CLR_STR);
free(content);
return -1;
}
if (req_id != conn->req_id) {
continue;
}
if (header.type == FCGI_END_REQUEST) {
FCGI_EndRequestBody *body = (FCGI_EndRequestBody *) content;
int app_status = (body->appStatusB3 << 24) | (body->appStatusB2 << 16) | (body->appStatusB1 << 8) |
body->appStatusB0;
if (body->protocolStatus != FCGI_REQUEST_COMPLETE) {
print(ERR_STR "FastCGI protocol error: %i" CLR_STR, body->protocolStatus);
}
if (app_status != 0) {
print(ERR_STR "Script terminated with exit code %i" CLR_STR, app_status);
}
close(conn->socket);
conn->socket = 0;
free(content);
return -2;
} else if (header.type == FCGI_STDERR) {
err = err || fastcgi_php_error(content, content_len, err_msg);
} else if (header.type == FCGI_STDOUT) {
break;
} else {
print(ERR_STR "Unknown FastCGI type: %i" CLR_STR, header.type);
}
free(content);
}
if (err) {
res->status = http_get_status(500);
return -3;
}
conn->out_buf = content;
conn->out_len = content_len;
conn->out_off = (unsigned short) (strstr(content, "\r\n\r\n") - content + 4);
char *buf = content;
unsigned short header_len = conn->out_off;
if (header_len <= 0) {
print(ERR_STR "Unable to parse header: End of header not found" CLR_STR);
return 1;
}
for (int i = 0; i < header_len; i++) {
if ((buf[i] >= 0x00 && buf[i] <= 0x1F && buf[i] != '\r' && buf[i] != '\n') || buf[i] == 0x7F) {
print(ERR_STR "Unable to parse header: Header contains illegal characters" CLR_STR);
return 2;
}
}
char *ptr = buf;
while (header_len != (ptr - buf)) {
char *pos0 = strstr(ptr, "\r\n");
if (pos0 == NULL) {
print(ERR_STR "Unable to parse header: Invalid header format" CLR_STR);
return 1;
}
ret = http_parse_header_field(&res->hdr, ptr, pos0);
if (ret != 0) return ret;
if (pos0[2] == '\r' && pos0[3] == '\n') {
return 0;
}
ptr = pos0 + 2;
}
return 0;
}
int fastcgi_send(fastcgi_conn *conn, sock *client, int flags) {
FCGI_Header header;
int ret;
char buf0[256];
int len;
char *content, *ptr;
unsigned short req_id, content_len;
char comp_out[4096];
int finish_comp = 0;
z_stream strm;
if (flags & FASTCGI_COMPRESS) {
int level = NECRONDA_ZLIB_LEVEL;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
if (deflateInit(&strm, level) != Z_OK) {
print(ERR_STR "Unable to init deflate: %s" CLR_STR, strerror(errno));
flags &= !FASTCGI_COMPRESS;
}
}
if (conn->out_buf != NULL && conn->out_len > conn->out_off) {
content = conn->out_buf;
ptr = content + conn->out_off;
content_len = conn->out_len - conn->out_off;
goto out;
}
while (1) {
ret = recv(conn->socket, &header, sizeof(header), 0);
if (ret < 0) {
print(ERR_STR "Unable to receive from PHP-FPM: %s" CLR_STR, strerror(errno));
return -1;
} else if (ret != sizeof(header)) {
print(ERR_STR "Unable to receive from PHP-FPM" CLR_STR);
return -1;
}
req_id = (header.requestIdB1 << 8) | header.requestIdB0;
content_len = (header.contentLengthB1 << 8) | header.contentLengthB0;
content = malloc(content_len + header.paddingLength);
ptr = content;
ret = recv(conn->socket, content, content_len + header.paddingLength, 0);
if (ret < 0) {
print(ERR_STR "Unable to receive from PHP-FPM: %s" CLR_STR, strerror(errno));
free(content);
return -1;
} else if (ret != (content_len + header.paddingLength)) {
print(ERR_STR "Unable to receive from PHP-FPM" CLR_STR);
free(content);
return -1;
}
if (header.type == FCGI_END_REQUEST) {
FCGI_EndRequestBody *body = (FCGI_EndRequestBody *) content;
int app_status = (body->appStatusB3 << 24) | (body->appStatusB2 << 16) | (body->appStatusB1 << 8) |
body->appStatusB0;
if (body->protocolStatus != FCGI_REQUEST_COMPLETE) {
print(ERR_STR "FastCGI protocol error: %i" CLR_STR, body->protocolStatus);
}
if (app_status != 0) {
print(ERR_STR "Script terminated with exit code %i" CLR_STR, app_status);
}
close(conn->socket);
conn->socket = 0;
free(content);
if (flags & FASTCGI_COMPRESS) {
finish_comp = 1;
content_len = 0;
goto out;
finish:
deflateEnd(&strm);
}
if (flags & FASTCGI_CHUNKED) {
if (client->enc) {
SSL_write(client->ssl, "0\r\n\r\n", 5);
} else {
send(client->socket, "0\r\n\r\n", 5, 0);
}
}
return 0;
} else if (header.type == FCGI_STDERR) {
print(ERR_STR "%.*s" CLR_STR, content_len, content);
} else if (header.type == FCGI_STDOUT) {
out:
if (flags & FASTCGI_COMPRESS) {
strm.avail_in = content_len;
strm.next_in = (unsigned char *) ptr;
}
do {
int buf_len = content_len;
if (flags & FASTCGI_COMPRESS) {
strm.avail_out = sizeof(comp_out);
strm.next_out = (unsigned char *) comp_out;
deflate(&strm, finish_comp ? Z_FINISH : Z_NO_FLUSH);
strm.avail_in = 0;
ptr = comp_out;
buf_len = (int) (sizeof(comp_out) - strm.avail_out);
}
if (buf_len != 0) {
len = sprintf(buf0, "%X\r\n", buf_len);
if (client->enc) {
if (flags & FASTCGI_CHUNKED) SSL_write(client->ssl, buf0, len);
SSL_write(client->ssl, ptr, buf_len);
if (flags & FASTCGI_CHUNKED) SSL_write(client->ssl, "\r\n", 2);
} else {
if (flags & FASTCGI_CHUNKED) send(client->socket, buf0, len, 0);
send(client->socket, ptr, buf_len, 0);
if (flags & FASTCGI_CHUNKED) send(client->socket, "\r\n", 2, 0);
}
}
} while ((flags & FASTCGI_COMPRESS) && strm.avail_out == 0);
if (finish_comp) goto finish;
} else {
print(ERR_STR "Unknown FastCGI type: %i" CLR_STR, header.type);
}
free(content);
}
}
int fastcgi_receive(fastcgi_conn *conn, sock *client, unsigned long len) {
unsigned long rcv_len = 0;
char *buf[16384];
int ret;
FCGI_Header header = {
.version = FCGI_VERSION_1,
.type = FCGI_STDIN,
.requestIdB1 = conn->req_id >> 8,
.requestIdB0 = conn->req_id & 0xFF,
.contentLengthB1 = 0,
.contentLengthB0 = 0,
.paddingLength = 0,
.reserved = 0
};
while (rcv_len < len) {
if (client->enc) {
ret = SSL_read(client->ssl, buf, sizeof(buf));
if (ret <= 0) {
print(ERR_STR "Unable to receive: %s" CLR_STR, ssl_get_error(client->ssl, rcv_len));
return -1;
}
} else {
ret = recv(client->socket, buf, sizeof(buf), 0);
if (ret <= 0) {
print(ERR_STR "Unable to receive: %s" CLR_STR, strerror(errno));
return -1;
}
}
rcv_len += ret;
header.contentLengthB1 = (ret >> 8) & 0xFF;
header.contentLengthB0 = ret & 0xFF;
if (send(conn->socket, &header, sizeof(header), 0) != sizeof(header)) goto err;
if (send(conn->socket, buf, ret, 0) != ret) {
err:
print(ERR_STR "Unable to send to PHP-FPM: %s" CLR_STR, strerror(errno));
return -2;
}
}
return 0;
}

147
src/fastcgi.h Normal file
View File

@ -0,0 +1,147 @@
/**
* Necronda Web Server
* FastCGI interface implementation (header file)
* src/fastcgi.h
* Lorenz Stechauner, 2020-12-26
*/
#ifndef NECRONDA_SERVER_FASTCGI_H
#define NECRONDA_SERVER_FASTCGI_H
#define FASTCGI_CHUNKED 1
#define FASTCGI_COMPRESS 2
typedef struct {
int socket;
unsigned short req_id;
char *out_buf;
unsigned short out_len;
unsigned short out_off;
} fastcgi_conn;
char *fastcgi_add_param(char *buf, const char *key, const char *value);
int fastcgi_init(fastcgi_conn *conn, unsigned int client_num, unsigned int req_num, const sock *client,
const http_req *req, const http_uri *uri);
int fastcgi_close_stdin(fastcgi_conn *conn);
int fastcgi_header(fastcgi_conn *conn, http_res *res, char *err_msg);
int fastcgi_send(fastcgi_conn *conn, sock *client, int flags);
int fastcgi_receive(fastcgi_conn *conn, sock *client, unsigned long len);
/*
* Listening socket file number
*/
#define FCGI_LISTENSOCK_FILENO 0
typedef struct {
unsigned char version;
unsigned char type;
unsigned char requestIdB1;
unsigned char requestIdB0;
unsigned char contentLengthB1;
unsigned char contentLengthB0;
unsigned char paddingLength;
unsigned char reserved;
} FCGI_Header;
/*
* Number of bytes in a FCGI_Header. Future versions of the protocol
* will not reduce this number.
*/
#define FCGI_HEADER_LEN 8
/*
* Value for version component of FCGI_Header
*/
#define FCGI_VERSION_1 1
/*
* Values for type component of FCGI_Header
*/
#define FCGI_BEGIN_REQUEST 1
#define FCGI_ABORT_REQUEST 2
#define FCGI_END_REQUEST 3
#define FCGI_PARAMS 4
#define FCGI_STDIN 5
#define FCGI_STDOUT 6
#define FCGI_STDERR 7
#define FCGI_DATA 8
#define FCGI_GET_VALUES 9
#define FCGI_GET_VALUES_RESULT 10
#define FCGI_UNKNOWN_TYPE 11
#define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE)
/*
* Value for requestId component of FCGI_Header
*/
#define FCGI_NULL_REQUEST_ID 0
typedef struct {
unsigned char roleB1;
unsigned char roleB0;
unsigned char flags;
unsigned char reserved[5];
} FCGI_BeginRequestBody;
typedef struct {
FCGI_Header header;
FCGI_BeginRequestBody body;
} FCGI_BeginRequestRecord;
/*
* Mask for flags component of FCGI_BeginRequestBody
*/
#define FCGI_KEEP_CONN 1
/*
* Values for role component of FCGI_BeginRequestBody
*/
#define FCGI_RESPONDER 1
#define FCGI_AUTHORIZER 2
#define FCGI_FILTER 3
typedef struct {
unsigned char appStatusB3;
unsigned char appStatusB2;
unsigned char appStatusB1;
unsigned char appStatusB0;
unsigned char protocolStatus;
unsigned char reserved[3];
} FCGI_EndRequestBody;
typedef struct {
FCGI_Header header;
FCGI_EndRequestBody body;
} FCGI_EndRequestRecord;
/*
* Values for protocolStatus component of FCGI_EndRequestBody
*/
#define FCGI_REQUEST_COMPLETE 0
#define FCGI_CANT_MPX_CONN 1
#define FCGI_OVERLOADED 2
#define FCGI_UNKNOWN_ROLE 3
/*
* Variable names for FCGI_GET_VALUES / FCGI_GET_VALUES_RESULT records
*/
#define FCGI_MAX_CONNS "FCGI_MAX_CONNS"
#define FCGI_MAX_REQS "FCGI_MAX_REQS"
#define FCGI_MPXS_CONNS "FCGI_MPXS_CONNS"
typedef struct {
unsigned char type;
unsigned char reserved[7];
} FCGI_UnknownTypeBody;
typedef struct {
FCGI_Header header;
FCGI_UnknownTypeBody body;
} FCGI_UnknownTypeRecord;
#endif //NECRONDA_SERVER_FASTCGI_H

289
src/http.c Normal file
View File

@ -0,0 +1,289 @@
/**
* Necronda Web Server
* HTTP implementation
* src/net/http.c
* Lorenz Stechauner, 2020-12-09
*/
#include "http.h"
#include "utils.h"
void http_to_camel_case(char *str, int mode) {
char last = '-';
char ch;
for (int i = 0; i < strlen(str); i++) {
ch = str[i];
if (mode == HTTP_CAMEL && last == '-' && ch >= 'a' && ch <= 'z') {
str[i] = (char) ((int) ch & 0x5F);
} else if (mode == HTTP_LOWER && ch >= 'A' && ch <= 'Z') {
str[i] = (char) ((int) ch | 0x20);
}
last = str[i];
}
}
void http_free_hdr(http_hdr *hdr) {
for (int i = 0; i < hdr->field_num; i++) {
free(hdr->fields[i][0]);
free(hdr->fields[i][1]);
}
hdr->field_num = 0;
}
void http_free_req(http_req *req) {
if (req->uri == NULL) free(req->uri);
req->uri = NULL;
http_free_hdr(&req->hdr);
}
void http_free_res(http_res *res) {
http_free_hdr(&res->hdr);
}
int http_parse_header_field(http_hdr *hdr, const char *buf, const char *end_ptr) {
char *pos1 = memchr(buf, ':', end_ptr - buf);
char *pos2;
if (pos1 == NULL) {
print(ERR_STR "Unable to parse header" CLR_STR);
return 3;
}
long len = pos1 - buf;
hdr->fields[hdr->field_num][0] = malloc(len + 1);
sprintf(hdr->fields[hdr->field_num][0], "%.*s", (int) len, buf);
http_to_camel_case(hdr->fields[hdr->field_num][0], HTTP_CAMEL);
pos1++;
pos2 = (char *) end_ptr - 1;
while (pos1[0] == ' ') pos1++;
while (pos2[0] == ' ') pos2--;
len = pos2 - pos1 + 1;
if (len <= 0) {
hdr->fields[hdr->field_num][1] = malloc(1);
hdr->fields[hdr->field_num][1][0] = 0;
} else {
hdr->fields[hdr->field_num][1] = malloc(len + 1);
sprintf(hdr->fields[hdr->field_num][1], "%.*s", (int) len, pos1);
}
hdr->field_num++;
return 0;
}
int http_receive_request(sock *client, http_req *req) {
unsigned long rcv_len, len;
char *ptr, *pos0, *pos1, *pos2;
char buf[CLIENT_MAX_HEADER_SIZE];
memset(buf, 0, sizeof(buf));
memset(req->method, 0, sizeof(req->method));
memset(req->version, 0, sizeof(req->version));
req->uri = NULL;
req->hdr.field_num = 0;
while (1) {
if (client->enc) {
rcv_len = SSL_read(client->ssl, buf, CLIENT_MAX_HEADER_SIZE);
if (rcv_len < 0) {
print(ERR_STR "Unable to receive: %s" CLR_STR, ssl_get_error(client->ssl, rcv_len));
return -1;
}
} else {
rcv_len = recv(client->socket, buf, CLIENT_MAX_HEADER_SIZE, 0);
if (rcv_len < 0) {
print(ERR_STR "Unable to receive: %s" CLR_STR, strerror(errno));
return -1;
}
}
if (rcv_len == 0) {
print("Unable to receive: closed");
return -1;
}
unsigned long header_len = strstr(buf, "\r\n\r\n") - buf + 4;
if (header_len <= 0) {
print(ERR_STR "Unable to parse header: End of header not found" CLR_STR);
return 5;
}
for (int i = 0; i < header_len; i++) {
if ((buf[i] >= 0x00 && buf[i] <= 0x1F && buf[i] != '\r' && buf[i] != '\n') || buf[i] == 0x7F) {
print(ERR_STR "Unable to parse header: Header contains illegal characters" CLR_STR);
return 4;
}
}
ptr = buf;
while (header_len != (ptr - buf)) {
pos0 = strstr(ptr, "\r\n");
if (pos0 == NULL) {
print(ERR_STR "Unable to parse header: Invalid header format" CLR_STR);
return 1;
}
if (req->version[0] == 0) {
if (memcmp(ptr, "GET ", 4) == 0) {
strcpy(req->method, "GET");
} else if (memcmp(ptr, "HEAD ", 5) == 0) {
strcpy(req->method, "HEAD");
} else if (memcmp(ptr, "POST ", 5) == 0) {
strcpy(req->method, "POST");
} else if (memcmp(ptr, "PUT ", 4) == 0) {
strcpy(req->method, "PUT");
} else if (memcmp(ptr, "DELETE ", 7) == 0) {
strcpy(req->method, "DELETE");
} else if (memcmp(ptr, "CONNECT ", 7) == 0) {
strcpy(req->method, "CONNECT");
} else if (memcmp(ptr, "OPTIONS ", 7) == 0) {
strcpy(req->method, "OPTIONS");
} else if (memcmp(ptr, "TRACE ", 6) == 0) {
strcpy(req->method, "TRACE");
} else {
print(ERR_STR "Unable to parse header: Invalid method" CLR_STR);
return 2;
}
pos1 = memchr(ptr, ' ', rcv_len - (ptr - buf)) + 1;
if (pos1 == NULL) goto err_hdr_fmt;
pos2 = memchr(pos1, ' ', rcv_len - (pos1 - buf)) + 1;
if (pos2 == NULL) {
err_hdr_fmt:
print(ERR_STR "Unable to parse header: Invalid header format" CLR_STR);
return 1;
}
if (memcmp(pos2, "HTTP/", 5) != 0 || memcmp(pos2 + 8, "\r\n", 2) != 0) {
print(ERR_STR "Unable to parse header: Invalid version" CLR_STR);
return 3;
}
len = pos2 - pos1 - 1;
req->uri = malloc(len + 1);
sprintf(req->uri, "%.*s", (int) len, pos1);
sprintf(req->version, "%.3s", pos2 + 5);
} else {
int ret = http_parse_header_field(&req->hdr, ptr, pos0);
if (ret != 0) return ret;
}
if (pos0[2] == '\r' && pos0[3] == '\n') {
return 0;
}
ptr = pos0 + 2;
}
}
}
char *http_get_header_field(const http_hdr *hdr, const char *field_name) {
char field_name_1[256], field_name_2[256];
strcpy(field_name_1, field_name);
http_to_camel_case(field_name_1, HTTP_LOWER);
for (int i = 0; i < hdr->field_num; i++) {
strcpy(field_name_2, hdr->fields[i][0]);
http_to_camel_case(field_name_2, HTTP_LOWER);
if (strcmp(field_name_1, field_name_2) == 0) {
return hdr->fields[i][1];
}
}
return NULL;
}
void http_add_header_field(http_hdr *hdr, const char *field_name, const char *field_value) {
size_t len_name = strlen(field_name);
size_t len_value = strlen(field_value);
char *_field_name = malloc(len_name + 1);
char *_field_value = malloc(len_value + 1);
strcpy(_field_name, field_name);
strcpy(_field_value, field_value);
http_to_camel_case(_field_name, HTTP_PRESERVE);
hdr->fields[hdr->field_num][0] = _field_name;
hdr->fields[hdr->field_num][1] = _field_value;
hdr->field_num++;
}
void http_remove_header_field(http_hdr *hdr, const char *field_name, int mode) {
char field_name_1[256], field_name_2[256];
strcpy(field_name_1, field_name);
http_to_camel_case(field_name_1, HTTP_LOWER);
for (int i = 0; i < hdr->field_num; i++) {
strcpy(field_name_2, hdr->fields[i][0]);
http_to_camel_case(field_name_2, HTTP_LOWER);
if (strcmp(field_name_1, field_name_2) == 0) {
for (int j = i; j < hdr->field_num - 1; j++) {
memcpy(hdr->fields[j], hdr->fields[j + 1], sizeof(hdr->fields[0]));
}
hdr->field_num--;
if (mode == HTTP_REMOVE_ONE) {
return;
} else if (mode == HTTP_REMOVE_ALL) {
i--;
}
}
}
}
int http_send_response(sock *client, http_res *res) {
char buf[CLIENT_MAX_HEADER_SIZE];
int len = 0;
int snd_len = 0;
len += sprintf(buf + len, "HTTP/%s %03i %s\r\n", res->version, res->status->code, res->status->msg);
for (int i = 0; i < res->hdr.field_num; i++) {
len += sprintf(buf + len, "%s: %s\r\n", res->hdr.fields[i][0], res->hdr.fields[i][1]);
}
len += sprintf(buf + len, "\r\n");
if (client->enc) {
snd_len = SSL_write(client->ssl, buf, len);
} else {
snd_len = send(client->socket, buf, len, 0);
}
return 0;
}
http_status *http_get_status(unsigned short status_code) {
for (int i = 0; i < sizeof(http_statuses) / sizeof(http_status); i++) {
if (http_statuses[i].code == status_code) {
return &http_statuses[i];
}
}
return NULL;
}
http_error_msg *http_get_error_msg(unsigned short status_code) {
for (int i = 0; i < sizeof(http_error_messages) / sizeof(http_error_msg); i++) {
if (http_error_messages[i].code == status_code) {
return &http_error_messages[i];
}
}
return NULL;
}
const char *http_get_status_color(http_status *status) {
unsigned short code = status->code;
if (code >= 100 && code < 200) {
return HTTP_1XX_STR;
} else if (code >= 200 && code < 300 || code == 304) {
return HTTP_2XX_STR;
} else if (code >= 300 && code < 400) {
return HTTP_3XX_STR;
} else if (code >= 400 && code < 500) {
return HTTP_4XX_STR;
} else if (code >= 500 && code < 600) {
return HTTP_5XX_STR;
}
return "";
}
char *http_format_date(time_t time, char *buf, size_t size) {
struct tm *timeinfo = gmtime(&time);
strftime(buf, size, "%a, %d %b %Y %H:%M:%S GMT", timeinfo);
return buf;
}
char *http_get_date(char *buf, size_t size) {
time_t rawtime;
time(&rawtime);
return http_format_date(rawtime, buf, size);
}

192
src/http.h Normal file
View File

@ -0,0 +1,192 @@
/**
* Necronda Web Server
* HTTP implementation (header file)
* src/net/http.h
* Lorenz Stechauner, 2020-12-09
*/
#ifndef NECRONDA_SERVER_HTTP_H
#define NECRONDA_SERVER_HTTP_H
#define HTTP_PRESERVE 0
#define HTTP_LOWER 1
#define HTTP_CAMEL 2
#define HTTP_REMOVE_ONE 0
#define HTTP_REMOVE_ALL 1
typedef struct {
unsigned short code;
char type[16];
char msg[32];
} http_status;
typedef struct {
unsigned short code;
char *err_msg;
} http_error_msg;
typedef struct {
char field_num;
char *fields[64][2];
} http_hdr;
typedef struct {
char method[8];
char *uri;
char version[3];
http_hdr hdr;
} http_req;
typedef struct {
http_status *status;
char version[3];
http_hdr hdr;
} http_res;
http_status http_statuses[] = {
{100, "Informational", "Continue"},
{101, "Informational", "Switching Protocols"},
{200, "Success", "OK"},
{201, "Success", "Created"},
{202, "Success", "Accepted"},
{203, "Success", "Non-Authoritative Information"},
{204, "Success", "No Content"},
{205, "Success", "Reset Content"},
{206, "Success", "Partial Content"},
{300, "Redirection", "Multiple Choices"},
{301, "Redirection", "Moved Permanently"},
{302, "Redirection", "Found"},
{303, "Redirection", "See Other"},
{304, "Redirection", "Not Modified"},
{305, "Redirection", "Use Proxy"},
{307, "Redirection", "Temporary Redirect"},
{308, "Redirection", "Permanent Redirect"},
{400, "Client Error", "Bad Request"},
{401, "Client Error", "Unauthorized"},
{402, "Client Error", "Payment Required"},
{403, "Client Error", "Forbidden"},
{404, "Client Error", "Not Found"},
{405, "Client Error", "Method Not Allowed"},
{406, "Client Error", "Not Acceptable"},
{407, "Client Error", "Proxy Authentication Required"},
{408, "Client Error", "Request Timeout"},
{409, "Client Error", "Conflict"},
{410, "Client Error", "Gone"},
{411, "Client Error", "Length Required"},
{412, "Client Error", "Precondition Failed"},
{413, "Client Error", "Request Entity Too Large"},
{414, "Client Error", "Request-URI Too Long"},
{415, "Client Error", "Unsupported Media Type"},
{416, "Client Error", "Range Not Satisfiable"},
{417, "Client Error", "Expectation Failed"},
{500, "Server Error", "Internal Server Error"},
{501, "Server Error", "Not Implemented"},
{502, "Server Error", "Bad Gateway"},
{503, "Server Error", "Service Unavailable"},
{504, "Server Error", "Gateway Timeout"},
{505, "Server Error", "HTTP Version Not Supported"},
};
http_error_msg http_error_messages[] = {
{400, "The request could not be understood by the server due to malformed syntax."},
{401, "The request requires user authentication."},
{403, "The server understood the request, but is refusing to fulfill it."},
{404, "The server has not found anything matching the Request-URI."},
{405, "The method specified in the Request-Line is not allowed for the resource identified by the Request-URI."},
{406, "The resource identified by the request is only capable of generating response entities which have content characteristics not acceptable according to the accept headers sent in the request."},
{407, "The request requires user authentication on the proxy."},
{408, "The client did not produce a request within the time that the server was prepared to wait."},
{409, "The request could not be completed due to a conflict with the current state of the resource."},
{410, "The requested resource is no longer available at the server and no forwarding address is known."},
{411, "The server refuses to accept the request without a defined Content-Length."},
{412, "The precondition given in one or more of the request-header fields evaluated to false when it was tested on the server."},
{413, "The server is refusing to process a request because the request entity is larger than the server is willing or able to process."},
{414, "The server is refusing to service the request because the Request-URI is longer than the server is willing to interpret."},
{415, "The server is refusing to service the request because the entity of the request is in a format not supported by the requested resource for the requested method."},
{416, "None of the ranges in the request's Range header field overlap the current extent of the selected resource or that the set of ranges requested has been rejected due to invalid ranges or an excessive request of small or overlapping ranges."},
{417, "The expectation given in an Expect request-header field could not be met by this server, or, if the server is a proxy, the server has unambiguous evidence that the request could not be met by the next-hop server."},
{500, "The server encountered an unexpected condition which prevented it from fulfilling the request."},
{501, "The server does not support the functionality required to fulfill the request."},
{502, "The server, while acting as a gateway or proxy, received an invalid response from the upstream server it accessed in attempting to fulfill the request."},
{503, "The server is currently unable to handle the request due to a temporary overloading or maintenance of the server."},
{504, "The server, while acting as a gateway or proxy, did not receive a timely response from the upstream server specified by the URI or some other auxiliary server it needed to access in attempting to complete the request."},
{505, "The server does not support, or refuses to support, the HTTP protocol version that was used in the request message."}
};
const char *http_default_document =
"<!DOCTYPE html>\n"
"<html lang=\"en\">\n"
"<head>\n"
" <title>%1$i %2$s</title>\n"
" <meta charset=\"UTF-8\"/>\n"
" <meta name=\"theme-color\" content=\"%6$s\"/>\n"
" <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black-translucent\"/>\n"
" <meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\"/>\n"
"%5$s"
" <style>\n"
" html{font-family:\"Arial\",sans-serif;--error:#C00000;--info:#E0C000;--color:var(--%4$s);}\n"
" body{background-color:#F0F0F0;margin:0.5em;}\n"
" main{max-width:600px;margin:2em auto;background-color:#FFFFFF;border: 1px solid var(--color);border-radius:4px;padding:1em 2em;}\n"
" h1,h2,h3,h4,h5,h6,h7{text-align:center;color:var(--color);}\n"
" h1{margin:0.5em 0;font-size:1.5em;}\n"
" p{text-align:center;}\n"
" div.footer{color:#808080;font-size:0.75em;text-align:center;margin:0.5em 0;}\n"
" </style>\n"
"</head>\n"
"<body>\n"
" <main>\n"
"%3$s"
" <div class=\"footer\">Necronda web server " NECRONDA_VERSION "</div>\n"
" </main>\n"
"</body>\n"
"</html>\n";
const char *http_error_document =
" <h1>%1$i %2$s :&#xFEFF;(</h1>\n"
" <p>%3$s</p>\n"
" <p>%4$s</p>\n";
const char *http_error_icon =
" <link rel=\"shortcut icon\" type=\"image/svg+xml\" sizes=\"any\" href=\"data:image/svg+xml;base64,"
"PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAw"
"L3N2ZyI+PHRleHQgeD0iNCIgeT0iMTIiIGZpbGw9IiNDMDAwMDAiIHN0eWxlPSJmb250LWZhbWls"
"eTonQXJpYWwnLHNhbnMtc2VyaWYiPjooPC90ZXh0Pjwvc3ZnPgo=\"/>\n";
void http_to_camel_case(char *str, int mode);
void http_free_hdr(http_hdr *hdr);
void http_free_req(http_req *req);
void http_free_res(http_res *res);
int http_receive_request(sock *client, http_req *req);
int http_parse_header_field(http_hdr *hdr, const char *buf, const char *end_ptr) ;
char *http_get_header_field(const http_hdr *hdr, const char *field_name);
void http_add_header_field(http_hdr *hdr, const char *field_name, const char *field_value);
void http_remove_header_field(http_hdr *hdr, const char *field_name, int mode);
int http_send_response(sock *client, http_res *res);
http_status *http_get_status(unsigned short status_code);
http_error_msg *http_get_error_msg(unsigned short status_code);
const char *http_get_status_color(http_status *status);
char *http_format_date(time_t time, char *buf, size_t size);
char *http_get_date(char *buf, size_t size);
#endif //NECRONDA_SERVER_HTTP_H

408
src/necronda-server.c Normal file
View File

@ -0,0 +1,408 @@
/**
* Necronda Web Server
* Main executable
* src/necronda-server.c
* Lorenz Stechauner, 2020-12-03
*/
#define _POSIX_C_SOURCE 199309L
#include "necronda-server.h"
#include "utils.c"
#include "uri.c"
#include "cache.c"
#include "http.c"
#include "client.c"
#include "fastcgi.c"
int active = 1;
void openssl_init() {
SSL_library_init();
SSL_load_error_strings();
ERR_load_BIO_strings();
OpenSSL_add_all_algorithms();
}
char *ssl_get_error(SSL *ssl, int ret) {
if (ret > 0) {
return NULL;
}
unsigned long ret2 = ERR_get_error();
char *err2 = strerror(errno);
char *err1 = (char *) ERR_reason_error_string(ret2);
switch (SSL_get_error(ssl, ret)) {
case SSL_ERROR_NONE:
return "none";
case SSL_ERROR_ZERO_RETURN:
return "closed";
case SSL_ERROR_WANT_READ:
return "want read";
case SSL_ERROR_WANT_WRITE:
return "want write";
case SSL_ERROR_WANT_CONNECT:
return "want connect";
case SSL_ERROR_WANT_ACCEPT:
return "want accept";
case SSL_ERROR_WANT_X509_LOOKUP:
return "want x509 lookup";
case SSL_ERROR_SYSCALL:
return ((ret2 == 0) ? ((ret == 0) ? "protocol violation" : err2) : err1);
case SSL_ERROR_SSL:
return err1;
default:
return "unknown error";
}
}
void destroy() {
fprintf(stderr, "\n" ERR_STR "Terminating forcefully!" CLR_STR "\n");
fflush(stderr);
int status = 0;
int ret;
int kills = 0;
for (int i = 0; i < MAX_CHILDREN; i++) {
if (children[i] != 0) {
ret = waitpid(children[i], &status, WNOHANG);
if (ret < 0) {
fprintf(stderr, ERR_STR "Unable to wait for child process (PID %i): %s" CLR_STR "\n",
children[i], strerror(errno));
fflush(stderr);
} else if (ret == children[i]) {
children[i] = 0;
if (status != 0) {
fprintf(stderr, ERR_STR "Child process with PID %i terminated with exit code %i" CLR_STR "\n",
ret, status);
fflush(stderr);
}
} else {
kill(children[i], SIGKILL);
kills++;
}
}
}
if (kills > 0) {
fprintf(stderr, ERR_STR "Killed %i child process(es)" CLR_STR "\n", kills);
fflush(stderr);
}
cache_unload();
exit(2);
}
void terminate() {
fprintf(stderr, "\nTerminating gracefully...\n");
fflush(stderr);
active = 0;
signal(SIGINT, destroy);
signal(SIGTERM, destroy);
for (int i = 0; i < NUM_SOCKETS; i++) {
shutdown(sockets[i], SHUT_RDWR);
close(sockets[i]);
}
int status = 0;
int wait_num = 0;
int ret;
for (int i = 0; i < MAX_CHILDREN; i++) {
if (children[i] != 0) {
ret = waitpid(children[i], &status, WNOHANG);
if (ret < 0) {
fprintf(stderr, ERR_STR "Unable to wait for child process (PID %i): %s" CLR_STR "\n",
children[i], strerror(errno));
fflush(stderr);
} else if (ret == children[i]) {
children[i] = 0;
if (status != 0) {
fprintf(stderr, ERR_STR "Child process with PID %i terminated with exit code %i" CLR_STR "\n",
ret, status);
fflush(stderr);
}
} else {
kill(children[i], SIGTERM);
wait_num++;
}
}
}
if (wait_num > 0) {
fprintf(stderr, "Waiting for %i child process(es)...\n", wait_num);
fflush(stderr);
}
for (int i = 0; i < MAX_CHILDREN; i++) {
if (children[i] != 0) {
ret = waitpid(children[i], &status, 0);
if (ret < 0) {
fprintf(stderr, ERR_STR "Unable to wait for child process (PID %i): %s" CLR_STR "\n",
children[i], strerror(errno));
fflush(stderr);
} else if (ret == children[i]) {
children[i] = 0;
if (status != 0) {
fprintf(stderr, ERR_STR "Child process with PID %i terminated with exit code %i" CLR_STR "\n",
ret, status);
fflush(stderr);
}
}
}
}
if (wait_num > 0) {
// Wait another 50 ms to let child processes write to stdout/stderr
signal(SIGINT, SIG_IGN);
signal(SIGTERM, SIG_IGN);
struct timespec ts = {.tv_sec = 0, .tv_nsec = 50000000};
nanosleep(&ts, &ts);
fprintf(stderr, "\nGoodbye\n");
fflush(stderr);
} else {
fprintf(stderr, "Goodbye\n");
fflush(stderr);
}
cache_unload();
exit(0);
}
int main(int argc, const char *argv[]) {
const int YES = 1;
fd_set socket_fds, read_socket_fds;
int max_socket_fd = 0;
int ready_sockets_num;
long client_num = 0;
char buf[1024];
int ret;
int client_fd;
sock client;
struct sockaddr_in6 client_addr;
unsigned int client_addr_len = sizeof(client_addr);
struct timeval timeout;
const struct sockaddr_in6 addresses[2] = {
{.sin6_family = AF_INET6, .sin6_addr = IN6ADDR_ANY_INIT, .sin6_port = htons(80)},
{.sin6_family = AF_INET6, .sin6_addr = IN6ADDR_ANY_INIT, .sin6_port = htons(443)}
};
printf("Necronda Web Server\n");
fflush(stdout);
for (int i = 1; i < argc; i++) {
const char *arg = argv[i];
unsigned long len = strlen(arg);
if ((len == 2 && strncmp(arg, "-h", 2) == 0) || (len == 6 && strncmp(arg, "--help", 6) == 0)) {
printf("Usage: necronda-server [-h] -w <PATH> -c <CERT-FILE> -p <KEY-FILE> [-g <DB-FILE>]\n"
"\n"
"Options:\n"
" -c, --cert <CERT-FILE> path to the full chain certificate file\n"
" -g, --geoip <DB-FILE> path to a Maxmind GeoIP Database file\n"
" -h, --help print this dialogue\n"
" -p, --privkey <KEY-FILE> path to the private key file\n"
" -w, --webroot <PATH> path to the web root directory\n");
return 0;
} else if ((len == 2 && strncmp(arg, "-w", 2) == 0) || (len == 9 && strncmp(arg, "--webroot", 9) == 0)) {
if (i == argc - 1) {
fprintf(stderr, ERR_STR "Unable to parse argument %s, usage: --webroot <WEBROOT>" CLR_STR "\n", arg);
fflush(stderr);
return 1;
}
webroot_base = argv[++i];
} else if ((len == 2 && strncmp(arg, "-c", 2) == 0) || (len == 6 && strncmp(arg, "--cert", 6) == 0)) {
if (i == argc - 1) {
fprintf(stderr, ERR_STR "Unable to parse argument %s, usage: --cert <CERT-FILE>" CLR_STR "\n", arg);
fflush(stderr);
return 1;
}
cert_file = argv[++i];
} else if ((len == 2 && strncmp(arg, "-p", 2) == 0) || (len == 9 && strncmp(arg, "--privkey", 9) == 0)) {
if (i == argc - 1) {
fprintf(stderr, ERR_STR "Unable to parse argument %s, usage: --privkey <KEY-FILE>" CLR_STR "\n", arg);
fflush(stderr);
return 1;
}
key_file = argv[++i];
} else if ((len == 2 && strncmp(arg, "-g", 2) == 0) || (len == 7 && strncmp(arg, "--geoip", 7) == 0)) {
if (i == argc - 1) {
fprintf(stderr, ERR_STR "Unable to parse argument %s, usage: --geoip <DB-FILE>" CLR_STR "\n", arg);
fflush(stderr);
return 1;
}
geoip_file = argv[++i];
} else {
fprintf(stderr, ERR_STR "Unable to parse argument '%s'" CLR_STR "\n", arg);
fflush(stderr);
return 1;
}
}
if (webroot_base == NULL) {
fprintf(stderr, ERR_STR "Error: --webroot is missing" CLR_STR "\n");
fflush(stderr);
return 1;
}
if (cert_file == NULL) {
fprintf(stderr, ERR_STR "Error: --cert is missing" CLR_STR "\n");
fflush(stderr);
return 1;
}
if (key_file == NULL) {
fprintf(stderr, ERR_STR "Error: --privkey is missing" CLR_STR "\n");
fflush(stderr);
return 1;
}
sockets[0] = socket(AF_INET6, SOCK_STREAM, 0);
if (sockets[0] < 0) goto socket_err;
sockets[1] = socket(AF_INET6, SOCK_STREAM, 0);
if (sockets[1] < 0) {
socket_err:
fprintf(stderr, ERR_STR "Unable to create socket: %s" CLR_STR "\n", strerror(errno));
fflush(stderr);
return 1;
}
for (int i = 0; i < NUM_SOCKETS; i++) {
if (setsockopt(sockets[i], SOL_SOCKET, SO_REUSEADDR, &YES, sizeof(YES)) < 0) {
fprintf(stderr, ERR_STR "Unable to set options for socket %i: %s" CLR_STR "\n", i, strerror(errno));
fflush(stderr);
return 1;
}
}
if (bind(sockets[0], (struct sockaddr *) &addresses[0], sizeof(addresses[0])) < 0) goto bind_err;
if (bind(sockets[1], (struct sockaddr *) &addresses[1], sizeof(addresses[1])) < 0) {
bind_err:
fprintf(stderr, ERR_STR "Unable to bind socket to address: %s" CLR_STR "\n", strerror(errno));
fflush(stderr);
return 1;
}
signal(SIGINT, terminate);
signal(SIGTERM, terminate);
ret = cache_init();
if (ret < 0) {
return 1;
} else if (ret != 0) {
return 0;
}
// TODO init geoip database
openssl_init();
client.ctx = SSL_CTX_new(TLS_server_method());
SSL_CTX_set_options(client.ctx, SSL_OP_SINGLE_DH_USE);
SSL_CTX_set_verify(client.ctx, SSL_VERIFY_NONE, NULL);
SSL_CTX_set_min_proto_version(client.ctx, TLS1_VERSION);
SSL_CTX_set_mode(client.ctx, SSL_MODE_ENABLE_PARTIAL_WRITE);
SSL_CTX_set_cipher_list(client.ctx, "HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4");
SSL_CTX_set_ecdh_auto(client.ctx, 1);
if (SSL_CTX_use_certificate_chain_file(client.ctx, cert_file) != 1) {
fprintf(stderr, ERR_STR "Unable to load certificate chain file: %s: %s" CLR_STR "\n",
ERR_reason_error_string(ERR_get_error()), cert_file);
fflush(stderr);
return 1;
}
if (SSL_CTX_use_PrivateKey_file(client.ctx, key_file, SSL_FILETYPE_PEM) != 1) {
fprintf(stderr, ERR_STR "Unable to load private key file: %s: %s" CLR_STR "\n",
ERR_reason_error_string(ERR_get_error()), key_file);
fflush(stderr);
return 1;
}
for (int i = 0; i < NUM_SOCKETS; i++) {
if (listen(sockets[i], LISTEN_BACKLOG) < 0) {
fprintf(stderr, ERR_STR "Unable to listen on socket %i: %s" CLR_STR "\n", i, strerror(errno));
fflush(stderr);
return 1;
}
}
FD_ZERO(&socket_fds);
for (int i = 0; i < NUM_SOCKETS; i++) {
FD_SET(sockets[i], &socket_fds);
if (sockets[i] > max_socket_fd) {
max_socket_fd = sockets[i];
}
}
fprintf(stderr, "Ready to accept connections\n");
fflush(stderr);
while (active) {
timeout.tv_sec = 1;
timeout.tv_usec = 0;
read_socket_fds = socket_fds;
ready_sockets_num = select(max_socket_fd + 1, &read_socket_fds, NULL, NULL, &timeout);
if (ready_sockets_num < 0) {
fprintf(stderr, ERR_STR "Unable to select sockets: %s" CLR_STR "\n", strerror(errno));
fflush(stderr);
return 1;
}
for (int i = 0; i < NUM_SOCKETS; i++) {
if (FD_ISSET(sockets[i], &read_socket_fds)) {
client_fd = accept(sockets[i], (struct sockaddr *) &client_addr, &client_addr_len);
if (client_fd < 0) {
fprintf(stderr, ERR_STR "Unable to accept connection: %s" CLR_STR "\n", strerror(errno));
fflush(stderr);
continue;
}
pid_t pid = fork();
if (pid == 0) {
// child
signal(SIGINT, SIG_IGN);
signal(SIGTERM, SIG_IGN);
client.socket = client_fd;
client.enc = i == 1;
return client_handler(&client, client_num, &client_addr);
} else if (pid > 0) {
// parent
client_num++;
close(client_fd);
for (int j = 0; j < MAX_CHILDREN; j++) {
if (children[j] == 0) {
children[j] = pid;
break;
}
}
} else {
fprintf(stderr, ERR_STR "Unable to create child process: %s" CLR_STR "\n", strerror(errno));
fflush(stderr);
}
}
}
int status = 0;
for (int i = 0; i < MAX_CHILDREN; i++) {
if (children[i] != 0) {
ret = waitpid(children[i], &status, WNOHANG);
if (ret < 0) {
fprintf(stderr, ERR_STR "Unable to wait for child process (PID %i): %s" CLR_STR "\n",
children[i], strerror(errno));
fflush(stderr);
} else if (ret == children[i]) {
children[i] = 0;
if (status != 0) {
fprintf(stderr, ERR_STR "Child process with PID %i terminated with exit code %i" CLR_STR "\n",
ret, status);
fflush(stderr);
}
}
}
}
}
return 0;
}

View File

@ -1,284 +0,0 @@
/**
* Necronda Web Server 3.0
* necronda-server.cpp - Main Executable
* Lorenz Stechauner, 2018-05-09
*/
#include "necronda-server.h"
#include <magic.h>
#include <iostream>
#include <thread>
#include <sys/time.h>
#include <sys/stat.h>
#include <signal.h>
#include <csignal>
using namespace std;
const char* webroot = "/srv/necronda/";
string getMimeType(string path) {
unsigned long pos = path.find_last_of('.');
string ext;
if (pos != string::npos) {
ext = path.substr(pos + 1, path.length() - pos);
}
magic_t magic = magic_open(MAGIC_MIME_TYPE);
magic_load(magic, "/usr/share/misc/magic.mgc");
string type = magic_file(magic, path.c_str());
magic_setflags(magic, MAGIC_MIME_ENCODING);
string charset = magic_file(magic, path.c_str());
if (type == "text/plain") {
if (ext == "css") {
type = "text/css";
} else if (ext == "js") {
type = "text/javascript";
}
}
magic_close(magic);
return type + "; charset=" + charset;
}
/**
* Sun, 06 Nov 1994 08:49:37 GMT
* @return
*/
std::string getTimestamp(string path) {
struct stat attrib;
stat(path.c_str(), &attrib);
return getTimestamp(attrib.st_ctime);
}
std::string getTimestamp(time_t time) {
char buffer[64];
struct tm *timeinfo = gmtime(&time);
strftime(buffer, sizeof(buffer), "%Y%m%d%H%M%S", timeinfo);
return string(buffer);
}
long getFileSize(string filename) {
struct stat stat_buf;
int rc = stat(filename.c_str(), &stat_buf);
return rc == 0 ? stat_buf.st_size : -1;
}
/**
* Returns a formatted time string
* @param micros Delta time to be formatted
* @return A formatted time string
*/
std::string formatTime(long micros) {
char buffer[64];
if (micros < 1000) {
sprintf(buffer, "%.3f ms", micros / 1000.0);
} else if (micros < 10000) {
sprintf(buffer, "%.2f ms", micros / 1000.0);
} else if (micros < 100000) {
sprintf(buffer, "%.1f ms", micros / 1000.0);
} else if (micros < 1000000) {
sprintf(buffer, "%.0f ms", micros / 1000.0);
} else {
sprintf(buffer, "%.1f s", micros / 1000000.0);
}
return std::string(buffer);
}
std::string formatSize(unsigned long bytes) {
char buffer[64];
if (bytes > 0x10000000000) {
sprintf(buffer, "%.1f TiB", (double) bytes / 0x10000000000);
} else if (bytes > 0x40000000) {
sprintf(buffer, "%.1f GiB", (double) bytes / 0x40000000);
} else if (bytes > 0x100000) {
sprintf(buffer, "%.1f MiB", (double) bytes / 0x100000);
} else if (bytes > 0x400) {
sprintf(buffer, "%.1f KiB", (double) bytes / 0x400);
} else {
sprintf(buffer, "%ld B", bytes);
}
return std::string(buffer);
}
string url_decode(string url) {
long pos = 0;
while ((pos = url.find('+', pos + 1)) != string::npos) {
url.replace(pos, 1, 1, ' ');
}
pos = 0;
while ((pos = url.find('%', pos + 1)) != string::npos) {
const char *num = url.substr(pos + 1, 2).c_str();
auto c = (char) strtol(num, nullptr, 16);
url.erase(pos, 3);
url.insert(pos, 1, c);
}
return url;
}
string url_encode(string url) {
char buff[4];
for (long pos = 0; pos < url.length(); pos++) {
auto c = (unsigned char) url[pos];
if (c < ' ' || c > '~' || c == ' ' || c == '#' || c == '?' || c == '&' || c == '=' || c == '\\' || c == '%') {
sprintf(buff, "%%%02X", c);
url.replace(pos, 1, buff);
}
}
return url;
}
string html_decode(string text) {
return text;
}
string html_encode(string text) {
return text;
}
string cli_encode(string text) {
char buff[5];
for (long pos = 0; pos < text.length(); pos++) {
auto c = (unsigned char) text[pos];
if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == ',' || c == '.' || c == '_' || c == '+' || c == ':' || c == '@' || c == '%' || c == '/' || c == '-')) {
sprintf(buff, "\\%.1s", &c);
text.replace(pos, 1, buff);
pos++;
}
}
return text;
}
string read_line(FILE* file) {
char *line;
size_t len = 0;
ssize_t read;
if ((read = getline(&line, &len, file)) < 0 || line == nullptr) {
return "";
}
string l = string(line);
if (l[l.length()-1] == '\n') {
l.erase(l.length()-1);
}
if (l[l.length()-1] == '\r') {
l.erase(l.length()-1);
}
return l;
}
#include "procopen.cpp"
#include "network/Address.cpp"
#include "network/Socket.cpp"
#include "URI.cpp"
#include "network/http/Http.cpp"
#include "network/http/HttpStatusCode.cpp"
#include "network/http/HttpHeader.cpp"
#include "network/http/HttpRequest.cpp"
#include "network/http/HttpResponse.cpp"
#include "network/http/HttpConnection.cpp"
string getWebRoot(string host) {
string root = webroot + host;
if (fileExists(root)) {
return root;
} else {
return "";
}
}
#include "client.cpp"
long clientnum = 0;
int main() {
cout << "Necronda Server 3.0" << endl << "by Lorenz Stechauner" << endl << endl;
signal(SIGPIPE, SIG_IGN);
SSL_load_error_strings();
SSL_library_init();
ERR_load_crypto_strings();
OpenSSL_add_all_algorithms();
int ret = system("mkdir -p /var/necronda /etc/necronda /tmp/necronda; touch /var/necronda/ETags");
if (ret != 0) {
cout << "Unable to create server files" << endl;
exit(1);
}
list<unsigned short> ports = {80, 443};
list<Socket> servers = {};
auto it = ports.begin();
for (int i = 0; i < ports.size(); i++) {
unsigned short port = *it;
advance(it, 1);
Socket server = Socket();
servers.push_back(server);
try {
server.setReuseAddress(true);
server.setReceiveTimeout(0);
server.setSendTimeout(0);
} catch (char *msg) {
cout << "Unable to set socket option: " << msg << endl;
exit(2);
}
try {
server.bind(port);
} catch (char *msg) {
cout << "Unable to bind socket to port " << port << ": " << msg << endl;
exit(3);
}
try {
server.listen(256);
} catch (char *msg) {
cout << "Unable to listen on socket: " << msg << endl;
exit(4);
}
}
cout << "Ready for connections" << endl;
while (true) {
try {
Socket::select(servers, {});
for (Socket server : servers) {
try {
Socket *socket = server.accept();
clientnum++;
thread *t = new thread(client_handler, socket, clientnum, server.getSocketPort() == 443);
} catch (char *msg) {
// Nothing
}
}
} catch (char *msg) {
cout << "Select: " << msg << endl;
break;
}
}
return 0;
}

View File

@ -1,41 +1,80 @@
//
// Created by lorenz on 5/17/18.
//
/**
* Necronda Web Server
* Main executable (header file)
* src/necronda-server.c
* Lorenz Stechauner, 2020-12-03
*/
#include <string>
#ifndef NECRONDA_SERVER_NECRONDA_SERVER_H
#define NECRONDA_SERVER_NECRONDA_SERVER_H
#ifndef NECRONDA_SERVER
#define NECRONDA_SERVER
using namespace std;
unsigned long getMicros();
string formatTime(long micros);
string formatSize(unsigned long bytes);
string getWebRoot(string host);
string getMimeType(string path);
string getTimestamp(string path);
string getTimestamp(time_t time);
long getFileSize(string filename);
string url_decode(string url);
string url_encode(string url);
string html_decode(string text);
string html_encode(string text);
string cli_encode(string text);
string read_line(FILE *file);
#include <stdio.h>
#include <sys/socket.h>
#include <signal.h>
#include <unistd.h>
#include <sys/select.h>
#include <string.h>
#include <errno.h>
#include <arpa/inet.h>
#include <wait.h>
#include <sys/types.h>
#include <stdio.h>
#include <openssl/err.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/conf.h>
#include <openssl/engine.h>
#include <openssl/dh.h>
#define NUM_SOCKETS 2
#define MAX_CHILDREN 1024
#define LISTEN_BACKLOG 16
#define REQ_PER_CONNECTION 100
#define CLIENT_TIMEOUT 3600
#define CHUNK_SIZE 4096
#define CLIENT_MAX_HEADER_SIZE 8192
#define FILE_CACHE_SIZE 1024
#define SHM_KEY 255641
#define ERR_STR "\x1B[1;31m"
#define CLR_STR "\x1B[0m"
#define BLD_STR "\x1B[1m"
#define WRN_STR "\x1B[1;33m"
#define HTTP_STR "\x1B[1;31m"
#define HTTPS_STR "\x1B[1;32m"
#define HTTP_1XX_STR "\x1B[1;32m"
#define HTTP_2XX_STR "\x1B[1;32m"
#define HTTP_3XX_STR "\x1B[1;33m"
#define HTTP_4XX_STR "\x1B[1;31m"
#define HTTP_5XX_STR "\x1B[1;31m"
#define NECRONDA_VERSION "4.0"
#define SERVER_STR "Necronda/" NECRONDA_VERSION
#define NECRONDA_DEFAULT "www.necronda.net"
#define NECRONDA_ZLIB_LEVEL 9
#ifndef MAGIC_FILE
#define MAGIC_FILE "/usr/share/file/misc/magic.mgc"
#endif
#ifndef PHP_FPM_SOCKET
#define PHP_FPM_SOCKET "/var/run/php-fpm/php-fpm.sock"
#endif
int sockets[NUM_SOCKETS];
pid_t children[MAX_CHILDREN];
const char *cert_file, *key_file, *webroot_base, *geoip_file;
typedef struct {
unsigned int enc:1;
int socket;
SSL_CTX *ctx;
SSL *ssl;
} sock;
char *ssl_get_error(SSL *ssl, int ret);
#endif //NECRONDA_SERVER_NECRONDA_SERVER_H

View File

@ -1,68 +0,0 @@
#include <iostream>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
#include "Address.h"
using namespace std;
Address::Address() {
}
Address::Address(string addr) {
// TODO
}
Address::Address(struct sockaddr_in *addr) {
address = ntohl(addr->sin_addr.s_addr);
}
struct sockaddr_in Address::toStruct(unsigned short port)const {
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(address);
addr.sin_port = htons(port);
return addr;
}
string Address::toString() const {
struct sockaddr_in addr = toStruct(0);
struct in_addr ipAddr = addr.sin_addr;
return inet_ntoa(ipAddr);
}
bool Address::isLocal() {
string a = toString();
return a.find("127.0.0.") == 0;
}
ostream& operator<<(ostream &str, const Address &addr) {
return str << addr.toString();
}
string operator+(string &str, const Address &addr) {
return str + addr.toString();
}
string operator+(string &str, const Address *addr) {
return str + addr->toString();
}
string operator+(const Address &addr, string &str) {
return addr.toString() + str;
}
string operator+(const Address *addr, string &str) {
return addr->toString() + str;
}

View File

@ -1,31 +0,0 @@
/**
* Necronda Web Server 3.0
* HttpHeader.h - HttpHeader Class definition
* Lorenz Stechauner, 2018-05-09
*/
#ifndef NECRONDA_ADDRESS
#define NECRONDA_ADDRESS
using namespace std;
class Address {
private:
unsigned int address;
public:
Address();
explicit Address(string address);
explicit Address(struct sockaddr_in *address);
struct sockaddr_in toStruct(unsigned short port) const;
string toString() const;
bool isLocal();
};
#endif

View File

@ -1,661 +0,0 @@
/**
* Necronda Web Server 3.0
* Socket.cpp - Socket Class methods
* Lorenz Stechauner, 2018-05-09
*/
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <cstring>
#include <utility>
#include <unistd.h>
#include <sstream>
#include <ctime>
#include <poll.h>
#include <openssl/ssl.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/x509.h>
#include <list>
#include <sys/stat.h>
#include "Address.h"
#include "Socket.h"
#include "http/Http.h"
using namespace std;
static void multi_ssl_init() {
SSL_load_error_strings();
SSL_library_init();
ERR_load_crypto_strings();
OpenSSL_add_all_algorithms();
}
char *multi_ssl_get_error(SSL *ssl, int ret) {
if (ret > 0) {
return nullptr;
}
unsigned long ret2 = ERR_get_error();
const char *err2 = strerror(errno);
const char *err1 = ERR_reason_error_string(ret2);
switch (SSL_get_error(ssl, ret)) {
case SSL_ERROR_NONE:
return (char *) "none";
case SSL_ERROR_ZERO_RETURN:
return (char *) "closed";
case SSL_ERROR_WANT_READ:
return (char *) "want_read";
case SSL_ERROR_WANT_WRITE:
return (char *) "want_write";
case SSL_ERROR_WANT_CONNECT:
return (char *) "want_connect";
case SSL_ERROR_WANT_ACCEPT:
return (char *) "want_accept";
case SSL_ERROR_WANT_X509_LOOKUP:
return (char *) "want_x509_lookup";
case SSL_ERROR_SYSCALL:
return (char *) ((ret2 == 0) ? (ret == 0) ? "protocol violation" : err2 : err1);
case SSL_ERROR_SSL:
return (char *) err1;
default:
return (char *) "unknown error";
}
}
char *strerror_socket(int nr) {
if (nr == EAGAIN || nr == EWOULDBLOCK) {
return (char *) "timeout";
} else if (nr == ECONNRESET) {
return (char *) "closed";
} else {
return strerror(nr);
}
}
Socket::Socket(int fd) {
this->fd = fd;
microsStart = getMicros();
microsLast = microsStart;
bytesSent = 0;
bytesReceived = 0;
enc = false;
ssl = nullptr;
ctx = nullptr;
clients = false;
servers = false;
}
Socket::Socket() {
fd = ::socket(AF_INET, SOCK_STREAM, 0);
if (fd == 0) {
throw strerror(errno);
}
enc = false;
microsStart = getMicros();
microsLast = microsStart;
bytesSent = 0;
bytesReceived = 0;
ssl = nullptr;
ctx = nullptr;
clients = false;
servers = false;
}
int Socket::getFd() {
return fd;
}
void Socket::setSocketOption(int option, bool value = true) {
int val = value ? 1 : 0;
if (::setsockopt(fd, SOL_SOCKET, option, &val, sizeof(val)) != 0) {
throw strerror(errno);
}
}
void Socket::bind(Address *address, unsigned short port) {
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY; // address.
addr.sin_port = htons(port);
if (::bind(fd, (struct sockaddr *) &addr, sizeof(addr)) != 0) {
throw strerror(errno);
}
}
void Socket::bind(unsigned short port) {
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(port);
if (::bind(fd, (struct sockaddr *) &addr, sizeof(addr)) != 0) {
throw strerror(errno);
}
}
void Socket::listen(int num) {
if (::listen(fd, num) != 0) {
throw strerror(errno);
}
}
void Socket::connect(Address, unsigned short) {
}
Socket* Socket::accept() {
int newfd = ::accept(fd, nullptr, nullptr);
if (newfd < 0) {
throw strerror(errno);
}
Socket *socket = new Socket(newfd);
socket->servers = true;
return socket;
}
void Socket::close() {
if (isSecured()) {
//SSL_shutdown(ssl);
SSL_free(ssl);
SSL_CTX_free(ctx);
}
if (::close(fd) != 0) {
throw strerror(errno);
}
}
Address *Socket::getPeerAddress() const {
struct sockaddr_storage addr;
socklen_t len = sizeof(addr);
getpeername(fd, (struct sockaddr *) &addr, &len);
struct sockaddr_in *s = (struct sockaddr_in *) &addr;
return new Address(s);
}
unsigned short Socket::getPeerPort() const {
struct sockaddr_storage addr;
socklen_t len = sizeof(addr);
getpeername(fd, (struct sockaddr *) &addr, &len);
return ntohs(((struct sockaddr_in *) &addr)->sin_port);
}
Address *Socket::getSocketAddress() const {
struct sockaddr_storage addr;
socklen_t len = sizeof(addr);
getsockname(fd, (struct sockaddr *) &addr, &len);
struct sockaddr_in *s = (struct sockaddr_in *) &addr;
return new Address(s);
}
unsigned short Socket::getSocketPort() const {
struct sockaddr_storage addr;
socklen_t len = sizeof(addr);
getsockname(fd, (struct sockaddr *) &addr, &len);
return ntohs(((struct sockaddr_in *) &addr)->sin_port);
}
void Socket::setReuseAddress(bool value) {
setSocketOption(SO_REUSEADDR, value);
}
void Socket::setReusePort(bool value) {
setSocketOption(SO_REUSEPORT, value);
}
string Socket::toString() const {
return "{[Socket]" + getSocketAddress()->toString() + ":" + to_string(getSocketPort()) + "<->" +
getPeerAddress()->toString() + ":" + to_string(getPeerPort()) + "}";
}
long Socket::send(string *str) {
return send(str->c_str(), str->length());
}
long Socket::send(string str) {
return send(str.c_str(), str.length());
}
long Socket::send(const char *str, long length) {
return send((void*) str, length);
}
long Socket::send(const char *str) {
return send(str, strlen(str));
}
long Socket::send(FILE *file) {
char buffer[CPPNET_CHUNK];
long all_len = 0;
long len = 0;
do {
len = fread(buffer, 1, CPPNET_CHUNK, file);
send(buffer, len);
all_len += len;
} while (len > 0 && len == CPPNET_CHUNK);
return all_len;
}
Socket::~Socket() {
}
long Socket::receive(void *buffer, int size) {
long len;
if (isSecured()) {
len = SSL_read(ssl, buffer, size);
if (len < 0) {
throw multi_ssl_get_error(ssl, (int) len);
}
} else {
len = recv(fd, buffer, (size_t) size, 0);
if (len < 0) {
throw strerror_socket(errno);
}
}
bytesReceived += len;
return len;
}
long Socket::peek(void *buffer, int size) {
long len;
if (isSecured()) {
len = SSL_peek(ssl, buffer, size);
if (len < 0) {
throw multi_ssl_get_error(ssl, (int) len);
}
} else {
len = recv(fd, buffer, (size_t) size, MSG_PEEK);
if (len < 0) {
throw strerror_socket(errno);
}
}
return len;
}
long Socket::send(void *buffer, int size) {
long len;
if (isSecured()) {
if (size != 0) {
len = SSL_write(ssl, buffer, size);
if (len <= 0) {
throw multi_ssl_get_error(ssl, (int) len);
}
} else {
len = 0;
}
} else {
len = ::send(fd, buffer, (size_t) size, 0);
if (len < 0) {
throw strerror_socket(errno);
}
}
bytesSent += len;
return len;
}
string Socket::receive() {
string *str = new string();
char buffer[CPPNET_CHUNK];
long len = 0;
do {
len = receive((void*) buffer, CPPNET_CHUNK);
str->append(buffer, (unsigned) len);
} while (len > 0 && len == CPPNET_CHUNK);
return *str;
}
string Socket::receive(long length) {
string *str = new string();
char buffer[CPPNET_CHUNK];
long len = 0;
long reclen = 0;
do {
len = receive((void*) buffer, CPPNET_CHUNK);
reclen += len;
str->append(buffer, (unsigned) len);
} while (reclen < length);
return *str;
}
string Socket::receive(string until) {
string *str = new string();
struct pollfd ufds[1];
ufds[0].fd = fd;
ufds[0].events = POLLIN | POLLOUT;
char buffer[CPPNET_CHUNK];
long len = 0;
do {
len = peek((void*) buffer, CPPNET_CHUNK);
if (len != 0) {
string s = string(buffer, (size_t) len);
size_t found = s.find(until);
long l = (found != string::npos) ? found + 1 : len;
long l2 = (found != string::npos) ? found : len;
str->append(buffer, (unsigned) l2);
receive((void *) buffer, (int) l);
if (found != string::npos) {
break;
}
}
if (poll(ufds, 1, 0) < 0) {
throw strerror_socket(errno);
} else if ((ufds[0].revents & POLLIN) == 0) {
if ((ufds[0].revents & POLLOUT) != 0) {
throw (char *) "error";
} else {
throw (char *) "want_write";
}
} else if ((ufds[0].revents & POLLERR) != 0) {
throw (char *) "error";
} else if (ufds[0].revents & (POLLRDHUP | POLLHUP | POLLNVAL) != 0) {
throw (char *) "closed";
}
} while (true);
return *str;
}
string Socket::receive(const char *until) {
return receive(until, (int) (strlen(until)));
}
string Socket::receive(const char *until, unsigned long strlen) {
return receive(string(until, strlen));
}
long Socket::receive(FILE *file) {
char buffer[CPPNET_CHUNK];
long len;
long rec = 0;
do {
len = receive((void*) buffer, CPPNET_CHUNK);
fwrite(buffer, 1, CPPNET_CHUNK, file);
rec += len;
} while (len > 0 && len == CPPNET_CHUNK);
return len;
}
long Socket::receive(FILE *file, long size) {
char buffer[CPPNET_CHUNK];
long len = 0;
long rec = 0;
while (size > rec) {
len = receive((void*) buffer, (CPPNET_CHUNK > (size - rec) && size >= 0)?(size - rec):CPPNET_CHUNK);
fwrite(buffer, 1, len, file);
rec += len;
}
return rec;
}
string Socket::receiveLine() {
string str = receive("\n");
if (str.length() > 0 && str.at(str.length() - 1) == '\r') {
str = str.substr(0, str.length() - 1);
}
return str;
}
long Socket::getDuration() {
return getMicros() - microsStart;
}
void Socket::setReceiveTimeout(unsigned long ms) {
struct timeval timeout;
if (ms == 0) {
timeout.tv_sec = 0;
timeout.tv_usec = 1;
} else {
timeout.tv_sec = ms / 1000;
timeout.tv_usec = (ms % 1000) * 1000;
}
if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char *) &timeout, sizeof(timeout)) < 0) {
throw strerror(errno);
}
}
void Socket::setSendTimeout(unsigned long ms) {
struct timeval timeout;
if (ms == 0) {
timeout.tv_sec = 0;
timeout.tv_usec = 1;
} else {
timeout.tv_sec = ms / 1000;
timeout.tv_usec = (ms % 1000) * 1000;
}
if (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (char *) &timeout, sizeof(timeout)) < 0) {
throw strerror(errno);
}
}
bool Socket::isServerSide() {
return servers;
}
bool Socket::isSecured() {
return enc;
}
bool Socket::isClientSide() {
return clients;
}
void Socket::sslHandshake(map<string, KeyPair> sni) {
/*if (isSecured()) {
throw (char *) "Socket already secured";
}
const SSL_METHOD *method;
if (isServerSide()) {
method = TLSv1_2_server_method();
} else if (isClientSide()) {
method = TLSv1_2_client_method();
} else {
method = TLSv1_2_method();
}
SSL_CTX *ctx = SSL_CTX_new(method);
SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE);
SSL_CTX_set_cipher_list(ctx, "HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4");
SSL_CTX_set_ecdh_auto(ctx, 1);
const char *certfile = keypair.fullchain.c_str();
const char *keyfile = keypair.privkey.c_str();
if (isServerSide()) {
if (SSL_CTX_use_certificate_file(ctx, certfile, SSL_FILETYPE_PEM) != 1) {
throw (char *) ERR_reason_error_string(ERR_get_error());
}
if (SSL_CTX_use_PrivateKey_file(ctx, keyfile, SSL_FILETYPE_PEM) != 1) {
throw (char *) ERR_reason_error_string(ERR_get_error());
}
}
SSL_CTX_set_tlsext_servername_callback
this->ctx = ctx;
this->ssl = SSL_new(ctx);
SSL_set_fd(ssl, fd);
enc = true;
while (true) {
int ret = 0;
if (isServerSide()) {
ret = SSL_accept(ssl);
} else if (isClientSide()) {
ret = SSL_connect(ssl);
} else {
ret = SSL_do_handshake(ssl);
}
if (ret <= 0 && ((isServerSide() && SSL_get_error(ssl, ret) != SSL_ERROR_WANT_READ) ||
(isClientSide() && SSL_get_error(ssl, ret) != SSL_ERROR_WANT_WRITE))) {
throw multi_ssl_get_error(ssl, ret);
} else if (ret == 1) {
break;
}
}*/
}
void Socket::sslHandshake() {
sslHandshake(KeyPair{"", ""});
}
void Socket::sslHandshake(KeyPair keypair) {
if (isSecured()) {
throw (char *) "Socket already secured";
}
const SSL_METHOD *method;
if (isServerSide()) {
method = TLSv1_2_server_method();
} else if (isClientSide()) {
method = TLSv1_2_client_method();
} else {
method = TLSv1_2_method();
}
SSL_CTX *ctx = SSL_CTX_new(method);
SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE);
SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, nullptr);
SSL_CTX_set_min_proto_version(ctx, SSL3_VERSION); // TLS1_VERSION
SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE);
SSL_CTX_set_cipher_list(ctx, "HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4");
SSL_CTX_set_ecdh_auto(ctx, 1);
const char *certfile = keypair.fullchain.c_str();
const char *keyfile = keypair.privkey.c_str();
if (isServerSide()) {
if (SSL_CTX_use_certificate_chain_file(ctx, certfile) != 1) {
throw (char *) ERR_reason_error_string(ERR_get_error());
}
if (SSL_CTX_use_PrivateKey_file(ctx, keyfile, SSL_FILETYPE_PEM) != 1) {
throw (char *) ERR_reason_error_string(ERR_get_error());
}
}
this->ctx = ctx;
this->ssl = SSL_new(ctx);
SSL_set_fd(ssl, fd);
enc = true;
while (true) {
int ret = 0;
if (isServerSide()) {
ret = SSL_accept(ssl);
} else if (isClientSide()) {
ret = SSL_connect(ssl);
} else {
ret = SSL_do_handshake(ssl);
}
if (ret <= 0 && ((isServerSide() && SSL_get_error(ssl, ret) != SSL_ERROR_WANT_READ) ||
(isClientSide() && SSL_get_error(ssl, ret) != SSL_ERROR_WANT_WRITE))) {
throw multi_ssl_get_error(ssl, ret);
} else if (ret == 1) {
break;
}
}
}
void Socket::sslHandshake(string privkey, string fullchain) {
sslHandshake(KeyPair{std::move(privkey), std::move(fullchain)});
}
long Socket::select(list<Socket> read, list<Socket> write, long millis) {
fd_set readfd, writefd;
int maxfd = 0;
FD_ZERO(&readfd);
FD_ZERO(&writefd);
for (Socket s : read) {
if (s.fd > maxfd) {
maxfd = s.fd;
}
FD_SET(s.fd, &readfd);
}
for (Socket s : write) {
if (s.fd > maxfd) {
maxfd = s.fd;
}
FD_SET(s.fd, &writefd);
}
struct timeval *tv = new struct timeval;
if (millis < 0) {
tv = nullptr;
} else if (millis == 0) {
tv->tv_sec = 0;
tv->tv_usec = 1;
} else {
tv->tv_sec = millis / 1000;
tv->tv_usec = (millis % 1000) * 1000;
}
int ret = ::select(maxfd + 1, &readfd, &writefd, nullptr, tv);
if (ret < 0) {
throw (char *) strerror(errno);
}
return ret;
}
long Socket::select(list<Socket> read, list<Socket> write) {
Socket::select(std::move(read), std::move(write), -1);
}
unsigned long Socket::getBytesSent() {
return bytesSent;
}
unsigned long Socket::getBytesReceived() {
return bytesReceived;
}
ostream &operator<<(ostream &str, const Socket &socket) {
return str << socket.toString();
}
ostream &operator<<(ostream &str, const Socket *socket) {
return str << socket->toString();
}
string operator+(string &str, const Socket &socket) {
return str + socket.toString();
}
string operator+(const Socket &socket, string &str) {
return socket.toString() + str;
}

View File

@ -1,174 +0,0 @@
/**
* Necronda Web Server 3.0
* Socket.h - Socket Class definition
* Lorenz Stechauner, 2018-05-09
*/
#ifndef NECRONDA_SOCKET
#define NECRONDA_SOCKET
#include <map>
#define CPPNET_CHUNK 16384
typedef struct {
string privkey;
string fullchain;
} KeyPair;
using namespace std;
class Socket {
private:
int fd;
SSL *ssl;
SSL_CTX *ctx;
bool enc;
bool servers;
bool clients;
unsigned long bytesSent;
unsigned long bytesReceived;
long microsStart;
long microsLast;
void setSocketOption(int, bool);
long send(void *buffer, int size);
long receive(void *buffer, int size);
long peek(void *buffer, int size);
public:
Socket();
explicit Socket(int filedescriptor);
~Socket();
void bind(Address *address, unsigned short port);
void bind(unsigned short port);
void listen(int count = 1);
void connect(Address address, unsigned short port);
Socket* accept();
void sslHandshake();
void sslHandshake(map<string, KeyPair> sni);
void sslHandshake(KeyPair keypair);
void sslHandshake(string privkey, string fullchain);
long send(string *str);
long send(string str);
long send(const char *str);
long send(const char *str, long length);
long send(FILE *file);
string receive();
string receive(long length);
string receive(string until);
string receive(const char *until, unsigned long strlen);
string receive(const char *until);
long receive(FILE *file);
string receiveLine();
void shutdown();
void close();
int getFd();
long getDuration();
Address *getSocketAddress() const;
unsigned short getSocketPort() const;
Address *getPeerAddress() const;
unsigned short getPeerPort() const;
string toString() const;
bool isServerSide();
bool isClientSide();
bool isSecured();
void setReuseAddress(bool value = true);
void setReusePort(bool value = true);
void setSendBufferSize(int value);
void setReceiveBufferSize(int value);
void setMinReceiveBytes(int value);
void setMinSendBytes(int value);
void setSendTimeout(unsigned long ms);
void setReceiveTimeout(unsigned long ms);
bool getReuseAddress();
bool getReusePort();
int getSendBufferSize();
int getReceiveBufferSize();
int getMinReceiveBytes();
int getMinSendBytes();
long getSendTimeout();
long getReceiveTimeout();
unsigned long getBytesSent();
unsigned long getBytesReceived();
static long select(list<Socket> read, list<Socket> write, long millis);
static long select(list<Socket> read, list<Socket> write);
long receive(FILE *file, long size);
};
Socket operator<<(Socket sock, const char *str);
Socket operator<<(Socket sock, string str);
ostream &operator<<(ostream &str, const Socket &socket);
ostream &operator<<(ostream &str, const Socket *socket);
string operator+(string &str, const Socket &socket);
string operator+(const Socket &socket, string &str);
#endif

View File

@ -1,32 +0,0 @@
//
// Created by lorenz on 7/10/18.
//
#include <sys/stat.h>
#include <sys/time.h>
#include "Http.h"
unsigned long getMicros() {
struct timeval tv;
gettimeofday(&tv, nullptr);
return (unsigned long) (1000000 * tv.tv_sec + tv.tv_usec);
}
string getHttpDate() {
time_t rawtime;
time(&rawtime);
return getHttpDate(rawtime);
}
string getHttpDate(string filename) {
struct stat attrib;
stat(filename.c_str(), &attrib);
return getHttpDate(attrib.st_ctime);
}
string getHttpDate(time_t time) {
char buffer[64];
struct tm *timeinfo = gmtime(&time);
strftime(buffer, sizeof(buffer), "%a, %d %b %Y %H:%M:%S GMT", timeinfo);
return string(buffer);
}

View File

@ -1,22 +0,0 @@
//
// Created by lorenz on 7/10/18.
//
#ifndef CPPNET_HTTP_H
#define CPPNET_HTTP_H
#include <ctime>
#include <string>
using namespace std;
unsigned long getMicros();
string getHttpDate(time_t time);
string getHttpDate();
string getHttpDate(string filename);
#endif //CPPNET_HTTP_H

View File

@ -1,194 +0,0 @@
#include <zlib.h>
#include <cassert>
#include <iostream>
#include <utility>
#include "HttpConnection.h"
#include "../Socket.h"
#include "HttpStatusCode.h"
#include "Http.h"
HttpConnection::HttpConnection() = default;
HttpConnection::HttpConnection(Socket *socket) {
this->socket = socket;
this->request = new HttpRequest(socket);
this->response = new HttpResponse();
microsStart = getMicros();
response->setVersion("1.1");
response->setField("Server", "Necronda/3.0");
}
void HttpConnection::respond(int statuscode) {
if (statuscode >= 400 && statuscode < 600) {
respond(statuscode,
"<!DOCTYPE html><html><head><title>" + to_string(statuscode) + " " +
::getStatusCode(statuscode).message +
"</title></head><body><center><h1>" + to_string(statuscode) + " " +
::getStatusCode(statuscode).message +
"</h1>" +
((request->isExistingField("Host")) ?
(request->isExistingField("Referer") &&
request->getField("Referer").find(request->getField("Host")) != string::npos) ?
"<p>Go back to the last page you visited: <a href=\"" + request->getField("Referer") + "\">" +
request->getField("Referer") + "</a></p>" :
"<p>Go back to the home page of <a href=\"//" +
request->getField("Host") + "/\">" +
request->getField("Host") +
"</a></p>" : "") + "</center></body></html>\r\n"
);
} else {
respond(statuscode, "");
}
}
void HttpConnection::respond(int statuscode, string payload) {
response->setStatusCode(statuscode);
response->setField("Date", getHttpDate());
response->setField("Content-Length", to_string(payload.length()));
response->sendHeader(socket);
socket->send(std::move(payload));
}
void HttpConnection::respond(int statuscode, FILE *file, bool compress, long start, long end) {
response->setStatusCode(statuscode);
response->setField("Transfer-Encoding", "chunked");
response->setField("Date", getHttpDate());
long shouldTransfer;
long transfered = 0;
fseek(file, 0, SEEK_END);
long len = ftell(file);
if (start != -1 && end != -1) {
fseek(file, start, SEEK_SET);
response->setField("Content-Length", to_string(end - start + 1));
shouldTransfer = end - start + 1;
compress = false;
} else {
fseek(file, 0, SEEK_SET);
shouldTransfer = len;
if (len >= 0 && !compress) {
response->setField("Content-Length", to_string(len));
}
}
if (compress) {
response->setField("Content-Encoding", "deflate");
}
response->sendHeader(socket);
if (compress) {
int level = 1;
int ret, flush;
unsigned have;
z_stream strm;
unsigned char in[CPPNET_CHUNK];
unsigned char out[CPPNET_CHUNK];
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
ret = deflateInit(&strm, level);
if (ret != Z_OK) {
throw (char *) "Unable to open file";
}
do {
strm.avail_in = (uInt) fread(in, 1, CPPNET_CHUNK, file);
if (ferror(file)) {
(void) deflateEnd(&strm);
throw (char *) strerror(errno);
}
flush = feof(file) ? Z_FINISH : Z_NO_FLUSH;
strm.next_in = in;
do {
strm.avail_out = CPPNET_CHUNK;
strm.next_out = out;
ret = deflate(&strm, flush);
assert(ret != Z_STREAM_ERROR);
have = CPPNET_CHUNK - strm.avail_out;
if (have != 0) {
char buffer[64];
sprintf(buffer, "%X\r\n", have);
socket->send(buffer);
socket->send((const char *) out, have);
socket->send("\r\n");
}
} while (strm.avail_out == 0);
assert(strm.avail_in == 0);
} while (flush != Z_FINISH);
assert(ret == Z_STREAM_END);
socket->send("0\r\n\r\n");
deflateEnd(&strm);
} else {
char buffer[CPPNET_CHUNK];
char buff[64];
while (true) {
unsigned long size = fread(buffer, 1, (size_t) ((CPPNET_CHUNK > (shouldTransfer - transfered) && shouldTransfer > 0) ? (shouldTransfer - transfered) : CPPNET_CHUNK), file);
transfered += size;
sprintf(buff, "%lX\r\n", size);
socket->send(buff);
socket->send((const char *) buffer, size);
socket->send("\r\n");
if (size == 0) {
break;
}
}
}
}
string HttpConnection::getField(string index) {
return request->getField(std::move(index));
}
string HttpConnection::getPath() {
return request->getPath();
}
void HttpConnection::setField(string index, string data) {
response->setField(std::move(index), std::move(data));
}
bool HttpConnection::isExistingField(string index) {
return request->isExistingField(std::move(index));
}
string HttpConnection::getMethod() {
return request->getMethod();
}
long HttpConnection::getDuration() {
return getMicros() - microsStart;
}
HttpStatusCode HttpConnection::getStatusCode() {
return response->getStatusCode();
}
void HttpConnection::redirect(int statuscode, string location) {
setField("Location", std::move(location));
respond(statuscode, "");
}
string HttpConnection::getResponseField(string index) {
return response->getField(std::move(index));
}
bool HttpConnection::isExistingResponseField(string index) {
return response->isExistingField(std::move(index));
}
string HttpConnection::cgiExport() {
return request->cgiExport();
}
void HttpConnection::removeField(string index) {
response->removeField(std::move(index));
}

View File

@ -1,55 +0,0 @@
#ifndef NECRONDA_HTTP_CONNECTION
#define NECRONDA_HTTP_CONNECTION
#include "../Socket.h"
#include "HttpResponse.h"
#include "HttpRequest.h"
class HttpConnection {
private:
Socket *socket{};
HttpRequest *request{};
HttpResponse *response{};
long microsStart{};
public:
explicit HttpConnection();
explicit HttpConnection(Socket *socket);
void respond(int statuscode);
void respond(int statuscode, string payload);
void respond(int statuscode, FILE *file, bool compress = false, long start = -1, long end = -1);
void redirect(int statuscode, string location);
bool isExistingField(string index);
bool isExistingResponseField(string index);
string getField(string index);
string getResponseField(string index);
string getPath();
string getMethod();
void setField(string index, string data);
long getDuration();
HttpStatusCode getStatusCode();
string cgiExport();
void removeField(string index);
};
#endif

View File

@ -1,122 +0,0 @@
/**
* Necronda Web Server 3.0
* HttpHeader.cpp - HttpHeader Class methods
* Lorenz Stechauner, 2018-05-09
*/
#include <map>
#include <iostream>
#include "../Socket.h"
#include "HttpHeader.h"
using namespace std;
string to_cgi(string text) {
for (auto & c: text) c = (char) toupper(c);
long pos = 0;
while ((pos = text.find('-', pos + 1)) != string::npos) {
text.replace(pos, 1, 1, '_');
}
return text;
}
/**
* Default Constructor
*/
HttpHeader::HttpHeader() {
fields = fields;
}
HttpHeader::HttpHeader(Socket *socket) : HttpHeader::HttpHeader() {
parse(socket);
}
void HttpHeader::parse(Socket *socket) {
while (true) {
string line = socket->receiveLine();
if (line.length() == 0) {
break;
} else {
unsigned long pos = line.find(':');
if (pos == string::npos) {
throw (char *) "Malformed header";
}
string index = line.substr(0, pos);
string data = line.substr(pos + 1, line.length() - pos);
while (index[0] == ' ') index.erase(index.begin() + 0);
while (index[index.length() - 1] == ' ') index.erase(index.end() - 1);
while (data[0] == ' ') data.erase(data.begin() + 0);
while (data[data.length() - 1] == ' ') data.erase(data.end() - 1);
setField(index, data);
}
}
}
/**
* Default Destructor
*/
HttpHeader::~HttpHeader() {
fields.clear();
}
/**
* Sets a field in the HTTP header
* e.g. Content-Length: 42
* @param index The field index
* @param data The field data
*/
void HttpHeader::setField(string index, string data) {
removeField(index);
fields.insert(make_pair(index, data));
}
void HttpHeader::removeField(string index) {
fields.erase(index);
}
/**
* Gets a field from the HTTP header
* e.g. Content-Length: 42
* @param index The field index
* @return The field data
*/
string HttpHeader::getField(string index) {
auto i = fields.find(index);
if (i != fields.end()) {
return fields.at(index);
} else {
return "";
}
}
bool HttpHeader::isExistingField(string index) {
auto i = fields.find(index);
return i != fields.end();
}
string HttpHeader::toString() {
string header = "";
for (auto it = fields.begin(); it != fields.end(); it++ ) {
header += it->first + ": " + it->second + "\r\n";
}
return header;
}
string HttpHeader::cgiExport() {
string header = "";
for (auto it = fields.begin(); it != fields.end(); it++ ) {
header += "HTTP_" + to_cgi(it->first) + "=" + cli_encode(it->second) + " ";
}
return header;
}

View File

@ -1,53 +0,0 @@
/**
* Necronda Web Server 3.0
* HttpHeader.h - HttpHeader Class definition
* Lorenz Stechauner, 2018-05-09
*/
#ifndef NECRONDA_HTTP_HEADER
#define NECRONDA_HTTP_HEADER
#include <cstring>
using namespace std;
struct comp {
bool operator()(const std::string& lhs, const std::string& rhs) const {
return strcasecmp(lhs.c_str(), rhs.c_str()) < 0;
}
};
/**
* Stores Key-Value Pairs for a HTTP header
* e.g.
* Content-Length: 64
* Host: example.org
*/
class HttpHeader {
private:
map<string, string, comp> fields;
public:
HttpHeader();
explicit HttpHeader(Socket *socket);
~HttpHeader();
void setField(string index, string data);
string getField(string index);
void removeField(string index);
bool isExistingField(string index);
void parse(Socket *socket);
string toString();
string cgiExport();
};
#endif

View File

@ -1,107 +0,0 @@
#include <string>
#include <utility>
#include <iostream>
#include "../Socket.h"
#include "HttpHeader.h"
#include "HttpRequest.h"
HttpRequest::HttpRequest() {
this->header = HttpHeader();
}
HttpRequest::HttpRequest(Socket *socket) : HttpRequest::HttpRequest() {
parseHeader(socket);
}
HttpRequest::HttpRequest(string method, string path, string version) : HttpRequest::HttpRequest() {
this->method = std::move(method);
this->path = std::move(path);
this->version = std::move(version);
}
void HttpRequest::parseHeader(Socket *socket) {
string line = socket->receiveLine();
unsigned long pos1 = line.find(' ');
unsigned long pos2;
bool invalid = false;
if (pos1 != string::npos) {
pos2 = line.find(' ', pos1 + 1);
if (pos2 != string::npos) {
method = line.substr(0, pos1);
for (auto &c: method) c = (char) toupper(c);
path = line.substr(pos1 + 1, pos2 - pos1 - 1);
version = line.substr(pos2 + 6, 3);
} else {
invalid = true;
}
} else {
pos2 = string::npos;
invalid = true;
}
if (!invalid && (line.substr(pos2 + 1, 5) != "HTTP/" || version[1] != '.' || path[0] != '/' || !(version[0] >= '0' && version[0] <= '9') || !(version[2] >= '0' && version[2] <= '9'))) {
invalid = true;
}
if (invalid) {
method = "";
path = "";
version = "";
throw (char *) "Malformed header";
}
header.parse(socket);
}
string HttpRequest::getMethod() {
return method;
}
string HttpRequest::getPath() {
return path;
}
string HttpRequest::getVersion() {
return version;
}
void HttpRequest::setMethod(string method) {
this->method = std::move(method);
}
void HttpRequest::setPath(string path) {
this->path = std::move(path);
}
void HttpRequest::setVersion(string version) {
this->version = std::move(version);
}
string HttpRequest::getField(string index) {
return header.getField(std::move(index));
}
void HttpRequest::setField(string index, string data) {
header.setField(std::move(index), std::move(data));
}
bool HttpRequest::isExistingField(string index) {
return header.isExistingField(std::move(index));
}
string HttpRequest::cgiExport() {
return header.cgiExport();
}

View File

@ -1,52 +0,0 @@
/**
* Necronda Web Server 3.0
* HttpHeader.h - HttpHeader Class definition
* Lorenz Stechauner, 2018-05-09
*/
#ifndef NECRONDA_HTTP_REQUEST
#define NECRONDA_HTTP_REQUEST
using namespace std;
class HttpRequest {
private:
HttpHeader header;
string method;
string path;
string version;
public:
HttpRequest();
explicit HttpRequest(Socket *socket);
HttpRequest(string method, string path, string version = "1.1");
void parseHeader(Socket *socket);
void sendHeader(Socket *socket);
string getField(string index);
void setField(string index, string data);
bool isExistingField(string index);
string getMethod();
string getPath();
string getVersion();
void setMethod(string method);
void setPath(string path);
void setVersion(string version);
string cgiExport();
};
#endif

View File

@ -1,70 +0,0 @@
//
// Created by lorenz on 5/17/18.
//
#include "HttpResponse.h"
#include <utility>
#include <iostream>
#include "HttpStatusCode.h"
HttpResponse::HttpResponse() {
this->header = HttpHeader();
}
HttpResponse::HttpResponse(Socket *socket) : HttpResponse::HttpResponse() {
this->parseHeader(socket);
}
HttpResponse::HttpResponse(int statuscode, string version) : HttpResponse::HttpResponse(::getStatusCode(statuscode), std::move(version)) {
}
HttpResponse::HttpResponse(HttpStatusCode statuscode, string version) : HttpResponse::HttpResponse() {
this->statuscode = statuscode;
this->version = std::move(version);
}
void HttpResponse::sendHeader(Socket *socket) {
socket->send("HTTP/" + version + " " + to_string(statuscode.code) + " " + statuscode.message + "\r\n" +
header.toString() + "\r\n");
}
string HttpResponse::getField(string index) {
return header.getField(std::move(index));
}
void HttpResponse::setField(string index, string data) {
header.setField(std::move(index), std::move(data));
}
bool HttpResponse::isExistingField(string index) {
return header.isExistingField(std::move(index));
}
HttpStatusCode HttpResponse::getStatusCode() {
return statuscode;
}
string HttpResponse::getVersion() {
return version;
}
void HttpResponse::setStatusCode(HttpStatusCode statuscode) {
this->statuscode = statuscode;
}
void HttpResponse::setStatusCode(int statuscode) {
this->statuscode = ::getStatusCode(statuscode);
}
void HttpResponse::setVersion(string version) {
this->version = std::move(version);
}
void HttpResponse::parseHeader(Socket *socket) {
}
void HttpResponse::removeField(string index) {
header.removeField(std::move(index));
}

View File

@ -1,50 +0,0 @@
#ifndef NECRONDA_HTTP_RESPONSE
#define NECRONDA_HTTP_RESPONSE
#include <string>
#include "HttpHeader.h"
#include "HttpStatusCode.h"
#include "../Socket.h"
class HttpResponse {
private:
HttpHeader header;
HttpStatusCode statuscode;
string version;
public:
HttpResponse();
explicit HttpResponse(Socket *socket);
explicit HttpResponse(int statuscode, string version = "1.1");
explicit HttpResponse(HttpStatusCode statuscode, string version = "1.1");
void parseHeader(Socket *socket);
void sendHeader(Socket *socket);
string getField(string index);
void setField(string index, string data);
bool isExistingField(string index);
HttpStatusCode getStatusCode();
string getVersion();
void setStatusCode(HttpStatusCode statuscode);
void setStatusCode(int statuscode);
void setVersion(string version);
void removeField(string index);
};
#endif

View File

@ -1,66 +0,0 @@
#include "HttpStatusCode.h"
/**
* Necronda Web Server 3.0
* HttpStatusCode.cpp - HTTP Status Code definition
* Lorenz Stechauner, 2018-05-16
* Reference: https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
*/
HttpStatusCode httpStatusCodes[] = {
HttpStatusCode{100, "Informational", "Continue", ""},
HttpStatusCode{101, "Informational", "Switching Protocols", ""},
HttpStatusCode{200, "Success", "OK", ""},
HttpStatusCode{201, "Success", "Created", ""},
HttpStatusCode{202, "Success", "Accepted", ""},
HttpStatusCode{203, "Success", "Non-Authoritative Information", ""},
HttpStatusCode{204, "Success", "No Content", ""},
HttpStatusCode{205, "Success", "Reset Content", ""},
HttpStatusCode{206, "Success", "Partial Content", ""},
HttpStatusCode{300, "Redirection", "Multiple Choices", ""},
HttpStatusCode{301, "Redirection", "Moved Permanently", ""},
HttpStatusCode{302, "Redirection", "Found", ""},
HttpStatusCode{303, "Redirection", "See Other", ""},
HttpStatusCode{304, "Redirection", "Not Modified", ""},
HttpStatusCode{305, "Redirection", "Use Proxy", ""},
HttpStatusCode{307, "Redirection", "Temporary Redirect", ""},
HttpStatusCode{308, "Redirection", "Permanent Redirect", ""},
HttpStatusCode{400, "Client Error", "Bad Request", ""},
HttpStatusCode{401, "Client Error", "Unauthorized", ""},
HttpStatusCode{402, "Client Error", "Payment Required", ""},
HttpStatusCode{403, "Client Error", "Forbidden", ""},
HttpStatusCode{404, "Client Error", "Not Found", ""},
HttpStatusCode{405, "Client Error", "Method Not Allowed", ""},
HttpStatusCode{406, "Client Error", "Not Acceptable", ""},
HttpStatusCode{407, "Client Error", "Proxy Authentication Required", ""},
HttpStatusCode{408, "Client Error", "Request Timeout", ""},
HttpStatusCode{409, "Client Error", "Conflict", ""},
HttpStatusCode{410, "Client Error", "Gone", ""},
HttpStatusCode{411, "Client Error", "Length Required", ""},
HttpStatusCode{412, "Client Error", "Precondition Failed", ""},
HttpStatusCode{413, "Client Error", "Request Entity Too Large", ""},
HttpStatusCode{414, "Client Error", "Request-URI Too Long", ""},
HttpStatusCode{415, "Client Error", "Unsupported Media Type", ""},
HttpStatusCode{416, "Client Error", "Requested Range Not Satisfiable", ""},
HttpStatusCode{417, "Client Error", "Expectation Failed", ""},
HttpStatusCode{500, "Server Error", "Internal Server Error", ""},
HttpStatusCode{501, "Server Error", "Not Implemented", ""},
HttpStatusCode{502, "Server Error", "Bad Gateway", ""},
HttpStatusCode{503, "Server Error", "Service Unavailable", ""},
HttpStatusCode{504, "Server Error", "Gateway Timeout", ""},
HttpStatusCode{505, "Server Error", "HTTP Version Not Supported", ""},
};
HttpStatusCode getStatusCode(int statuscode) {
for (HttpStatusCode sc : httpStatusCodes) {
if (sc.code == statuscode) {
return sc;
}
}
throw (char *) "Invalid status code";
}

View File

@ -1,15 +0,0 @@
#ifndef NECRONDA_HTTP_STATUSCODE
#define NECRONDA_HTTP_STATUSCODE
typedef struct {
short code; // The status code (e.g. 200)
const char *type; // The status type type (e.g Success)
const char *message; // The status code message (e.g. OK)
const char *description; // The status code description (currently not used)
} HttpStatusCode;
HttpStatusCode getStatusCode(int statuscode);
#endif

View File

@ -1,41 +0,0 @@
//
// Created by lorenz on 5/30/18.
//
#include "procopen.h"
stds procopen(const char* command) {
int pipes[3][2];
pipe(pipes[PARENT_READ_PIPE]);
pipe(pipes[PARENT_WRITE_PIPE]);
pipe(pipes[PARENT_ERROR_PIPE]);
int pid = fork();
if(pid == 0) {
dup2(CHILD_READ_FD, STDIN_FILENO);
dup2(CHILD_WRITE_FD, STDOUT_FILENO);
dup2(CHILD_ERROR_FD, STDERR_FILENO);
close(CHILD_READ_FD);
close(CHILD_WRITE_FD);
close(CHILD_ERROR_FD);
close(PARENT_READ_FD);
close(PARENT_WRITE_FD);
close(PARENT_ERROR_FD);
system(command);
exit(0);
} else {
close(CHILD_READ_FD);
close(CHILD_WRITE_FD);
close(CHILD_ERROR_FD);
return stds{fdopen(PARENT_WRITE_FD, "w"), fdopen(PARENT_READ_FD, "r"), fdopen(PARENT_ERROR_FD, "r"), (pid_t) pid};
}
}

View File

@ -1,41 +0,0 @@
//
// Created by lorenz on 5/30/18.
//
#include <zconf.h>
#include <cstdlib>
#include <cstdio>
#ifndef NECRONDA_PROCOPEN
#define NECRONDA_PROCOPEN
#define PARENT_WRITE_PIPE 0
#define PARENT_READ_PIPE 1
#define PARENT_ERROR_PIPE 2
#define READ_FD 0
#define WRITE_FD 1
#define PARENT_READ_FD ( pipes[PARENT_READ_PIPE][READ_FD] )
#define PARENT_WRITE_FD ( pipes[PARENT_WRITE_PIPE][WRITE_FD] )
#define PARENT_ERROR_FD ( pipes[PARENT_ERROR_PIPE][READ_FD] )
#define CHILD_READ_FD ( pipes[PARENT_WRITE_PIPE][READ_FD] )
#define CHILD_WRITE_FD ( pipes[PARENT_READ_PIPE][WRITE_FD] )
#define CHILD_ERROR_FD ( pipes[PARENT_ERROR_PIPE][WRITE_FD] )
typedef struct {
FILE* stdin;
FILE* stdout;
FILE* stderr;
pid_t pid;
} stds;
stds procopen(const char* command);
#endif

176
src/uri.c Normal file
View File

@ -0,0 +1,176 @@
/**
* Necronda Web Server
* URI and path handlers
* src/uri.c
* Lorenz Stechauner, 2020-12-13
*/
#include "uri.h"
int path_is_directory(const char *path) {
struct stat statbuf;
return stat(path, &statbuf) == 0 && S_ISDIR(statbuf.st_mode) != 0;
}
int path_is_file(const char *path) {
struct stat statbuf;
return stat(path, &statbuf) == 0 && S_ISDIR(statbuf.st_mode) == 0;
}
int path_exists(const char *path) {
struct stat statbuf;
return stat(path, &statbuf) == 0;
}
int uri_init(http_uri *uri, const char *webroot, const char *uri_str, int dir_mode) {
char buf0[1024];
char buf1[1024];
char buf2[1024];
char buf3[1024];
uri->webroot = NULL;
uri->req_path = NULL;
uri->path = NULL;
uri->pathinfo = NULL;
uri->query = NULL;
uri->filename = NULL;
uri->uri = NULL;
uri->meta = NULL;
uri->is_static = 1;
uri->is_dir = 0;
if (uri_str[0] != '/') {
return 1;
}
uri->webroot = malloc(strlen(webroot) + 1);
strcpy(uri->webroot, webroot);
char *query = strchr(uri_str, '?');
if (query == NULL) {
uri->query = NULL;
} else {
query[0] = 0;
query++;
ssize_t size = strlen(query) + 1;
uri->query = malloc(size);
strcpy(uri->query, query);
}
ssize_t size = strlen(uri_str) + 1;
uri->req_path = malloc(size);
url_decode(uri_str, uri->req_path, &size);
if (query != NULL) {
query[-1] = '?';
}
if (strstr(uri->req_path, "/../") != NULL || strstr(uri->req_path, "/./") != NULL) {
return 2;
}
size = strlen(uri->req_path) + 1;
uri->path = malloc(size);
uri->pathinfo = malloc(size);
strcpy(uri->path, uri->req_path);
if (uri->path[strlen(uri->path) - 1] == '/') {
uri->path[strlen(uri->path) - 1] = 0;
strcpy(uri->pathinfo, "/");
} else {
strcpy(uri->pathinfo, "");
}
while (1) {
sprintf(buf0, "%s%s", uri->webroot, uri->path);
sprintf(buf1, "%s.php", buf0);
sprintf(buf2, "%s.html", buf0);
if (strlen(uri->path) <= 1 || path_exists(buf0) || path_is_file(buf1) || path_is_file(buf2)) {
break;
}
char *ptr;
parent_dir:
ptr = strrchr(uri->path, '/');
size = strlen(ptr);
sprintf(buf3, "%.*s%s", (int) size, ptr, uri->pathinfo);
strcpy(uri->pathinfo, buf3);
ptr[0] = 0;
}
if (uri->pathinfo[0] != 0) {
sprintf(buf3, "%s", uri->pathinfo + 1);
strcpy(uri->pathinfo, buf3);
}
if (path_is_file(buf0)) {
uri->filename = malloc(strlen(buf0) + 1);
strcpy(uri->filename, buf0);
ssize_t len = strlen(uri->path);
if (strncmp(uri->path + len - 5, ".html", 5) == 0) {
uri->path[len - 5] = 0;
} else if (strncmp(uri->path + len - 4, ".php", 4) == 0) {
uri->path[len - 4] = 0;
uri->is_static = 0;
}
} else if (path_is_file(buf1)) {
uri->is_static = 0;
uri->filename = malloc(strlen(buf1) + 1);
strcpy(uri->filename, buf1);
} else if (path_is_file(buf2)) {
uri->filename = malloc(strlen(buf2) + 1);
strcpy(uri->filename, buf2);
} else {
uri->is_dir = 1;
strcpy(uri->path + strlen(uri->path), "/");
sprintf(buf1, "%s%sindex.php", uri->webroot, uri->path);
sprintf(buf2, "%s%sindex.html", uri->webroot, uri->path);
if (path_is_file(buf1)) {
uri->filename = malloc(strlen(buf1) + 1);
strcpy(uri->filename, buf1);
uri->is_static = 0;
} else if (path_is_file(buf2)) {
uri->filename = malloc(strlen(buf2) + 1);
strcpy(uri->filename, buf2);
} else {
if (dir_mode == URI_DIR_MODE_FORBIDDEN) {
uri->is_static = 1;
} else if (dir_mode == URI_DIR_MODE_LIST) {
uri->is_static = 0;
} else if (dir_mode == URI_DIR_MODE_INFO) {
if (strlen(uri->path) > 1) {
uri->path[strlen(uri->path) - 1] = 0;
sprintf(buf0, "/%s", uri->pathinfo);
strcpy(uri->pathinfo, buf0);
goto parent_dir;
}
}
}
}
if (strcmp(uri->path + strlen(uri->path) - 5, "index") == 0) {
uri->path[strlen(uri->path) - 5] = 0;
}
if (strcmp(uri->pathinfo, "index.php") == 0 || strcmp(uri->pathinfo, "index.html") == 0) {
uri->pathinfo[0] = 0;
}
sprintf(buf0, "%s%s%s%s%s", uri->path,
(strlen(uri->pathinfo) == 0 || uri->path[strlen(uri->path) - 1] == '/') ? "" : "/", uri->pathinfo,
uri->query != NULL ? "?" : "", uri->query != NULL ? uri->query : "");
uri->uri = malloc(strlen(buf0) + 1);
strcpy(uri->uri, buf0);
return 0;
}
void uri_free(http_uri *uri) {
if (uri->webroot != NULL) free(uri->webroot);
if (uri->req_path != NULL) free(uri->req_path);
if (uri->path != NULL) free(uri->path);
if (uri->pathinfo != NULL) free(uri->pathinfo);
if (uri->query != NULL) free(uri->query);
if (uri->filename != NULL) free(uri->filename);
if (uri->uri != NULL) free(uri->uri);
uri->webroot = NULL;
uri->req_path = NULL;
uri->path = NULL;
uri->pathinfo = NULL;
uri->query = NULL;
uri->filename = NULL;
uri->uri = NULL;
}

45
src/uri.h Normal file
View File

@ -0,0 +1,45 @@
/**
* Necronda Web Server
* URI and path handlers (header file)
* src/uri.h
* Lorenz Stechauner, 2020-12-13
*/
#ifndef NECRONDA_SERVER_URI_H
#define NECRONDA_SERVER_URI_H
#include <sys/stat.h>
#define URI_DIR_MODE_FORBIDDEN 0
#define URI_DIR_MODE_LIST 1
#define URI_DIR_MODE_INFO 2
typedef struct {
char etag[64];
char type[24];
char charset[16];
char filename_comp[256];
struct stat stat;
} meta_data;
typedef struct {
char *webroot; // "/srv/www/www.test.org"
char *req_path; // "/account/login"
char *path; // "/account/"
char *pathinfo; // "login"
char *query; // "username=test"
char *filename; // "/account/index.php"
char *uri; // "/account/login?username=test"
meta_data *meta;
unsigned char is_static:1;
unsigned char is_dir:1;
} http_uri;
int uri_init(http_uri *uri, const char *webroot, const char *uri_str, int dir_mode);
int uri_init_cache(http_uri *uri);
void uri_free(http_uri *uri);
#endif //NECRONDA_SERVER_URI_H

96
src/utils.c Normal file
View File

@ -0,0 +1,96 @@
/**
* Necronda Web Server
* Utilities
* src/utils.c
* Lorenz Stechauner, 2020-12-03
*/
#include "utils.h"
char *format_duration(unsigned long micros, char *buf) {
if (micros < 10000) {
sprintf(buf, "%.1f ms", (double) micros / 1000);
} else if (micros < 1000000) {
sprintf(buf, "%li ms", micros / 1000);
} else if (micros < 60000000) {
sprintf(buf, "%.1f s", (double) micros / 1000000);
} else if (micros < 6000000000) {
sprintf(buf, "%.1f min", (double) micros / 1000000 / 60);
} else {
sprintf(buf, "%li min", micros / 1000000 / 60);
}
return buf;
}
int url_encode(const char *str, char *enc, ssize_t *size) {
char *ptr = enc;
char ch;
memset(enc, 0, *size);
for (int i = 0; i < strlen(str); i++, ptr++) {
if ((ptr - enc) >= *size) {
return -1;
}
ch = str[i];
if (ch == ':' || ch == '/' || ch == '?' || ch == '#' || ch == '[' || ch == ']' || ch == '@' || ch == '!' ||
ch == '$' || ch == '&' || ch == '\'' || ch == '(' || ch == ')' || ch == '*' || ch == '+' || ch == ',' ||
ch == ';' || ch == '=' || ch < ' ' || ch > '~') {
if ((ptr - enc + 2) >= *size) {
return -1;
}
sprintf(ptr, "%%%02X", ch);
ptr += 2;
} else if (ch == ' ') {
ptr[0] = '+';
} else {
ptr[0] = ch;
}
}
*size = ptr - enc;
return 0;
}
int encode_url(const char *str, char *enc, ssize_t *size) {
char *ptr = enc;
unsigned char ch;
memset(enc, 0, *size);
for (int i = 0; i < strlen(str); i++, ptr++) {
if ((ptr - enc) >= *size) {
return -1;
}
ch = str[i];
if (ch > 0x7F || ch == ' ') {
if ((ptr - enc + 2) >= *size) {
return -1;
}
sprintf(ptr, "%%%02X", ch);
ptr += 2;
} else {
ptr[0] = (char) ch;
}
}
*size = ptr - enc;
return 0;
}
int url_decode(const char *str, char *dec, ssize_t *size) {
char *ptr = dec;
char ch, buf[3];
memset(dec, 0, *size);
for (int i = 0; i < strlen(str); i++, ptr++) {
if ((ptr - dec) >= *size) {
return -1;
}
ch = str[i];
if (ch == '+') {
ch = ' ';
} else if (ch == '%') {
memcpy(buf, str + i + 1, 2);
buf[2] = 0;
ch = (char) strtol(buf, NULL, 16);
i += 2;
}
ptr[0] = ch;
}
*size = ptr - dec;
return 0;
}

31
src/utils.h Normal file
View File

@ -0,0 +1,31 @@
/**
* Necronda Web Server
* Utilities (header file)
* src/utils.h
* Lorenz Stechauner, 2020-12-03
*/
#ifndef NECRONDA_SERVER_UTILS_H
#define NECRONDA_SERVER_UTILS_H
char *log_prefix;
#define out_1(fmt) fprintf(stdout, "%s" fmt "\n", log_prefix)
#define out_2(fmt, args...) fprintf(stdout, "%s" fmt "\n", log_prefix, args)
#define out_x(x, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, FUNC, ...) FUNC
#define print(...) out_x(, ##__VA_ARGS__, out_2(__VA_ARGS__), out_2(__VA_ARGS__), out_2(__VA_ARGS__), \
out_2(__VA_ARGS__), out_2(__VA_ARGS__), out_2(__VA_ARGS__), out_2(__VA_ARGS__), \
out_2(__VA_ARGS__), out_1(__VA_ARGS__)); fflush(stdout)
char *format_duration(unsigned long micros, char *buf);
int url_encode(const char *str, char *enc, ssize_t *size);
int encode_url(const char *str, char *enc, ssize_t *size);
int url_decode(const char *str, char *dec, ssize_t *size);
#endif //NECRONDA_SERVER_UTILS_H