From 3ca1a225798d0f6ac6b1cd6556c6854a897475fd Mon Sep 17 00:00:00 2001 From: Aaron Kalair Date: Tue, 26 Jun 2012 15:03:51 +0100 Subject: [PATCH] Initial Commit --- .gitignore | 7 + 400.html | 15 ++ 404.html | 15 ++ README.md | 23 +++ makefile | 2 + public_html/index.html | 15 ++ server.c | 458 +++++++++++++++++++++++++++++++++++++++++ 7 files changed, 535 insertions(+) create mode 100644 .gitignore create mode 100644 400.html create mode 100644 404.html create mode 100644 README.md create mode 100644 makefile create mode 100644 public_html/index.html create mode 100644 server.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a7586d1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +# Ignore the compiled file +server + +# Ignore any text editor backups +*~ + + diff --git a/400.html b/400.html new file mode 100644 index 0000000..3ecd9b7 --- /dev/null +++ b/400.html @@ -0,0 +1,15 @@ + + + + + 400 - Bad Request + + + + + +400 Bad Request + + + + diff --git a/404.html b/404.html new file mode 100644 index 0000000..d0b4a6f --- /dev/null +++ b/404.html @@ -0,0 +1,15 @@ + + + + + 404 - Page Not Found + + + + + +Woops couldn't find that page + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9c44fc9 --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +A Simple Webserver Written in C +=============================== + +This is a simple webserver written in the C programming language. + +It uses a pool of 10 connections to serve multiple requests concurrently and it keeps track of how much data it has output, printing it to the standard output stream. + + +Compiling and Using the System +============================== + +On a Linux system system simply use the makefile to compile the server. + +On a Mac use this command to compile the server: + +gcc cs241server.c –o cs241server + + +To run the server type ./server into a terminal that is in the directory where the executable file is located. + +By default the server runs on port 2001, so to try it out navigate to + +localhost:2001 in a webbrowser diff --git a/makefile b/makefile new file mode 100644 index 0000000..1ec3e9a --- /dev/null +++ b/makefile @@ -0,0 +1,2 @@ +server : server.c + gcc server.c -lrt -o server diff --git a/public_html/index.html b/public_html/index.html new file mode 100644 index 0000000..253f855 --- /dev/null +++ b/public_html/index.html @@ -0,0 +1,15 @@ + + + + + Well Done ! + + + + + +It looks like everything is working ! + + + + diff --git a/server.c b/server.c new file mode 100644 index 0000000..448edc0 --- /dev/null +++ b/server.c @@ -0,0 +1,458 @@ +#include // socket definitions +#include // socket types +#include // inet (3) funtions +#include // misc. UNIX functions +#include // signal handling +#include // standard library +#include // input/output library +#include // string library +#include // error number library +#include // for O_* constants +#include // mmap library +#include // various type definitions +#include // more constants + +// global constants +#define PORT 2001 // port to connect on +#define LISTENQ 10 // number of connections + +int list_s; // listening socket + +// structure to hold the return code and the filepath to serve to client. +typedef struct { + int returncode; + char *filename; +} httpRequest; + +// Structure to hold variables that will be placed in shared memory +typedef struct { + pthread_mutex_t mutexlock; + int totalbytes; +} sharedVariables; + +// headers to send to clients +char *header200 = "HTTP/1.0 200 OK\nServer: CS241Serv v0.1\nContent-Type: text/html\n\n"; +char *header400 = "HTTP/1.0 400 Bad Request\nServer: CS241Serv v0.1\nContent-Type: text/html\n\n"; +char *header404 = "HTTP/1.0 404 Not Found\nServer: CS241Serv v0.1\nContent-Type: text/html\n\n"; + +// get a message from the socket until a blank line is recieved +char *getMessage(int fd) { + + // A file stream + FILE *sstream; + + // Try to open the socket to the file stream and handle any failures + if( (sstream = fdopen(fd, "r")) == NULL) + { + fprintf(stderr, "Error opening file descriptor in getMessage()\n"); + exit(EXIT_FAILURE); + } + + // Size variable for passing to getline + size_t size = 1; + + char *block; + + // Allocate some memory for block and check it went ok + if( (block = malloc(sizeof(char) * size)) == NULL ) + { + fprintf(stderr, "Error allocating memory to block in getMessage\n"); + exit(EXIT_FAILURE); + } + + // Set block to null + *block = '\0'; + + // Allocate some memory for tmp and check it went ok + char *tmp; + if( (tmp = malloc(sizeof(char) * size)) == NULL ) + { + fprintf(stderr, "Error allocating memory to tmp in getMessage\n"); + exit(EXIT_FAILURE); + } + // Set tmp to null + *tmp = '\0'; + + // Int to keep track of what getline returns + int end; + // Int to help use resize block + int oldsize = 1; + + // While getline is still getting data + while( (end = getline( &tmp, &size, sstream)) > 0) + { + // If the line its read is a caridge return and a new line were at the end of the header so break + if( strcmp(tmp, "\r\n") == 0) + { + break; + } + + // Resize block + block = realloc(block, size+oldsize); + // Set the value of oldsize to the current size of block + oldsize += size; + // Append the latest line we got to block + strcat(block, tmp); + } + + // Free tmp a we no longer need it + free(tmp); + + // Return the header + return block; + +} + +// send a message to a socket file descripter +int sendMessage(int fd, char *msg) { + return write(fd, msg, strlen(msg)); +} + +// Extracts the filename needed from a GET request and adds public_html to the front of it +char * getFileName(char* msg) +{ + // Variable to store the filename in + char * file; + // Allocate some memory for the filename and check it went OK + if( (file = malloc(sizeof(char) * strlen(msg))) == NULL) + { + fprintf(stderr, "Error allocating memory to file in getFileName()\n"); + exit(EXIT_FAILURE); + } + + // Get the filename from the header + sscanf(msg, "GET %s HTTP/1.1", file); + + // Allocate some memory not in read only space to store "public_html" + char *base; + if( (base = malloc(sizeof(char) * (strlen(file) + 18))) == NULL) + { + fprintf(stderr, "Error allocating memory to base in getFileName()\n"); + exit(EXIT_FAILURE); + } + + char* ph = "public_html"; + + // Copy public_html to the non read only memory + strcpy(base, ph); + + // Append the filename after public_html + strcat(base, file); + + // Free file as we now have the file name in base + free(file); + + // Return public_html/filetheywant.html + return base; +} + +// parse a HTTP request and return an object with return code and filename +httpRequest parseRequest(char *msg){ + httpRequest ret; + + // A variable to store the name of the file they want + char* filename; + // Allocate some memory to filename and check it goes OK + if( (filename = malloc(sizeof(char) * strlen(msg))) == NULL) + { + fprintf(stderr, "Error allocating memory to filename in parseRequest()\n"); + exit(EXIT_FAILURE); + } + // Find out what page they want + filename = getFileName(msg); + + // Check if its a directory traversal attack + char *badstring = ".."; + char *test = strstr(filename, badstring); + + // Check if they asked for / and give them index.html + int test2 = strcmp(filename, "public_html/"); + + // Check if the page they want exists + FILE *exists = fopen(filename, "r" ); + + // If the badstring is found in the filename + if( test != NULL ) + { + // Return a 400 header and 400.html + ret.returncode = 400; + ret.filename = "400.html"; + } + + // If they asked for / return index.html + else if(test2 == 0) + { + ret.returncode = 200; + ret.filename = "public_html/index.html"; + } + + // If they asked for a specific page and it exists because we opened it sucessfully return it + else if( exists != NULL ) + { + + ret.returncode = 200; + ret.filename = filename; + // Close the file stream + fclose(exists); + } + + // If we get here the file they want doesn't exist so return a 404 + else + { + ret.returncode = 404; + ret.filename = "404.html"; + } + + // Return the structure containing the details + return ret; +} + +// print a file out to a socket file descriptor +int printFile(int fd, char *filename) { + + /* Open the file filename and echo the contents from it to the file descriptor fd */ + + // Attempt to open the file + FILE *read; + if( (read = fopen(filename, "r")) == NULL) + { + fprintf(stderr, "Error opening file in printFile()\n"); + exit(EXIT_FAILURE); + } + + // Get the size of this file for printing out later on + int totalsize; + struct stat st; + stat(filename, &st); + totalsize = st.st_size; + + // Variable for getline to write the size of the line its currently printing to + size_t size = 1; + + // Get some space to store each line of the file in temporarily + char *temp; + if( (temp = malloc(sizeof(char) * size)) == NULL ) + { + fprintf(stderr, "Error allocating memory to temp in printFile()\n"); + exit(EXIT_FAILURE); + } + + + // Int to keep track of what getline returns + int end; + + // While getline is still getting data + while( (end = getline( &temp, &size, read)) > 0) + { + sendMessage(fd, temp); + } + + // Final new line + sendMessage(fd, "\n"); + + // Free temp as we no longer need it + free(temp); + + // Return how big the file we sent out was + return totalsize; + +} + +// clean up listening socket on ctrl-c +void cleanup(int sig) { + + printf("Cleaning up connections and exiting.\n"); + + // try to close the listening socket + if (close(list_s) < 0) { + fprintf(stderr, "Error calling close()\n"); + exit(EXIT_FAILURE); + } + + // Close the shared memory we used + shm_unlink("/sharedmem"); + + // exit with success + exit(EXIT_SUCCESS); +} + +int printHeader(int fd, int returncode) +{ + // Print the header based on the return code + switch (returncode) + { + case 200: + sendMessage(fd, header200); + return strlen(header200); + break; + + case 400: + sendMessage(fd, header400); + return strlen(header400); + break; + + case 404: + sendMessage(fd, header404); + return strlen(header404); + break; + } +} + + +// Increment the global count of data sent out +int recordTotalBytes(int bytes_sent, sharedVariables *mempointer) +{ + // Lock the mutex + pthread_mutex_lock(&(*mempointer).mutexlock); + // Increment bytes_sent + (*mempointer).totalbytes += bytes_sent; + // Unlock the mutex + pthread_mutex_unlock(&(*mempointer).mutexlock); + // Return the new byte count + return (*mempointer).totalbytes; +} + + +int main(int argc, char *argv[]) { + int conn_s; // connection socket + short int port = PORT; // port number + struct sockaddr_in servaddr; // socket address structure + + // set up signal handler for ctrl-c + (void) signal(SIGINT, cleanup); + + // create the listening socket + if ((list_s = socket(AF_INET, SOCK_STREAM, 0)) < 0 ) { + fprintf(stderr, "Error creating listening socket.\n"); + exit(EXIT_FAILURE); + } + + // set all bytes in socket address structure to zero, and fill in the relevant data members + memset(&servaddr, 0, sizeof(servaddr)); + servaddr.sin_family = AF_INET; + servaddr.sin_addr.s_addr = htonl(INADDR_ANY); + servaddr.sin_port = htons(port); + + // bind to the socket address + if (bind(list_s, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0 ) { + fprintf(stderr, "Error calling bind()\n"); + exit(EXIT_FAILURE); + } + + + // Listen on socket list_s + if( (listen(list_s, 10)) == -1) + { + fprintf(stderr, "Error Listening\n"); + exit(EXIT_FAILURE); + } + + // Set up some shared memory to store our shared variables in + + // Close the shared memory we use just to be safe + shm_unlink("/sharedmem"); + + int sharedmem; + + // Open the memory + if( (sharedmem = shm_open("/sharedmem", O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR)) == -1) + { + fprintf(stderr, "Error opening sharedmem in main() errno is: %s ", strerror(errno)); + exit(EXIT_FAILURE); + } + + // Set the size of the shared memory to the size of my structure + ftruncate(sharedmem, sizeof(sharedVariables) ); + + // Map the shared memory into our address space + sharedVariables *mempointer; + + // Set mempointer to point at the shared memory + mempointer = mmap(NULL, sizeof(sharedVariables), PROT_READ | PROT_WRITE, MAP_SHARED, sharedmem, 0); + + // Check the memory allocation went OK + if( mempointer == MAP_FAILED ) + { + fprintf(stderr, "Error setting shared memory for sharedVariables in recordTotalBytes() error is %d \n ", errno); + exit(EXIT_FAILURE); + } + // Initalise the mutex + pthread_mutex_init(&(*mempointer).mutexlock, NULL); + // Set total bytes sent to 0 + (*mempointer).totalbytes = 0; + + // Size of the address + int addr_size = sizeof(servaddr); + + // Sizes of data were sending out + int headersize; + int pagesize; + int totaldata; + // Number of child processes we have spawned + int children = 0; + // Variable to store the ID of the process we get when we spawn + pid_t pid; + + // Loop infinitly serving requests + while(1) + { + + // If we haven't already spawned 10 children fork + if( children <= 10) + { + pid = fork(); + children++; + } + + // If the pid is -1 the fork failed so handle that + if( pid == -1) + { + fprintf(stderr,"can't fork, error %d\n" , errno); + exit (1); + } + + // Have the child process deal with the connection + if ( pid == 0) + { + // Have the child loop infinetly dealing with a connection then getting the next one in the queue + while(1) + { + // Accept a connection + conn_s = accept(list_s, (struct sockaddr *)&servaddr, &addr_size); + + // If something went wrong with accepting the connection deal with it + if(conn_s == -1) + { + fprintf(stderr,"Error accepting connection \n"); + exit (1); + } + + // Get the message from the file descriptor + char * header = getMessage(conn_s); + + // Parse the request + httpRequest details = parseRequest(header); + + // Free header now were done with it + free(header); + + // Print out the correct header + headersize = printHeader(conn_s, details.returncode); + + // Print out the file they wanted + pagesize = printFile(conn_s, details.filename); + + // Increment our count of total datasent by all processes and get back the new total + totaldata = recordTotalBytes(headersize+pagesize, mempointer); + + // Print out which process handled the request and how much data was sent + printf("Process %d served a request of %d bytes. Total bytes sent %d \n", getpid(), headersize+pagesize, totaldata); + + // Close the connection now were done + close(conn_s); + } + } + } + + return EXIT_SUCCESS; +}