diff --git a/run.sh b/run.sh index db010aa..e36d85c 100644 --- a/run.sh +++ b/run.sh @@ -4,4 +4,4 @@ make && \ echo -e "-- Successfully finished compiling!\n" && \ sleep 0.0625 && \ echo -e "-- Starting Server...\n" && \ - ./bin/necronda-server + authbind ./bin/necronda-server diff --git a/src/URI.cpp b/src/URI.cpp index e3631fc..0ec6573 100644 --- a/src/URI.cpp +++ b/src/URI.cpp @@ -50,6 +50,7 @@ URI::URI(string webroot, string reqpath) { if (webroot[webroot.length() - 1] == '/') { webroot.erase(webroot.length() - 1); } + reqpath = url_decode(reqpath); if (reqpath.find("/../") != string::npos) { throw (char *) "Invalid path"; } @@ -116,8 +117,9 @@ string URI::getFilePath() { } string URI::getRelativeFilePath() { - string rel = getRelativePath(); - // TODO + string str = getFilePath(); + long len = getWebRoot().length(); + return str.substr(len, str.length() - len); } string URI::getNewPath() { @@ -127,7 +129,7 @@ string URI::getNewPath() { } } if (relpath != reqpath) { - return relpath + (queryinit? "?" + query : ""); + return url_encode(relpath) + (queryinit? "?" + query : ""); } else { return ""; } @@ -138,7 +140,7 @@ FILE *URI::openFile() { } string URI::getFilePathInfo() { - return getAbsolutePath().erase(getFilePath().length(), getAbsolutePath().length()); + return ""; //getAbsolutePath().erase(getFilePath().length(), getAbsolutePath().length()); } string URI::getFileType() { @@ -146,7 +148,7 @@ string URI::getFileType() { } bool URI::isStatic() { - return true; + return getExtension(filepath) != "php"; } string URI::getQuery() { diff --git a/src/client.cpp b/src/client.cpp index 6c5981d..9f28b8b 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -19,18 +19,23 @@ #include "necronda-server.h" #include "network/http/HttpStatusCode.h" #include "URI.h" +#include "procopen.h" /** * Writes log messages to the console * @param prefix The connection prefix - * @param string The string to be written + * @param str The string to be written */ -void log(const char *prefix, const string &string) { - printf("%s%s\r\n", prefix, string.c_str()); +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"); +} + string getETag(string filename) { ifstream etags = ifstream("/var/necronda/ETags"); @@ -106,6 +111,24 @@ string getETag(string filename) { return md5; } +#include +#include + +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; +} + /** * Handles (keep-alive) HTTP connections * @param prefix The connection prefix @@ -114,8 +137,9 @@ string getETag(string filename) { * @param num The Connection Number in the client * @return Should the server wait for another header? */ -bool connection_handler(const char *prefix, Socket *socket, long id, long num) { +bool connection_handler(const char *preprefix, const char *col1, const char *col2, Socket *socket, long id, long num) { bool error = false; + char *prefix = (char *) preprefix; try { HttpConnection req(socket); try { @@ -131,19 +155,32 @@ bool connection_handler(const char *prefix, Socket *socket, long id, long num) { req.respond(400); } else { string host = req.getField("Host"); + long pos = host.find(':'); + if (pos != string::npos) { + host.erase(pos, host.length() - pos); + } - string str = string(prefix); - unsigned long pos = str.find('[', 8); - string n = str.substr(0, 8) + host + str.substr(pos - 1, str.length() - pos + 1); + FILE *name = popen(("dig +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()); + } + + char buffer[1024]; + sprintf(buffer, "[\x1B[1m%s\x1B[0m][%i]%s[%s][%i]%s ", host.c_str(), socket->getSocketPort(), col1, + hostbuffer, socket->getPeerPort(), col2); - char buffer[256]; - sprintf(buffer, "%s", n.c_str()); prefix = buffer; URI path = URI(getWebRoot(host), req.getPath()); - log(prefix, req.getMethod() + " " + req.getPath()); + log(prefix, "\x1B[1m" + req.getMethod() + " " + req.getPath() + "\x1B[0m"); FILE *file = path.openFile(); + pid_t childpid = 0; if (!path.getNewPath().empty()) { req.redirect(302, path.getNewPath()); @@ -157,6 +194,8 @@ bool connection_handler(const char *prefix, Socket *socket, long id, long num) { if (type.find("inode/") == 0) { req.respond(403); + } else if (path.getRelativeFilePath().find("/.") != string::npos) { + req.respond(403); } else { req.setField("Content-Type", type); req.setField("Last-Modified", getHttpDate(path.getFilePath())); @@ -183,7 +222,6 @@ bool connection_handler(const char *prefix, Socket *socket, long id, long num) { if (req.getMethod() != "GET" && req.getMethod() != "POST" && req.getMethod() != "PUT") { invalidMethod = true; } - system("php"); } if (invalidMethod) { @@ -191,15 +229,82 @@ bool connection_handler(const char *prefix, Socket *socket, long id, long num) { } else if (etag) { req.respond(304); } else { + int statuscode = 200; + 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(hostbuffer) + + " 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"; - bool compress = type.find("text/") == 0 && req.isExistingField("Accept-Encoding") && + stds pipes = procopen(cmd.c_str()); + childpid = pipes.pid; + + char fdbuffer[4096]; + if (req.getMethod() == "POST" || req.getMethod() == "PUT") { + long len = req.isExistingField("Content-Length") ? strtol(req.getField("Content-Length").c_str(), nullptr, 10) : -1; + socket->receive(pipes.stdin); + } + fclose(pipes.stdin); + + string line; + while (!(line = read_line(pipes.stderr)).empty()) { + log_error(prefix, line); + } + fclose(pipes.stderr); + + while (!(line = read_line(pipes.stdout)).empty()) { + long 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 == "Location") { + statuscode = 303; + } + + req.setField(index, data); + } + + fclose(file); + file = pipes.stdout; + + + } + + bool compress = path.isStatic() && type.find("text/") == 0 && req.isExistingField("Accept-Encoding") && req.getField("Accept-Encoding").find("deflate") != string::npos; if (compress) { req.setField("Accept-Ranges", "none"); } - if (req.isExistingField("Range")) { + 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); @@ -231,16 +336,39 @@ bool connection_handler(const char *prefix, Socket *socket, long id, long num) { } } } else { - req.respond(200, file, compress); + req.respond(statuscode, file, compress); } } } fclose(file); + if (childpid > 0) { + waitpid(childpid, nullptr, 0); + } } } } HttpStatusCode status = req.getStatusCode(); - log(prefix, to_string(status.code) + " " + status.message + " (" + formatTime(req.getDuration()) + ")"); + int code = status.code; + string color = ""; + string comment = ""; + if ((code >= 200 && code < 300) || code == 304) { + color = "\x1B[1;32m"; // Success (Cached): 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") : ""); + } 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(); + } + log(prefix, + color + to_string(status.code) + " " + status.message + comment + " (" + formatTime(req.getDuration()) + + ")\x1B[0m"); } catch (char *msg) { HttpStatusCode status = req.getStatusCode(); log(prefix, to_string(status.code) + " " + status.message + " (" + formatTime(req.getDuration()) + ")"); @@ -290,26 +418,25 @@ bool connection_handler(const char *prefix, Socket *socket, long id, long num) { */ void client_handler(Socket *socket, long id, bool ssl) { const char *prefix; + char const *col1; + char const *col2 = "\x1B[0m"; { - char const *col1; - char const *col2 = "\x1B[0m"; auto group = (int) (id % 6); if (group == 0) { - col1 = "\x1B[1;31m"; + col1 = "\x1B[0;31m"; // Red } else if (group == 1) { - col1 = "\x1B[1;32m"; + col1 = "\x1B[0;32m"; // Green } else if (group == 2) { - col1 = "\x1B[1;34m"; + col1 = "\x1B[0;34m"; // Blue } else if (group == 3) { - col1 = "\x1B[1;33m"; + col1 = "\x1B[0;33m"; // Yellow } else if (group == 4) { - col1 = "\x1B[1;35m"; + col1 = "\x1B[0;35m"; // Magenta } else { - col1 = "\x1B[1;36m"; + col1 = "\x1B[0;36m"; // Cyan } - string *a = new string((string) - col1 + "[" + socket->getSocketAddress()->toString() + "][" + - to_string(socket->getSocketPort()) + "]" + + string *a = new string("[" + socket->getSocketAddress()->toString() + "][" + + to_string(socket->getSocketPort()) + "]" + col1 + "[" + socket->getPeerAddress()->toString() + "][" + to_string(socket->getPeerPort()) + "]" + col2 + " "); prefix = a->c_str(); @@ -338,7 +465,7 @@ void client_handler(Socket *socket, long id, bool ssl) { long reqnum = 0; if (!err) { - while (connection_handler(prefix, socket, id, ++reqnum)); + while (connection_handler(prefix, col1, col2, socket, id, ++reqnum)); reqnum--; } diff --git a/src/necronda-server.cpp b/src/necronda-server.cpp index 9c1df0a..31c621c 100644 --- a/src/necronda-server.cpp +++ b/src/necronda-server.cpp @@ -142,6 +142,75 @@ string getWebRoot(string host) { } + +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 = nullptr; + size_t len = 0; + ssize_t read; + if ((read = getline(&line, &len, file)) < 0) { + 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" diff --git a/src/necronda-server.h b/src/necronda-server.h index 4ff3ae3..452911e 100644 --- a/src/necronda-server.h +++ b/src/necronda-server.h @@ -33,5 +33,17 @@ 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); + #endif diff --git a/src/procopen.cpp b/src/procopen.cpp new file mode 100644 index 0000000..6a53f60 --- /dev/null +++ b/src/procopen.cpp @@ -0,0 +1,41 @@ +// +// 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}; + } +} + + diff --git a/src/procopen.h b/src/procopen.h new file mode 100644 index 0000000..7edbc84 --- /dev/null +++ b/src/procopen.h @@ -0,0 +1,41 @@ +// +// Created by lorenz on 5/30/18. +// + + +#include +#include +#include + +#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