mod_rewrite Module
The mod_rewrite module for proftpd is a powerful tool
for rewriting FTP commands received from clients. It has been used to
automatically append (or remove) domain names from logins, to translate
Windows paths (using backslashes) to Unix paths (using slashes), to handle
case-insensitive files, etc. One of the great things about
mod_rewrite is that any modification made to the commands
is transparent to the client; that is, FTP clients are completely unaware
that their commands are being changed on-the-fly.
The following is a collection of examples of how mod_rewrite
has been used. If you use mod_rewrite and would like to contribute
your recipe/configuration, please let us know!
Since much of mod_rewrite's power is based on regular expressions
and pattern matching, I highly recommend that you read through this
introduction to POSIX regular expressions, and use the regex tool for
testing out your regexes against paths/strings:
http://www.castaglia.org/proftpd/doc/contrib/regexp.html
Case Sensitivity
The following example configuration shows how to configure
mod_rewrite so that all files uploaded to the FTP server will have
all-uppercase filenames:
<IfModule mod_rewrite.c>
RewriteEngine on
# Have a log for double-checking any errors
RewriteLog /var/log/ftpd/rewrite.log
# Define a map that uses the internal "toupper" function
RewriteMap uppercase int:toupper
# Make the file names used by STOR be in all uppercase
RewriteCondition %m STOR
# Apply the map to the command parameters
RewriteRule ^(.*) ${uppercase:$1}
</IfModule>
What if you wanted to make the filename always be uppercase for uploaded files, but not any directories in the path leading up the file name? Using the above, if you did:
ftp> cd /upload ftp> put file1.txtThe file would appear as "/upload/FILE1.TXT". But if you did:
ftp> put /upload/file1.txtthe file would appear as "/UPLOAD/FILE1.TXT", which may not be what you want. To handle this, you need to change the "^(.*)" pattern in the above
RewriteRule directive. The "^(.*)" regular expression
matches the entire parameter string. Instead, you might try this pattern:
RewriteRule (.*/)?(.*)$ ${uppercase:$2}
which tries to isolate into match group 2 (i.e. $2) the
part of the argument string which is not followed by any slashes.
Somewhat similar is the situation where the admin found, for case-sensitivity
reasons, that it was easier to rewrite all FTP commands (except
PASS, since passwords are case-sensitive) to be lowercase:
<IfModule mod_rewrite.c>
RewriteEngine on
# Define a map that uses the internal "tolower" function
RewriteMap lowercase int:tolower
# Rewrite all commands except PASS
RewriteCondition %m !PASS
RewriteRule ^(.*) ${lowercase:$1}
</IfModule>
This means an FTP client can refer to "/DiR/Dir2/FiLe" when on the server the
file is actually "/dir/dir2/file"; it works for uploads, too. (This works
especially well for Windows clients.)
Changing the Filenames
One user had the following problem: Files uploaded via a web browser had their
filenames changed by the browser. Specifically, the web browser changed
any spaces in the filenames to "%20" (URL encoding for a space character).
Fortunately, the user was able to use mod_rewrite to undo the
change or, as shown below, to change that "%20" to an underscore:
<IfModule mod_rewrite.c>
RewriteEngine on
# Define a map that uses the internal "replaceall" function
RewriteMap replace int:replaceall
# We only want to use this rule on STOR commands
RewriteCondition %m STOR
# Apply the map to the command parameters. Use '!' as the delimiter,
# not '/', as the path sent might contain slashes
RewriteRule ^(.*) "${replace:!$1!%20!_}"
</IfModule>
Another site wanted to "tag" each uploaded file name with the current process
ID (PID), to ensure some sort of file name uniqueness. Enter
mod_rewrite!
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteCondition %m STOR
RewriteRule (.*) $1.%P
</IfModule>
This appends the PID of the current session process to any uploaded filename.
For more variables like %P, see the RewriteCondition and RewriteRule descriptions.
Replacing Backslashes With Slashes
Some sites have FTP clients which seem to send CWD and
RETR/STOR commands which use Windows-style
backslashes, e.g. "path\to\file". And ideally, these sites would like
to work seamlessly with such clients, without having to get the clients to
change. Can mod_rewrite be used to change those backslashes
into more Unix-friendly regular slashes? Absolutely.
The following mod_rewrite configuration should do the trick:
<IfModule mod_rewrite.c>
RewriteEngine on
# Use the replaceall internal RewriteMap
RewriteMap replace int:replaceall
RewriteRule (.*) "${replace:!$1!\\\\!/}"'
</IfModule>
Yes, you will need the four consecutive backslashes there, in order to make it
past proftpd's config file parser (which thinks backslashes are escape
sequences) as well as the regular expression compiler.
Modifying User Names
Is there a way that I can transparently change the login name that the FTP
client sends, from one set of known login names to the new set of names
that should be used by the FTP server? But of course! For this example,
let us assume that you have a text file which maps the old login names to
the new login names. Using mod_rewrite's RewriteMap
directive and that text file, this becomes simple:
<IfModule mod_rewrite.c>
RewriteEngine on
# Tell mod_rewrite where to find the "usermap" text file
RewriteMap usermap txt:/path/to/usermap.txt
# For USER commands, use the "usermap" file to translate the login names
RewriteCondition %m USER
RewriteRule (.*) ${usermap:$1}
</IfModule>
Rather than having a fixed map of old-to-new login names, what if you wanted
to always append the same prefix (or suffix) to every login name? For
example, what if you wanted every login name on your FTP server to look
like "user@domain.com", but the clients were sending simply "user". This
solution does not need RewriteMap; instead, you simply use:
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteCondition %m USER
RewriteRule (.*) $1@domain.com
</IfModule>
And if instead you wanted to use a fixed prefix, rather than a suffix, the
only difference would be in the RewriteRule directive, e.g.:
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteCondition %m USER
RewriteRule (.*) PREFIX$1
</IfModule>
Another interesting use case is where your clients might send the login name in a variety of constructions, e.g.:
mod_rewrite to strip off any potential prefix
and suffix? Regular expressions can be tricky, but using the regex
tool mentioned above, I worked out the following configuration that does
the trick:
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteCondition %m USER
RewriteRule ^(.*#)?([0-9A-Za-z]+)(@)? $2
</IfModule>
And if you simply wanted to have all user names be in lowercase, despite what the FTP clients send, it's merely a matter of:
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteMap lowercase int:tolower
RewriteCondition %m USER
RewriteRule (.*) ${lowercase:$1}
</IfModule>
Handling Clients' Bad PORT Commands
A ProFTPD admin encountered a case where one of their customers refused to
use anything but the standard command-line FTP client that comes with Windows.
That FTP client does not support passive data transfers; it always
uses the PORT command to do active data transfers. However,
one issue with the PORT command is that the parameter contains
an IP address. In this situation, the FTP client was behind a NAT, and the
client was sending the internal LAN address in its PORT
command. Could mod_rewrite be used to solve the problem, and
allow that bad FTP client to use active data transfers despite its' sending
of an unusable (to the FTP server) IP address? Yes!
The solution was to use mod_rewrite to rewrite the address in
the sent PORT command, replacing the internal LAN address with
the IP address of the client that proftpd saw. Below is the
configuation used to make this work:
# This is necessary, to keep proftpd from complaining about mismatched
# addresses in this situation
AllowForeignAddress on
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteMap replace int:replaceall
# Substitute in the IP address of the client, regardless of the address
# the client tells us to use in the PORT command
RewriteCondition %m ^PORT$
RewriteRule ([0-9]+,[0-9]+,[0-9]+,[0-9]+)(.*) ${replace:/$1/$1/%a$2}
# Replace the periods in the client address with commas, as per RFC959
# requirements
RewriteCondition %m ^PORT$
RewriteRule (.*) ${replace:/$1/./,}
</IfModule>
SITE Commands
The mod_rewrite module can also handle some
SITE commands, specifically:
SITE CHGRP
SITE CHMOD
mod_site module, which is part of
the normal proftpd build.
One site needed to make sure that any backslashes (e.g. used by Windows
clients) were translated to slashes, including in these SITE
commands. As of ProFTPD 1.3.2 (see Bug #2915), this can be accomplished using the following:
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteMap replace int:replaceall
RewriteCondition %m "^SITE CHMOD$" [NC]
RewriteRule "^(.*) +(.*)$" "$1 ${replace:!$2!\\\\!/}"'
</IfModule>
Notice how, for SITE CHGRP and SITE CHMOD commands,
the %m parameter in the RewriteCondition must
match the string "SITE CHGRP" or "SITE CHMOD", not just "SITE". This is
important -- and it only works for the
SITE CHGRP/SITE CHMOD commands. The use of the
"[NC]" modifier helps to catch those cases where the client might send
"SITE chmod", for instance.
Redirecting FTP Requests
One user wanted to know if mod_rewrite could be used to
redirect a request, just like one might do using Apache's
mod_rewrite, something like:
RewriteRule /(.*) ftp://newname.domain.com/$1The above
RewriteRule would work, but it would not actually
redirect the FTP client to the URL. FTP unfortuntely does not support
redirection of requests to other servers, at the protocol level, unlike HTTP.
However, it is possible to redirect a request to some other directory on the same machine. For example, if you wanted to have any file uploaded by a client go into the "/Incoming/" directory, no matter where the client wanted to upload the file, you could use:
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteCondition %m STOR
RewriteRule (.*/)?(.*) /Incoming/$2
</IfModule>
URL Encoded Characters
On very rare occasions, you may find yourself dealing with URL-encoded
characters in your FTP command parameters. If you have worked with web servers
and URLs, you will accustomed to seeing sequences like "%20" in URLs; these are
URL encoded characters (as per RFC2369). Unescaping these URL-encoded sequences is exactly what the
"unescape" RewriteMap builtin function handles.
Handling Non-ASCII Characters
If you need to handle non-ASCII characters in your mod_rewrite
rules, then you may need to generate your configuration using a scripting
language, rather than using your editor. For example, my editor does not
handle non-ASCII characters well; it displays them as ?.
Here's an example, using Perl, to replace "ä" with "ae" in uploaded file
names. Note that "ä" in hex notation is 0xE4:
my $rewrite_rule = 'RewriteRule (.*) ${replace:/$1/' . chr(0xE4) . '/ae}';
my $config = '/path/to/proftpd.conf';
if (open(my $fh, "> $config")) {
print $fh EOR;
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteLog /path/to/rewrite.log
RewriteCondition %m ^STOR$
$rewrite_rule
</IfModule>
EOR
}