mod_rewrite
This module is contained in the mod_rewrite.c file for ProFTPD 1.3.x, and is not compiled by default. Installation instructions are discussed here.
mod_rewrite.c
The most current version of mod_rewrite is distributed with the ProFTPD source code.
A discussion of the usage of this module follows.
Please contact TJ Saunders <tj at castaglia.org> with any questions, concerns, or suggestions regarding this module.
<VirtualHost>
<Global>
<Anonymous>
<Directory>
The RewriteCondition directive defines a rule condition. Precede a RewriteRule directive with one or more RewriteCondition directives. The following rewriting rule is only used if its pattern matches the current state of the FTP command and if these additional conditions apply too.
RewriteCondition
RewriteRule
Condition is a string which can contain the following expanded constructs in addition to plain text:
$N
Use an extra $ character to escape a sequence which looks like a RewriteRule backreference, e.g. $$N.
$
%N
Use an extra % character to escape a sequence which looks like a RewriteCondition backreference, e.g. %%N.
%
RewriteMap
${map-name:lookup-key|default-value}
USER
ServerName
%{ENV:variable-name}
Pattern is the condition pattern, i.e., a regular expression which is applied to the current instance of the condition, i.e., condition is evaluated and then matched against pattern. You can prefix the pattern string with a '!' character (exclamation mark) to specify a non-matching pattern.
The pattern can also be one of these special variants:
<
>
=
-d
-f
-s
-l
Flags, if present, modify how this RewriteCondition is evaluated. Supported flags are:
OR
AND
RewriteCondition %h ^host1.* [OR] RewriteCondition %h ^host2.* [OR] RewriteCondition %h ^host3.* RewriteRule ...some special stuff for any of these hosts...
The RewriteEngine directive enables or disables the module's runtime rewriting engine. If it is set to off this module does no parsing or rewriting at all. Use this directive to disable the module instead of commenting out all mod_rewrite directives.
RewriteEngine
The RewriteLock directive sets the filename for a synchronization lockfile which mod_rewrite needs to communicate with RewriteMaps of type fifo. Set file to a local absolute path (not on a NFS-mounted device) when you want to use a rewriting FIFO. It is not required for other types of rewriting maps.
RewriteLock
The RewriteLog directive is used to specify a log file for mod_rewrite reporting and debugging, and can be done a per-server basis. The file parameter must be the full path to the file to use for logging. Note that this path must not be to a world-writeable directory and, unless AllowLogSymlinks is explicitly set to on (generally a bad idea), the path must not be a symbolic link. In general, this directive should only be used for debugging your mod_rewrite configuration, and should be removed once debugging is completed; do not use this directive in a production configuration.
RewriteLog
AllowLogSymlinks
If file is "none", no logging will be done at all; this setting can be used to override a RewriteLog setting inherited from a <Global> context.
The RewriteMap directive defines a rewriting map which can be used inside rule substitution strings by the mapping-functions to insert/substitute fields through a key lookup. The source of this lookup can be of various types.
The map-name is the name of the map and will be used to specify a mapping-function for the substitution strings of a rewriting rule via one of the following constructs:
${ map-name : lookup-key } ${ map-name : lookup-key | default-value}
${
:
}
|
The following combinations for map-type and map-src can be used:
txt
This is the standard rewriting map feature where the map-src is a plain ASCII file containing either blank lines, comment lines (starting with a '#' character) or pairs like the following - one per line.
matching-key subst-value
Example:
# -------------------------------------------- # usermap.txt -- map for rewriting user names # -------------------------------------------- Dave.Admin dave # The Uber-admin root anonymous # no one should be logging in as root anyway
RewriteMap real-to-user txt:/path/to/file/usermap.txt
fifo
For this rewriting map, map-src is a FIFO (a.k.a. named pipe). To create it, you can use the mkfifo(1) command. An external program that opens the FIFO for reading and writing must be started before proftpd is started. This program can communicate with the rewriting engine via the FIFO. For each mapping lookup, it can read the key to lookup as a newline-terminated string from the FIFO. It then has to write back to the FIFO the looked-up value as a newline-terminated string, or just simply newline character (denoting an empty string) if there is no corresponding value for the given key).
mkfifo(1)
proftpd
An example program which will implement a 1:1 mapping (i.e., key == value) could be:
#!/usr/bin/perl use strict; use File::Basename qw(basename); use Getopt::Long; use IO::Handle; use IO::Select; my $default_delay = 0.5; my $program = basename($0); my %opts = (); GetOptions(\%opts, 'delay=f', 'fifo=s', 'help', 'verbose'); usage() if $opts{'help'}; my $delay = $opts{'delay'} ? $opts{'delay'} : $default_delay; die "$program: missing required --fifo parameter\n" unless $opts{'fifo'}; my $fifo = $opts{'fifo'}; my $verbose = $opts{'verbose'} ? 1 : 0; open(my $fifo_fh, "+> $fifo") or die "$program: unable to open $fifo: $!\n"; # Instantiate a Select object for knowing when to read from and write to # the FIFO. my $sel = IO::Select->new(); while (1) { # Blocking select() for reading. $sel->add($fifo_fh); print STDERR "$program: selecting for reading\n" if $verbose; my ($rfh) = $sel->can_read(); my $key = <$rfh>; print STDERR "$program: read '$key'\n" if $verbose; # Lookup a value for the given key. my $value = lookup_value($key); # Clear the Select object's filehandles. $sel->remove(); print $fifo_fh "$value\n" if $verbose; $fifo_fh->flush(); print STDERR "$program: wrote '$value'\n" if $verbose; # Wait for the buffer's byte to be cleared before reading again. wait_fifo($fifo_fh); } close($fifo_fh); print STDOUT "$program: done\n" if $verbose; exit 0; # -------------------------------------------------------------------------- sub lookup_value { my ($key) = @_; # NOTE: do something to obtain a value for the given key here. chomp(my $value = $key); return $value; } # -------------------------------------------------------------------------- sub usage { print STDOUT <<END_OF_USAGE; usage: $program [options] --delay Configure the buffer check delay. The default is $default_delay seconds. --fifo Configure the path to the FIFO. Required. --help Displays this message. --verbose Enables verbose output while $program runs. END_OF_USAGE exit 0; } # -------------------------------------------------------------------------- sub wait_fifo { my ($fh) = @_; # Now we get tricky. Use ioctl(2) to poll the number of bytes to # be read from the FIFO filehandle. When the number drops to zero, # it means that the data we just wrote has been read from the buffer # by some other process, so we can go back to the top of this loop. # Otherwise, if this program loops faster than the reader/writer on # the other end of the FIFO, we'd end up reading the data we just # wrote. Quite annoying, actually. # # Note: this value must be manually extracted from the system header files # using the following program: # # -------- fionread.c ------------------- # #include <sys/ioctl.h> # # int main(int argc, char *argv[]) { # printf("%#08x\n", FIONREAD); # return 0; # } # --------------------------------------- # # > cc -o fionread fionread.c # > ./fionread my $FIONREAD = 0x00541b; my $size = pack('L', 0); ioctl($fh, $FIONREAD, $size) or die "$program: unable to use ioctl: $!\n"; $size = unpack('L', $size); while ($size != 0) { print STDERR "$program: waiting for buffer to be read\n" if $verbose; select(undef, undef, undef, $delay); $size = pack('L', 0); ioctl($fh, $FIONREAD, $size) or die "$program: unable to use ioctl: $!\n"; $size = unpack('L', $size); } }
lookup_value()
int
Here the map-src is a mod_rewrite built-in function. Currently you cannot create your own, but the following functions already exist:
Note: For plain text files the looked-up keys are cached in-core until the mtime of the text map file changes or the server does a restart. This way you can have map-functions in rules which are used for every request. This is no problem, because the parsing of the text files only happens once!
mtime
The RewriteMaxReplace directive is used to increase the number of replacements/substitutions that mod_rewrite will perform, when rewriting commands. By default, mod_rewrite will only replace up to 8 occurrences of a pattern in the input string; if there are more than 8 replacement occurrences, then the input string will be unchanged. If your input strings happen to have more than 8 occurrences to be replaced, you can use the RewriteMaxReplace to increase that limit.
RewriteMaxReplace
For example, to increase the limit to 32 occurrences to be replaced, use:
RewriteMaxReplace 32
The RewriteRule directive is the real rewriting workhorse. The configuration directive can occur more than once. Each directive defines a single rewriting rule. The order of definition of these rules is important, because this order is used when applying the rules at run-time.
Note that each RewriteRule must be preceded by one (or more) RewriteCondition directive.
Pattern can be POSIX regular expression which gets applied to the current FTP command argument(s).
Some hints about the syntax of regular expressions:
. Any single character [chars] Character class: one of chars [^chars] Character class: none of chars text1|text2 Alternative: text1 or text2
chars
text1
text2
? 0 or 1 of the preceding text * 0 or N of the preceding text (N > 0) + 1 or N of the preceding text (N > 1)
(text) Grouping of text (either to set the borders of an alternative or for making backreferences where the Nth group can be used on the RHS of a RewriteRule with $N)
^ Start of line anchor $ End of line anchor
\char Escape that particular char (for instance to specify the chars ".[]()" etc.)
For more information about regular expressions have a look at your local regex(3) manpage. If you are interested in more detailed information about regular expressions and their variants (POSIX regex, Perl regex, etc.) have a look at the following dedicated book on this topic:
regex(3)
Mastering Regular Expressions Jeffrey E.F. Friedl Nutshell Handbook Series O'Reilly & Associates, Inc. 1997 ISBN 1-56592-257-3
Additionally in mod_rewrite the NOT character ('!') is a possible pattern prefix. This gives you the ability to negate a pattern; to say, for instance: "if the current argument(s) does NOT match this pattern". This can be used for exceptional cases, where it is easier to match the negative pattern, or as a last default rule.
Notice: When using the NOT character to negate a pattern you cannot have grouped wildcard parts in the pattern. This is impossible because when the pattern does NOT match, there are no contents for the groups. In consequence, if negated patterns are used, you cannot use $N in the substitution string.
Substitution of a rewriting rule is the string which is substituted for (or replaces) the original argument(s) for which pattern matched. Beside plain text you can use:
%P process ID %t Unix time since the epoch, in seconds
All of the rewriting rules are applied to substitution. The command argument(s) is completely replaced by the substitution.
Flags, if present, modify how this RewriteRule is evaluated. Supported flags are:
When processing a RewriteRule, the mod_rewrite engine will first execute the RewriteRule's regular expression against the command parameters. If that expression fails, the RewriteRule is skipped. Any RewriteConditions that are attached to the rule are processed only if the rule's expression matches first.
One of the consequences is that the rewritten path may run afoul of any configured AllowFilter, DenyFilter, PathAllowFilter, or PathDenyFilter directives, causing unexpected or unwanted transfers. Please keep this in mind when configuring RewriteRules.
AllowFilter
DenyFilter
PathAllowFilter
PathDenyFilter
Some metas are not available at certain times (e.g. %U/%u for USER/PASS commands, etc)...
Examples The following example configuration shows how to configure mod_rewrite so that all files uploaded to the 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>
<IfModule mod_rewrite.c> RewriteEngine on RewriteLog /var/log/ftpd/rewrite.log # 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 RewriteRule ^(.*) "${replace:/$1/ /_}" </IfModule>
$ ./configure --with-modules=mod_rewrite
$ ./configure --enable-dso --with-shared=mod_rewrite
$ make $ make install
Alternatively, if your proftpd was compiled with DSO support, you can use the prxs tool to build mod_rewrite as a shared module:
prxs
$ prxs -c -i -d mod_rewrite.c
Logging The mod_rewrite module supports trace logging, via the module-specific log channels:
proftpd.conf
TraceLog /path/to/ftpd/trace.log Trace rewrite:20