#include #include #include #include #include #include #include #include #include #include // Local Includes #include "httpStruct.h" #include "socketHelp.h" #include "returnRequest.h" #include "server.h" #include "utils.h" #define WEB_ROOT "/var/www/html/" //#define WEB_ROOT "content/" #define BUFF_READ 1024 static int verbose_flag = 0; bool enableHTTPS = 0; static int timeout = 0; int printDebug(char message[]) { if (verbose_flag == 1) { printf("[Debug] %s\n", message); } return 0; } void php_cgi(char *sPath) { int phpPipe[2]; char *buf = NULL; size_t bufLen = 1024; buf = malloc(bufLen); pipe(phpPipe); pid_t pid; pid = fork(); if (pid == 0) { char script[500]; close(phpPipe[0]); dup2(phpPipe[1], STDOUT_FILENO); strcpy(script, "SCRIPT_FILENAME="); strcat(script, WEB_ROOT); strcat(script, sPath); putenv("GATEWAY_INTERFACE=CGI/1.1"); putenv(script); putenv("QUERY_STRING="); putenv("REQUEST_METHOD=GET"); putenv("REDIRECT_STATUS=true"); putenv("SERVER_PROTOCOL=HTTP/1.1"); putenv("REMOTE_HOST=127.0.0.1"); execl("/usr/bin/php-cgi", "php-cgi", NULL); } else if (pid < 0) { printDebug("Error forking php exec"); } else { size_t buffCount; close(phpPipe[1]); do { buffCount = read(phpPipe[0], buf, BUFF_READ); if (strlen(buf) == bufLen) { bufLen *= 2; buf = realloc(buf, bufLen); } } while (buffCount == 0); } close(phpPipe[0]); printf("PHP: %s\n", buf); return; } int parseHTTPRequest(unsigned char *buffer, struct HTTPRequest *r) { char temp[1]; // Used to check newlines char *token = calloc(8, sizeof(char)); int line = 0; int contentCheck = 0; unsigned char *checkLine = calloc(1000, sizeof(unsigned char)); unsigned char *logLine = malloc(sizeof(unsigned char)); char * varCheck; // Creating empty requestBody r->requestBody = calloc(8, sizeof(char)); for (int i = 0; i <= strlen((char *)buffer); i++) { temp[0] = buffer[i]; // Checking for newline character OR end of string if (((!strcmp(temp, "\n")) && (i != 0)) || (i == strlen((char *)buffer))) { // Config Check if (line == 0) { // Grabbing first line for type parsing logLine = calloc(strlen((char *)checkLine), sizeof(char)); strcpy((char *)logLine, (char *)checkLine); token = strtok((char *)checkLine, " "); // HTTP Request Type if ((!strcmp(token, "GET")) || (!strcmp(token, "POST")) || (!strcmp(token, "PUT")) || (!strcmp(token, "DELETE")) || (!strcmp(token, "CONNECT"))) { // Grabbing HTTP Request Type r->requestType = malloc(strlen(token)); strcpy(r->requestType, token); // Grabbing HTTP Request Dir token = strtok(NULL, " "); varCheck = strchr(token, '?'); if (varCheck != NULL) { *varCheck = ' '; } sscanf(token, "%ms %ms", &r->requestDir, &r->requestVars); // Grabbing HTTP Request Version token = strtok(NULL, ""); token[strlen(token) - 1] = '\0'; // Fixing version end char r->requestVersion = malloc(strlen(token)); strcpy(r->requestVersion, token); } else { // First line contains unsupported request free(logLine); free(checkLine); return 2; } // Log Request PrintLog(logLine); } else { // Gather information on >first line if (contentCheck) { // Once content check is set to one everything after is part of the body r->requestBody = realloc(r->requestBody, (strlen((char *)checkLine) + strlen((char *)r->requestBody) + 1)); strcat((char *)r->requestBody, (char *)checkLine); // Adding newline to requestBody sprintf((char *)r->requestBody, "%s\n", r->requestBody); } else { // Information parsing !content if (strlen((char *)checkLine) == 1) { // Looking for blank empty line to end header info contentCheck = 1; } token = strtok((char *)checkLine, ":"); // Host Check if (!strcmp(token, "Host")) { token = strtok(NULL, ""); r->requestHost = malloc(strlen(token)); strcpy(r->requestHost, token); } else if (!strcmp(token, "Content-Type")) { token = strtok(NULL, ":"); r->requestConType = malloc(strlen(token)); strcpy(r->requestConType, token); } else if (!strcmp(token, "Content-Length")) { token = strtok(NULL, ":"); if (token == NULL) { printDebug("Content-length parsing error"); return -1; } r->requestConLen = malloc(strlen(token)); strcpy(r->requestConLen, token); } } } if (strlen((char *)checkLine) > 0) { // Clear checkLine memset(checkLine,0,strlen((char *)checkLine)); } line++; } else { strcat((char *)checkLine, temp); } } if (strlen(r->requestType) == 0) { free(logLine); free(checkLine); return -1; } free(logLine); free(checkLine); return 0; } int handleGetRequest(int socket, struct HTTPRequest *r, SSL *ssl) { char errResponse[256]; char ch; size_t size = 8; int i = 0; unsigned char *fileContent = calloc(size, sizeof(unsigned char)); FILE *fp; char *workingReqDir; // If requesting root directory change to index.html if (!strcmp(r->requestDir, "/")) { workingReqDir = "index.html"; } else { workingReqDir = r->requestDir; } char *reqDir = calloc((strlen(WEB_ROOT) + strlen(workingReqDir) + 1), sizeof(char)); sprintf(reqDir, "%s%s", WEB_ROOT, workingReqDir); fp = fopen(reqDir, "r"); if (fp == NULL) { sprintf(errResponse, "Error opening file: %s", workingReqDir); printDebug(errResponse); return404Request(socket, ssl); return -1; } while((ch = fgetc(fp)) != EOF) { if (i == size) { size *= 2; fileContent = realloc(fileContent, size); } fileContent[i] = ch; i++; } return200Request(socket, fileContent, ssl); fclose(fp); free(reqDir); free(fileContent); return 0; } int handlePUTRequest(int socket, struct HTTPRequest *r, SSL *ssl) { FILE *fp; char *reqDir; char * workingReqDir; char errResponse[256]; if (!strcmp(r->requestDir, "/")) { workingReqDir = "index.html"; } else { workingReqDir = r->requestDir; } reqDir = calloc(strlen(WEB_ROOT) + strlen(workingReqDir), sizeof(char)); sprintf(reqDir, "%s%s", WEB_ROOT, workingReqDir); fp = fopen(reqDir, "w"); if (fp == NULL) { sprintf(errResponse, "Error opening file: %s", workingReqDir); printDebug(errResponse); return return404Request(socket, ssl); } fprintf(fp, "%s", r->requestBody); free(reqDir); fclose(fp); return return201Request(socket, r->requestBody, ssl); } int handleDELETERequest(int socket, struct HTTPRequest *r, SSL *ssl) { char *reqDir; char * workingReqDir; char logLine[256]; if (!strcmp(r->requestDir, "/")) { workingReqDir = "index.html"; } else { workingReqDir = r->requestDir; } reqDir = calloc(strlen(WEB_ROOT) + strlen(workingReqDir), sizeof(char)); sprintf(reqDir, "%s%s", WEB_ROOT, workingReqDir); if (remove(reqDir) == 0) { sprintf(logLine, "Deleted file"); free(reqDir); return return200Request(socket, NULL, ssl); } else { printDebug("Failed to delete file"); free(reqDir); if (errno == 1) { // Permission denided return return403Request(socket, ssl); } else { // Catch all (likely file != exist) return return404Request(socket, ssl); } } return 0; } int handlePOSTRequest(int socket, struct HTTPRequest *r, SSL *ssl) { // Supported data types application/x-www-form-urlencoded application/json if (r->requestConLen == NULL) { // Return 411 if length var not specified printDebug("Content Length was not sent in POST request"); return return411Request(socket, ssl); } //r->requestBody[strlen((char *)r->requestBody) - 1] = '\0'; // Resetting request content length to remove trailing newline if (r->requestConType != NULL) { if (!strcmp(r->requestConType, "application/json")) { printf("JSON Request\n"); } else if (!strcmp(r->requestConType, "application/x-www-form-utlencoded")) { printf("Application form\n"); } } else { printf("HERE\n"); } php_cgi(r->requestDir); return return501Request(socket, ssl); } int handleRequest(unsigned char buffer[], int socket, SSL *ssl) { struct HTTPRequest r; // Holds relevant HTTP request information r.requestConLen = malloc(sizeof(char)); r.requestConType = NULL; int checkerr = 0; // Grabbing relevant information out of request checkerr = parseHTTPRequest(buffer, &r); if (checkerr != 0) { // Checking for HTTP parsing error printDebug("Error ;("); if (checkerr == -1) { printDebug("Error reading request, returning empty 500"); return500Request(socket, ssl); } else { printDebug("Error parsing, returning 501"); return501Request(socket, ssl); } } else { checkerr = checkHTTPVersion(r.requestVersion); if (checkerr != 0) { return505Request(socket, ssl); } else { if (!strcmp(r.requestType, "GET")) { handleGetRequest(socket, &r, ssl); } else if (!strcmp(r.requestType, "POST")) { handlePOSTRequest(socket, &r, ssl); } else if (!strcmp(r.requestType, "PUT")) { handlePUTRequest(socket, &r, ssl); } else if (!strcmp(r.requestType, "DELETE")) { handleDELETERequest(socket, &r, ssl); } else if (!strcmp(r.requestType, "CONNECT")) { return200Request(socket, NULL, ssl); } else { return500Request(socket, ssl); } } } free(r.requestConLen); return 0; } void timeoutChild(int sig) { timeout = 1; } int main(int argc, char **argv) { struct sockaddr_in address; int server_fd, new_socket; int port = 0; // Define what port server listens on int checkerr = 0; // Used for error checking int addrlen = sizeof(address); char *certFile = malloc(sizeof(char)); char *privKeyFile = malloc(sizeof(char)); certFile[0] = '\0'; privKeyFile[0] = '\0'; uint32_t listenAddrNum = -1; char *listenAddr = malloc(sizeof(char)); listenAddr[0] = '\0'; SSL_CTX *ctx = NULL; size_t bufSize = BUFF_READ; unsigned char *buffer = calloc(bufSize, sizeof(unsigned char)); // Setting up options static const struct option long_options[] = { {"help", no_argument, NULL, 'h'}, {"port", required_argument, NULL, 'p'}, {"listen", required_argument, NULL, 'l'}, {"cert", required_argument, NULL, 'c'}, {"privkey", required_argument, NULL, 'k'}, {"verbose", no_argument, &verbose_flag, 1}, {0, 0, 0, 0} }; const char* usage = "Usage: seaweb [options]\n\n" " -h --help\t\t\tShows this message\n" " -p --port\t\t\tStarts webserver on passed port\n" " -l --listen\t\t\tDefines what addr to listen on (default 0.0.0.0)\n" " -c --cert\t\t\tPath to certificate\n" " -k --privkey\t\t\tPath to private key\n" "\n" "\n" " --verbose\t\t\tPrints debug messages\n" "\n"; int c; while (1) { int option_index = 0; c = getopt_long(argc, argv, "hp:l:c:k:", long_options, &option_index); if(c == -1) { // Break if no more options are present to parse break; } switch(c) { case 'h': printf("%s", usage); exit(EXIT_SUCCESS); case 'p': sscanf(optarg, "%d", &port); break; case 'l': listenAddr = calloc(strlen(optarg), sizeof(char)); strcpy(listenAddr, optarg); listenAddrNum = addr2sin_addr(listenAddr); if (listenAddrNum == -1) { exit(EXIT_FAILURE); } break; case 'c': certFile = calloc(strlen(optarg), sizeof(char)); strcpy(certFile, optarg); printf("Cert: %s\n", certFile); break; case 'k': privKeyFile = calloc(strlen(optarg), sizeof(char)); strcpy(privKeyFile, optarg); printf("Priv: %s\n", privKeyFile); break; } } // Argument checks if (port == 0) { // Setting default port if none is passed port = 8080; } if (!((strlen(certFile) > 0) == (strlen(privKeyFile) > 0))) { // XNOR of string lengths // Checking what flag was not passed if (strlen(certFile)) { // Privkey not passed printf("If certificate file is specified, a private key files needs to be specified\n"); } else if (strlen(privKeyFile)) { // Cert not passed printf("If privkey file is specified, a certificate files needs to be specified\n"); } printf("Exiting...\n"); exit(EXIT_FAILURE); } else if ( (strlen(certFile) > 0) && (strlen(privKeyFile) > 0) ) { // Enabling HTTPS enableHTTPS = 1; } if ( enableHTTPS == 1 ) { printf("Opening secure socket on port: %d\n", port); checkerr = createSecureSocket(port, &server_fd, &address, &addrlen, listenAddrNum, &ctx, certFile, privKeyFile); if ( ctx == NULL ) { printf("Error creating ctx\n"); } } else { printf("Opening socket on port: %d\n", port); checkerr = createSocket(port, &server_fd, &address, &addrlen, listenAddrNum); } if (checkerr != 0) { perror("Error creating socket"); exit(EXIT_FAILURE); } // Handle incoming requests while(1) { if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t *)&addrlen))<0) { perror("Accept connection error"); exit(EXIT_FAILURE); } // Forking process pid_t pid; pid = fork(); if (pid == 0) { pid_t pid2; pid2 = fork(); if (pid2 == 0) { bufSize = BUFF_READ; if ( enableHTTPS ) { size_t buffCont = 1; SSL *ssl; ssl = SSL_new(ctx); SSL_set_fd(ssl, new_socket); SSL_accept(ssl); do { buffCont = SSL_read(ssl, buffer, BUFF_READ); if (strlen((char *)buffer) == bufSize) { bufSize *= 2; buffer = realloc(buffer, bufSize); } } while(buffCont == 0); if (strlen((char *)buffer) != 0) { handleRequest(buffer, new_socket, ssl); free(buffer); } else { printDebug("Error reading from socket"); } } else { size_t buffCont = 1; do { buffCont = read(new_socket, buffer, BUFF_READ); if (strlen((char *)buffer) == bufSize) { bufSize *= 2; buffer = realloc(buffer, bufSize); } } while(buffCont == 0); handleRequest(buffer, new_socket, NULL); free(buffer); } exit(EXIT_SUCCESS); } else if (pid2 < 0) { printDebug("Error forking supervisor..."); } else { int status; signal(SIGALRM, timeoutChild); alarm(2); pause(); if (timeout) { status = waitpid(pid, NULL, WNOHANG); if (status == 0) { printDebug("Killing child"); kill(pid2, 9); wait(NULL); } } exit(EXIT_SUCCESS); } close(new_socket); } else if (pid < 0) { printDebug("Error forking..."); } else { signal(SIGCHLD, SIG_IGN); close(new_socket); continue; } } free(privKeyFile); free(certFile); free(listenAddr); free(buffer); close(server_fd); exit(EXIT_SUCCESS); }