With the virtual environment set up and the default ProFTP server running, let’s configure ProFTP to serve two virtual FTP hosts that allow anonymous logins each.
What Are We Making?
Here is a review diagram of the virtual FTP system we are creating.
- Linux Mint 18.3 is the host running VirtualBox 5.2.4.
- The virtual machine guest is Linux Mint 18.3 MATE updated and running kernel 4.14.17.
- In the guest, the FTP server is ProFTP.
- ProFTP will serve two virtual hosts: one running on the default port 21 and the other running on port 4000 (no port conflicts on this system).
- FTP data is stored on a separate virtual hard disk to keep it separate from the system drive and for easier maintenance.
- SSL/TLS encrypts both FTP server connections for both control and data. Each virtual host uses a different SSL certificate.
- On the FTP drive, each virtual host has its own directory for easier management.
How Does Anonymous FTP Login Work?
Normally, any user with an account on a Linux system can log in via FTP using his username and password for his account on the system.
Anonymous is different. We want any user, even those users who do not have accounts, to log in via FTP and access the files located in a special anonymous directory.
To make this happen, we fist create a dedicated user on the system containing the FTP server. This is a standard user with limited permissions (for example, do not assign the ability to install software). We configure proftpd so that any user who logs in with the username anonymous with a blank password takes on the role of the ftp user account and operates under the permissions of of the ftp user.
For example, suppose we have a standard user named Zippy on the virtual Linux system where proftpd runs. Zippy ‘s username is zippy, and he has a real password that lets him log into the system locally or remotely via SSH or FTP.
What we do is configure a virtual host in proftpd so that any remote connection that logs in as anonymous (with or without a password) takes on the role of the zippy user. From the eyes of Linux, it looks like zippy has logged in but the anonymous user does not access zippy’s home (unless you want anonymous logins to do that, which is not recommended). Instead, an anonymous user is treated as the user zippy who is limited to the anonymous directory (which can be any directory you like).
Whether one or one hundred remote users connect anonymously as the user anonymous, they will be seen as one or one hundred zippy logins limited to the same anonymous directory.
For convenience, our anonymous servers will not require a password for anonymous logins.
The primary configuration file for proftpd (the name of ProFTP) is located in /etc/proftpd/proftpd.conf, so open this file as root in a text editor. Other configuration files exist, but ignore them for now.
sudo xed /etc/proftpd/proftpd.conf
Within are a number of directives: commands that tell proftpd what to do. These directives are based on Apache server directives, so if you understand how Apache’s configuration files are set up, then this should be familiar. If not, they can usually be understood by reading them.
Many directives exist that will allow us to configure proftpd to our wishes. For a full reference, please see the proftpd documentation for details about each directive. We will only cover the directives we need for building our virtual hosts.
Blocks and Directives
When you first view proftpd.conf, you might notice a similarity with XML and HTML. Directives are usually grouped within matching opening and closing “tags” (or directives).
<Anonymous /media/ftpdata/vhost1> AnonRequirePassword off <Limit LOGIN> AllowAll </Limit> User ftpuser Group ftpuser UserAlias anonymous ftpuser <Directory *> <Limit WRITE> DenyAll </Limit> </Directory> </Anonymous>
This custom proftpd.conf snippet defines an anonymous login. Notice how it begins with an <Anonymous> directive and ends with a </Anonymous> with a forward slash. This is an Anonymous block. Other blocks may be nested inside the Anonymous block. A Limit block is nested within a Directory block, and it applies directives to the Directory block. The previous Limit block applies to the Anonymous block. In this way, directives control what is possible within a block. Again, block must open and close properly. No overlapping blocks or else proftpd will not start.
Other directives are single-line directives. User, Group, and AnonRequirePassword are examples.
When reading the proftpd documentation, you will encounter what is called the context. A context is where a directive may be applied. You cannot use every directive in every context. Some are specific.
So, what is a context? there are four to be aware of:
- server config
VirtualHost, Anonymous, and Global are blocks that begin and end with directives like the example above. If you see a directive that is allowed in an Anonymous context, then you can use that directive inside an Anonymous block (between the <Anonymous> and </Anonymous> directives). The same applies to VirtualHost (for virtual FTP servers we will be using) and Global (directives common to all FTP servers proftpd is running).
server config refers to the proftpd server itself, and its directives are located in proftpd.conf outside of any block if they are meant to affect the entire server not just a context of the server.
Context is important to notice because if you try to use a directive in, say, an anonymous context that does not support anonymous, then proftpd will not start, and an error message will appear. The proftpd.conf file must be correct with its directives before proftpd will run.
An example is the DefaultRoot directive. A look at its documentation shows that it runs within the server config, <VirtualHost>, and <Global> contexts but not the <Anonymous> context. If we try to use DefaultRoot within and Anonymous block, proftpd will return an error and refuse to start.
For error details, enter journalctl -xe.
This is a capture of the relevant part of the error listing. We can see the reason: DefaultRoot directive not allowed in Anonymous context on line 83. To fix it, remove DefaultRoot from the Anonymous block.
Check if proftpd is Running
At the command line, use systemctl to find out the current state of proftpd.
systemctl proftpd status
If proftpd is running, you should see something like this:
If you are ever in doubt about whether proftpd is running or not, this is a useful way to check if the server is up or not in order to diagnose problems.
Creating the First FTP Virtual Host
We will start by creating a single virtual host that allows anonymous logins at the default FTP port of 21. After this is working properly, we will add the second virtual host and encryption. We will be using three contexts: server config, <Global>, and <VirtualHost>.
- server config – applies to proftpd itself and affects the entire server and virtual hosts.
- <Global> – contains directives common to all virtual hosts. Rather than repeat the same directives for each virtual host, define the common elements here. This way, if we add more virtual hosts, they will automatically inherit these common directives and spare us extra typing.
- <VirtualHost> – the virtual FTP server. Each vritual FTP server has its own <VirtualHost> context with its own opening and closing VirtualHost directives. Any directives that appear within a VirtualHost block are applied to that virtual host only, not to the others. This allows us to tweak each virtual host with specific directives.
Backup the existing default proftpd.conf file (in case you need it later) because we are going to start with a blank configuration.
sudo cp /etc/proftpd/proftpd.conf /etc/proftpd/proftpd.conf.bak
Open proftpd.conf in a text editor and delete everything within it.
sudo xed /etc/proftpd/proftpd.conf
These directive exist outside of any context, so they will appear on lines by themselves.
# ----------------------------------------------------------------------------- # SERVER CONFIG # ----------------------------------------------------------------------------- UseIPv6 off MaxInstances 30 SystemLog /var/log/proftpd/proftpd.log <IfModule mod_quotatab.c> QuotaEngine off </IfModule> <IfModule mod_ratio.c> Ratios off </IfModule> <IfModule mod_delay.c> DelayEngine on </IfModule> <IfModule mod_ctrls.c> ControlsEngine off ControlsMaxClients 2 ControlsLog /var/log/proftpd/controls.log ControlsInterval 5 ControlsSocket /var/run/proftpd/proftpd.sock </IfModule> <IfModule mod_ctrls_admin.c> AdminControlsEngine off </IfModule> Include /etc/proftpd/modules.conf
Any line that begins with the hash (#) character is a comment that is ignored by proftpd.
Much of this is the same from the default configuration, but the three directive to take note of are located at the top.
- UseIPv6 off – Disables IPv6 addressing because this gave me problems on an IPv4-only network. If IPv6 works for you, then feel free to set it to on.
- MaxInstances 30 – Limits to 30 child processes but can be increased or decreased as needed. Each proftpd child process is one connection, and we want to limit this in order to protect against DoS attacks. Sure, there should be no danger of DoS on a private LAN of your making, but this is good security practice regardless. Remember during installation when proftpd prompted for standalone or inetd? MaxInstances only works in standalone mode, not inetd.
- SystemLog /var/log/proftpd/proftpd.log – Where the proftpd log file is located. This log file is for the server itself, not the individual virtual hosts. Virtual host log files are defined separately. Use an absolute path for the log file.
Everything else consists of reorganized default directives from the default proftpd.conf file. Grouped together for readability. However, notice the <IfModule> blocks. IfModule is a conditional directive. If the given file listed in the opening IfModule directive evaluates to true (has been loaded or it exists), then the directives within the IfModule block will apply. Modules, such as mod_delay.c and mod_quotatab.c are text-based configuration files that further configure proftpd.
We will not need to edit these for this project, so leave them alone.
This concludes the server config section, which we only need to declare once. Next, the Global context.
The directive here apply to all virtual host we will create. Think of this as the common denominator. When a virtual FTP server runs, anything existing here will take effect. The purpose is to reduce redundancy and make the configuration file more readable. If editing is required that must apply to all FTP virtual servers, we can make those changes in one location instead of making changes to each virtual host individually and risk introducing errors or forgetting to make the changes for each.
# ----------------------------------------------------------------------------- # GLOBALS # ----------------------------------------------------------------------------- <Global> DisplayLogin welcome.msg DisplayChdir .message DenyFilter \*.*/,"%" ListOptions "-l" DefaultRoot ~ RequireValidShell on MultilineRFC2228 on ShowSymlinks on TimeoutNoTransfer 600 TimeoutStalled 600 TimeoutIdle 1200 Umask 022 022 AllowOverwrite on TransferLog /var/log/proftpd/xferlog IdentLookups off MaxClients 30 DeferWelcome on </Global>
These directives are best covered by the proftpd documentation. they tailor how logins happen, welcome messages, timeouts, and more. A few notable directives are explained.
- DefaultRoot ~ – Jail the user to his home directory (represented by the tilde character ~). This should always be enabled for security. When a normal user who has an account on the Linux system logs in, he will restricted to his home directory on the system. We do not want users exploring the entire filesystem. DefaultRoot does not apply to anonymous logins and it cannot be used in an anonymous context because Anonymous defines its own chroot directory. However, DefaultRoot is included in Global in case we want to add another non-anonymous virtual host later. This ensures that we do not forget to enable DefaultRoot.
- RequireValidShell on – Regular users must have a valid shell (listed in /etc/shells) in order to log in. If a user’s shell is set to /bin/false, for example, then he cannot log in. Another security precaution.
- DeferWelcome,DisplayLogin,DisplayChdir – These directives display message after logging in and changing directories. However, some FTP clients do not display messages. Filezilla is one of them. If you have set a custom FTP welcome message with fancy ASCII art in welcome.msg and you are using Filezilla, then the message will never appear. However, if you are using a command-line ftp client (ftp), then the messages will appear. This is an FTP client issue, not a server issue. If messages do not appear is whatever FTP client you are using to text your server, do not beat your head over it. Ignore it and move on.
- TransferLog /var/log/proftpd/xferlog – Uploads and downloads along with IP addresses are logged here.
- DenyFilter \*.*/,”%” – A regular expression list of pattern NOT to accept. Users can connect to your FTP server via command line ftp clients and type FTP commands manually (FTP is not limited to point and click GUIs). This means users can enter some obtuse commands that might exploit the server. If there are some patterns you wish to avoid because you might think they could break your system, then enter those patterns here.
Everything up to this point was preparation. VirtualHost is where the actual virtual FTP server is defined. It can be a normal FTP server that requires valid user accounts, or it can be anonymous. In this case, we are going to create an anonymous FTP virtual server.
First, Create a User Account Dedicated to Anonymous Logins
We need to create a dedicated user account on the virtual Linux server that will only be used to for anonymous FTP logins. When a user logs in as the username anonymous, he will take on the role of this account and all permissions associated with it. This limits the abilities of the anonymous login.
In the virtual Minux Mint 18.3 MATE machine running proftpd, open Control Center > Users and Groups. Add a new standard user. Do not assign any administrative privileges for security. For example, do not make the user a member of the sudo group in Manage Groups. Big no, no.
zippy is used here as an example, but you may use anything you like. Assign a real password to the user account. This password is not used for the anonymous FTP login. It is needed for a regular login just like any other user. Close and open a command prompt for the next step.
Create a Dedicated Directory for the First Virtual Host
The dedicated virtual drive will store all FTP data. There will be a one directory devoted to each virtual host we create. With the open terminal navigate to the root of this drive. Our example project mounted this drive at /media/ftpdata.
Decide on a name for this virtual ftp server. Let’s call it Zippy’s FTP, so the directory hosting its data will be zippyftp. Create this directory in /media/ftpdata.
sudo mkdir /media/ftpdata/zippyftp
Set the Group ID
Right now, root owns the directory. When a user logs in anonymously and “becomes” the user zippy, we want all files uploaded by zippy to automatically be assigned the zippy primary group as if zippy himself copied those files to /media/ftpdata/zippyftp. Yes, we can configure dedicated upload and download directories with various permissions to control what users do, but let’s keep it simple for now and treat zippyftp as one directory where anything can happen. (We are setting up the basics to ensure that everything works for now.)
sudo chgrp zippy /media/ftpdata/zippyftp sudo chmod g+w /media/ftpdata/zippydata sudo chmod g+s /media/ftpdata/zippyftp
- chgrp sets the primary group as zippy, and the second command sets the group ID so all new files and directories will be assigned to the zippy group. We want any file uploaded by any anonymous user to be owned by the zippy group. Any user who is a member of the zippy group may access the files.
- chmod g+w allows any member of the zippy group (any anonymous login) to write to the directory. If the write permission is not set on the directory, then all uploads will fail even if the FTP server permits uploading.
- chmod g+s sets the group ID permission on zippyftp so any uploaded files or created directories will be owned by the zippy group. This command as it is assumes that zippyftp is empty. Any existing files will not be affected.
This is where Linux permissions and FTP must work together. The Linux permissions on a directory dictate what the FTP server may do. ProFTP will not override Linux permissions, so it must work within them. If you find that you cannot uploads files, double check that the directory into which you are uploading has write permissions and the proper user or group.
Okay, Back to VirtualHost
here is a basic anonymous virtual FTP server configuration in proftpd.
# ============================================================================= # Zippy's FTP # ============================================================================= <VirtualHost 192.168.1.30> ServerName "Zippy's FTP" Port 21 DefaultServer on TransferLog /var/log/proftpd/zippy_xfer.log DisplayLogin welcome.msg <Anonymous /media/ftpdata/zippyftp> User zippy Group zippy AnonRequirePassword off UserAlias anonymous zippy <Limit LOGIN> AllowAll </Limit> HideUser root HideGroup root </Anonymous> </VirtualHost>
- <VirtualHost 192.168.1.30> This is the virtual host bound to IP address 192.168.1.30, which is the IP address of the virtual machine that proftpd is running on. All Virtual hosts will share this IP address, but they will use different ports to differentiate them. If your server uses multiple network interfaces and you have multiple IP addresses assigned to them, then you can have different virtual hosts bound to different IP addresses. This project uses a single IP address.
- ServerName “Zippy’s FTP” The server name that appears in the FTP client, including Filezilla. Use whatever you like.
- Port 21 The port the FTP server listens on for the control channel. FTP uses two ports, not one. The second port used for the data channel is this port number minus 1, so ports 21 and 20 are used by this virtual host. Take care that the other port does not conflict with an existing service or else proftpd will run into problems.
- TransferLog /var/log/proftpd/zippy_xfer.log The transfer log for this server. each virtual host may have its own transfer log that records uploads and downloads along with IP addresses. Useful for watching transfer activity in real time.
- DisplayLogin welcome.msg Displays a welcome message after login for FTP clients that support it. Does not appear in Filezilla.
- <Anonymous> block Defines the anonymous login.
- User zippy Run this virtual host as the user zippy. Inherit zippy’s permissions. This is why we limit zippy’s account.
- Group zippy Run this virtual host as the group zippy.
- AnonRequirePassword off Password field my be left blank. Anonymous password logins are usually irrelevant anyway, but setting to on requires that anonymous users enter something, usually an email address, in the password field. This removes the password requirement entirely. Only username anonymous is required for login. This is useful for, say, repository mirrors that are accessed by apt.
- UserAlias anonymous zippy Aliases anonymous with the authenticated user zippy.
- <Limit LOGIN> AllowAll </Limit> Allow anyone from any IP address to log in to this virtual host.
- HideUser root Hide all directories owned by root. This is useful if your FTP directory contains files necessary for other users but you want to hide them from anonymous logins.
- HideGroup root Same as HideUser, but hides directories whose group ownership is root.
Note that HideUser and HideGroup will not hide directories owned by zippy, the logged in anonymous user. If you add the HideGroup zippy directive, then it will have no effect. zippy directories will still appear for anonymous logins.
Restarting With Changes
save the configuration, but keep it open for further editing. Restart proftpd.
sudo service proftpd restart
If all went well and there are no errors in the configuration file, then proftpd will restart by showing just a prompt.
Open an FTP client, such as Filezilla, and try to log in as anonymous. (Filezilla is available for free from the repository.)
- Host: Enter the IP address of the virtual host, which is 192.168.1.30 in this project.
- Username: anonymous
- Password: Leave blank
- Port: Leave blank (assumes port 21)
Click Quick Connect, and you should see the contents of /media/ftpdata/zippyftp. A few files were put in place ahead of time to avoid an empty directory upon login.
Port 21 is the default FTP port, so it is assumed. If you want to connect using port 21, then there is no need to enter a port number. However, if the virtual host exists on a different port, which the second virtual host will, then we must enter the port number.
Notice that the anonymous user is limited to the zippyftp directory as his root directory. (The anonymous FTP root is different from the Linux root directory.)
Try creating directories and uploading files using Filezilla.
If you can create and delete, then permissions are working properly. Notice in the log above that the delete failed for the file welcome.msg. This is because the immutable attribute was set on that file from within Linux earlier. Any file with the immutable attribute set cannot be deleted, even by the root user. This is useful for protecting certain read-only files in public directories.
Setting the Immutable Attribute
The immutable attribute can be set only from within Linux, not FTP. Open a terminal in zippyftp and choose a file to set to immutable. welcome.msg in this case.
sudo chattr +i welcome.msg
The file is protected. It will still appear and it can still be downloaded and read, but not overwritten or deleted from the FTP server. To view the extended file attributes, use lsattr.
To remove the immutable attribute, use the chattr command again but with -i.
sudo chattr -i welcome.msg
Adding the Second Virtual Host
If all is working properly, then let’s create the second virtual host. We have already covered the details, so this is a matter of repeating with minor changes.
- Add a new user named bumble (or whatever you prefer) the will be the account for all anonymous logins to the second virtual host. This is to keep it separate from zippy. If user bumble does no exist, then an anonymous login is will be rejected.
- Create a new ftp directory in /media/ftpdata for the second virtual host. This projects created /media/ftpdata/bumbleftp. All uploads and downloads with the bumble FTP are stored here.
- Change the group ownership of bumbleftp to the bumble group. sudo chgrp bumble /media/ftpdata/bumbleftp.
- Set the group write permission for bumbleftp. sudo chmod g+w /media/ftpdata/bumbleftp.
- Set the group ID of bumbleftp. sudo chmod g+s /media/ftpdata/bumbleftp.
Leave everything else in proftpd.conf as it is. Since we are practically duplicating an existing virtual host, we can copy and paste. Copy the entire VirtualHost block and paste it again in the same proftpd.conf file. Order does not matter, but for readability, group the virtual hosts one after the other. Do not nest the virtual hosts within each other. They must be separate blocks.
This virtual host will be called Bumble’s FTP, so make the changes specific to bumble.
# ============================================================================= # Bumble's FTP # ============================================================================= <VirtualHost 192.168.1.30> ServerName "Bumble's FTP" Port 4000 TransferLog /var/log/proftpd/bumble_xfer.log DisplayLogin welcome.msg <Anonymous /media/ftpdata/bumbleftp> User bumble Group bumble AnonRequirePassword off UserAlias anonymous bumble <Limit LOGIN> AllowAll </Limit> HideUser root HideGroup root </Anonymous> </VirtualHost>
- Everything is mostly the same as before. Note that we needed to remove the DefaultServer directive since there can only be one enabled. We want the zippy ftp to be the default FTP server.
- Port 4000 – This is important. We can only have one server on the same IP address using ports 21 and 20, so we need to specify a different, unused port. 4000 was chosen, but any free port will work. Keep in mind that proftpd also requires the port -1, so ports 4000 and 3999 are used. Make sure nothing is using port 3999 (in this example).
- Every zippy was changed to bumble.
- The Anonymous directive specifies /media/ftpdata/bumbleftp as the root for anonymous FTP logins using port 4000.
- HideUser and HideGroup are not allowed in a Global context, which is why they are repeated.
Save the configuration, and restart the FTP server to enact the changes.
sudo service proftpd restart
If all went well, proftpd should be running. (Use systemctl proftpd status to check.)
Testing the Second Virtual Host
In Filezilla, close the connection to Zippy FTP if it is still open, and enter the same IP address to connect to the Bumble FTP server.
- Host: 192.168.1.30
- Username: anonymous
- Password: Leave blank
- Port: 4000
Remember to enter 4000 for the port. If left blank, you will connect to Zippy’s FTP.
If all went well, you should see a successful connection to Bumble’s FTP. The welcome_to_bumble file was added earlier for confirmation.
The socket is now 192.168.1.30:4000 (for bumble), not 192.168.1.30:21 (for zippy). Both are anonymous FTP servers running in the same virtual machine at IP address 192.168.1.30. The port differentiates the two.
Users need not specify a port when connecting to Zippy’s FTP. Port 21 is assumed, so Zippy’s FTP is the default FTP server for the masses. Bumble’s FTP requires that port 4000 be specified, so this can be the lesser-known FTP server. Of course, this is no real security, but it illustrates how to set up two virtual FTP hosts. Both are seen as individual FTP servers.
Try creating files, directories, and deleting them. If you see a Critical file transfer error when attempting to upload a file, then check the directory permissions of the directory you are attempting to upload to.
Permissions are easy to overlook. For example, the error above occurred when trying to upload a file to bumbleftp when logged into the Bumble FTP server. It turned out that bumbleftp did not have its group write permission set, nor did it have its group ID set to bumble.
chmod and chgrp fixed this.
Once the permission and group changes were made to the bumbleftp directory, uploads were allowed. It is fine if root owns the directories. Notice the ‘s‘ in rws? That indicates that the group ID is set for that directory. All files created are assigned the same group ownership as the directory’s group, not the user’s group.
Adding More Virtual Hosts
If you want to add three, four, or more virtual hosts, then the process is the same but with different users, directories, and ports. Restart proftpd to register the changes.
Watching the Log Files
Remember the TransferLog directive for each virtual host? We can view the transfer logs of both virtual FTP servers in real time. Open two terminals (or use tmux).
Terminal 1 for zippyftp:
watch -n 1 cat /var/log/proftpd/zippy_xfer.log
Terminal 2 for bumbleftp:
watch -n 1 cat /var/log/proftpd/bumble_xfer.log
These log watching terminals update every second to witness any uploads or downloads.
For added monitoring, you can run a bandwidth monitor, such as bmon, to view server network activity. Your imagination is your best friend at this point.
With two anonymous virtual FTP hosts running in a virtual machine, we have finished the essentials. At this point, it would be a matter of configuring each FTP server to your wishes, which are too individual to list here.
The next improvement we can make is to encrypt the FTP traffic in order to prevent packet sniffing or network spying. We will cover that next time.
In the meantime, have fun with your new FTP server!