Contents
changelog:
- 17 April 2019: clarify that do not care what port number data connections are initiated from and that default client data port numbers do not need to be supported.
- 17 April 2019: note that we do not care if you support the default client data port
- 19 April 2019: also mention this semester’s slides
Your Task
-
Download the skeleton code that includes a Makefile template that produces an
ftp-server
binary that does nothing but accept a port number from the command-line and print out a message about nothing being implemented. (The skeleton also includes a test program for a particular corner case, described below.) - In C or C++, implement an FTP server that supports FTP as described in RFC 959 implementing
at least the following commands:
- USER (which should not require a password or any particular user name)
- QUIT
- PORT
- TYPE
- MODE
- STRU
- STOR
- RETR
- NOOP
- LIST
- MKD
(Note that your implementation of some of these commands may be trivial.)
If you choose, you may implement additional commands. For any command you do not implement, you must return a response indicating that the command is not implemented.
In addition:
- Your server (for its server socket) must bind to the port number specified on the command line and always bind to 127.0.0.1 (IPv4 localhost).
- The root directory and default directory of your server should be the current working directory when it is executed, so storing or retrieving either
/foo
orfoo
should access thefoo
file in the server’s current working directory. - The
LIST
command should send the result of runningls -l
on the given directory if it exists or result in an error otherwise. You can run the ls command to do this; you do not need to reimplement it. - You do not need to support RECORD or PAGE structure (only FILE). A client using the
STRU R
command to change from FILE mode should result in the response “504 Command not implemented for that parameter”. - You do not need to support any transmission mode besides STREAM. (You may give an error if a client tries to set another transmission mode.)
- Except for the
LIST
command, you only need to support the Image file type (“binary mode”); that is, you will return byte-for-byte copies of files, rather than havdling theNVT-ASCII
(“text mode”) representation. If a client attempts atSTOR
orRETR
without first switching the binary mode, it should result in the response “451 Requested action aborted: local error in processing”. (ForLIST
, you need to allow transfers with the ASCII Non-print type, but we will not check whether you transform newlines into CRLFs.) - Your server only needs to handle one active client at a time.
- You server only needs to support IPv4. When/if using
getaddrinfo
, I’d recommend settingai_family
toAF_INET
in the hints. - You may assume that FTP commands are no longer than 4096 bytes.
- You may reject any pathnames containing “..” to prevent clients from accessing things outside the server’s current working directory. (It is okay if you reject pathnames which have “..” in the middle of a filename like “/foo..bar”.)
- Your server’s data connections may be initiated from any port number (but must connect to the client).
- We will not test if you support the default client data port (the port number used if no PORT command is run).
- Create a .tar.gz archive, like the one
make submit
would create and submit via the usual submission site.
FTP References
-
RFC 959 — the official specification. Sections 4, 5 and 6 are most useful. Section 7 has examples.
-
List of raw FTP commands at http://www.nsftools.com/tips/RawFTP.htm
Sockets References
-
the POSIX documentation — search for individual functions like
socket
,getaddrinfo
,bind
,accept
,listen
. -
this example echo server and example echo client.
-
the slides from 9 April and 11 April and their lecture recordings
-
the slides and lecture recording from 15 November Fall 2018
Hints
Testing
-
We suggest primarily testing with an FTP client like the
ftp
command in your VM. You can run a command lineftp 127.0.0.1 PORT-NUMBER
where PORT-NUMBER is the same value you passed as an argument to your server program.
-
You should be able to do at least the following in the FTP client: (This list may not be exhaustive.)
- Create a directory
- Get an error when trying to create a directory that already exists.
- After setting binary mode, store a file
- After setting binary mode, store a file that contains NUL characters in its contents
- After setting binary mode, retrieve a file
- After setting binary mode, retrieve a file that contains NUL characters in its contents
- After setting binary mode, fail to retrieve a file that doesn’t exist
- List the root directory by running
ls
without an argument - List the root directory by running
ls /
- List a subdirectory by running
ls the-subdir
orls /the-subdir
- Quit
- Restart the client and connect again to the server after quitting (without restarting the server)
-
Note that you will need to test your server with a port number higher than 1024.
test-partial-reads
-
We have supplied, with the skeleton code, a test-partial-reads program which connects to your server and sends the following commands
USER foo MKD testSplitName QUIT
but the
MKD
command is written by writingMKD testSplit
, waiting one second, then writingName<CRLF>
. This can help test whether your server handles the case whereread
returns less than expected in the control channel. (In practice, this would probably only be a problem for much longer commands or if someone were manually typing in commands or if you wanted to add support for a client sending multiple commands without waiting for replies to those commands.)
General advice on order of implementation
(This does not represent the only, nor perhaps the best approach.)
-
I would suggest first getting the server working enough that the FTP client can connect and you can parse and reply to the commands it sends with a not implemented message.
-
Commands that require a data connection are probably the hardest to implement, so I would save them for last.
LIST
is probably the most complex command to implement.
Dealing with partial reads
- When using
read
, it is possible that you will read less than a line or parts of multiple lines. To deal with this, I recommend keeping a buffer of bytes you’ve read but not processed, searching that buffer for the newline character sequence, and, as long newlines aren’t present, calling read again to add to the buffer.
Select specification details
-
Lines sent over the network typically end with a CRLF, which is a two byte sequence.
-
The specification requires that clients not send anything after they connect until they receive an initial message from the server. This is the most common cause of the client hanging.
-
Unless the specification clearly specifies an exact status code to use (like it does for MKD), you can generally supply any sensible status code listed in the specification’s list of status codes with the same first digit.
-
The specification has a very long list of potential error codes. We do not expect you to be able to produce all of these; for example, it is acceptable to allow additional variants of a command and therefore never report a “syntax error” for a particular command.
What is most important is that you do not claim operations are successful (2xx status) when they are not, and that your server produces codes that accurately reflect the state of any data connection.
make directory
mkdir()
is the POSIX functions creating directories respectively
on QUIT
- QUIT should not exit the server, only close the current connection.
on strace
- The
strace
command can show you all the system calls your server is making. Notably, this will show you everything being read/written to the network, so I find it helpful for debugging servers that seem not to be behaving correctly.