Building a simple FTP server in F#

Posted on November 6, 2011

##Why build a FTP server

I’ve just started learning F# and the application I decided to build needs a way of exposing a read-only directory structure/file system. I initially looked at WebDAV but it seemed unnecessarily complex for what I was trying to do, there also is apparently a compatibility issue between the windows implementation and the standard. FTP on the other hand is well documented, well supported and cross platform.

As it turns out building a FTP server that supports the minimum number of features is actually pretty simple. By implementing only ten of the many possible FTP commands I was able to build a FTP server that windows explorer and a number of FTP clients could happily use.

I was very surprised how easy building the server was. I’m sure I’ll use this approach for a number of different applications in the future. Hopefully I can convince you to do the same :)

Two things to note however; firstly this is obviously not meant to be a production grade server but it could certainly be taken to that extent if required. Secondly this is only my third day of F# so I may well have missed some F# tricks or done some things in a non-idiomatic way.

##The FTP Protocol

FTP is a line based protocol like telnet. This is part of what makes implementing it so easy.

For example this is what an authentication conversation would look like

That is it, pretty simple, right?

For more details on the protocol you can take a look at RFC959.

There are also many sites that explain the protocol in plain English. Here are three sites that I found particularly useful in getting started

  1. An Overview of the File Transfer Protocol http://www.ncftp.com/libncftp/doc/ftp_overview.html
  2. List of raw FTP commands http://www.nsftools.com/tips/RawFTP.htm
  3. FTP, File Transfer Protocol http://www.networksorcery.com/enp/protocol/ftp.htm

##The minimal set of FTP commands

To get the FTP server to support a few FTP clients (FileZilla, explorer and ftp.exe command line client) I had to implement the following commands

Clearly there is a lot that I have not implemented and I may extend what I support (e.g. CDUP, PASSV etc) but for now this is all I need.

##Code structure

The full source code is available on githib at https://github.com/andrevdm/FSharpFtpServer.

The code is structured as follows

The idea here is that it is easy to plug in different functionality, for example different directory providers. I’m using this server to expose a virtual file system (something like the Git file system, more about that in a future blog) so I’ve not put much effort into the FilySystemDirectoryProvider but it should be pretty simple to get it working correctly.

##Code examples

The login conversation was show above this is how it looks in the code.

When a client connects the server sends a 220 “hello” command.

Here Send is a method that takes the code (220), the sting (“Hello”) and the socket to respond to.

The logged in state is then managed by the loginState mutable state variable. This can have one of the following values

type ftpLoginState = 
  | ExpectUserName 
  | ExpectPassword 
  | LoggedIn

The incoming lines are sent to the Handle() method

override t.Handle( line, socket ) = 
async{ 
   if line = "QUIT" then
    t.Stop() 
   else   
    match loginState with
     | ExpectUserName -> do! t.HandleLoginUserName( line, socket ) 
     | ExpectPassword -> do! t.HandleLoginPassword( line, socket ) 
     | LoggedIn -> do! t.HandleCommand( line, socket ) 
     | _ -> failwith ("unknown ftpLoginState " + loginState.ToString()) 
}

This method works as follows

  1. If the QUIT command is received then call the Stop() method to terminate the connection
  2. All other commands are then interpreted based on the current login state

    1. If expecting a user name call HandleLoginUserName
    2. If expecting a password call HandleLoginPassword
    3. If logged in the call the HandleCommand method which handles the main FTP commands
    4. Any other state is invalid

The HandleCommand method then responds to the rest of the FTP commands.

member t.HandleCommand( line, socket ) = 
  async{ 
     let c,r = t.SplitCmd line
 
     match c with
        | "PORT" -> do! t.SetPort( r, socket ) 
        | "NLST"
        | "LIST" -> do! t.SendList( r, socket )                    
        | "PWD" -> do! t.Send 257 ("\"" + dirProvider.CurrentPath + "\" is the current directory") socket 
        | "TYPE" -> do! t.Send 200 "ignored" socket 
        | "RETR" -> do! t.RetrieveFile( r, socket ) 
        | "CWD" -> 
          if dirProvider.ChangeDir( r ) then do! t.Send 200 "directory changed" socket 
          else do! t.Send 552 "Invalid directory" socket 
        | _ -> do! t.Send 502 "Command not implemented" socket
 
  }

The SplitCmd call is a simple method to split the received line.

member t.SplitCmd( line ) = 

  let m = Regex.Match( line, @"^(?<cmd>[^ ]+)( (?<rest>.*))?" ) 


  if not m.Success then failwith ("invalid command: " + line) 


  (m.Groups.["cmd"].Value, m.Groups.["rest"].Value)

##LIST / NLST

The LIST and NLST commands request the server to send a directory listing. Rather bizarrely there is no actual standard for this format. However most FTP server return the listing matching the standard unix ls format and most FTP clients expect this too.

Here is how the FileSystemDirectoryProvider returns the files and sub-directories of the current path

member t.List() = 
   let dir = new StringBuilder() 
    
   let info = new DirectoryInfo( physicalDirectory )
 
   info.GetFiles() 
      |> Array.iter (fun f -> 
        dir.AppendFormat( "-r--r--r--  1   owner   group {1} 1970 01 01  {0}", f.Name, f.Length ).AppendLine() |> ignore )
 
   info.GetDirectories() 
      |> Array.iter (fun d -> 
        dir.AppendFormat( "dr--r--r--  1   owner   group {1} 1970 01 01  {0}", d.Name, 0 ).AppendLine() |> ignore )
 
   dir.ToString()

This listing is then sent back to the client by the FtpHandler, see the section on the PORT command below

member t.SendList( p, socket ) = 
  async{ 
     do! t.Send 150 "Opening ASCII mode data connection for /bin/ls" socket 
   
     use sender = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) 
     let endpoint = IPEndPoint(ipAddr, port) 
     sender.Connect( endpoint ) 
   
     let! sent = sender.AsyncSend( Encoding.ASCII.GetBytes( dirProvider.List() ) ) 
     do! t.Send 226 "Listing completed." socket 
  }

##PORT and active mode data transfer

FTP uses different ports for its command and data communications. The command socket is always on the port that you connect on (typically port 21), data ports are created dynamically as required.

There are two ways in which ports are assigned

  1. In active mode the client creates a socket to listen for data on and tells the server the IP and port number
  2. In passive mode the client requests that server create a listener and return the IP and port. Passive mode is often preferred as it is simpler to support behind a firewall.

It was marginally simpler for me to implement active mode so for now that is all the server supports.

In active mode a data transfer like the LIST command described above would look like this

So the client creates a listening socket and sends the details to the server. The port is split into high,low. The number = high * 256 + low. In this case 4*256+1 = 1026

The client then requests the list.

Finally the server then

  1. Tells the client that it is about to send the data
  2. Sends the data over the new data connection
  3. Tells the client that the data was sent successfully

##In summary

I was able to build a simple FTP server in a few hundred lines of F#. I’m sure I’ll be using this code in a number of projects in the future. I hope that it will prove useful to you as well.