You are on page 1of 12

The Web Server

Shana Blair and Mark Kimmet University of Notre Dame, Notre Dame IN 46556, USA

Abstract
Since the creation of the World Wide Web by Tim Berners-Lee in 1990, internet web servers have been a necessity for transferring information. Currently however, loopholes and backdoors in web servers like Microsofts IIS are widely known and are a cause for concern. We have set out to build a web server that will side step many of these known vulnerabilities by eliminating all of the defaults set by programs like IIS, and by giving us more control over what is served and how it is served.

1 Introduction
Today nobody thinks twice about web servers or how they work. We go about our web surfing activities rarely thinking about what goes on behinds the scenes. Every copy of Windows comes with its own personal web server and you can download Apache web server online for free. No matter how much we take them for granted, they make up the backbone of the World Wide Web, serving information to all corners of the world. With this in mind, we endeavored to create a web server to gain better knowledge of how the World Wide Web works. In so doing we have additionally addressed several key security concerns of Microsofts popular IIS web server.

2 Implementation
2.1 The Process Behind a Web Page Request
2.1.1 The Request

When you sit down at your computer and point your browser to your favorite website there are several transactions that take place. First, your browser sends a request to the server. This request header has a defined format that is set by the W3C, the organization that establishes the standards for communicating through HTTP (HyperText Transfer Protocol) as well as other internet standards. This header looks similar to the following: GET /index.html HTTP/1.1 Host: www.nd.edu/~mkimmet Date: 09 Mar 2002 10:22:09 GMT User-Agent: Mozilla/4.0 1

Accept: text/html, image/jpeg /*blank line*/ 2.1.2 Parsing the Request

The next step is done on the servers end. The server takes the request header received and parses it to get the necessary data it needs to fill the request. From the first line it determines what type of request was made (GET, HEAD, POST), the file that was requested, in this case \index.html, and the HTTP version that will be accepted. Below this information are the various other optional attributes like date, host, user-agent, language-accepted, and content types accepted. 2.1.3 Developing a Response

After it has this information parsed, it uses it to create a response header. First it will verify that the file exists and the user has permissions to view it. Next it will build the response header, and then send the header and the file (if applicable) to the client IP. A example header looks like this: HTTP/1.1 200 OK Server: NCSA/2 Location: http://www.nd.edu/~mkimmet/index.html Content-type: text/html Content-length: 67 /* blank line */ In the first line of the header it responds with the HTTP version used and the response code (was it successful). In the following optional lines, it will give information on the server software used the content type being return and the content length. Finally a blank line separates the header and the actual file code.

2.2

Our Implementation

We broke the implementation into three separate classes: the server class (Myserver), the header class (Header), and the response class (Response). 2.2.1 Myserver Class

This is the main class from which all of our actions emanate. From here we store all our general information about the server; we control the starting of the server as well as all the subsequent connections that are created. When we receive a request from a client browser over the internet, we create a new process to handle this request. We used the Practical Sockets foundation, by Baylor Universitys CSE department, to allow us easy control over socket connections in a process-driven environment. Once a process is created we then create a header object.

2.2.2

Header Class

The header class takes in the header buffer sent by the client browser, and instantiates a Header object that parses and stores the data from the clients header. 2.2.3 Response Class

Using the header class the response class checks to make sure the requested file is valid and it exists. Based on this information it generates a response filling in the necessary information. Once this is performed, the process sends the response and the file (if necessary) to the client IP.

2.3

Logging the file request

We have also implemented a form of logging to keep tract of what files are accessed on our server. The file is saved as a comma delimited file with the IP that made the request, the file that was requested and finally the response code.

2.4

Security

Our first security check is made when the response class validates the file name. In so doing it makes sure that the file requested does not contain ../ which otherwise could be used to gain access to any file on the server. If the request is made with ../ in the file name, our server returns a 403 (forbidden) error as seen below.

Another feature we have built in is checking to make sure the file exists. When a file that is requested is found to be non-existent, a 404 error is returned to the user, similar to the error below.

To further protect us from popular viruses circulating the internet we do not use the default directories that IIS web servers use. Some of these default scripting directories can be used by viruses like the CodeRed virus to gain write permissions to the server, thus allowing a hacker to set up a robot that allows them to use your computer to attack further vulnerable systems or to launch Denial Of Service (DOS) attacks against commercial websites. Overall our web server gives us more control over what and how we serve; only serving images, text and html and not serving any scripts like asp that could be manipulated for evil. We can decide what directory to use as our root directory and we can change this every time we run our server. Having written the code ourselves gives us the reassurance that we know exactly what our server is doing.

Difficulties Faced

The majority of problems we encountered were due to our lack of experience with creating web servers. This was a new area of programming for both of us and implementing a web server requires an extensive knowledge of internet transfer protocol,

what constitutes a header and response, and many other aspects of programming with the internet that we had not expected. Although our server may seem simple on the outside, underneath the user interface it is very intricate. There were a number of subtle programming issues that ended up being extremely important to the functionality of the web server and caused us many hours of intense scrutiny on only a few lines of code. One of these was the case if our content length was off by only one, then the response to the client would not work. Some of our other difficulties came with trying to implement a GUI for our web server. Neither of us had any experience with GUIs in C++ so many hours were spent trying just to learn how to set up a GUI. Then our other problems were finding out how to stop the MFC precompiled headers on the files that did not need them and figuring out how to get input from text boxes and then manipulate it. Our GUI still is not at the level we would like it to be. Our program is much more reliable when run from the console version. Also, our web server is a little touchy about what sort of computer it will run on. We believe this is because of the socket class we are using and how the computer it is running on is connected to the internet and how it deals with security issues. Our last set of difficulties came with implementing Design by Contract. At some point in our coding, the assertions we supplied started causing the program to end in an abnormal termination. Although we worked on this for a number of hours, we were not able to locate the point at which something went wrong. Therefore, although we did implement Design by Contract throughout our whole project, the assertions have been disabled so that our program will execute correctly.

Conclusion

4.1 Goals Accomplished


We started this project with the view that implementing a web server would be a challenging yet entirely possible project. We looked forward to learning about something that not many people know about and everyone takes for granted. Web servers are essential to present-day information sharing. We did not realize quite how complex it would be, though. We started out with big goals for our web server, but during the process realized we needed to rethink those goals somewhat. At first we planned to have our server handle every type of request method, every type of response code, and all possible content types. This soon proved to be too much to implement in such a short time. Now our server handles the GET request method, three response codes, and five content types. We are excited that our web server works and retrieves pages, because we had to work so many hours just to get it to do that. Also, the console version is quite reliable and we were able to implement a couple of forms of security. One of them is making sure the user can only access files in a specified folder and its subsequent directories. Also, we provided for the case that the user would try to

access folders using ..\. We did not use virtual directories like Microsoft does, and because of this, viruses like Code Red cannot get in through our web server. We were able to provide a function that creates a log file. This way, the user could keep track of what is being accessed on the server, who is doing the accessing, and other similar information. Other extras we implemented include allowing the user to set the port number to listen on and allowing the user to set the root directory from which to serve web pages. Our goal of creating a GUI was accomplished, but it is not up to the standards that we originally expected. At the conclusion of our project, our web server provides a highly useful utility for retrieving web pages for clients. The console version is stable and secure, and our code was written in a way so that adding improvements will be simple. We both have learned so much about a topic inherent to present-day information sharing that we barely knew anything about before this project.

4.1

Future Work

The task of creating a web server was more complex than we anticipated, and there is room for a number of improvements of our project. First, we would like to make it so that the assertions can be turned back on, because then the server would be extremely stable and reliable. Then we would not have to worry about someone using the character array for the header to overflow the buffer and gain access to the rest of the files in the computer. Secondly, we would like to provide the rest of the response codes and content types. This is something that would require a lot more time, but with the way we designed our code, could be easily implemented. We would like to allow form inputs and processing. We also could implement virtual directories, although our web server would not be quite as secure. Lastly, we would like to provide even more security by limiting access based on user rights and enabling password protected directories. Our server is a basic web server, but it provides a strong base to implement a much more advanced server. We did not worry so much about speed as we did about security and reliability for this project. Increasing the speed of our server also could be a goal for future work.

References
Hughes, Merlin. Java Network Programming. Greenwich: Manning, 1999. Stevens, W. Richard. TCP/IP Illustrated, Volume 3. Reading: Addison-Wesley Publishing company, 1996. Rexford, Jennifer. Web Protocols and Practice. Boston: Addison-Wesley 2001. 6

Microsofts HTTP Revealed (a HTTP Primer) accessed on Dec 1, 2002. <http://www.microsoft.com/mind/0796/protocol/protocol.asp> W3Cs HTTP Definition accessed on Dec 3, 2002. <http://www.w3.org/Protocols/HTTP/HTTP2.html> Practical Sockets foundation by Baylor University CSE Dept <http://cs.ecs.baylor.edu/~donahoo/practical/CSockets/practical/>

Appendix A Screen Shots

The Main Server Application Screen

The Help Me Screen

Appendix B Selected Code Segments


B.1 Myserver Class
//constructor explicit Myserver(unsigned short p, char serverName[100], char logFile[100], char rootDirectory[100], char defaultFile[100]){ string tempserverName = serverName; string templogFile = logFile; string temprootDirectory = rootDirectory; // Preconditions Require(tempserverName.size()<100, "No overflowing"); Require(templogFile.size()<100, "No overflowing"); Require(temprootDirectory.size()<100, "No overflowing"); // Implementation setmy_port(p); strcpy(my_http_version, HTTPVERSION); setmy_server_name(serverName); setmy_log_file(logFile); setmy_default_file("index.html"); setmy_root_directory(rootDirectory); setmy_root_directory_string(rootDirectory);

//----------------------Listens for connections---------------------------------// LISTEN for connections from the web (Turn on) void listenup(void){ //cout << getmy_server_name() << " running..." << endl; try { TCPServerSocket servSock(my_port); for (;;) { // Run forever HandleTCPClient(servSock.accept()); } } catch (SocketException &e) { cerr << e.what() << endl; exit(1); } // NOT REACHED } // Server Socket object // Wait for a client to connect

//----------------------Establish and Service Request----------------------------// CREATE connection for each client void HandleTCPClient(TCPSocket *sock) { // handles errors involved with connecting to the client ip address //cout << "Handling client "; try { //cout << sock->getForeignAddress() << ":"; } catch (SocketException e) { cerr << "Unable to get foreign address" << endl; } try { //cout << sock->getForeignPort(); } catch (SocketException e) { cerr << "Unable to get foreign port" << endl; } //cout << endl;

// GET HEADER SENT FROM CLIENT char echoBuffer[RCVBUFSIZE]; char headerbuffer[RCVBUFSIZE]; int recvMsgSize, headerSize; string theheader = ""; // reads the header sent from the buffer and save it as a char array, and record its size while ((recvMsgSize = sock->recv(echoBuffer, RCVBUFSIZE)) > 0) { strcpy(headerbuffer, echoBuffer); //retrieves the header headerSize = recvMsgSize; //retrieves its size break; } // CREATE HEADER Header currentHeader(headerbuffer, headerSize, getmy_default_file()); // sets the full path and file name char fullpath[202]=""; string dir, gfile; dir = getmy_root_directory_string(); // gets the root directory string gfile = currentHeader.get_path_info();// gets the file requested string // places them in a character array for use with reading from files for(int z=0; z<dir.size() && z<100; z++) { fullpath[z] = dir.at(z); } // if the last character of the default directory is not a slash add one if (fullpath[z-1] != '\\') { fullpath[z] = '\\'; z++; } // adds the file name to the character array, completing the full path for(int x=0; x<gfile.size() && x<100 && z<202; x++) { fullpath[z] = gfile.at(x); z++; } // end set full file name and path // creates Response header Response currentResponse(currentHeader, getmy_server_name(), fullpath); // SEND OUT TO LOG FILE logRequest(sock->getForeignAddress(), currentHeader.get_path_info(), currentResponse.getmy_status()); //BUILD HEADER TO SEND THEM char final[1200] = ""; // actually build the header strcpy(final, getmy_http_version()); strcat(final, " "); strcat(final, currentResponse.getmy_status()); strcat(final, "\r\nServer: "); strcat(final, getmy_server_name()); strcat(final, "\r\nContent-type: "); strcat(final, currentResponse.getmy_content_type()); strcat(final, "\r\nContent-length: "); strcat(final, currentResponse.getmy_content_length()); strcat(final, "\r\n\r\n");

10

// SENDING THE FILE TO THE CLIENT // SEND THE CLIENT THE HEADER sock->send(final, strlen(final)); // SEND THEM THE FILE ONLY IF IT WAS SUCCESSFUL if (strcmp(currentResponse.getmy_status(), "202 OK")){ char next2[1] = ""; int counter2=0; ifstream test; test.open(fullpath, ios::in | ios::binary); if (test.fail()){ // if the file does not exist do not send them anything } else { test.get(next2[0]); // ACUTALLY SEND THEM THE FILE while(! test.eof()){ sock->send(next2, 1); test.get(next2[0]); counter2++; } } test.close(); } delete sock; }

B.2 Header Class


// constructor explicit Header(char header[1000], int size, char defaultFile[100]) { // Preconditions Require(size<1000, "No overflow"); string tempheader = header; Require(tempheader.size()<1000, "No overflow"); //Implementation set_Header_fields(header, size, defaultFile); // Postconditions Ensure(my_header_length>0, "Not empty"); Ensure(my_header_length<1000, "Not overflowing buffer"); Ensure(my_accept_num>=0, "Not negative"); Ensure(my_encoding_num>=0, "Not negative"); Ensure(my_path_info.size()>0 && my_version.size()>0 && my_request_method.size()>0, "Header at least has one line"); } // parses the header and sets each of the fields void set_Header_fields( char header[], int size, char defaultFile[100]) { // Preconditions Require(size>0, "Not empty"); // local variable declarations my_header_length = size;

11

int my_string_size = 0; int my_accept_size = 0; int my_path_size = 0; int my_encoding_size = 0; char c; // current element in array char c2; // next element in array // More Preconditions Observe("Old my_string_size", my_string_size); Observe("Old my_accept_size", my_accept_size); Observe("Old my_path_size", my_path_size); Observe("Old my_encoding_size", my_encoding_size); // Implementation

B.3 Response Class


// constructor explicit Response(Header myHeader, char sname[100], char fullpath[202]){ string tempsname = sname; string tempfullpath = fullpath; // Preconditions Require(tempsname.size()<100, "No overflowing"); Require(tempfullpath.size()<202, "No overflowing"); // Implementation //check if valid filename/check if file exist, then set the status validation(myHeader.get_path_info(), fullpath);

//check if valid filename/check if file exist, then set the status void validation(string gfile, char fullpath[202]){ //check to see if they are trying to access something they shouldnt if (gfile.find("..\\") < 1000 && gfile.find("..\\") >= 0) setmy_status("403"); else { //if it is a valid path check to make sure the file exists ifstream fin; fin.open(fullpath, ios::in | ios::binary); //if the file does not exist log it as a 404 if (fin.fail()){ setmy_status("404"); } else //if it does exist say we've found it with 200 { setmy_status("200 OK"); } fin.close(); } }

12

You might also like