#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; }