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.
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: