CMSC 491N - Network Programming - Fall 2004

FTP Server Assignment


The goal of this assignment is to build a forking TCP server program that behaves as a simple FTP server. You will build on the basic TCP server from the first assignment.

In all problems you should make an effort to handle errors in a reasonable way, they will be tested with invalid inputs or usage. Give the user a meaningful message if they misuse the program or an error occurs.

The sample input/output shown in the problem statements are not an exhaustive list of tests, you should invent and try others.

Your code should have some comments. They should indicate who wrote the program, what the purpose of the program is, how to run the program and explain what any important or potentially confusing code is doing.

Assignment Due: Wed 27 Oct

To submit: submit cs491n ftp <list of files>

The <list of files> should include all your source files (.c and .h) as well as the Makefile you used to build the program (if you used one). If you didn't use a Makefile, include comments or a README indicating how you built it.


FTP Server, ftp_serv

Write a forking FTP Server program in C using TCP sockets. It should allow the user to connect, see a list of files available, and download their choices of the files. It should allow for multiple clients at a time. It should operate in "passive" and "binary" modes only.

Usage

Your program should take some optional command switches to specify behavior.

The -p switch will tell the server which port to listen on (default = 7000).

The -d switch should tell the server which directory to offer files from (default = current working directory, ".").

The -l switch should tell the server the name of a file to log messages to when running (default = "./LOG").

sample server startup
$ ftp_serv                              # default operation
started ftp server on 0.0.0.0:7000
started ftp server with pid 5527
started ftp server on port 7000
started ftp server for directory .
started ftp server with logfile ./LOG

$ ftp_serv -p 8001 -d srv -l MY.LOG     # all options
started ftp server on 0.0.0.0:8001
started ftp server with pid 5528
started ftp server on port 8001
started ftp server for directory srv
started ftp server with logfile MY.LOG

Your program should operate with a typical ftp client. The user should be required to enter passive mode before any data is sent (by LIST or RETR).

The commands your server must recognize and respond to:

The server should reply to every command with a response that includes a numeric code and a text message. Any commands not recognized should be responded to with a 500 message (the client needs a response so it can keep going).

The client MUST enter passive mode and send the PASV commands to you. You reply with a message containing the IP/port that the client will contact for the data channel. If the client does not do this, it is their problem, we're only handling passive mode.

sample client operation
$ ftp localhost 8001
Connected to localhost.
200 Accepted
500 command not recognized
500 command not recognized
KERBEROS_V4 rejected as an authentication type
Name (localhost:gslong): greg
331 user ok, send password
Password:
230 password ok, you are in
ftp> passive
Passive mode on.
ftp> ls
227 entering passive mode (127,0,0,1,131,189)
150 opening data connection for list
big
a
b
226 transfer complete
ftp> binary
200 Type set to I
ftp> get big
local: big remote: big
227 entering passive mode (127,0,0,1,131,191)
150 opening BINARY data connection for file download
226 transfer complete
100400 bytes received in 0.0066 seconds (1.5e+04 Kbytes/s)
ftp> get a
local: a remote: a
227 entering passive mode (127,0,0,1,131,193)
150 opening BINARY data connection for file download
226 transfer complete
38 bytes received in 2.3e-05 seconds (1.6e+03 Kbytes/s)
ftp> get notthere
local: notthere remote: notthere
227 entering passive mode (127,0,0,1,131,195)
550 file not found
ftp> quit
200 exiting
$ diff a ../srv/a
$ cmp big ../srv/big              # validate they came out ok
$
sample server operation
$ ftp_serv -p 8001 -d srv -l my.log
started ftp server on 0.0.0.0:8001       # some stdout msgs for me
started ftp server with pid 5565         # rest to logfile
started ftp server on port 8001
started ftp server for directory srv
started ftp server with logfile my.log
<ctrl-c>

$ cat my.log
started ftp server on 0.0.0.0:8001
started ftp server with pid 5565
started ftp server on port 8001
started ftp server for directory srv
started ftp server with logfile my.log
127.0.0.1:33724: new connection
127.0.0.1:33724: command: AUTH GSSAPI
127.0.0.1:33724: command: AUTH KERBEROS_V4
127.0.0.1:33724: command: USER greg
127.0.0.1:33724: command: PASS pass
127.0.0.1:33724: command: SYST
127.0.0.1:33724: command: PASV
ftp server entering passive mode on (127,0,0,1,3,189) (port 33725)
127.0.0.1:33724: command: LIST
127.0.0.1:33724: command: TYPE I
127.0.0.1:33724: command: PASV
ftp server entering passive mode on (127,0,0,1,3,191) (port 33727)
127.0.0.1:33724: command: RETR big
127.0.0.1:33724: command: PASV
ftp server entering passive mode on (127,0,0,1,3,193) (port 33729)
127.0.0.1:33724: command: RETR a
127.0.0.1:33724: command: PASV
ftp server entering passive mode on (127,0,0,1,3,195) (port 33731)
127.0.0.1:33724: command: RETR notthere
ftp server cannot open file: No such file or directory
127.0.0.1:33724: command: QUIT
127.0.0.1:33724: end connection

Details

Before the server begins to listen for connections, it should verify that the directory to serve files from exists and has at least one file in it. But beyond that, assume the list of available files can change at any time, the server should recheck it frequently. The opendir()/readdir()/closedir() functions might be useful here. Files whose names begin with "." should not be shown to the client or allowed to be transfered. You can assume the directory full of files will only contain files, not subdirectories.

Each client that connects should talk to a different fork()d child of the server. That child handles all communications (command and data) with the client. Each client is only expected to have a single data connection running at a time.

For logging, try to log anything of interest. The examples shown above are representative. Error messages should definitely be logged. You can overwrite the old logfile if it exists, or choose to append to it, your choice.

For transferring files, send the data as you normally would. You might need to have the client request binary mode and send a response to it's TYPE command to fool it, but otherwise you don't need to do anything differently. Should need to have the client emit "TYPE I" to enter binary mode for file transfer and "TYPE A" to have it enter ascii mode for the LIST data transfer.

Use "diff" or "cmp" as appropriate to make sure your files downloaded properly. Be sure to test with large files.

When different clients are connected at the same time and you have multiple child processes handling them, be sure to properly lock the logfile when writing messages. The flock() function is sufficient in a logging function for everyone to call.

You should be able to build this a piece at a time. Use what you read as incoming requests from the ftp client to help you understand what you should be sending back and when. Try performing ftp sessions to servers to see how they behave with a client.

FTP client behavior may vary. They may send more commands than you are expecting. They may demand more than just "\n" for a line terminator. You may need to manually enable binary and passive modes.

You should consider using getopt() to parse the commandline switches.


Q: Having a problem using getsockname() to get the local IP address.
A: Yea. If you're using INADDR_ANY it will just give you back 0.0.0.0 for your IP address. This is a problem when trying to send over a string to the client for passive mode containing the IP address. Consider using an ioctl() described in the lecture notes (csock 08) to fetch the IP address of eth0. You might not notice the problem when running your client and server on the same machine, but when they are on different ones it will be a problem.

Q: Will you be testing with ncftp?
A: No. It sends way too many unexpected commands, I'll stick with a "plain" ftp client.

Q: Are there other ways to test besides using an ftp client?
A: Yes. If you have the "nc" (netcat) command, try this:

$ nc <servername> <port>
220 welcome
USER anonymous
331 give me password
PASS foo
230 you cool
PASV
227 going passive (x,x,x,x,x,x)
...

(telnet also should work, for both you'll need a second session to hit the data channel.)

Q: Should we worry about security? Like letting people download files with paths in the names? (ie: RETR ../../../etc/passwd)
A: Only let them have files in the current directory. Enforce them giving you a username/password before letting them do anything else.


Other

Some other, NOT required things you might want to try: