Now that you have a working web server you need a simple way to transfer your web contents to it and most people are probably used to ftp for that task. FreeBSD comes with an ftp server built-in and you just have to activate it.
Access ftp from internal LAN
Making the ftp server accessible from the LAN only is a very simple procedure. The ftp server is very small and can be started by Inetd when requested, just like we start the pop3 server, and everything is already prepared for it. Edit /etc/inetd.conf and find the following line:
#ftp stream tcp nowait root /usr/libexec/ftpd ftpd -l
Uncomment it and save the file.
ftp stream tcp nowait root /usr/libexec/ftpd ftpd -l
Note that there is an almost identical line below for IPv6 (tcp6) but leave it alone. It's only used if you want to access the ftp server with IPv6.
Now you could restart Inetd to make it read the new configuration but there is another way to make it read its configuration without having to stop it. Many services can be forced to reread their configuration without restarting it by sending it the HUP signal.
# killall -HUP inetd
You should now be able to ftp to your router from your LAN and you logon with the normal user accounts you have added in FreeBSD.
Access ftp from the Internet
Allowing ftp access from the Internet complicates things. Ftp is a very old protocol and there are both security issues and technical issues that must be addressed.
Remember that there is no encryption support in FreeBSD's ftp server so all logons are made in plain text. Anyone who is able to eavesdrop on your Internet connection can see your password. Preferably you should establish an ssh tunnel to the router and ftp through it but this is after all a hobby project so let's not make it too complicated right now. However, an ftp server is probably the second best target for a hacker after root shell access and you will have plenty of hacker attempts as long as you run the ftp server on its standard port 21. The first thing to do is therefore to move it somewhere else. This is security by obscurity but it's the best you can do for now. Open /etc/services in your editor and find the ftp tcp line and make a copy of it (don't mix it up with the udp version right below it):
ftp 21/tcp #File Transfer [Control]
Select a new port on random between 1024 and 49151 and make sure it isn't already defined in this file. In this example I'll use 12345. Paste the old ftp line in its new place and change the service name to ftp2 instead and adjust the port number. Then save the file and exit.
ftp2 12345/tcp #File Transfer [hidden]
Open /etc/inetd.conf again and restore the comment sign first on the ftp line that you removed previously. Then make a copy of that line, paste it below and change it like this (of course removing the comment on this line):
ftp2 stream tcp nowait root /usr/libexec/ftpd ftpd -ll
ftp2 will make sure it won't interfere with ftp and yes, those are double lower case 'L's at the end. They will increase the level of logging so you better can monitor the use of your ftp server. After you reload Inetd's configuration, the ftp server should now respond on port 12345 instead of port 21.
# killall -HUP inetd
Active and passive mode
To better understand the firewall rules you need to add to make this work, I first have to get a little technical and explain how ftp communication works and why it sucks so immensely.
Under normal circumstance the client opens a random local outgoing port and connects to port 21 on the ftp server (this will be 12345 in my example). This is the control connection and when it's established it's used to send all commands to the server, like requests for directory listings and requests for file transfers. The actual data transfer, be it a directory listing or a file, isn't carried out here but will be performed on a totally separate and parallel connection.
In active mode the client opens another random local port, sends this port number and its local IP address to the server and then tells the server over the control connection to connect to it on this secondary port and transfer the file. This will be problematic because most clients today are behind firewalls and the firewall has no way of knowing that an internal client has opened a local port and expects the firewall to suddenly let the external ftp server initiate a connection through the firewall to that port. Remember that no data has been sent from the client on this port so no state has been created in the firewall to allow traffic back in. An even bigger problem is that most clients probably are behind a NAT device and use internal 192.168.x.x addresses. This is the IP address the ftp server is sent and expected to connect to and the server has of course no chance in hell to do that since these IP addresses aren't routable on the Internet at all. The data you requested will disappear in cyberspace and never come close to your computer. You probably realize now that the ftp protocol was invented long before there was NAT and firewalls.
In passive mode the client can work around these limitations. The control connection is established in the same way as in active mode but the data connection procedure is now reversed. In passive mode the server opens a random listening port, sends the client this port number and its IP address over the control connection and tells the client to connect to the server and transfer the file. This eliminates the problem for the client since outgoing traffic usually is allowed through the firewall. Instead it's up to the ftp server administrator to solve the firewall and NAT problems which in this example means you! :-)
NAT won't be a problem for your ftp server since it runs directly on the router and uses its public IP address when a connection is made from the Internet. You only have to deal with the firewall rules and you need a rule allowing incoming connections on port 12345 but also a rule allowing incoming connections on all those random data ports the ftp server can listen to. By default in FreeBSD these incoming data ports can be anyone from 49152 to 65535. Opening up more than 16,000 ports in the firewall is of course unacceptable. Any misconfigured service, that accidentally binds to the external interface and a port in that range, would be completely unprotected. If you really need external ftp access to your router you can't eliminate this risk completely but you can at least narrow the port range considerably. That however could theoretically lead to a situation where the ftp server runs out of available ports. The port range above is called "Dynamic and/or Private Ports" in FreeBSD and its boundaries are controlled by two kernel variables. The ftp server reads these variables when it starts. To look at their current values you can type the following:
# sysctl net.inet.ip.portrange.hifirst net.inet.ip.portrange.hifirst: 49152 # sysctl net.inet.ip.portrange.hilast net.inet.ip.portrange.hilast: 65535
Keep the upper boundary and only change the lower value, reserving 256 ports:
# sysctl -w net.inet.ip.portrange.hifirst=65280 net.inet.ip.portrange.hifirst: 49152 -> 65280
Make this new value permanent by adding it to /etc/sysctl.conf:
# echo net.inet.ip.portrange.hifirst=65280 >> /etc/sysctl.conf
Then add the following inbound rules last in the section called # Incoming to router. Their exact placement doesn't actually matter right now since there are no other rules there monitoring the same ports. In general though, you want to keep high priority, low bandwidth rules in the beginning of each section and default priority, high bandwidth rules at the end. The placement of the friends ftp rules before the default ftp rules are important though in case you want to allow ftp access to some of your friends in the future. You want that traffic to go into q_p2p instead of q_def so it must be matched first by the rule set.
pass in quick on $ext_if inet proto tcp from <friends> to ($ext_if) port ftp2 flags S/SA synproxy state queue (q_p2p, q_p2) pass in quick on $ext_if inet proto tcp from any to ($ext_if) port ftp2 flags S/SA synproxy state queue (q_def, q_p2) pass in quick on $ext_if inet proto tcp from <friends> to ($ext_if) port 65280:65535 flags S/SA synproxy state queue (q_p2p, q_p2) pass in quick on $ext_if inet proto tcp from any to ($ext_if) port 65280:65535 flags S/SA synproxy state queue (q_def, q_p2)
Don't forget to reload the firewall rules.
# pfctl -nf /etc/pf.conf # pfctl -F rules ; pfctl -f /etc/pf.conf
Creating a public ftp account
There are many configuration files controlling the ftp server's behaviour and you can read all about them in the manual page. You can get acquainted with a couple of them by creating a general ftp storage area with a single user account that will only be allowed to access your router through ftp. First add a new user like you did with the web user but make sure you select nologin as the shell this time.
# adduser Username: upload Full name: upload Uid (Leave empty for default): Login group [upload]: Login group is upload. Invite upload into other groups? : Login class [default]: Shell (sh csh tcsh nologin) [sh]: nologin Home directory [/home/upload]: Use password-based authentication? [yes]: Use an empty password? (yes/no) [no]: Use a random password? (yes/no) [no]: Enter password: Enter password again: Lock out the account after creation? [no]:
Verify that the information is correct and accept the new user.
Username : upload Password : ***** Full Name : upload Uid : 1004 Class : Groups : upload Home : /home/upload Shell : /usr/sbin/nologin Locked : no OK? (yes/no): yes adduser: INFO: Successfully added (upload) to the user database. Add another user? (yes/no): no Goodbye!
The nologin shell used here is a special script that shows the user a message and immediately logs him off if he tries to logon with ssh. You should also lock (chroot) the user to his home folder so he can't poke around among the system files. A regular user don't even have read access to the most sensitive files in the system but there are still plenty of files he can access so for this purpose it's better to lock him down in his home folder which then will act as the general storage area. In principle you could've chosen a completely different folder for this but /home is as good as any. To chroot this user you add the userid to /etc/ftpchroot.
# echo upload >> /etc/ftpchroot
For the user to be allowed ftp access he must have a valid shell as listed in /etc/shells. For some reason nologin isn't listed here by default so you'll have to add it.
# echo /usr/sbin/nologin >> /etc/shells
Don't forget to add upload to /etc/mail/aliases and recompile that database.
# cd /etc/mail # echo "upload: pp" >> aliases # make
Now you can try to logon with upload through ssh and make sure you don't get in. Then a test through ftp and make sure you can't get outside your home folder. While you're there you can delete the hidden dot-files. They're not needed for ftp access.
- Kernel variables can be changed in /etc/sysctl.conf.
- All ftp activity will be logged in /var/log/xferlog
- I'll never feel comfortable with opening all those ports. Trying the user directive in the pf rules gets complicated since it's Inetd listening to the ftp port initially and it runs as user root. I would also have to write specific outgoing rules with the user directive to capture the active connections. Would a proxy of some sort be a better option? One that could integrate with pf smoothly if it even exists. Pointers are welcome.
- Once again I lack the knowledge of setting up an ftp server that supports encryption. There are a few in the Ports Collection so a nice guide for this would be appreciated.