CS 485 Programming Assignment 5
Restricted remote shell

Due: Friday, 22 April, 2016 by 11:59:59 PM. Late submissions will be accepted until Tuesday, 26 April without penalty. The absolute last day to submit (with a 30% penalty) is Friday, 29 April.

Programming assignments in CS 485G are individual work. You may discuss approaches with other students, but may not share code or pseudocode for the assignment. If do get ideas from somebody, or use snippets of code from elsewhere, you must cite the source in your documentation.

Added note: There are security problems inherent in an unencrypted protocol such as this one; and with storing passwords in plain text. That is why the ssh protocol was invented. The "Limitations" section of your README should briefly discuss these problems, and other security implications of your program.

Update: If you are using C++, you will need a slightly different version of csapp.h. This version includes the necessary compatibility code to allow the header to be used from both C and C++ code. Otherwise you will receive errors about undefined functions. See the Wikipedia article on "name mangling" for more details about why this is necessary.

Also, be careful that you compile csapp.c with a C compiler (gcc), not a C++ compiler (g++). The simplest way to do this is to list the object file csapp.o, rather than the source code csapp.c, as a prerequisite for your executable. That is, if you have a rule like:

    rrsh-server: server.cpp csapp.c
then change it to:
    rrsh-server: server.cpp csapp.o
Make will be smart enough to figure out that it needs to create csapp.o using the C compiler.

Clarification: If the command was disallowed, there is no need to fork.

Contents

Background

Networks have long provided users with the ability to access and control remote computers through remote shells. Examples include rsh, telnet, and ssh. Each has different features and security characteristics. The remote machine (i.e., the machine being controlled) runs a server process that accepts connections, reads the user's identity over the TCP connection, verifies that user is allowed to access the system, sends a response over the TCP connection to say it is ready for commands. It then repeatedly reads input from the user and returns output from the commands until the session ends (e.g., the user types exit). The user's machine runs a client application that initiates a connection to the remote server, sends the user's name and password and then waits for the server to send back a message granting access. It then reads input from the user, sends it to the remote server to be executed, and then waits for the server to send back the command's output, at which point it starts the process all over again.

In this project you will implement a very simple, and far more restrictive, remote shell. You will write a Restricted Remote Shell server (called rrsh-server). It restricts access in two ways. First, your rrsh-server will wait for users to connect and then will consult a configuration file (rrshusers.txt) to verify that the user is allowed to access the rrsh-server and has provided the correct password. Second, the rrsh-server will verify that the commands typed by the user are allowed commands. In particular, the rrsh-server will look in a configuration file called rrshcommands.txt for a list of allowable programs (commands) that can be run. Commands that are not in the list will be denied by your rrsh server; this is important not just for correctness, but for the security of your account.

You will also write the rrsh-client program. The rrsh-client program will read commands from the user's terminal and then pass them on to the rrsh-server to be executed on the remote machine.

The goal of this project is to help you understand the basics about network programming.

Specifications

I. The rrsh-server

Your rrsh-server should listen for incoming TCP connections using the IPaddress INADDR_ANY and a TCPport number specified as a command line parameter to your rrsh-server. Your rrsh-server will be invoked with the command

    ./rrsh-server TCPport
where TCPport is the port number on which your rrsh-server will listen for incoming connections. See the example echo server from the notes and textbook as an example of how to create a server listening on a particular TCP port.

Your rrsh-server will read two config files from the current working directory at startup:

  1. rrshusers.txt contains a list of users that are allowed to access the server. The file contains one user per line, where a user is specified by their username and password (separated by one or more space characters). Blank lines should be ignored. Usernames and passwords can be up to 40 characters long. If necessary, you may impose a limit of 512 user-password pairs, but this limit should be documented in your README.
  2. rrshcommands.txt contains a list of commands (i.e., executable programs) that user is allowed execute. The file contains one command (program) per line. Programs are specified using the file's full path name (e.g., /bin/ls). Only the path name, and not parameters, are included in this file. Blank lines should be ignored. If necessary, you may impose a limit of 512 commands (lines), and 128 characters per command name, but this should be documented in your README.

The ``login protocol'' used by the rrsh-client to access the rrsh-cserver will exchange the following information:

Usernames and passwords can be up to 40 characters long. The server will only accept and handle commands for one user at at time (i.e., only one user can be "logged in" at any given time).

After a user has successfully "logged in" to the server, the client will begin sending commands to the server. Each command line can be up to 128 characters long and will end with '\n'. The server will parse the command to determine what program to run: the program will be the first token on the command line. You may wish to use the parser from program 4, but rrsh should ignore any redirections specified in the command—this is important for the safety of your account! As long as your server is running, anyone in the world can run any of the commands it allows.

The server then checks to see that the requested program is listed in the rrshcommands.txt files. If the command is not allowed (i.e., cannot be found in the rrshcommands.txt file), the rrsh-server will return (i.e., send) the string "Cannot execute 'program' on this server\n", where program is the name of the executable program (file) that was not found in the rrshcommands.txt file. If it is allowed, the server will then fork() and execv() (not execvp) a child to handle the user's command.

Before calling execv(), the child process must perform the necessary dup2() operations so that:

Again, these are the only redirections your program should perform: ignore any redirections specified by the user.

When the child process terminates (whether successful or not), or after sending the "Cannot execute..." message, the rrsh-server (parent process) will send the string "\nRRSH COMMAND COMPLETED\n" to the rrsh-client, so that the client will know that the command finished and it can now send another command. The initial newline is necessary in case the command's output did not end in a newline, so that the client is guaranteed to see the string on its own line.

Your rrsh-server (i.e., the parent process) should print out the following information to the terminal (standard output):

II. The rrsh-client

You will also write the code for the rrsh-client program. You will invoke the rrsh-client program with the following command line arguments:

    ./rrsh-client MachineName TCPport
where the MachineName specifies the DNS name or the IP address (in dotted decimal format) where the rrsh-server is running, and TCPport is the port number where the rrsh-server is listening.

Your rrsh-client program with then prompt the user for a username, and then for a password (printing each prompt on a new line). Having read the user's response to these questions, the rrsh-client will establish a TCP connection to the rrsh-server and send the login information (as describe above). If the login fails, the client will terminate.

If the login succeeds, the rrsh-client will print the prompt "rrsh> " and will read a command from the user. It will then send the command (unmodified) to the rrsh-server and wait for the output from the command. When the command completes, your rrsh-client will receive the string "RRSH COMMAND COMPLETED" on its own line, which indicates the command finished. The rrsh-client should print to the terminal all of the program's output, up to but not including the string "RRSH COMMAND COMPLETED". This particular string is only to be read by the client, and should not be displayed to the user.

At this point your rrsh-client will reissue the prompt, read another command, send it to the rrsh-server, etc. It will continue to process commands until the command entered by the user is "quit". When the rrsh-client reads a quit command, it will terminate (without sending the command to the server).

Constraints and technical restrictions

Your code for this project must be written in C or C++ and must use either:
  1. The native Unix TCP/IP socket interface (i.e., socket calls: socket, bind, connect, write, etc.; or the CSAPP error-handling wrapper functionss Socket, Bind, etc.); or
  2. the helper functions and raw I/O library calls provided by the textbook (for example, open_clientfd, Open_listenfd, Rio_writen, and Rio_readlineb). These functions can be found on in the files csapp.h and csapp.c either on the book's code web page (http://csapp.cs.cmu.edu/3e/code.html) or in the lecture 16 example code.

    Note: If you are using C++ for your project, use this version of csapp.h instead, to avoid linker errors about undefined functions.

Your program should compile with the -Wall compiler flag (enable all warnings), without producing any compiler or linker warnings or errors.

It must be possible to compile and run your program on the class virtual machines. The sequence of commands make clean; make should build both parts of your program without any manual intervention.

Error handling

If any system call or library function fails, your program should report the reason for a failure in an error message to stderr, and either (1) handle and correct the error, (2) terminate the program (or the child process), or (3) (in built-in commands only) end the command without terminating the program. If your program does terminate because of an error, it should indicate this by calling exit with a nonzero status code.

For this assignment, you may use the wrapper functions provided in csapp.c. These functions, with names beginning with a capital letter (Open, Socket, and so on) call the underlying (lowercase) system call or library function, then print an appropriate error message and exit the program if there was an error.

Hints and advice

Getting started

We recommend that you start by modifying the echo client and server provided in the Lecture 16 code, and in the textbook under directory netp/.

You will need to modify the client and server to talk the rrsh protocol described above. In particular, the client must read several lines from the server, until it sees the line "RRSH COMMAND COMPLETED"; and the server must execute the lines it reads as commands, rather than simply echoing them back to the client. You will also need to add code to read the configuration files, and then check the login username against the rrshusers.txt configuration file on each login, and the command against the rrshcommands.txt configuration file on each command.

Testing your rrsh-client and rrsh-server

Your code must compile and run on your VM. If you want to write your code on another machine and port it to your VM later, that is up to you, but it is your responsibility to get the code running on your VM before you submit it.

You can run both your server and your client on your VM. If you would like to run your rrsh-client on other machines to be sure it works from anywhere in the Internet, any Linux machine should work. OS X might work as well, but there are no guarantees there.

An example rrsh-client session might look like the following:

  bash $ ./rrsh-client 127.0.0.1 4850
  Username: testuser
  Password: LetMeIn
  Login Approved
  rrsh > /bin/ls /tmp
  config-err-gUsMxg  config-err-I4qlYd  qt_temp.jd2802  qt_temp.jd4494

  rrsh > /bin/cat /etc/issue
  Ubuntu 14.04.3 LTS \n \l


  rrsh > /bin/grep 14 /etc/issue
  Cannot execute '/bin/grep' on this server

  rrsh > quit
  bash $ ./rrsh-client 127.0.0.1 4850
  Username: testuser
  Password: WrongPassword
  Login Failed
  bash $

A corresponding server session might look like the following:

  bash $ ./rrsh-server 4850
  User testuser logging in from 192.168.128.9 at TCP port 9648.
  User testuser successfully logged in.
  User testuser sent the command '/bin/ls /tmp' to be executed.
  Executing the command '/bin/ls /tmp' on behalf of testuser.
  User testuser sent the command '/bin/cat /etc/issue' to be executed.
  Executing the command '/bin/cat /etc/issue' on behalf of testuser.
  User testuser sent the command '/bin/grep 14 /etc/issue' to be executed.
  The command '/bin/grep 14 /etc/issue' is not allowed.
  User testuser disconnected.
  User testuser logging in from 192.168.128.9 at TCP port 9648.
  User testuser denied access.
  ^C
  bash $

The example configuration files that would go with the above session could be:

rrshusers.txt

  testuser     LetMeIn
  cs485user p4password

rrshcommands.txt

  /bin/ls
  /bin/cat
  /bin/wc

Documentation

Your submission should include a README file. This should be a plain text file with at least the following sections:

What to submit

Submit a zip or tar.gz file containing a directory with the following files:

To make a .tar.gz archive of the directory program5, you can use a command like:

    tar czvf cs485-program5.tar.gz program5/
To make a .zip archive:
    zip -r cs485-program5.zip program5/

Submit your .tar.gz or .zip file at the CS Portal website, under course CS485G006 and assignment "Programming Assignment 5 - rrsh".

Grading

This assignment will be scored out of 100 points:



Based on an assignment by James Griffioen
Modifications by Neil Moore.