Purpose In this lab, you’ll use what you’ve learned about Racket’s universe to create a chat client that works with a server on another machine. Once you have learned how to create the client, you’ll use it to exploit vulnerabilities in different servers.
You also get practice working with code written by others - including code that is insufficiently tested, like this code written by your time-strapped or lazy TAs.
To start, you will need to download the initial chat client to edit. (Right click and save target.) Right now, this client only works on your computer. Your job is to modify it to connect to the server.
TAs: The students have not covered universe in lecture, so go through the basics with them before letting them loose.
The Universe teachpack allows for client/server programs– that is, programs where a server manages multiple clients, and a client and the server communicate through Messages.
To start our client, we use the big-bang form that we’ve been using all semester, while the server is started (by your friendly TAs) using a new universe form. A longer description of client/server programming can be found on the main Universe HelpDesk page.
Client programs look very similar to the kinds of interactive programs we’ve already written. There are just a few changes involved:
A name clause to identify the client,
A register clause to connect to a server, and
New protocols for sending (Packages) and receiving (Messages) to/from the server.
Connecting to the Server
To connect we use the clause (register String), where the String is either a hostname or an IP address. When big-bang is executed, it attempts to connect to the universe running on the given machine. If the connection fails the program (i.e., the client) will run locally without sending Messages.
In addition, a client should specify a name using (name String) to identify it to other clients. In our chat program, we will use it as the nickname of each connected user.
Sending Messages to the server
To send Messages to the server, functions that originally returned a new World can instead (optionally) return a package. For example, the on-key clause originally took a function with the following contract:
; World KeyEvent -> World
But now we’ll treat it as:
; World KeyEvent -> [Or World Package] ; An [Or X Y] is one of: ; - X ; - Y
A package consists of two items: a new World (as before) and a Message to send to the server.
; A Package is: (make-package World Message) ; A Message is an SExpr ; An Atom is one of: ; - Number ; - Symbol ; - String ; An SExpr is one of: ; - Atom ; - [List-of SExpr]
First things first... your name’s not Alfred E. Neuman right? Modify your nick to something reasonable (keep it at most PG-13, please). Now look through the code, figure out where we’ve marked **** MODIFY HERE ****, and try to get an idea of what the code does.
Now that you have a background on the World, Universe, and the client code, we can start setting things up. Your basic chat client currently echoes what you’ve typed to your screen whenever you hit Enter. What we need to do is make the client connect to the server, and start sending and receiving Messages.
To start out, we are going to send simple S-expressions for Messages: Strings. Server 0 will also send Messages to the client (you) as Strings.
A sample server Message: "Alfred : Knock knock!" A sample client Message: "Neuman : Who's there?"
Exercise 1 Update the handle-key client function to return a Package whenever you hit Enter. Unlike the local implementation it should not add text to the buffer, but instead just send it as a Message to the server. Remember to update the signature to mirror the fact that handle-key may now return a World or Package.
Exercise 2 Design the function handle-msg that can be installed with on-receive. The function should take a World (i.e., a Client) and a Message (i.e., a String) and add the Message to the line buffer of the client. When you’re done, go to the big-bang form and uncomment (on-receive handle-msg).
Exercise 3 Adjust the big-bang form to register with Server 0. On the board, your TA should have the names of the corresponding computers.
Now run it! It should now function as a little chat client. You may see other people connecting, and you should be able to send them Messages. The TAs should be starting up a client up on the projector (as you read this) so you can see people connecting, sending Messages, and disconnecting as they work on the lab.
As it turns out, in creating this server, the TAs didn’t test the code thoroughly enough, and there’s a vulnerability in the code that will allow you to crash the server based on the Messages it receives from the clients.
Exercise 4 Figure out how to crash Server 0.
Hint This is a very common approach used to bring down websites by overwhelming the servers.
Alright, we wrote more tests, figured out exactly what the problem was, and fixed it in a new server: Server 1. While we were at it, we decided to enhance the protocol to let you send Messages that are more complex than just Strings.
This makes the protocol more extensible to deal with future changes and additions. We do this by defining a Message to be a more complex SExpr: a non-empty [Listof Atom]. (Remember that an Atom is a String, Symbol, or Number.) The first element of the list is a String that defines the type of the Message and the meaning of the rest of the Atoms in the list depend on the Message type.
The first type of Message you will handle will be "MSG".
Clients send these Messages in the form (list "MSG" <content>), where content is a String - the content of the Message.
The server will add the approprite client name to the Message and forward the Message to the other clients in the format (list "MSG" <name> <content>), where name and content are Strings, name is the name of the client who sent the Message, and content is the content of the Message.
A sample server Message: (list "MSG" "Someone" "Knock knock!")
A sample client Message: (list "MSG" "Who's there?")
Exercise 5 Change your handle-msg (handling Messages from the server) and handle-key (sending client Messages to the server) functions to accomodate these new types of Messages instead of just Strings. Make sure you handle "MSG" Messages. (You can disregard all other types for now; we’ll do more with that later.) Make sure that you are now registering your client with Server 1, and try it out.
Exercise 6 Once you have successfully used this new protocol, try to find a way to break the server.
Thanks to your work catching our errors, the TAs have fixed this issue with the server and created Server 2. This server has also gained a new feature: you can now choose the color of your name in for the chat window.
We have now added a new type of Message the server will accept, in the form (list "COLOR" Number), where the Number is an integer from 1-8 that specifies one of the following colors for your nickname to appear as:
This will set a color for your instance of the chat client, which will be stored by the server and will also be seen by other users. In order for the client to know what color to draw Messages that it receives, incoming Messages from the Server are now in the form (list "MSG" <name> <content> <color>). We have already written the code for you to properly display to the proper nickname color based on packages received from the server to add to your handle-msg function:
(define (handle-msg c msg) (cond ... [(string=? (first msg) "MSG") (local [(define color (if (and (= (length msg) 3) (image-color? (third msg))) (third msg) FONT-COLOR))] (make-client (client-name c) (cons (list (string-append (second msg) NICK-DIVIDER (third msg)) color) (client-lines c)) (client-editor c) (client-curse-on? c)))] ...))
Now it’s up to you to implement the sending of the color Messages to the server.
Exercise 7 When a user’s Message starts with "/color", this should send a Message to the server with the "COLOR" type. For example, if Mitch typed /color 7, your chat client should send the Message (list "COLOR" 7).
Hint To help you out, we already wrote the functions string-prefix? and string-remove-prefix in the chat client file, with the following signatures:
; string-prefix? : String String -> Boolean ; checks to see if the second string is a prefix of the first ; string-remove-prefix : String String -> String ; returns a new string that is the same as the first string except ; with the second string removed as a prefix
Exercise 8 Now that you made your client work, use is to find our error in Server 2 and crash the server. Again.
After all of those mistakes, the TAs put our heads together and drank nothing but coffee for a week straight, but we think we finally came up with a bulletproof server: Server 3. But after all that work, we still didn’t have time to test everything. And Prof. Shivers has been known to sneak into our code after we finish it because he’s a fan of easter eggs.
Exercise 9 Connect your client to Server 3 and put it to the test. Can you find a way to crash the server?
Exercise 10 As we’ve pointed out many times, bad things happen when you don’t test your code. Look at how many errors we had! And look at how many tests are missing from the chat client we initially gave you! Go through the chat client and add tests to the functions that were missing them.