| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | # -*- perl -*- | 
| 2 |  |  |  |  |  |  |  | 
| 3 |  |  |  |  |  |  | # Net::FTPServer A Perl FTP Server | 
| 4 |  |  |  |  |  |  | # Copyright (C) 2000 Bibliotech Ltd., Unit 2-3, 50 Carnwath Road, | 
| 5 |  |  |  |  |  |  | # London, SW6 3EG, United Kingdom. | 
| 6 |  |  |  |  |  |  | # | 
| 7 |  |  |  |  |  |  | # This program is free software; you can redistribute it and/or modify | 
| 8 |  |  |  |  |  |  | # it under the terms of the GNU General Public License as published by | 
| 9 |  |  |  |  |  |  | # the Free Software Foundation; either version 2 of the License, or | 
| 10 |  |  |  |  |  |  | # (at your option) any later version. | 
| 11 |  |  |  |  |  |  | # | 
| 12 |  |  |  |  |  |  | # This program is distributed in the hope that it will be useful, | 
| 13 |  |  |  |  |  |  | # but WITHOUT ANY WARRANTY; without even the implied warranty of | 
| 14 |  |  |  |  |  |  | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
| 15 |  |  |  |  |  |  | # GNU General Public License for more details. | 
| 16 |  |  |  |  |  |  | # | 
| 17 |  |  |  |  |  |  | # You should have received a copy of the GNU General Public License | 
| 18 |  |  |  |  |  |  | # along with this program; if not, write to the Free Software | 
| 19 |  |  |  |  |  |  | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | 
| 20 |  |  |  |  |  |  |  | 
| 21 |  |  |  |  |  |  |  | 
| 22 |  |  |  |  |  |  | =pod | 
| 23 |  |  |  |  |  |  |  | 
| 24 |  |  |  |  |  |  | =head1 NAME | 
| 25 |  |  |  |  |  |  |  | 
| 26 |  |  |  |  |  |  | Net::FTPServer - A secure, extensible and configurable Perl FTP server | 
| 27 |  |  |  |  |  |  |  | 
| 28 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 29 |  |  |  |  |  |  |  | 
| 30 |  |  |  |  |  |  | ftpd.sh [--help] [-d] [-v] [-p port] [-s] [-S] [-V] [-C conf_file] | 
| 31 |  |  |  |  |  |  | [-P pidfile] [-o option=value] | 
| 32 |  |  |  |  |  |  |  | 
| 33 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 34 |  |  |  |  |  |  |  | 
| 35 |  |  |  |  |  |  | C is a secure, extensible and configurable FTP | 
| 36 |  |  |  |  |  |  | server written in Perl. | 
| 37 |  |  |  |  |  |  |  | 
| 38 |  |  |  |  |  |  | Current features include: | 
| 39 |  |  |  |  |  |  |  | 
| 40 |  |  |  |  |  |  | * Authenticated FTP access. | 
| 41 |  |  |  |  |  |  | * Anonymous FTP access. | 
| 42 |  |  |  |  |  |  | * Complete implementation of current RFCs. | 
| 43 |  |  |  |  |  |  | * ASCII or binary type file transfers. | 
| 44 |  |  |  |  |  |  | * Active or passive mode file transfers. | 
| 45 |  |  |  |  |  |  | * Run standalone or from inetd(8). | 
| 46 |  |  |  |  |  |  | * Security features: chroot, resource limits, tainting, | 
| 47 |  |  |  |  |  |  | protection against buffer overflows. | 
| 48 |  |  |  |  |  |  | * IP-based and/or IP-less virtual hosts. | 
| 49 |  |  |  |  |  |  | * Complete access control system. | 
| 50 |  |  |  |  |  |  | * Anonymous read-only FTP personality. | 
| 51 |  |  |  |  |  |  | * Virtual filesystem allows files to be served | 
| 52 |  |  |  |  |  |  | from a database. | 
| 53 |  |  |  |  |  |  | * Directory aliases and CDPATH support. | 
| 54 |  |  |  |  |  |  | * Extensible command set. | 
| 55 |  |  |  |  |  |  | * Generate archives on the fly. | 
| 56 |  |  |  |  |  |  |  | 
| 57 |  |  |  |  |  |  | =head1 INSTALLING AND RUNNING THE SERVER | 
| 58 |  |  |  |  |  |  |  | 
| 59 |  |  |  |  |  |  | A standard C file is supplied with the distribution. | 
| 60 |  |  |  |  |  |  | Full documentation for all the possible options which you | 
| 61 |  |  |  |  |  |  | may use in this file is contained in this manual page. See | 
| 62 |  |  |  |  |  |  | the section CONFIGURATION below. | 
| 63 |  |  |  |  |  |  |  | 
| 64 |  |  |  |  |  |  | After doing C, the standard C file should | 
| 65 |  |  |  |  |  |  | have been installed in C. You will probably need to | 
| 66 |  |  |  |  |  |  | edit this file to suit your local configuration. | 
| 67 |  |  |  |  |  |  |  | 
| 68 |  |  |  |  |  |  | Also after doing C, several start-up scripts will have | 
| 69 |  |  |  |  |  |  | been installed in C. (On Debian in C or | 
| 70 |  |  |  |  |  |  | C). Each start-up script starts the server in a | 
| 71 |  |  |  |  |  |  | different configuration: either as a full FTP server, or as an | 
| 72 |  |  |  |  |  |  | anonymous-only read-only FTP server, etc. | 
| 73 |  |  |  |  |  |  |  | 
| 74 |  |  |  |  |  |  | The commonly used scripts are: | 
| 75 |  |  |  |  |  |  |  | 
| 76 |  |  |  |  |  |  | * /usr/sbin/ftpd.pl | 
| 77 |  |  |  |  |  |  | * /usr/sbin/ro-ftpd.pl | 
| 78 |  |  |  |  |  |  |  | 
| 79 |  |  |  |  |  |  | The first script is for the full FTP server. | 
| 80 |  |  |  |  |  |  |  | 
| 81 |  |  |  |  |  |  | These scripts assume that the C interpreter can be found on the | 
| 82 |  |  |  |  |  |  | current C<$PATH>. In the rare situation when this is not the case, you | 
| 83 |  |  |  |  |  |  | may need to edit these scripts. | 
| 84 |  |  |  |  |  |  |  | 
| 85 |  |  |  |  |  |  | =head2 STANDALONE SERVER | 
| 86 |  |  |  |  |  |  |  | 
| 87 |  |  |  |  |  |  | If you have a high load site, you will want to run C | 
| 88 |  |  |  |  |  |  | as a standalone server. To start C as a standalone | 
| 89 |  |  |  |  |  |  | server, do: | 
| 90 |  |  |  |  |  |  |  | 
| 91 |  |  |  |  |  |  | /usr/sbin/ftpd.pl -S | 
| 92 |  |  |  |  |  |  |  | 
| 93 |  |  |  |  |  |  | You may want to add this to your local start-up files so that | 
| 94 |  |  |  |  |  |  | the server starts automatically when you boot the machine. | 
| 95 |  |  |  |  |  |  |  | 
| 96 |  |  |  |  |  |  | To stop the server, do: | 
| 97 |  |  |  |  |  |  |  | 
| 98 |  |  |  |  |  |  | killall ftpd.pl | 
| 99 |  |  |  |  |  |  |  | 
| 100 |  |  |  |  |  |  | (Note: C points out that the above is a Linux-ism. Solaris | 
| 101 |  |  |  |  |  |  | administrators may get a nasty shock if they type C as C! | 
| 102 |  |  |  |  |  |  | Just kill the parent C process by hand instead). | 
| 103 |  |  |  |  |  |  |  | 
| 104 |  |  |  |  |  |  | =head2 RUNNING FROM INETD | 
| 105 |  |  |  |  |  |  |  | 
| 106 |  |  |  |  |  |  | Add the following line to C: | 
| 107 |  |  |  |  |  |  |  | 
| 108 |  |  |  |  |  |  | ftp stream tcp nowait root /usr/sbin/tcpd ftpd.pl | 
| 109 |  |  |  |  |  |  |  | 
| 110 |  |  |  |  |  |  | (This assumes that you have the C package installed to | 
| 111 |  |  |  |  |  |  | provide basic access control through C and | 
| 112 |  |  |  |  |  |  | C. This access control is in addition to any access | 
| 113 |  |  |  |  |  |  | control which you may configure through C.) | 
| 114 |  |  |  |  |  |  |  | 
| 115 |  |  |  |  |  |  | After editing this file you will need to inform C: | 
| 116 |  |  |  |  |  |  |  | 
| 117 |  |  |  |  |  |  | killall -HUP inetd | 
| 118 |  |  |  |  |  |  |  | 
| 119 |  |  |  |  |  |  | =head2 RUNNING FROM XINETD | 
| 120 |  |  |  |  |  |  |  | 
| 121 |  |  |  |  |  |  | C is a modern alternative to C which is supposedly | 
| 122 |  |  |  |  |  |  | simpler to configure. In practice, however, it has proven to be quite | 
| 123 |  |  |  |  |  |  | difficult to configure services under C (mainly because | 
| 124 |  |  |  |  |  |  | C gives no diagnostic information when things go wrong). The | 
| 125 |  |  |  |  |  |  | following configuration has worked for me: | 
| 126 |  |  |  |  |  |  |  | 
| 127 |  |  |  |  |  |  | Create the file C containing: | 
| 128 |  |  |  |  |  |  |  | 
| 129 |  |  |  |  |  |  | # default: on | 
| 130 |  |  |  |  |  |  | # description: Net::FTPServer, a secure, \ | 
| 131 |  |  |  |  |  |  | #              extensible, configurable FTP server. | 
| 132 |  |  |  |  |  |  | # | 
| 133 |  |  |  |  |  |  | service ftp | 
| 134 |  |  |  |  |  |  | { | 
| 135 |  |  |  |  |  |  | socket_type             = stream | 
| 136 |  |  |  |  |  |  | wait                    = no | 
| 137 |  |  |  |  |  |  | user                    = root | 
| 138 |  |  |  |  |  |  | server                  = /usr/sbin/ftpd.pl | 
| 139 |  |  |  |  |  |  | log_on_success          += DURATION USERID | 
| 140 |  |  |  |  |  |  | log_on_failure          += USERID | 
| 141 |  |  |  |  |  |  | disable                 = no | 
| 142 |  |  |  |  |  |  | } | 
| 143 |  |  |  |  |  |  |  | 
| 144 |  |  |  |  |  |  | Check any other possible FTP server configurations to ensure they | 
| 145 |  |  |  |  |  |  | are all disabled (ie. C in all other files). | 
| 146 |  |  |  |  |  |  |  | 
| 147 |  |  |  |  |  |  | Restart C using: | 
| 148 |  |  |  |  |  |  |  | 
| 149 |  |  |  |  |  |  | /etc/init.d/xinetd restart | 
| 150 |  |  |  |  |  |  |  | 
| 151 |  |  |  |  |  |  | =head1 COMMAND LINE FLAGS | 
| 152 |  |  |  |  |  |  |  | 
| 153 |  |  |  |  |  |  | --help           Display help and exit | 
| 154 |  |  |  |  |  |  | -d, -v           Enable debugging | 
| 155 |  |  |  |  |  |  | -p PORT          Listen on port PORT instead of the default port | 
| 156 |  |  |  |  |  |  | -s               Run in daemon mode (default: run from inetd) | 
| 157 |  |  |  |  |  |  | -S               Run in background and in daemon mode | 
| 158 |  |  |  |  |  |  | -V               Show version information and exit | 
| 159 |  |  |  |  |  |  | -C CONF          Use CONF as configuration file (default: | 
| 160 |  |  |  |  |  |  | /etc/ftpd.conf) | 
| 161 |  |  |  |  |  |  | -P PIDFILE       Save pid into PIDFILE (daemon mode only) | 
| 162 |  |  |  |  |  |  | -o option=value  Override config file option with value | 
| 163 |  |  |  |  |  |  | --test           Test mode (used only in automatic testing scripts) | 
| 164 |  |  |  |  |  |  |  | 
| 165 |  |  |  |  |  |  | =head1 CONFIGURING AND EXTENDING THE SERVER | 
| 166 |  |  |  |  |  |  |  | 
| 167 |  |  |  |  |  |  | C can be configured and extended in a number | 
| 168 |  |  |  |  |  |  | of different ways. | 
| 169 |  |  |  |  |  |  |  | 
| 170 |  |  |  |  |  |  | Firstly, almost all common server configuration can be carried | 
| 171 |  |  |  |  |  |  | out by editing the configuration file C. | 
| 172 |  |  |  |  |  |  |  | 
| 173 |  |  |  |  |  |  | Secondly, commands can be loaded into the server at run-time | 
| 174 |  |  |  |  |  |  | to provide custom extensions to the common FTP command set. | 
| 175 |  |  |  |  |  |  | These custom commands are written in Perl. | 
| 176 |  |  |  |  |  |  |  | 
| 177 |  |  |  |  |  |  | Thirdly, one of several different supplied I can be | 
| 178 |  |  |  |  |  |  | chosen. Personalities can be used to make deep changes to the FTP | 
| 179 |  |  |  |  |  |  | server: for example, there is a supplied personality which allows the | 
| 180 |  |  |  |  |  |  | FTP server to serve files from a relational database. By subclassing | 
| 181 |  |  |  |  |  |  | C, C and | 
| 182 |  |  |  |  |  |  | C you may also write your own | 
| 183 |  |  |  |  |  |  | personalities. | 
| 184 |  |  |  |  |  |  |  | 
| 185 |  |  |  |  |  |  | The next sections talk about each of these possibilities in turn. | 
| 186 |  |  |  |  |  |  |  | 
| 187 |  |  |  |  |  |  | =head2 CONFIGURATION | 
| 188 |  |  |  |  |  |  |  | 
| 189 |  |  |  |  |  |  | A standard C file is supplied with C | 
| 190 |  |  |  |  |  |  | in the distribution. The possible configuration options are listed in | 
| 191 |  |  |  |  |  |  | full below. | 
| 192 |  |  |  |  |  |  |  | 
| 193 |  |  |  |  |  |  | Simple configuration options can also be given on the command line | 
| 194 |  |  |  |  |  |  | using the C<-o> option. Command line configuration options override | 
| 195 |  |  |  |  |  |  | those from the configuration file. | 
| 196 |  |  |  |  |  |  |  | 
| 197 |  |  |  |  |  |  | =over 4 | 
| 198 |  |  |  |  |  |  |  | 
| 199 |  |  |  |  |  |  | =item EInclude filenameE | 
| 200 |  |  |  |  |  |  |  | 
| 201 |  |  |  |  |  |  | Use the EInclude filenameE directive to include | 
| 202 |  |  |  |  |  |  | the contents of C directly at the current point | 
| 203 |  |  |  |  |  |  | within the configuration file. | 
| 204 |  |  |  |  |  |  |  | 
| 205 |  |  |  |  |  |  | You cannot use EIncludeE within a EHostE | 
| 206 |  |  |  |  |  |  | section, or at least you I but it wonE<39>t work the | 
| 207 |  |  |  |  |  |  | way you expect. | 
| 208 |  |  |  |  |  |  |  | 
| 209 |  |  |  |  |  |  | =item EIncludeWildcard wildcardE | 
| 210 |  |  |  |  |  |  |  | 
| 211 |  |  |  |  |  |  | Include all files matching C at this point in | 
| 212 |  |  |  |  |  |  | the file. The files are included in alphabetical order. | 
| 213 |  |  |  |  |  |  |  | 
| 214 |  |  |  |  |  |  | You cannot use EIncludeWildcardE within a EHostE | 
| 215 |  |  |  |  |  |  | section, or at least you I but it wonE<39>t work the | 
| 216 |  |  |  |  |  |  | way you expect. | 
| 217 |  |  |  |  |  |  |  | 
| 218 |  |  |  |  |  |  | =item debug | 
| 219 |  |  |  |  |  |  |  | 
| 220 |  |  |  |  |  |  | Run with debugging. Equivalent to the command line C<-d> option. | 
| 221 |  |  |  |  |  |  |  | 
| 222 |  |  |  |  |  |  | Default: 0 | 
| 223 |  |  |  |  |  |  |  | 
| 224 |  |  |  |  |  |  | Example: C | 
| 225 |  |  |  |  |  |  |  | 
| 226 |  |  |  |  |  |  | =item port | 
| 227 |  |  |  |  |  |  |  | 
| 228 |  |  |  |  |  |  | The TCP port number on which the FTP server listens when | 
| 229 |  |  |  |  |  |  | running in daemon mode (see C option below). | 
| 230 |  |  |  |  |  |  |  | 
| 231 |  |  |  |  |  |  | Default: The standard ftp/tcp service port from C | 
| 232 |  |  |  |  |  |  |  | 
| 233 |  |  |  |  |  |  | Example: C | 
| 234 |  |  |  |  |  |  |  | 
| 235 |  |  |  |  |  |  | =item daemon mode | 
| 236 |  |  |  |  |  |  |  | 
| 237 |  |  |  |  |  |  | Run as a daemon. If set, the FTP server will open a listening | 
| 238 |  |  |  |  |  |  | socket on its default port number, accept new connections and | 
| 239 |  |  |  |  |  |  | fork off a new process to handle each connection. If not set | 
| 240 |  |  |  |  |  |  | (the default), the FTP server will handle a single connection | 
| 241 |  |  |  |  |  |  | on stdin/stdout, which is suitable for use from inetd. | 
| 242 |  |  |  |  |  |  |  | 
| 243 |  |  |  |  |  |  | The equivalent command line options are C<-s> and C<-S>. | 
| 244 |  |  |  |  |  |  |  | 
| 245 |  |  |  |  |  |  | Default: 0 | 
| 246 |  |  |  |  |  |  |  | 
| 247 |  |  |  |  |  |  | Example: C | 
| 248 |  |  |  |  |  |  |  | 
| 249 |  |  |  |  |  |  | =item run in background | 
| 250 |  |  |  |  |  |  |  | 
| 251 |  |  |  |  |  |  | Run in the background. If set, the FTP server will fork into | 
| 252 |  |  |  |  |  |  | the background before running. | 
| 253 |  |  |  |  |  |  |  | 
| 254 |  |  |  |  |  |  | The equivalent command line option is C<-S>. | 
| 255 |  |  |  |  |  |  |  | 
| 256 |  |  |  |  |  |  | Default: 0 | 
| 257 |  |  |  |  |  |  |  | 
| 258 |  |  |  |  |  |  | Example: C | 
| 259 |  |  |  |  |  |  |  | 
| 260 |  |  |  |  |  |  | =item error log | 
| 261 |  |  |  |  |  |  |  | 
| 262 |  |  |  |  |  |  | If set, then all warning and error messages are appended to | 
| 263 |  |  |  |  |  |  | this file. If not set, warning and error messages get sent to | 
| 264 |  |  |  |  |  |  | STDERR and to syslog. | 
| 265 |  |  |  |  |  |  |  | 
| 266 |  |  |  |  |  |  | Having an error log is I. | 
| 267 |  |  |  |  |  |  |  | 
| 268 |  |  |  |  |  |  | Default: (not set, warnings and errors go to syslog) | 
| 269 |  |  |  |  |  |  |  | 
| 270 |  |  |  |  |  |  | Example: C | 
| 271 |  |  |  |  |  |  |  | 
| 272 |  |  |  |  |  |  | =item rotate log files | 
| 273 |  |  |  |  |  |  |  | 
| 274 |  |  |  |  |  |  | If set, and if the log file names contain a '%' directive, then the | 
| 275 |  |  |  |  |  |  | server will check if a new log file is needed whenever the system | 
| 276 |  |  |  |  |  |  | accepts a new connection.  This implements a log rotation feature for | 
| 277 |  |  |  |  |  |  | long-running servers. | 
| 278 |  |  |  |  |  |  |  | 
| 279 |  |  |  |  |  |  | If not set, then any '%' directive will be evaluated only when the log | 
| 280 |  |  |  |  |  |  | files gets created. | 
| 281 |  |  |  |  |  |  |  | 
| 282 |  |  |  |  |  |  | Default: (not set, log file name evaluated only once) | 
| 283 |  |  |  |  |  |  |  | 
| 284 |  |  |  |  |  |  | Example: C | 
| 285 |  |  |  |  |  |  |  | 
| 286 |  |  |  |  |  |  | =item maintainer email | 
| 287 |  |  |  |  |  |  |  | 
| 288 |  |  |  |  |  |  | MaintainerE<39>s email address. | 
| 289 |  |  |  |  |  |  |  | 
| 290 |  |  |  |  |  |  | Default: root@I | 
| 291 |  |  |  |  |  |  |  | 
| 292 |  |  |  |  |  |  | Example: C | 
| 293 |  |  |  |  |  |  |  | 
| 294 |  |  |  |  |  |  | =item class | 
| 295 |  |  |  |  |  |  |  | 
| 296 |  |  |  |  |  |  | Assign users into classes. One or more C directives can be | 
| 297 |  |  |  |  |  |  | added to the configuration file to aggregate individual users into | 
| 298 |  |  |  |  |  |  | larger groups of users called classes. | 
| 299 |  |  |  |  |  |  |  | 
| 300 |  |  |  |  |  |  | By default all anonymous users are in class C and every | 
| 301 |  |  |  |  |  |  | other user is in class C. | 
| 302 |  |  |  |  |  |  |  | 
| 303 |  |  |  |  |  |  | The configuration file can contain zero or more C | 
| 304 |  |  |  |  |  |  | directives. The format of the class directive is either: | 
| 305 |  |  |  |  |  |  |  | 
| 306 |  |  |  |  |  |  | class: CLASSNAME USERNAME[,USERNAME[,...]] | 
| 307 |  |  |  |  |  |  |  | 
| 308 |  |  |  |  |  |  | or: | 
| 309 |  |  |  |  |  |  |  | 
| 310 |  |  |  |  |  |  | class: CLASSNAME { perl code ... } | 
| 311 |  |  |  |  |  |  |  | 
| 312 |  |  |  |  |  |  | Examples of the first form are: | 
| 313 |  |  |  |  |  |  |  | 
| 314 |  |  |  |  |  |  | class: staff rich | 
| 315 |  |  |  |  |  |  | class: students ann,mary,pete | 
| 316 |  |  |  |  |  |  |  | 
| 317 |  |  |  |  |  |  | User C will be placed into class C, and users C, | 
| 318 |  |  |  |  |  |  | C and C will be placed into class C. | 
| 319 |  |  |  |  |  |  |  | 
| 320 |  |  |  |  |  |  | Examples of the second form are: | 
| 321 |  |  |  |  |  |  |  | 
| 322 |  |  |  |  |  |  | class: family { /jones$/ } | 
| 323 |  |  |  |  |  |  | class: friends { $_ ne "jeff" } | 
| 324 |  |  |  |  |  |  |  | 
| 325 |  |  |  |  |  |  | Any username ending in C (eg. C, C) will be | 
| 326 |  |  |  |  |  |  | in class C. Any other user except C will be placed in | 
| 327 |  |  |  |  |  |  | class C. Note that the Perl code must be surrounded by | 
| 328 |  |  |  |  |  |  | C<{...}> and must return a boolean true or false value. The username | 
| 329 |  |  |  |  |  |  | is available as C<$_>. The Perl code is arbitrary: it might, for | 
| 330 |  |  |  |  |  |  | example, use an external file or database lookup in order to work out | 
| 331 |  |  |  |  |  |  | if a user belongs to a class. | 
| 332 |  |  |  |  |  |  |  | 
| 333 |  |  |  |  |  |  | C directives are evaluated in the order in which they appear in | 
| 334 |  |  |  |  |  |  | the configuration file until one matches the username. | 
| 335 |  |  |  |  |  |  |  | 
| 336 |  |  |  |  |  |  | Default: Anonymous users are assigned to class C and | 
| 337 |  |  |  |  |  |  | everyone else is assigned to class C. | 
| 338 |  |  |  |  |  |  |  | 
| 339 |  |  |  |  |  |  | =item timeout | 
| 340 |  |  |  |  |  |  |  | 
| 341 |  |  |  |  |  |  | Timeout on control connection. If a command has not been | 
| 342 |  |  |  |  |  |  | received after this many seconds, the server drops the | 
| 343 |  |  |  |  |  |  | connection. You may set this to zero to disable timeouts | 
| 344 |  |  |  |  |  |  | completely (although this is not recommended). | 
| 345 |  |  |  |  |  |  |  | 
| 346 |  |  |  |  |  |  | Default: 900 (seconds) | 
| 347 |  |  |  |  |  |  |  | 
| 348 |  |  |  |  |  |  | Example: C | 
| 349 |  |  |  |  |  |  |  | 
| 350 |  |  |  |  |  |  | =item limit memory | 
| 351 |  |  |  |  |  |  |  | 
| 352 |  |  |  |  |  |  | =item limit nr processes | 
| 353 |  |  |  |  |  |  |  | 
| 354 |  |  |  |  |  |  | =item limit nr files | 
| 355 |  |  |  |  |  |  |  | 
| 356 |  |  |  |  |  |  | Resource limits. These limits are applied to each child | 
| 357 |  |  |  |  |  |  | process and are important in avoiding denial of service (DoS) | 
| 358 |  |  |  |  |  |  | attacks against the FTP server. | 
| 359 |  |  |  |  |  |  |  | 
| 360 |  |  |  |  |  |  | Resource         Default   Unit | 
| 361 |  |  |  |  |  |  | limit memory       16384   KBytes  Amount of memory per child | 
| 362 |  |  |  |  |  |  | limit nr processes    10   (none)  Number of processes | 
| 363 |  |  |  |  |  |  | limit nr files        20   (none)  Number of open files | 
| 364 |  |  |  |  |  |  |  | 
| 365 |  |  |  |  |  |  | To instruct the server I to limit a particular resource, set the | 
| 366 |  |  |  |  |  |  | limit to C<-1>. | 
| 367 |  |  |  |  |  |  |  | 
| 368 |  |  |  |  |  |  | Example: | 
| 369 |  |  |  |  |  |  |  | 
| 370 |  |  |  |  |  |  | limit memory:       32768 | 
| 371 |  |  |  |  |  |  | limit nr processes:    20 | 
| 372 |  |  |  |  |  |  | limit nr files:        40 | 
| 373 |  |  |  |  |  |  |  | 
| 374 |  |  |  |  |  |  | limit nr processes:    -1 | 
| 375 |  |  |  |  |  |  |  | 
| 376 |  |  |  |  |  |  | =item max clients | 
| 377 |  |  |  |  |  |  |  | 
| 378 |  |  |  |  |  |  | Limit on the number of clients who can simultaneously connect. | 
| 379 |  |  |  |  |  |  | If this limit is ever reached, new clients will immediately be | 
| 380 |  |  |  |  |  |  | closed.  It will not even ask the client to login.  This | 
| 381 |  |  |  |  |  |  | feature works in daemon mode only. | 
| 382 |  |  |  |  |  |  |  | 
| 383 |  |  |  |  |  |  | Default: 255 | 
| 384 |  |  |  |  |  |  |  | 
| 385 |  |  |  |  |  |  | Example: C | 
| 386 |  |  |  |  |  |  |  | 
| 387 |  |  |  |  |  |  | =item max clients message | 
| 388 |  |  |  |  |  |  |  | 
| 389 |  |  |  |  |  |  | Message to display when ``max clients'' has been reached. | 
| 390 |  |  |  |  |  |  |  | 
| 391 |  |  |  |  |  |  | You may use the following % escape sequences within the | 
| 392 |  |  |  |  |  |  | message for internal variables: | 
| 393 |  |  |  |  |  |  |  | 
| 394 |  |  |  |  |  |  | %x  ``max clients'' setting that has been reached | 
| 395 |  |  |  |  |  |  | %E  maintainer email address (from ``maintainer email'' | 
| 396 |  |  |  |  |  |  | setting above) | 
| 397 |  |  |  |  |  |  | %G  time in GMT | 
| 398 |  |  |  |  |  |  | %R  remote hostname or IP address if ``resolve addresses'' | 
| 399 |  |  |  |  |  |  | is not set | 
| 400 |  |  |  |  |  |  | %L  local hostname | 
| 401 |  |  |  |  |  |  | %T  local time | 
| 402 |  |  |  |  |  |  | %%  just an ordinary ``%'' | 
| 403 |  |  |  |  |  |  |  | 
| 404 |  |  |  |  |  |  | Default: Maximum connections reached | 
| 405 |  |  |  |  |  |  |  | 
| 406 |  |  |  |  |  |  | Example: C | 
| 407 |  |  |  |  |  |  |  | 
| 408 |  |  |  |  |  |  | =item resolve addresses | 
| 409 |  |  |  |  |  |  |  | 
| 410 |  |  |  |  |  |  | Resolve addresses. If set, attempt to do a reverse lookup on | 
| 411 |  |  |  |  |  |  | client addresses for logging purposes. If you set this then | 
| 412 |  |  |  |  |  |  | some clients may experience long delays when they try to | 
| 413 |  |  |  |  |  |  | connect. Not recommended on high load servers. | 
| 414 |  |  |  |  |  |  |  | 
| 415 |  |  |  |  |  |  | Default: 0 | 
| 416 |  |  |  |  |  |  |  | 
| 417 |  |  |  |  |  |  | Example: C | 
| 418 |  |  |  |  |  |  |  | 
| 419 |  |  |  |  |  |  | =item require resolved addresses | 
| 420 |  |  |  |  |  |  |  | 
| 421 |  |  |  |  |  |  | Require resolved addresses. If set, client addresses must validly resolve | 
| 422 |  |  |  |  |  |  | otherwise clients will not be able to connect. If you set this | 
| 423 |  |  |  |  |  |  | then some clients will not be able to connect, even though it is | 
| 424 |  |  |  |  |  |  | probably the fault of their ISP. | 
| 425 |  |  |  |  |  |  |  | 
| 426 |  |  |  |  |  |  | Default: 0 | 
| 427 |  |  |  |  |  |  |  | 
| 428 |  |  |  |  |  |  | Example: C | 
| 429 |  |  |  |  |  |  |  | 
| 430 |  |  |  |  |  |  | =item change process name | 
| 431 |  |  |  |  |  |  |  | 
| 432 |  |  |  |  |  |  | Change process name. If set (the default) then the FTP server will | 
| 433 |  |  |  |  |  |  | change its process name to reflect the IP address or hostname of | 
| 434 |  |  |  |  |  |  | the client. If not set then the FTP server will not try to change | 
| 435 |  |  |  |  |  |  | its process name. | 
| 436 |  |  |  |  |  |  |  | 
| 437 |  |  |  |  |  |  | Default: 1 | 
| 438 |  |  |  |  |  |  |  | 
| 439 |  |  |  |  |  |  | Example: C | 
| 440 |  |  |  |  |  |  |  | 
| 441 |  |  |  |  |  |  | =item greeting type | 
| 442 |  |  |  |  |  |  |  | 
| 443 |  |  |  |  |  |  | Greeting type. The greeting is printed before the user has logged in. | 
| 444 |  |  |  |  |  |  | Possible greeting types are: | 
| 445 |  |  |  |  |  |  |  | 
| 446 |  |  |  |  |  |  | full     Full greeting, including hostname and version number. | 
| 447 |  |  |  |  |  |  | brief    Hostname only. | 
| 448 |  |  |  |  |  |  | terse    Nothing | 
| 449 |  |  |  |  |  |  | text     Display greeting from ``greeting text'' option. | 
| 450 |  |  |  |  |  |  |  | 
| 451 |  |  |  |  |  |  | The SITE VERSION command can also reveal the version number. You | 
| 452 |  |  |  |  |  |  | may need to turn this off by setting C | 
| 453 |  |  |  |  |  |  | below. | 
| 454 |  |  |  |  |  |  |  | 
| 455 |  |  |  |  |  |  | Default: full | 
| 456 |  |  |  |  |  |  |  | 
| 457 |  |  |  |  |  |  | Example: C | 
| 458 |  |  |  |  |  |  |  | 
| 459 |  |  |  |  |  |  | =item greeting text | 
| 460 |  |  |  |  |  |  |  | 
| 461 |  |  |  |  |  |  | Greeting text. If the C is set to C then this | 
| 462 |  |  |  |  |  |  | contains the text to display. | 
| 463 |  |  |  |  |  |  |  | 
| 464 |  |  |  |  |  |  | Default: none | 
| 465 |  |  |  |  |  |  |  | 
| 466 |  |  |  |  |  |  | Example: Cll be your server today.> | 
| 467 |  |  |  |  |  |  |  | 
| 468 |  |  |  |  |  |  | =item welcome type | 
| 469 |  |  |  |  |  |  |  | 
| 470 |  |  |  |  |  |  | Welcome type. The welcome is printed after a user has logged in. | 
| 471 |  |  |  |  |  |  | Possible welcome types are: | 
| 472 |  |  |  |  |  |  |  | 
| 473 |  |  |  |  |  |  | normal   Normal welcome message: ``Welcome <>.'' | 
| 474 |  |  |  |  |  |  | text     Take the welcome message from ``welcome text'' option. | 
| 475 |  |  |  |  |  |  | file     Take the welcome message from ``welcome file'' file. | 
| 476 |  |  |  |  |  |  |  | 
| 477 |  |  |  |  |  |  | Default: normal | 
| 478 |  |  |  |  |  |  |  | 
| 479 |  |  |  |  |  |  | Example: C | 
| 480 |  |  |  |  |  |  |  | 
| 481 |  |  |  |  |  |  | =item welcome text | 
| 482 |  |  |  |  |  |  |  | 
| 483 |  |  |  |  |  |  | If C is set to C, then this contains the text | 
| 484 |  |  |  |  |  |  | to be printed after a user has logged in. | 
| 485 |  |  |  |  |  |  |  | 
| 486 |  |  |  |  |  |  | You may use the following % escape sequences within the welcome | 
| 487 |  |  |  |  |  |  | text to substitute for internal variables: | 
| 488 |  |  |  |  |  |  |  | 
| 489 |  |  |  |  |  |  | %E  maintainer's email address (from ``maintainer email'' | 
| 490 |  |  |  |  |  |  | setting above) | 
| 491 |  |  |  |  |  |  | %G  time in GMT | 
| 492 |  |  |  |  |  |  | %R  remote hostname or IP address if ``resolve addresses'' | 
| 493 |  |  |  |  |  |  | is not set | 
| 494 |  |  |  |  |  |  | %L  local hostname | 
| 495 |  |  |  |  |  |  | %m  user's home directory (see ``home directory'' below) | 
| 496 |  |  |  |  |  |  | %T  local time | 
| 497 |  |  |  |  |  |  | %U  username given when logging in | 
| 498 |  |  |  |  |  |  | %u  currently a synonym for %U, but in future will be | 
| 499 |  |  |  |  |  |  | determined from RFC931 authentication, like wu-ftpd | 
| 500 |  |  |  |  |  |  | %%  just an ordinary ``%'' | 
| 501 |  |  |  |  |  |  |  | 
| 502 |  |  |  |  |  |  | Default: none | 
| 503 |  |  |  |  |  |  |  | 
| 504 |  |  |  |  |  |  | Example: C | 
| 505 |  |  |  |  |  |  |  | 
| 506 |  |  |  |  |  |  | =item welcome file | 
| 507 |  |  |  |  |  |  |  | 
| 508 |  |  |  |  |  |  | If C is set to C, then this contains the file | 
| 509 |  |  |  |  |  |  | to be printed after a user has logged in. | 
| 510 |  |  |  |  |  |  |  | 
| 511 |  |  |  |  |  |  | You may use any of the % escape sequences defined in C | 
| 512 |  |  |  |  |  |  | above. | 
| 513 |  |  |  |  |  |  |  | 
| 514 |  |  |  |  |  |  | Default: none | 
| 515 |  |  |  |  |  |  |  | 
| 516 |  |  |  |  |  |  | Example: C | 
| 517 |  |  |  |  |  |  |  | 
| 518 |  |  |  |  |  |  | =item home directory | 
| 519 |  |  |  |  |  |  |  | 
| 520 |  |  |  |  |  |  | Home directory. This is the home directory where we put the | 
| 521 |  |  |  |  |  |  | user once they have logged in. This only applies to non-anonymous | 
| 522 |  |  |  |  |  |  | logins. Anonymous logins are always placed in "/", which is at the | 
| 523 |  |  |  |  |  |  | root of their chrooted environment. | 
| 524 |  |  |  |  |  |  |  | 
| 525 |  |  |  |  |  |  | You may use an absolute path here, or else one of the following | 
| 526 |  |  |  |  |  |  | special forms: | 
| 527 |  |  |  |  |  |  |  | 
| 528 |  |  |  |  |  |  | %m   Use home directory from password file or from NSS. | 
| 529 |  |  |  |  |  |  | %U   Username. | 
| 530 |  |  |  |  |  |  | %%   A single % character. | 
| 531 |  |  |  |  |  |  |  | 
| 532 |  |  |  |  |  |  | For example, to force a user to start in C<~/anon-ftp> when they | 
| 533 |  |  |  |  |  |  | log in, set this to C<%m/anon-ftp>. | 
| 534 |  |  |  |  |  |  |  | 
| 535 |  |  |  |  |  |  | Note that setting the home directory does not perform a chroot. | 
| 536 |  |  |  |  |  |  | Use the C setting below to jail users into a | 
| 537 |  |  |  |  |  |  | particular directory. | 
| 538 |  |  |  |  |  |  |  | 
| 539 |  |  |  |  |  |  | Home directories are I to the current root directory. | 
| 540 |  |  |  |  |  |  |  | 
| 541 |  |  |  |  |  |  | In the anonymous read-only (ro-ftpd) personality, set home | 
| 542 |  |  |  |  |  |  | directory to C> or else you will get a warning whenever a user | 
| 543 |  |  |  |  |  |  | logs in. | 
| 544 |  |  |  |  |  |  |  | 
| 545 |  |  |  |  |  |  | Default: %m | 
| 546 |  |  |  |  |  |  |  | 
| 547 |  |  |  |  |  |  | Examples: | 
| 548 |  |  |  |  |  |  |  | 
| 549 |  |  |  |  |  |  | home directory: %m/anon-ftp | 
| 550 |  |  |  |  |  |  | home directory: / | 
| 551 |  |  |  |  |  |  |  | 
| 552 |  |  |  |  |  |  | =item root directory | 
| 553 |  |  |  |  |  |  |  | 
| 554 |  |  |  |  |  |  | Root directory. Immediately after logging in, perform a chroot | 
| 555 |  |  |  |  |  |  | into the named directory. This only applies to non-anonymous | 
| 556 |  |  |  |  |  |  | logins, and furthermore it only applies if you have a non-database | 
| 557 |  |  |  |  |  |  | VFS installed. Database VFSes typically cannot perform chroot | 
| 558 |  |  |  |  |  |  | (or, to be more accurate, they have a different concept of | 
| 559 |  |  |  |  |  |  | chroot - typically assigning each user their own completely | 
| 560 |  |  |  |  |  |  | separate namespace). | 
| 561 |  |  |  |  |  |  |  | 
| 562 |  |  |  |  |  |  | You may use %m and %U as above. | 
| 563 |  |  |  |  |  |  |  | 
| 564 |  |  |  |  |  |  | For example, to jail a user under C<~/anon-ftp> after login, do: | 
| 565 |  |  |  |  |  |  |  | 
| 566 |  |  |  |  |  |  | home directory: / | 
| 567 |  |  |  |  |  |  | root directory: %m/anon-ftp | 
| 568 |  |  |  |  |  |  |  | 
| 569 |  |  |  |  |  |  | Notice that the home directory is I to the current | 
| 570 |  |  |  |  |  |  | root directory. | 
| 571 |  |  |  |  |  |  |  | 
| 572 |  |  |  |  |  |  | Default: (none) | 
| 573 |  |  |  |  |  |  |  | 
| 574 |  |  |  |  |  |  | Example: C | 
| 575 |  |  |  |  |  |  |  | 
| 576 |  |  |  |  |  |  | =item time zone | 
| 577 |  |  |  |  |  |  |  | 
| 578 |  |  |  |  |  |  | Time zone to be used for MDTM and LIST stat information. | 
| 579 |  |  |  |  |  |  |  | 
| 580 |  |  |  |  |  |  | Default: GMT | 
| 581 |  |  |  |  |  |  |  | 
| 582 |  |  |  |  |  |  | Examples: | 
| 583 |  |  |  |  |  |  |  | 
| 584 |  |  |  |  |  |  | time zone: Etc/GMT+3 | 
| 585 |  |  |  |  |  |  | time zone: Europe/London | 
| 586 |  |  |  |  |  |  | time zone: US/Mountain | 
| 587 |  |  |  |  |  |  |  | 
| 588 |  |  |  |  |  |  | =item local address | 
| 589 |  |  |  |  |  |  |  | 
| 590 |  |  |  |  |  |  | Local addresses. If you wish the FTP server (in daemon mode) to | 
| 591 |  |  |  |  |  |  | only bind to a particular local interface, then give its address | 
| 592 |  |  |  |  |  |  | here. | 
| 593 |  |  |  |  |  |  |  | 
| 594 |  |  |  |  |  |  | Default: none | 
| 595 |  |  |  |  |  |  |  | 
| 596 |  |  |  |  |  |  | Example: C | 
| 597 |  |  |  |  |  |  |  | 
| 598 |  |  |  |  |  |  | =item allow anonymous | 
| 599 |  |  |  |  |  |  |  | 
| 600 |  |  |  |  |  |  | Allow anonymous access. If set, then allow anonymous access through | 
| 601 |  |  |  |  |  |  | the C and C accounts. | 
| 602 |  |  |  |  |  |  |  | 
| 603 |  |  |  |  |  |  | Default: 0 | 
| 604 |  |  |  |  |  |  |  | 
| 605 |  |  |  |  |  |  | Example: C | 
| 606 |  |  |  |  |  |  |  | 
| 607 |  |  |  |  |  |  | =item anonymous password check | 
| 608 |  |  |  |  |  |  |  | 
| 609 |  |  |  |  |  |  | =item anonymous password enforce | 
| 610 |  |  |  |  |  |  |  | 
| 611 |  |  |  |  |  |  | Validate email addresses. Normally when logging in anonymously, | 
| 612 |  |  |  |  |  |  | you are asked to enter your email address as a password. These options | 
| 613 |  |  |  |  |  |  | can be used to check and enforce email addresses in this field (to | 
| 614 |  |  |  |  |  |  | some extent, at least -- you obviously canE<39>t force someone to | 
| 615 |  |  |  |  |  |  | enter a true email address). | 
| 616 |  |  |  |  |  |  |  | 
| 617 |  |  |  |  |  |  | The C option may be set to C, | 
| 618 |  |  |  |  |  |  | C, C or C. If set to C then | 
| 619 |  |  |  |  |  |  | the user must enter a valid RFC 822 email address as password. If | 
| 620 |  |  |  |  |  |  | set to C then a valid RFC 822 email address must be | 
| 621 |  |  |  |  |  |  | entered, and various common browser email addresses like | 
| 622 |  |  |  |  |  |  | C and CUser@> are refused. If set to C | 
| 623 |  |  |  |  |  |  | then we just check that the address contains an @ char. If set to | 
| 624 |  |  |  |  |  |  | C, then we do no checking. The default is C. | 
| 625 |  |  |  |  |  |  |  | 
| 626 |  |  |  |  |  |  | If the C option is set and the | 
| 627 |  |  |  |  |  |  | password fails the check above, then the user will not be allowed | 
| 628 |  |  |  |  |  |  | to log in. The default is 0 (unset). | 
| 629 |  |  |  |  |  |  |  | 
| 630 |  |  |  |  |  |  | These options only have effect when C is set. | 
| 631 |  |  |  |  |  |  |  | 
| 632 |  |  |  |  |  |  | Example: | 
| 633 |  |  |  |  |  |  |  | 
| 634 |  |  |  |  |  |  | anonymous password check: rfc822 | 
| 635 |  |  |  |  |  |  | anonymous password enforce: 1 | 
| 636 |  |  |  |  |  |  |  | 
| 637 |  |  |  |  |  |  | =item allow proxy ftp | 
| 638 |  |  |  |  |  |  |  | 
| 639 |  |  |  |  |  |  | Allow proxy FTP. If this is set, then the FTP server can be told to | 
| 640 |  |  |  |  |  |  | actively connect to addresses and ports on any machine in the world. | 
| 641 |  |  |  |  |  |  | This is not such a great idea, but required if you follow the RFC | 
| 642 |  |  |  |  |  |  | very closely. If not set (the default), the FTP server will only | 
| 643 |  |  |  |  |  |  | connect back to the client machine. | 
| 644 |  |  |  |  |  |  |  | 
| 645 |  |  |  |  |  |  | Default: 0 | 
| 646 |  |  |  |  |  |  |  | 
| 647 |  |  |  |  |  |  | Example: C | 
| 648 |  |  |  |  |  |  |  | 
| 649 |  |  |  |  |  |  | =item allow connect low port | 
| 650 |  |  |  |  |  |  |  | 
| 651 |  |  |  |  |  |  | Allow the FTP server to connect back to ports E 1024. This is rarely | 
| 652 |  |  |  |  |  |  | useful and could pose a serious security hole in some circumstances. | 
| 653 |  |  |  |  |  |  |  | 
| 654 |  |  |  |  |  |  | Default: 0 | 
| 655 |  |  |  |  |  |  |  | 
| 656 |  |  |  |  |  |  | Example: C | 
| 657 |  |  |  |  |  |  |  | 
| 658 |  |  |  |  |  |  | =item passive port range | 
| 659 |  |  |  |  |  |  |  | 
| 660 |  |  |  |  |  |  | What range of local ports will the FTP server listen on in passive | 
| 661 |  |  |  |  |  |  | mode? Choose a range here like C<1024-5999,49152-65535>. The special | 
| 662 |  |  |  |  |  |  | value C<0> means that the FTP server will use a kernel-assigned | 
| 663 |  |  |  |  |  |  | ephemeral port. | 
| 664 |  |  |  |  |  |  |  | 
| 665 |  |  |  |  |  |  | Default: 49152-65535 | 
| 666 |  |  |  |  |  |  |  | 
| 667 |  |  |  |  |  |  | Example: C | 
| 668 |  |  |  |  |  |  |  | 
| 669 |  |  |  |  |  |  | =item ftp data port | 
| 670 |  |  |  |  |  |  |  | 
| 671 |  |  |  |  |  |  | Which source port to use for active (non-passive) mode when connecting | 
| 672 |  |  |  |  |  |  | to the client for PORT mode transfers.  The special value C<0> means | 
| 673 |  |  |  |  |  |  | that the FTP server will use a kernel-assigned ephemeral port.  To | 
| 674 |  |  |  |  |  |  | strictly follow RFC, this should be set to C.  This may | 
| 675 |  |  |  |  |  |  | be required for certain brain-damaged firewall configurations.  However, | 
| 676 |  |  |  |  |  |  | for security reasons, the default setting is intentionally set to C<0> | 
| 677 |  |  |  |  |  |  | to utilize a kernel-assigned ephemeral port.  Use this directive at | 
| 678 |  |  |  |  |  |  | your own risk! | 
| 679 |  |  |  |  |  |  |  | 
| 680 |  |  |  |  |  |  | SECURITY PRECAUTIONS: | 
| 681 |  |  |  |  |  |  |  | 
| 682 |  |  |  |  |  |  | 1) Unfortunately, to use a port E 1024 requires super-user | 
| 683 |  |  |  |  |  |  | privileges.  Thus, low ports will not work unless the FTP server is | 
| 684 |  |  |  |  |  |  | invoked as super-user.  This also implies that all processes handling | 
| 685 |  |  |  |  |  |  | the client connections must also I super-user throughout | 
| 686 |  |  |  |  |  |  | the entire session.  It is highly discouraged to use a low port. | 
| 687 |  |  |  |  |  |  |  | 
| 688 |  |  |  |  |  |  | http://cr.yp.to/ftp/security.html | 
| 689 |  |  |  |  |  |  | (See "Connection laundering" section) | 
| 690 |  |  |  |  |  |  |  | 
| 691 |  |  |  |  |  |  | 2) There sometimes exists a danger of needing to connect to the | 
| 692 |  |  |  |  |  |  | same remote host:port.  Using the same IP/port on both sides | 
| 693 |  |  |  |  |  |  | will cause connect() to fail if the old socket is still being | 
| 694 |  |  |  |  |  |  | broken down.  This condition will not occur if using an ephemeral | 
| 695 |  |  |  |  |  |  | port. | 
| 696 |  |  |  |  |  |  |  | 
| 697 |  |  |  |  |  |  | http://groups.google.com/groups?selm=fa.epucqgv.1l2kl0e@ifi.uio.no | 
| 698 |  |  |  |  |  |  | (See "unable to create socket" comment) | 
| 699 |  |  |  |  |  |  |  | 
| 700 |  |  |  |  |  |  | 3) Many hackers use source port 20 to blindly circumvent certain | 
| 701 |  |  |  |  |  |  | naive firewalls.  Using an ephemeral port (the default) may help | 
| 702 |  |  |  |  |  |  | discourage such dangerous naivety. | 
| 703 |  |  |  |  |  |  |  | 
| 704 |  |  |  |  |  |  | man nmap | 
| 705 |  |  |  |  |  |  | (See the -g option) | 
| 706 |  |  |  |  |  |  |  | 
| 707 |  |  |  |  |  |  | Default: 0 | 
| 708 |  |  |  |  |  |  |  | 
| 709 |  |  |  |  |  |  | Example: C | 
| 710 |  |  |  |  |  |  |  | 
| 711 |  |  |  |  |  |  | =item max login attempts | 
| 712 |  |  |  |  |  |  |  | 
| 713 |  |  |  |  |  |  | Maximum number of login attempts before we drop the connection | 
| 714 |  |  |  |  |  |  | and issue a warning in the logs. Wu-ftpd defaults this to 5. | 
| 715 |  |  |  |  |  |  |  | 
| 716 |  |  |  |  |  |  | Default: 3 | 
| 717 |  |  |  |  |  |  |  | 
| 718 |  |  |  |  |  |  | Example: C | 
| 719 |  |  |  |  |  |  |  | 
| 720 |  |  |  |  |  |  | =item pam authentication | 
| 721 |  |  |  |  |  |  |  | 
| 722 |  |  |  |  |  |  | Use PAM for authentication. Required on systems such as Red Hat Linux | 
| 723 |  |  |  |  |  |  | and Solaris which use PAM for authentication rather than the normal | 
| 724 |  |  |  |  |  |  | C mechanisms. You will need to have the C | 
| 725 |  |  |  |  |  |  | Perl module installed for this to work. | 
| 726 |  |  |  |  |  |  |  | 
| 727 |  |  |  |  |  |  | Default: 0 | 
| 728 |  |  |  |  |  |  |  | 
| 729 |  |  |  |  |  |  | Example: C | 
| 730 |  |  |  |  |  |  |  | 
| 731 |  |  |  |  |  |  | =item pam application name | 
| 732 |  |  |  |  |  |  |  | 
| 733 |  |  |  |  |  |  | If PAM authentication is enabled, then this is the PAM application | 
| 734 |  |  |  |  |  |  | name. I have used C as the default which is the same name | 
| 735 |  |  |  |  |  |  | that wu-ftpd chooses. FreeBSD users will want to use C here. | 
| 736 |  |  |  |  |  |  |  | 
| 737 |  |  |  |  |  |  | Default: ftp | 
| 738 |  |  |  |  |  |  |  | 
| 739 |  |  |  |  |  |  | Example: C | 
| 740 |  |  |  |  |  |  |  | 
| 741 |  |  |  |  |  |  | =item password file | 
| 742 |  |  |  |  |  |  |  | 
| 743 |  |  |  |  |  |  | Only in the C personality, this allows you to specify a password | 
| 744 |  |  |  |  |  |  | file which is used for authentication. If you enable this option, then | 
| 745 |  |  |  |  |  |  | normal PAM or C is bypassed and this password file is | 
| 746 |  |  |  |  |  |  | used instead. | 
| 747 |  |  |  |  |  |  |  | 
| 748 |  |  |  |  |  |  | Each line in the password file has the following format: | 
| 749 |  |  |  |  |  |  |  | 
| 750 |  |  |  |  |  |  | username:crypted_password:unix_user[:root_directory] | 
| 751 |  |  |  |  |  |  |  | 
| 752 |  |  |  |  |  |  | Comments and blank lines are ignored. | 
| 753 |  |  |  |  |  |  |  | 
| 754 |  |  |  |  |  |  | For example, a line with: | 
| 755 |  |  |  |  |  |  |  | 
| 756 |  |  |  |  |  |  | guest:ab01FAX.bQRSU:rich:/home/rich/guest-uploads | 
| 757 |  |  |  |  |  |  |  | 
| 758 |  |  |  |  |  |  | would allow someone to log in as C with password | 
| 759 |  |  |  |  |  |  | C<123456>. After logging in, the FTP server will assume the identity | 
| 760 |  |  |  |  |  |  | of the real Unix user C, and will chroot itself into the | 
| 761 |  |  |  |  |  |  | C directory. | 
| 762 |  |  |  |  |  |  |  | 
| 763 |  |  |  |  |  |  | (Note that because ordinary PAM/C is bypassed, it would no | 
| 764 |  |  |  |  |  |  | longer be possible for a user to log in directly with the username | 
| 765 |  |  |  |  |  |  | C). | 
| 766 |  |  |  |  |  |  |  | 
| 767 |  |  |  |  |  |  | Crypted passwords can be generated using the following command: | 
| 768 |  |  |  |  |  |  |  | 
| 769 |  |  |  |  |  |  | perl -e 'print crypt ("123456", "ab"), "\n"' | 
| 770 |  |  |  |  |  |  |  | 
| 771 |  |  |  |  |  |  | Replace C<123456> with the actual password, and replace C  with two  | 
| 772 |  |  |  |  |  |  | random letters from the set C<[a-zA-Z0-9./]>. (The two random letters | 
| 773 |  |  |  |  |  |  | are the so-called I and are used to make dictionary attacks | 
| 774 |  |  |  |  |  |  | against the password file more difficult - see C). | 
| 775 |  |  |  |  |  |  |  | 
| 776 |  |  |  |  |  |  | The userE<39>s home directory comes from the real Unix password file | 
| 777 |  |  |  |  |  |  | (or nsswitch-configured source) for the real Unix user.  You cannot | 
| 778 |  |  |  |  |  |  | use password files to override this, and so if you are using the | 
| 779 |  |  |  |  |  |  | optional C parameter, it would make sense to add | 
| 780 |  |  |  |  |  |  | C into your configuration file. | 
| 781 |  |  |  |  |  |  |  | 
| 782 |  |  |  |  |  |  | Anonymous logins are B affected by the C | 
| 783 |  |  |  |  |  |  | option. Use the C flag to control whether anonymous | 
| 784 |  |  |  |  |  |  | logins are permitted in the C back-end. | 
| 785 |  |  |  |  |  |  |  | 
| 786 |  |  |  |  |  |  | Password files are not the height of security, but they are included | 
| 787 |  |  |  |  |  |  | because they can sometimes be useful. In particular if the password | 
| 788 |  |  |  |  |  |  | file can be read by untrusted users then it is likely that those same | 
| 789 |  |  |  |  |  |  | users can run the I program and eventually find out your | 
| 790 |  |  |  |  |  |  | passwords. Some small additional security is offered by having the | 
| 791 |  |  |  |  |  |  | password file readable only by root (mode 0600). In future we may | 
| 792 |  |  |  |  |  |  | offer MD5 or salted SHA-1 hashed passwords to make this harder. | 
| 793 |  |  |  |  |  |  |  | 
| 794 |  |  |  |  |  |  | A curious artifact of the implementation allows you to list the same | 
| 795 |  |  |  |  |  |  | user with multiple different passwords. Any of the passwords is then | 
| 796 |  |  |  |  |  |  | valid for logins (and you could even have the user map to different | 
| 797 |  |  |  |  |  |  | real Unix users in different chrooted directories!) | 
| 798 |  |  |  |  |  |  |  | 
| 799 |  |  |  |  |  |  | Default: (none) | 
| 800 |  |  |  |  |  |  |  | 
| 801 |  |  |  |  |  |  | Example: C | 
| 802 |  |  |  |  |  |  |  | 
| 803 |  |  |  |  |  |  | =item pidfile | 
| 804 |  |  |  |  |  |  |  | 
| 805 |  |  |  |  |  |  | Location of the file to store the process ID (PID). | 
| 806 |  |  |  |  |  |  | Applies only to the deamonized process, not the child processes. | 
| 807 |  |  |  |  |  |  |  | 
| 808 |  |  |  |  |  |  | Default: (no pidfile created) | 
| 809 |  |  |  |  |  |  |  | 
| 810 |  |  |  |  |  |  | Example: C | 
| 811 |  |  |  |  |  |  |  | 
| 812 |  |  |  |  |  |  | =item client logging | 
| 813 |  |  |  |  |  |  |  | 
| 814 |  |  |  |  |  |  | Location to store all client commands sent to the server. | 
| 815 |  |  |  |  |  |  | The format is the date, the pid, and the command. | 
| 816 |  |  |  |  |  |  | Following the pid is a "-" if not authenticated the | 
| 817 |  |  |  |  |  |  | username if the connection is authenticated. | 
| 818 |  |  |  |  |  |  | Example of before and after authentication: | 
| 819 |  |  |  |  |  |  |  | 
| 820 |  |  |  |  |  |  | [Wed Feb 21 18:41:32 2001][23818:-]USER rob | 
| 821 |  |  |  |  |  |  | [Wed Feb 21 18:41:33 2001][23818:-]PASS 123456 | 
| 822 |  |  |  |  |  |  | [Wed Feb 21 18:41:33 2001][23818:*]SYST | 
| 823 |  |  |  |  |  |  |  | 
| 824 |  |  |  |  |  |  | Default: (no logging) | 
| 825 |  |  |  |  |  |  |  | 
| 826 |  |  |  |  |  |  | Examples: | 
| 827 |  |  |  |  |  |  |  | 
| 828 |  |  |  |  |  |  | client logging: /var/log/ftpd.log | 
| 829 |  |  |  |  |  |  | client logging: /tmp/ftpd_log.$hostname | 
| 830 |  |  |  |  |  |  |  | 
| 831 |  |  |  |  |  |  | =item xfer logging | 
| 832 |  |  |  |  |  |  |  | 
| 833 |  |  |  |  |  |  | Location of transfer log.  The format was taken from | 
| 834 |  |  |  |  |  |  | wu-ftpd and ProFTPD xferlog. (See also "man xferlog") | 
| 835 |  |  |  |  |  |  |  | 
| 836 |  |  |  |  |  |  | Default: (no logging) | 
| 837 |  |  |  |  |  |  |  | 
| 838 |  |  |  |  |  |  | Examples: | 
| 839 |  |  |  |  |  |  |  | 
| 840 |  |  |  |  |  |  | xfer logging: /var/log/xferlog | 
| 841 |  |  |  |  |  |  | xfer logging: /tmp/xferlog.$hostname | 
| 842 |  |  |  |  |  |  |  | 
| 843 |  |  |  |  |  |  | =item hide passwords in client log | 
| 844 |  |  |  |  |  |  |  | 
| 845 |  |  |  |  |  |  | If set to 1, then password (C) commands will not be | 
| 846 |  |  |  |  |  |  | logged in the client log. This option has no effect unless | 
| 847 |  |  |  |  |  |  | client logging is enabled. | 
| 848 |  |  |  |  |  |  |  | 
| 849 |  |  |  |  |  |  | Default: 0 (PASS lines will be shown) | 
| 850 |  |  |  |  |  |  |  | 
| 851 |  |  |  |  |  |  | Example: C | 
| 852 |  |  |  |  |  |  |  | 
| 853 |  |  |  |  |  |  | =item enable syslog | 
| 854 |  |  |  |  |  |  |  | 
| 855 |  |  |  |  |  |  | Enable syslogging. If set, then Net::FTPServer will send much | 
| 856 |  |  |  |  |  |  | information to syslog. On many systems, this information will | 
| 857 |  |  |  |  |  |  | be available in /var/log/messages or /var/adm/messages. If | 
| 858 |  |  |  |  |  |  | clear, syslogging is disabled. | 
| 859 |  |  |  |  |  |  |  | 
| 860 |  |  |  |  |  |  | Default: 1 | 
| 861 |  |  |  |  |  |  |  | 
| 862 |  |  |  |  |  |  | Example: C | 
| 863 |  |  |  |  |  |  |  | 
| 864 |  |  |  |  |  |  | =item ident timeout | 
| 865 |  |  |  |  |  |  |  | 
| 866 |  |  |  |  |  |  | Timeout for ident authentication lookups. | 
| 867 |  |  |  |  |  |  | A timeout (in seconds) must be specified in order to | 
| 868 |  |  |  |  |  |  | enable ident lookups.  There is no way to specify an | 
| 869 |  |  |  |  |  |  | infinite timeout.  Use 0 to disable this feature. | 
| 870 |  |  |  |  |  |  |  | 
| 871 |  |  |  |  |  |  | Default: 0 | 
| 872 |  |  |  |  |  |  |  | 
| 873 |  |  |  |  |  |  | Example: C | 
| 874 |  |  |  |  |  |  |  | 
| 875 |  |  |  |  |  |  | =item access control rule | 
| 876 |  |  |  |  |  |  |  | 
| 877 |  |  |  |  |  |  | =item user access control rule | 
| 878 |  |  |  |  |  |  |  | 
| 879 |  |  |  |  |  |  | =item retrieve rule | 
| 880 |  |  |  |  |  |  |  | 
| 881 |  |  |  |  |  |  | =item store rule | 
| 882 |  |  |  |  |  |  |  | 
| 883 |  |  |  |  |  |  | =item delete rule | 
| 884 |  |  |  |  |  |  |  | 
| 885 |  |  |  |  |  |  | =item list rule | 
| 886 |  |  |  |  |  |  |  | 
| 887 |  |  |  |  |  |  | =item mkdir rule | 
| 888 |  |  |  |  |  |  |  | 
| 889 |  |  |  |  |  |  | =item rename rule | 
| 890 |  |  |  |  |  |  |  | 
| 891 |  |  |  |  |  |  | =item chdir rule | 
| 892 |  |  |  |  |  |  |  | 
| 893 |  |  |  |  |  |  | Access control rules. | 
| 894 |  |  |  |  |  |  |  | 
| 895 |  |  |  |  |  |  | Access control rules are all specified as short snippets of | 
| 896 |  |  |  |  |  |  | Perl script. This allows the maximum configurability -- you | 
| 897 |  |  |  |  |  |  | can express just about any rules you want -- but at the price | 
| 898 |  |  |  |  |  |  | of learning a little Perl. | 
| 899 |  |  |  |  |  |  |  | 
| 900 |  |  |  |  |  |  | You can use the following variables from the Perl: | 
| 901 |  |  |  |  |  |  |  | 
| 902 |  |  |  |  |  |  | $hostname      Resolved hostname of the client [1] | 
| 903 |  |  |  |  |  |  | $ip            IP address of the client | 
| 904 |  |  |  |  |  |  | $user          User name [2] | 
| 905 |  |  |  |  |  |  | $class         Class of user [2] | 
| 906 |  |  |  |  |  |  | $user_is_anonymous  True if the user is an anonymous user [2] | 
| 907 |  |  |  |  |  |  | $pathname      Full pathname of the file being affected [2] | 
| 908 |  |  |  |  |  |  | $filename      Filename of the file being affected [2,3] | 
| 909 |  |  |  |  |  |  | $dirname       Directory name containing file being affected [2] | 
| 910 |  |  |  |  |  |  | $type          'A' for ASCII, 'B' for binary, 'L8' for local 8-bit | 
| 911 |  |  |  |  |  |  | $form          Always 'N' | 
| 912 |  |  |  |  |  |  | $mode          Always 'S' | 
| 913 |  |  |  |  |  |  | $stru          Always 'F' | 
| 914 |  |  |  |  |  |  |  | 
| 915 |  |  |  |  |  |  | Notes: | 
| 916 |  |  |  |  |  |  |  | 
| 917 |  |  |  |  |  |  | [1] May be undefined, particularly if C is not set. | 
| 918 |  |  |  |  |  |  |  | 
| 919 |  |  |  |  |  |  | [2] Not available in C since the user has not | 
| 920 |  |  |  |  |  |  | logged in at this point. | 
| 921 |  |  |  |  |  |  |  | 
| 922 |  |  |  |  |  |  | [3] Not available for C .  | 
| 923 |  |  |  |  |  |  |  | 
| 924 |  |  |  |  |  |  | Access control rule. The FTP server will not accept any connections | 
| 925 |  |  |  |  |  |  | from a site unless this rule succeeds. Note that only C<$hostname> | 
| 926 |  |  |  |  |  |  | and C<$ip> are available to this rule, and unless C | 
| 927 |  |  |  |  |  |  | and C are both set C<$hostname> may | 
| 928 |  |  |  |  |  |  | be undefined. | 
| 929 |  |  |  |  |  |  |  | 
| 930 |  |  |  |  |  |  | Default: 1 | 
| 931 |  |  |  |  |  |  |  | 
| 932 |  |  |  |  |  |  | Examples: | 
| 933 |  |  |  |  |  |  |  | 
| 934 |  |  |  |  |  |  | (a) Deny connections from *.badguys.com: | 
| 935 |  |  |  |  |  |  |  | 
| 936 |  |  |  |  |  |  | access control rule: defined ($hostname) && \ | 
| 937 |  |  |  |  |  |  | $hostname !~ /\.badguys\.com$/ | 
| 938 |  |  |  |  |  |  |  | 
| 939 |  |  |  |  |  |  | (b) Only allow connections from local network 10.0.0.0/24: | 
| 940 |  |  |  |  |  |  |  | 
| 941 |  |  |  |  |  |  | access control rule: $ip =~ /^10\./ | 
| 942 |  |  |  |  |  |  |  | 
| 943 |  |  |  |  |  |  | User access control rule. After the user logs in successfully, | 
| 944 |  |  |  |  |  |  | this rule is then called to determine if the user may be permitted | 
| 945 |  |  |  |  |  |  | access. | 
| 946 |  |  |  |  |  |  |  | 
| 947 |  |  |  |  |  |  | Default: 1 | 
| 948 |  |  |  |  |  |  |  | 
| 949 |  |  |  |  |  |  | Examples: | 
| 950 |  |  |  |  |  |  |  | 
| 951 |  |  |  |  |  |  | (a) Only allow ``rich'' to log in from 10.x.x.x network: | 
| 952 |  |  |  |  |  |  |  | 
| 953 |  |  |  |  |  |  | user access control rule: $user ne "rich" || \ | 
| 954 |  |  |  |  |  |  | $ip =~ /^10\./ | 
| 955 |  |  |  |  |  |  |  | 
| 956 |  |  |  |  |  |  | (b) Only allow anonymous users to log in if they come from | 
| 957 |  |  |  |  |  |  | hosts with resolving hostnames (``resolve addresses'' must | 
| 958 |  |  |  |  |  |  | also be set): | 
| 959 |  |  |  |  |  |  |  | 
| 960 |  |  |  |  |  |  | user access control rule: !$user_is_anonymous || \ | 
| 961 |  |  |  |  |  |  | defined ($hostname) | 
| 962 |  |  |  |  |  |  |  | 
| 963 |  |  |  |  |  |  | (c) Do not allow user ``jeff'' to log in at all: | 
| 964 |  |  |  |  |  |  |  | 
| 965 |  |  |  |  |  |  | user access control rule: $user ne "jeff" | 
| 966 |  |  |  |  |  |  |  | 
| 967 |  |  |  |  |  |  | Retrieve rule. This rule controls who may retrieve (download) files. | 
| 968 |  |  |  |  |  |  |  | 
| 969 |  |  |  |  |  |  | Default: 1 | 
| 970 |  |  |  |  |  |  |  | 
| 971 |  |  |  |  |  |  | Examples: | 
| 972 |  |  |  |  |  |  |  | 
| 973 |  |  |  |  |  |  | (a) Do not allow anyone to retrieve ``/etc/*'' or any file anywhere | 
| 974 |  |  |  |  |  |  | called ``.htaccess'': | 
| 975 |  |  |  |  |  |  |  | 
| 976 |  |  |  |  |  |  | retrieve rule: $dirname !~ m(^/etc/) && $filename ne ".htaccess" | 
| 977 |  |  |  |  |  |  |  | 
| 978 |  |  |  |  |  |  | (b) Only allow anonymous users to retrieve files from under the | 
| 979 |  |  |  |  |  |  | ``/pub'' directory. | 
| 980 |  |  |  |  |  |  |  | 
| 981 |  |  |  |  |  |  | retrieve rule: !$user_is_anonymous || $dirname =~ m(^/pub/) | 
| 982 |  |  |  |  |  |  |  | 
| 983 |  |  |  |  |  |  | Store rule. This rule controls who may store (upload) files. | 
| 984 |  |  |  |  |  |  |  | 
| 985 |  |  |  |  |  |  | In the anonymous read-only (ro-ftpd) personality, it is not | 
| 986 |  |  |  |  |  |  | possible to upload files anyway, so setting this rule has no | 
| 987 |  |  |  |  |  |  | effect. | 
| 988 |  |  |  |  |  |  |  | 
| 989 |  |  |  |  |  |  | Default: 1 | 
| 990 |  |  |  |  |  |  |  | 
| 991 |  |  |  |  |  |  | Examples: | 
| 992 |  |  |  |  |  |  |  | 
| 993 |  |  |  |  |  |  | (a) Only allow users to upload files to the ``/incoming'' | 
| 994 |  |  |  |  |  |  | directory. | 
| 995 |  |  |  |  |  |  |  | 
| 996 |  |  |  |  |  |  | store rule: $dirname =~ m(^/incoming/) | 
| 997 |  |  |  |  |  |  |  | 
| 998 |  |  |  |  |  |  | (b) Anonymous users can only upload files to ``/incoming'' | 
| 999 |  |  |  |  |  |  | directory. | 
| 1000 |  |  |  |  |  |  |  | 
| 1001 |  |  |  |  |  |  | store rule: !$user_is_anonymous || $dirname =~ m(^/incoming/) | 
| 1002 |  |  |  |  |  |  |  | 
| 1003 |  |  |  |  |  |  | (c) Disable file upload. | 
| 1004 |  |  |  |  |  |  |  | 
| 1005 |  |  |  |  |  |  | store rule: 0 | 
| 1006 |  |  |  |  |  |  |  | 
| 1007 |  |  |  |  |  |  | Delete rule. This rule controls who may delete files or rmdir directories. | 
| 1008 |  |  |  |  |  |  |  | 
| 1009 |  |  |  |  |  |  | In the anonymous read-only (ro-ftpd) personality, it is not | 
| 1010 |  |  |  |  |  |  | possible to delete files anyway, so setting this rule has no | 
| 1011 |  |  |  |  |  |  | effect. | 
| 1012 |  |  |  |  |  |  |  | 
| 1013 |  |  |  |  |  |  | Default: 1 | 
| 1014 |  |  |  |  |  |  |  | 
| 1015 |  |  |  |  |  |  | Example: C | 
| 1016 |  |  |  |  |  |  |  | 
| 1017 |  |  |  |  |  |  | List rule. This rule controls who may list out the contents of a | 
| 1018 |  |  |  |  |  |  | directory. | 
| 1019 |  |  |  |  |  |  |  | 
| 1020 |  |  |  |  |  |  | Default: 1 | 
| 1021 |  |  |  |  |  |  |  | 
| 1022 |  |  |  |  |  |  | Example: C   | 
| 1023 |  |  |  |  |  |  |  | 
| 1024 |  |  |  |  |  |  | Mkdir rule. This rule controls who may create a subdirectory. | 
| 1025 |  |  |  |  |  |  |  | 
| 1026 |  |  |  |  |  |  | In the anonymous read-only (ro-ftpd) personality, it is not | 
| 1027 |  |  |  |  |  |  | possible to create directories anyway, so setting this rule has | 
| 1028 |  |  |  |  |  |  | no effect. | 
| 1029 |  |  |  |  |  |  |  | 
| 1030 |  |  |  |  |  |  | Default: 1 | 
| 1031 |  |  |  |  |  |  |  | 
| 1032 |  |  |  |  |  |  | Example: C | 
| 1033 |  |  |  |  |  |  |  | 
| 1034 |  |  |  |  |  |  | Rename rule. This rule controls which files or directories can be renamed. | 
| 1035 |  |  |  |  |  |  |  | 
| 1036 |  |  |  |  |  |  | Default: 1 | 
| 1037 |  |  |  |  |  |  |  | 
| 1038 |  |  |  |  |  |  | Example: C | 
| 1039 |  |  |  |  |  |  |  | 
| 1040 |  |  |  |  |  |  | Chdir rule. This rule controls which directories are acceptable to a | 
| 1041 |  |  |  |  |  |  | CWD or CDUP. | 
| 1042 |  |  |  |  |  |  |  | 
| 1043 |  |  |  |  |  |  | Example: C | 
| 1044 |  |  |  |  |  |  |  | 
| 1045 |  |  |  |  |  |  | =item chdir message file | 
| 1046 |  |  |  |  |  |  |  | 
| 1047 |  |  |  |  |  |  | Change directory message file. If set, then the first time (per | 
| 1048 |  |  |  |  |  |  | session) that a user goes into a directory which contains a file | 
| 1049 |  |  |  |  |  |  | matching this name, that file will be displayed. | 
| 1050 |  |  |  |  |  |  |  | 
| 1051 |  |  |  |  |  |  | The file may contain any of the following % escape sequences: | 
| 1052 |  |  |  |  |  |  |  | 
| 1053 |  |  |  |  |  |  | %C  current working directory | 
| 1054 |  |  |  |  |  |  | %E  maintainer's email address (from ``maintainer email'' | 
| 1055 |  |  |  |  |  |  | setting above) | 
| 1056 |  |  |  |  |  |  | %G  time in GMT | 
| 1057 |  |  |  |  |  |  | %R  remote hostname or IP address if ``resolve addresses'' | 
| 1058 |  |  |  |  |  |  | is not set | 
| 1059 |  |  |  |  |  |  | %L  local hostname | 
| 1060 |  |  |  |  |  |  | %m  user's home directory (see ``home directory'' below) | 
| 1061 |  |  |  |  |  |  | %T  local time | 
| 1062 |  |  |  |  |  |  | %U  username given when logging in | 
| 1063 |  |  |  |  |  |  | %u  currently a synonym for %U, but in future will be | 
| 1064 |  |  |  |  |  |  | determined from RFC931 authentication, like wu-ftpd | 
| 1065 |  |  |  |  |  |  | %%  just an ordinary ``%'' | 
| 1066 |  |  |  |  |  |  |  | 
| 1067 |  |  |  |  |  |  | Default: (none) | 
| 1068 |  |  |  |  |  |  |  | 
| 1069 |  |  |  |  |  |  | Example: C | 
| 1070 |  |  |  |  |  |  |  | 
| 1071 |  |  |  |  |  |  | =item allow rename to overwrite | 
| 1072 |  |  |  |  |  |  |  | 
| 1073 |  |  |  |  |  |  | Allow the rename (RNFR/RNTO) command to overwrite files. If unset, | 
| 1074 |  |  |  |  |  |  | then we try to test whether the rename command would overwrite a | 
| 1075 |  |  |  |  |  |  | file and disallow it. However there are some race conditions with | 
| 1076 |  |  |  |  |  |  | this test. | 
| 1077 |  |  |  |  |  |  |  | 
| 1078 |  |  |  |  |  |  | Default: 1 | 
| 1079 |  |  |  |  |  |  |  | 
| 1080 |  |  |  |  |  |  | Example: C | 
| 1081 |  |  |  |  |  |  |  | 
| 1082 |  |  |  |  |  |  | =item allow store to overwrite | 
| 1083 |  |  |  |  |  |  |  | 
| 1084 |  |  |  |  |  |  | Allow the store commands (STOR/STOU/APPE) to overwrite files. If unset, | 
| 1085 |  |  |  |  |  |  | then we try to test whether the store command would overwrite a | 
| 1086 |  |  |  |  |  |  | file and disallow it. However there are some race conditions with | 
| 1087 |  |  |  |  |  |  | this test. | 
| 1088 |  |  |  |  |  |  |  | 
| 1089 |  |  |  |  |  |  | Default: 1 | 
| 1090 |  |  |  |  |  |  |  | 
| 1091 |  |  |  |  |  |  | Example: C | 
| 1092 |  |  |  |  |  |  |  | 
| 1093 |  |  |  |  |  |  | =item alias | 
| 1094 |  |  |  |  |  |  |  | 
| 1095 |  |  |  |  |  |  | Define an alias C for directory C. For example, the command | 
| 1096 |  |  |  |  |  |  | C would allow the user to access the | 
| 1097 |  |  |  |  |  |  | C directory directly just by typing C. | 
| 1098 |  |  |  |  |  |  |  | 
| 1099 |  |  |  |  |  |  | Aliases only apply to the cd (CWD) command. The C command checks | 
| 1100 |  |  |  |  |  |  | for directories in the following order: | 
| 1101 |  |  |  |  |  |  |  | 
| 1102 |  |  |  |  |  |  | foo in the current directory | 
| 1103 |  |  |  |  |  |  | an alias called foo | 
| 1104 |  |  |  |  |  |  | foo in each directory in the cdpath (see ``cdpath'' command below) | 
| 1105 |  |  |  |  |  |  |  | 
| 1106 |  |  |  |  |  |  | You may list an many aliases as you want. | 
| 1107 |  |  |  |  |  |  |  | 
| 1108 |  |  |  |  |  |  | Alias names cannot contain slashes (/). | 
| 1109 |  |  |  |  |  |  |  | 
| 1110 |  |  |  |  |  |  | Although alias dirs may start without a slash (/), this is unwise and | 
| 1111 |  |  |  |  |  |  | itE<39>s better that they always start with a slash (/) char. | 
| 1112 |  |  |  |  |  |  |  | 
| 1113 |  |  |  |  |  |  | General format: C I> | 
| 1114 |  |  |  |  |  |  |  | 
| 1115 |  |  |  |  |  |  | =item cdpath | 
| 1116 |  |  |  |  |  |  |  | 
| 1117 |  |  |  |  |  |  | Define a search path which is used when changing directories. For | 
| 1118 |  |  |  |  |  |  | example, the command C would allow | 
| 1119 |  |  |  |  |  |  | the user to access the C directory | 
| 1120 |  |  |  |  |  |  | directly by just typing C. | 
| 1121 |  |  |  |  |  |  |  | 
| 1122 |  |  |  |  |  |  | The C command checks for directories in the following order: | 
| 1123 |  |  |  |  |  |  |  | 
| 1124 |  |  |  |  |  |  | foo in the current directory | 
| 1125 |  |  |  |  |  |  | an alias called foo (see ``alias'' command above) | 
| 1126 |  |  |  |  |  |  | foo in each directory in the cdpath | 
| 1127 |  |  |  |  |  |  |  | 
| 1128 |  |  |  |  |  |  | General format: C [I [I ...]]> | 
| 1129 |  |  |  |  |  |  |  | 
| 1130 |  |  |  |  |  |  | =item allow site version command | 
| 1131 |  |  |  |  |  |  |  | 
| 1132 |  |  |  |  |  |  | SITE VERSION command. If set, then the SITE VERSION command reveals | 
| 1133 |  |  |  |  |  |  | the current Net::FTPServer version string. If unset, then the command | 
| 1134 |  |  |  |  |  |  | is disabled. | 
| 1135 |  |  |  |  |  |  |  | 
| 1136 |  |  |  |  |  |  | Default: 1 | 
| 1137 |  |  |  |  |  |  |  | 
| 1138 |  |  |  |  |  |  | Example: C | 
| 1139 |  |  |  |  |  |  |  | 
| 1140 |  |  |  |  |  |  | =item allow site exec command | 
| 1141 |  |  |  |  |  |  |  | 
| 1142 |  |  |  |  |  |  | SITE EXEC command. If set, then the SITE EXEC command allows arbitrary | 
| 1143 |  |  |  |  |  |  | commands to be executed on the server as the current user. If unset, | 
| 1144 |  |  |  |  |  |  | then this command is disabled. The default is disabled for obvious | 
| 1145 |  |  |  |  |  |  | security reasons. | 
| 1146 |  |  |  |  |  |  |  | 
| 1147 |  |  |  |  |  |  | If you do allow SITE EXEC, you may need to increase the per process | 
| 1148 |  |  |  |  |  |  | memory, processes and files limits above. | 
| 1149 |  |  |  |  |  |  |  | 
| 1150 |  |  |  |  |  |  | Default: 0 | 
| 1151 |  |  |  |  |  |  |  | 
| 1152 |  |  |  |  |  |  | Example: C | 
| 1153 |  |  |  |  |  |  |  | 
| 1154 |  |  |  |  |  |  | =item enable archive mode | 
| 1155 |  |  |  |  |  |  |  | 
| 1156 |  |  |  |  |  |  | Archive mode. If set (the default), then archive mode is | 
| 1157 |  |  |  |  |  |  | enabled, allowing users to request, say, C and | 
| 1158 |  |  |  |  |  |  | get a version of C which is gzip-compressed on the | 
| 1159 |  |  |  |  |  |  | fly. If zero, then this feature is disabled. See the | 
| 1160 |  |  |  |  |  |  | section ARCHIVE MODE elsewhere in this manual for details. | 
| 1161 |  |  |  |  |  |  |  | 
| 1162 |  |  |  |  |  |  | Since archive mode is implemented using external commands, | 
| 1163 |  |  |  |  |  |  | you need to ensure that programs such as C, | 
| 1164 |  |  |  |  |  |  | C, C, C, etc. are available on | 
| 1165 |  |  |  |  |  |  | the C<$PATH> (even in the chrooted environment), and you also | 
| 1166 |  |  |  |  |  |  | need to substantially increase the normal per-process memory, | 
| 1167 |  |  |  |  |  |  | processes and files limits. | 
| 1168 |  |  |  |  |  |  |  | 
| 1169 |  |  |  |  |  |  | Default: 1 | 
| 1170 |  |  |  |  |  |  |  | 
| 1171 |  |  |  |  |  |  | Example: C | 
| 1172 |  |  |  |  |  |  |  | 
| 1173 |  |  |  |  |  |  | =item archive zip temporaries | 
| 1174 |  |  |  |  |  |  |  | 
| 1175 |  |  |  |  |  |  | Temporary directory for generating ZIP files in archive mode. | 
| 1176 |  |  |  |  |  |  | In archive mode, when generating ZIP files, the FTP server is | 
| 1177 |  |  |  |  |  |  | capable of either creating a temporary file on local disk | 
| 1178 |  |  |  |  |  |  | containing the ZIP contents, or can generate the file completely | 
| 1179 |  |  |  |  |  |  | in memory. The former method saves memory. The latter method | 
| 1180 |  |  |  |  |  |  | (only practical on small ZIP files) allows the server to work | 
| 1181 |  |  |  |  |  |  | more securely and in certain read-only chrooted environments. | 
| 1182 |  |  |  |  |  |  |  | 
| 1183 |  |  |  |  |  |  | (Unfortunately the ZIP file format itself prevents ZIP files | 
| 1184 |  |  |  |  |  |  | from being easily created on the fly). | 
| 1185 |  |  |  |  |  |  |  | 
| 1186 |  |  |  |  |  |  | If not specified in the configuration file, this option | 
| 1187 |  |  |  |  |  |  | defaults to using C. If there are local users on the | 
| 1188 |  |  |  |  |  |  | FTP server box, then this can lead to various C races, | 
| 1189 |  |  |  |  |  |  | so for maximum security you will probably want to change | 
| 1190 |  |  |  |  |  |  | this. | 
| 1191 |  |  |  |  |  |  |  | 
| 1192 |  |  |  |  |  |  | If specified, and set to a string, then the string is the | 
| 1193 |  |  |  |  |  |  | name of a directory which is used for storing temporary zip | 
| 1194 |  |  |  |  |  |  | files. This directory must be writable, and must exist inside | 
| 1195 |  |  |  |  |  |  | the chrooted environment (if chroot is being used). | 
| 1196 |  |  |  |  |  |  |  | 
| 1197 |  |  |  |  |  |  | If specified, but set to "0" or an empty string, then | 
| 1198 |  |  |  |  |  |  | the server will always generate the ZIP file in memory. | 
| 1199 |  |  |  |  |  |  |  | 
| 1200 |  |  |  |  |  |  | In any case, if the directory is found at runtime to be | 
| 1201 |  |  |  |  |  |  | unwritable, then the server falls back to creating ZIP | 
| 1202 |  |  |  |  |  |  | files in memory. | 
| 1203 |  |  |  |  |  |  |  | 
| 1204 |  |  |  |  |  |  | Default: C | 
| 1205 |  |  |  |  |  |  |  | 
| 1206 |  |  |  |  |  |  | Example: C | 
| 1207 |  |  |  |  |  |  |  | 
| 1208 |  |  |  |  |  |  | Example: C | 
| 1209 |  |  |  |  |  |  |  | 
| 1210 |  |  |  |  |  |  | =item site command | 
| 1211 |  |  |  |  |  |  |  | 
| 1212 |  |  |  |  |  |  | Custom SITE commands. Use this command to define custom SITE | 
| 1213 |  |  |  |  |  |  | commands. Please read the section LOADING CUSTOMIZED SITE | 
| 1214 |  |  |  |  |  |  | COMMANDS in this manual page for more detailed information. | 
| 1215 |  |  |  |  |  |  |  | 
| 1216 |  |  |  |  |  |  | The C command has the form: | 
| 1217 |  |  |  |  |  |  |  | 
| 1218 |  |  |  |  |  |  | C I> | 
| 1219 |  |  |  |  |  |  |  | 
| 1220 |  |  |  |  |  |  | I is the name of the command (eg. for SITE README you | 
| 1221 |  |  |  |  |  |  | would set I == C). I is a file containing the | 
| 1222 |  |  |  |  |  |  | code of the site command in the form of an anonymous Perl | 
| 1223 |  |  |  |  |  |  | subroutine. The file should have the form: | 
| 1224 |  |  |  |  |  |  |  | 
| 1225 |  |  |  |  |  |  | sub { | 
| 1226 |  |  |  |  |  |  | my $self = shift;		# The FTPServer object. | 
| 1227 |  |  |  |  |  |  | my $cmd = shift;		# Contains the command itself. | 
| 1228 |  |  |  |  |  |  | my $rest = shift;		# Contains any parameters passed by the user. | 
| 1229 |  |  |  |  |  |  |  | 
| 1230 |  |  |  |  |  |  | :     : | 
| 1231 |  |  |  |  |  |  | :     : | 
| 1232 |  |  |  |  |  |  |  | 
| 1233 |  |  |  |  |  |  | $self->reply (RESPONSE_CODE, RESPONSE_TEXT); | 
| 1234 |  |  |  |  |  |  | } | 
| 1235 |  |  |  |  |  |  |  | 
| 1236 |  |  |  |  |  |  | You may define as many site commands as you want. You may also | 
| 1237 |  |  |  |  |  |  | override site commands from the current personality here. | 
| 1238 |  |  |  |  |  |  |  | 
| 1239 |  |  |  |  |  |  | Example: | 
| 1240 |  |  |  |  |  |  |  | 
| 1241 |  |  |  |  |  |  | site command: quota /usr/local/lib/ftp/quota.pl | 
| 1242 |  |  |  |  |  |  |  | 
| 1243 |  |  |  |  |  |  | and the file C contains: | 
| 1244 |  |  |  |  |  |  |  | 
| 1245 |  |  |  |  |  |  | sub { | 
| 1246 |  |  |  |  |  |  | my $self = shift;		# The FTPServer object. | 
| 1247 |  |  |  |  |  |  | my $cmd = shift;		# Contains "QUOTA". | 
| 1248 |  |  |  |  |  |  | my $rest = shift;		# Contains parameters passed by user. | 
| 1249 |  |  |  |  |  |  |  | 
| 1250 |  |  |  |  |  |  | # ... Some code to compute the user's quota ... | 
| 1251 |  |  |  |  |  |  |  | 
| 1252 |  |  |  |  |  |  | $self->reply (200, "Your quota is $quota MB."); | 
| 1253 |  |  |  |  |  |  | } | 
| 1254 |  |  |  |  |  |  |  | 
| 1255 |  |  |  |  |  |  | The client types C and the server responds with: | 
| 1256 |  |  |  |  |  |  |  | 
| 1257 |  |  |  |  |  |  | "200 Your quota is 12.5 MB.". | 
| 1258 |  |  |  |  |  |  |  | 
| 1259 |  |  |  |  |  |  | =item EHost hostnameE ... E/HostE | 
| 1260 |  |  |  |  |  |  |  | 
| 1261 |  |  |  |  |  |  | EHost hostnameE ... E/HostE encloses | 
| 1262 |  |  |  |  |  |  | commands which are applicable only to a particular | 
| 1263 |  |  |  |  |  |  | host. C may be either a fully-qualified | 
| 1264 |  |  |  |  |  |  | domain name (for IP-less virtual hosts) or an IP | 
| 1265 |  |  |  |  |  |  | address (for IP-based virtual hosts). You should read | 
| 1266 |  |  |  |  |  |  | the section VIRTUAL HOSTS in this manual page for | 
| 1267 |  |  |  |  |  |  | more information on the different types of virtual | 
| 1268 |  |  |  |  |  |  | hosts and how to set it up in more detail. | 
| 1269 |  |  |  |  |  |  |  | 
| 1270 |  |  |  |  |  |  | Note also that unless you have set C, | 
| 1271 |  |  |  |  |  |  | all EHostE sections will be ignored. | 
| 1272 |  |  |  |  |  |  |  | 
| 1273 |  |  |  |  |  |  | =item enable virtual hosts | 
| 1274 |  |  |  |  |  |  |  | 
| 1275 |  |  |  |  |  |  | Unless this option is uncommented, virtual hosting is disabled | 
| 1276 |  |  |  |  |  |  | and the EHostE sections in the configuration file have no effect. | 
| 1277 |  |  |  |  |  |  |  | 
| 1278 |  |  |  |  |  |  | Default: 0 | 
| 1279 |  |  |  |  |  |  |  | 
| 1280 |  |  |  |  |  |  | Example: C | 
| 1281 |  |  |  |  |  |  |  | 
| 1282 |  |  |  |  |  |  | =item virtual host multiplex | 
| 1283 |  |  |  |  |  |  |  | 
| 1284 |  |  |  |  |  |  | IP-less virtual hosts. If you want to enable IP-less virtual | 
| 1285 |  |  |  |  |  |  | hosts, then you must set up your DNS so that all hosts map | 
| 1286 |  |  |  |  |  |  | to a single IP address, and place that IP address here. This | 
| 1287 |  |  |  |  |  |  | is roughly equivalent to the Apache C option. | 
| 1288 |  |  |  |  |  |  |  | 
| 1289 |  |  |  |  |  |  | IP-less virtual hosting is an experimental feature which | 
| 1290 |  |  |  |  |  |  | requires changes to clients. | 
| 1291 |  |  |  |  |  |  |  | 
| 1292 |  |  |  |  |  |  | Default: (none) | 
| 1293 |  |  |  |  |  |  |  | 
| 1294 |  |  |  |  |  |  | Example: C | 
| 1295 |  |  |  |  |  |  |  | 
| 1296 |  |  |  |  |  |  | Example EHostE section. Allow the dangerous SITE EXEC command | 
| 1297 |  |  |  |  |  |  | on local connections. (Note that this is still dangerous). | 
| 1298 |  |  |  |  |  |  |  | 
| 1299 |  |  |  |  |  |  |  | 
| 1300 |  |  |  |  |  |  | ip: 127.0.0.1 | 
| 1301 |  |  |  |  |  |  | allow site exec command: 1 | 
| 1302 |  |  |  |  |  |  |  | 
| 1303 |  |  |  |  |  |  |  | 
| 1304 |  |  |  |  |  |  | Example EHostE section. This shows you how to do IP-based | 
| 1305 |  |  |  |  |  |  | virtual hosts. I assume that you have set up your DNS so that | 
| 1306 |  |  |  |  |  |  | C maps to IP C<1.2.3.4> and C | 
| 1307 |  |  |  |  |  |  | maps to IP C<1.2.3.5>, and you have set up suitable IP aliasing | 
| 1308 |  |  |  |  |  |  | in the kernel. | 
| 1309 |  |  |  |  |  |  |  | 
| 1310 |  |  |  |  |  |  | You do not need the C command if you have configured reverse | 
| 1311 |  |  |  |  |  |  | DNS correctly AND you trust your local DNS servers. | 
| 1312 |  |  |  |  |  |  |  | 
| 1313 |  |  |  |  |  |  |  | 
| 1314 |  |  |  |  |  |  | ip: 1.2.3.4 | 
| 1315 |  |  |  |  |  |  | root directory: /home/bob | 
| 1316 |  |  |  |  |  |  | home directory: / | 
| 1317 |  |  |  |  |  |  | user access control rule: $user eq "bob" | 
| 1318 |  |  |  |  |  |  | maintainer email: bob@bob.example.com | 
| 1319 |  |  |  |  |  |  |  | 
| 1320 |  |  |  |  |  |  |  | 
| 1321 |  |  |  |  |  |  |  | 
| 1322 |  |  |  |  |  |  | ip: 1.2.3.5 | 
| 1323 |  |  |  |  |  |  | root directory: /home/jane | 
| 1324 |  |  |  |  |  |  | home directory: / | 
| 1325 |  |  |  |  |  |  | allow anonymous: 1 | 
| 1326 |  |  |  |  |  |  | user access control rule: $user_is_anonymous | 
| 1327 |  |  |  |  |  |  | maintainer email: jane@jane.example.com | 
| 1328 |  |  |  |  |  |  |  | 
| 1329 |  |  |  |  |  |  |  | 
| 1330 |  |  |  |  |  |  | These rules set up two virtual hosts called C | 
| 1331 |  |  |  |  |  |  | and C. The former is located under bob's | 
| 1332 |  |  |  |  |  |  | home directory and only he is allowed to log in. The latter is | 
| 1333 |  |  |  |  |  |  | located under jane's home directory and only allows anonymous | 
| 1334 |  |  |  |  |  |  | access. | 
| 1335 |  |  |  |  |  |  |  | 
| 1336 |  |  |  |  |  |  | Example EHostE section. This shows you how to do IP-less | 
| 1337 |  |  |  |  |  |  | virtual hosts. Note that IP-less virtual hosts are a highly | 
| 1338 |  |  |  |  |  |  | experimental feature, and require the client to support the | 
| 1339 |  |  |  |  |  |  | HOST command. | 
| 1340 |  |  |  |  |  |  |  | 
| 1341 |  |  |  |  |  |  | You need to set up your DNS so that both C | 
| 1342 |  |  |  |  |  |  | and C point to your own IP address. | 
| 1343 |  |  |  |  |  |  |  | 
| 1344 |  |  |  |  |  |  | virtual host multiplex: 1.2.3.4 | 
| 1345 |  |  |  |  |  |  |  | 
| 1346 |  |  |  |  |  |  |  | 
| 1347 |  |  |  |  |  |  | root directory: /home/bob | 
| 1348 |  |  |  |  |  |  | home directory: / | 
| 1349 |  |  |  |  |  |  | user access control rule: $user eq "bob" | 
| 1350 |  |  |  |  |  |  |  | 
| 1351 |  |  |  |  |  |  |  | 
| 1352 |  |  |  |  |  |  |  | 
| 1353 |  |  |  |  |  |  | root directory: /home/jane | 
| 1354 |  |  |  |  |  |  | home directory: / | 
| 1355 |  |  |  |  |  |  | allow anonymous: 1 | 
| 1356 |  |  |  |  |  |  | user access control rule: $user_is_anonymous | 
| 1357 |  |  |  |  |  |  |  | 
| 1358 |  |  |  |  |  |  |  | 
| 1359 |  |  |  |  |  |  | =item log socket type | 
| 1360 |  |  |  |  |  |  |  | 
| 1361 |  |  |  |  |  |  | Socket type for contacting syslog. This is the argument to | 
| 1362 |  |  |  |  |  |  | the C function. | 
| 1363 |  |  |  |  |  |  |  | 
| 1364 |  |  |  |  |  |  | Default: unix | 
| 1365 |  |  |  |  |  |  |  | 
| 1366 |  |  |  |  |  |  | Example: C | 
| 1367 |  |  |  |  |  |  |  | 
| 1368 |  |  |  |  |  |  | =item listen queue | 
| 1369 |  |  |  |  |  |  |  | 
| 1370 |  |  |  |  |  |  | Length of the listen queue when running in daemon mode. | 
| 1371 |  |  |  |  |  |  |  | 
| 1372 |  |  |  |  |  |  | Default: 10 | 
| 1373 |  |  |  |  |  |  |  | 
| 1374 |  |  |  |  |  |  | Example: C | 
| 1375 |  |  |  |  |  |  |  | 
| 1376 |  |  |  |  |  |  | =item tcp window | 
| 1377 |  |  |  |  |  |  |  | 
| 1378 |  |  |  |  |  |  | Set TCP window. See RFC 2415 | 
| 1379 |  |  |  |  |  |  | I. | 
| 1380 |  |  |  |  |  |  | This setting only affects the data | 
| 1381 |  |  |  |  |  |  | socket. ItE<39>s not likely that you will need to or should change | 
| 1382 |  |  |  |  |  |  | this setting from the system-specific default. | 
| 1383 |  |  |  |  |  |  |  | 
| 1384 |  |  |  |  |  |  | Default: (system-specific TCP window size) | 
| 1385 |  |  |  |  |  |  |  | 
| 1386 |  |  |  |  |  |  | Example: C | 
| 1387 |  |  |  |  |  |  |  | 
| 1388 |  |  |  |  |  |  | =item tcp keepalive | 
| 1389 |  |  |  |  |  |  |  | 
| 1390 |  |  |  |  |  |  | Set TCP keepalive. | 
| 1391 |  |  |  |  |  |  |  | 
| 1392 |  |  |  |  |  |  | Default: (system-specific keepalive setting) | 
| 1393 |  |  |  |  |  |  |  | 
| 1394 |  |  |  |  |  |  | Example: C | 
| 1395 |  |  |  |  |  |  |  | 
| 1396 |  |  |  |  |  |  | =item command filter | 
| 1397 |  |  |  |  |  |  |  | 
| 1398 |  |  |  |  |  |  | Command filter. If set, then all commands are checked against | 
| 1399 |  |  |  |  |  |  | this regular expression before being executed. If a command | 
| 1400 |  |  |  |  |  |  | doesnE<39>t match the filter, then the command connection is | 
| 1401 |  |  |  |  |  |  | immediately dropped. This is equivalent to the C | 
| 1402 |  |  |  |  |  |  | command in ProFTPD. Remember to include C<^...$> around the filter. | 
| 1403 |  |  |  |  |  |  |  | 
| 1404 |  |  |  |  |  |  | Default: (no filter) | 
| 1405 |  |  |  |  |  |  |  | 
| 1406 |  |  |  |  |  |  | Example: C | 
| 1407 |  |  |  |  |  |  |  | 
| 1408 |  |  |  |  |  |  | =item restrict command | 
| 1409 |  |  |  |  |  |  |  | 
| 1410 |  |  |  |  |  |  | Advanced command filtering. The C directive takes | 
| 1411 |  |  |  |  |  |  | the form: | 
| 1412 |  |  |  |  |  |  |  | 
| 1413 |  |  |  |  |  |  | restrict command: "COMMAND" perl code ... | 
| 1414 |  |  |  |  |  |  |  | 
| 1415 |  |  |  |  |  |  | If the user tries to execute C, then the C is | 
| 1416 |  |  |  |  |  |  | evaluated first. If it evaluates to true, then the command is allowed | 
| 1417 |  |  |  |  |  |  | to proceed. Otherwise the server reports an error back to the user and | 
| 1418 |  |  |  |  |  |  | does not execute the command. | 
| 1419 |  |  |  |  |  |  |  | 
| 1420 |  |  |  |  |  |  | Note that the C is the FTP protocol command, which is not | 
| 1421 |  |  |  |  |  |  | necessarily the same as the command which users will type in on their | 
| 1422 |  |  |  |  |  |  | FTP clients. Please read RFC 959 to see some of the more common FTP | 
| 1423 |  |  |  |  |  |  | protocol commands. | 
| 1424 |  |  |  |  |  |  |  | 
| 1425 |  |  |  |  |  |  | The Perl code has the same variables available to it as for access | 
| 1426 |  |  |  |  |  |  | control rules (eg. C<$user>, C<$class>, C<$ip>, etc.). The code | 
| 1427 |  |  |  |  |  |  | I alter the global C<$_> variable (which contains the | 
| 1428 |  |  |  |  |  |  | complete command). | 
| 1429 |  |  |  |  |  |  |  | 
| 1430 |  |  |  |  |  |  | Default: all commands are allowed by default | 
| 1431 |  |  |  |  |  |  |  | 
| 1432 |  |  |  |  |  |  | Examples: | 
| 1433 |  |  |  |  |  |  |  | 
| 1434 |  |  |  |  |  |  | Only allow users in the class C to delete files and | 
| 1435 |  |  |  |  |  |  | directories: | 
| 1436 |  |  |  |  |  |  |  | 
| 1437 |  |  |  |  |  |  | restrict command: "DELE" $class eq "nukers" | 
| 1438 |  |  |  |  |  |  | restrict command: "RMD" $class eq "nukers" | 
| 1439 |  |  |  |  |  |  |  | 
| 1440 |  |  |  |  |  |  | Only allow staff to use the C command: | 
| 1441 |  |  |  |  |  |  |  | 
| 1442 |  |  |  |  |  |  | restrict command: "SITE WHO" $class eq "staff" | 
| 1443 |  |  |  |  |  |  |  | 
| 1444 |  |  |  |  |  |  | Only allow C to run the C command: | 
| 1445 |  |  |  |  |  |  |  | 
| 1446 |  |  |  |  |  |  | allow site exec command: 1 | 
| 1447 |  |  |  |  |  |  | restrict command: "SITE EXEC" $user eq "rich" | 
| 1448 |  |  |  |  |  |  |  | 
| 1449 |  |  |  |  |  |  | =item command wait | 
| 1450 |  |  |  |  |  |  |  | 
| 1451 |  |  |  |  |  |  | Go slow. If set, then the server will sleep for this many seconds | 
| 1452 |  |  |  |  |  |  | before beginning to process each command. This command would be | 
| 1453 |  |  |  |  |  |  | a lot more useful if you could apply it only to particular | 
| 1454 |  |  |  |  |  |  | classes of connection. | 
| 1455 |  |  |  |  |  |  |  | 
| 1456 |  |  |  |  |  |  | Default: (no wait) | 
| 1457 |  |  |  |  |  |  |  | 
| 1458 |  |  |  |  |  |  | Example: C | 
| 1459 |  |  |  |  |  |  |  | 
| 1460 |  |  |  |  |  |  | =item no authentication commands | 
| 1461 |  |  |  |  |  |  |  | 
| 1462 |  |  |  |  |  |  | The list of commands which a client may issue before they have | 
| 1463 |  |  |  |  |  |  | authenticated themselves is very limited. Obviously C and | 
| 1464 |  |  |  |  |  |  | C are allowed (otherwise a user would never be able to log | 
| 1465 |  |  |  |  |  |  | in!), also C, C, C and C. C is also permitted | 
| 1466 |  |  |  |  |  |  | (although dubious). Any other commands not on this list will | 
| 1467 |  |  |  |  |  |  | result in a I<530 Not logged in.> error. | 
| 1468 |  |  |  |  |  |  |  | 
| 1469 |  |  |  |  |  |  | This list ought to contain at least C, C and C | 
| 1470 |  |  |  |  |  |  | otherwise the server wonE<39>t be very functional. | 
| 1471 |  |  |  |  |  |  |  | 
| 1472 |  |  |  |  |  |  | Some commands cannot be added here -- eg. adding C or C | 
| 1473 |  |  |  |  |  |  | to this list is likely to make the FTP server crash, or else enable | 
| 1474 |  |  |  |  |  |  | users to read files only available to root. Hence use this with | 
| 1475 |  |  |  |  |  |  | great care. | 
| 1476 |  |  |  |  |  |  |  | 
| 1477 |  |  |  |  |  |  | Default: USER PASS QUIT LANG HOST FEAT HELP | 
| 1478 |  |  |  |  |  |  |  | 
| 1479 |  |  |  |  |  |  | Example: C | 
| 1480 |  |  |  |  |  |  |  | 
| 1481 |  |  |  |  |  |  | =item EPerlE ... E/PerlE | 
| 1482 |  |  |  |  |  |  |  | 
| 1483 |  |  |  |  |  |  | Use the EPerlE directive to write Perl code directly | 
| 1484 |  |  |  |  |  |  | into your configuration file. Here is a simple example: | 
| 1485 |  |  |  |  |  |  |  | 
| 1486 |  |  |  |  |  |  |  | 
| 1487 |  |  |  |  |  |  | use Sys::Hostname; | 
| 1488 |  |  |  |  |  |  | $config{'maintainer email'} = "root\@" . hostname (); | 
| 1489 |  |  |  |  |  |  | $config{port} = 8000 + 21; | 
| 1490 |  |  |  |  |  |  | $config{debug} = $ENV{FTP_DEBUG} ? 1 : 0; | 
| 1491 |  |  |  |  |  |  |  | 
| 1492 |  |  |  |  |  |  |  | 
| 1493 |  |  |  |  |  |  | As shown in the example, to set a configuration option called | 
| 1494 |  |  |  |  |  |  | C, you simply assign to the variable C<$config{foo}>. | 
| 1495 |  |  |  |  |  |  |  | 
| 1496 |  |  |  |  |  |  | All normal Perl functionality is available to you, including | 
| 1497 |  |  |  |  |  |  | use of C if you need to run an external Perl script. | 
| 1498 |  |  |  |  |  |  |  | 
| 1499 |  |  |  |  |  |  | The EPerlE and E/PerlE directives must each appear | 
| 1500 |  |  |  |  |  |  | on a single line on their own. | 
| 1501 |  |  |  |  |  |  |  | 
| 1502 |  |  |  |  |  |  | To assign multiple configuration options with the same name, | 
| 1503 |  |  |  |  |  |  | use an array ref: | 
| 1504 |  |  |  |  |  |  |  | 
| 1505 |  |  |  |  |  |  |  | 
| 1506 |  |  |  |  |  |  | my @aliases = ( "foo /pub/foo", | 
| 1507 |  |  |  |  |  |  | "bar /pub/bar", | 
| 1508 |  |  |  |  |  |  | "baz /pub/baz" ); | 
| 1509 |  |  |  |  |  |  | $config{alias} = \@aliases; | 
| 1510 |  |  |  |  |  |  |  | 
| 1511 |  |  |  |  |  |  |  | 
| 1512 |  |  |  |  |  |  | You cannot use a EPerlE section within a EHostE | 
| 1513 |  |  |  |  |  |  | section. Instead, you must simulate it by assigning to the | 
| 1514 |  |  |  |  |  |  | C<%host_config> variable like this: | 
| 1515 |  |  |  |  |  |  |  | 
| 1516 |  |  |  |  |  |  |  | 
| 1517 |  |  |  |  |  |  | $host_config{'localhost.localdomain'}{ip} = "127.0.0.1"; | 
| 1518 |  |  |  |  |  |  | $host_config{'localhost.localdomain'}{'allow site exec command'}= 1; | 
| 1519 |  |  |  |  |  |  |  | 
| 1520 |  |  |  |  |  |  |  | 
| 1521 |  |  |  |  |  |  | The above is equivalent to the following ordinary EHostE | 
| 1522 |  |  |  |  |  |  | section: | 
| 1523 |  |  |  |  |  |  |  | 
| 1524 |  |  |  |  |  |  |  | 
| 1525 |  |  |  |  |  |  | ip: 127.0.0.1 | 
| 1526 |  |  |  |  |  |  | allow site exec command: 1 | 
| 1527 |  |  |  |  |  |  |  | 
| 1528 |  |  |  |  |  |  |  | 
| 1529 |  |  |  |  |  |  | You may also assign to the C<$self> variable in order to set | 
| 1530 |  |  |  |  |  |  | variables directly in the C object itself. This | 
| 1531 |  |  |  |  |  |  | is pretty hairy, and hence not recommended, but you dig your own | 
| 1532 |  |  |  |  |  |  | hole if you want. Here is a contrived example: | 
| 1533 |  |  |  |  |  |  |  | 
| 1534 |  |  |  |  |  |  |  | 
| 1535 |  |  |  |  |  |  | $self->{version_string} = "my FTP server/1.0"; | 
| 1536 |  |  |  |  |  |  |  | 
| 1537 |  |  |  |  |  |  |  | 
| 1538 |  |  |  |  |  |  | A cleaner, but more complex way to do this would be to use | 
| 1539 |  |  |  |  |  |  | a personality. | 
| 1540 |  |  |  |  |  |  |  | 
| 1541 |  |  |  |  |  |  | The EPerlE directive is potentially quite powerful. | 
| 1542 |  |  |  |  |  |  | Here is a good idea that Rob Brown had: | 
| 1543 |  |  |  |  |  |  |  | 
| 1544 |  |  |  |  |  |  |  | 
| 1545 |  |  |  |  |  |  | my %H; | 
| 1546 |  |  |  |  |  |  | dbmopen (%H, "/etc/ftpd.db", 0644); | 
| 1547 |  |  |  |  |  |  | %config = %H; | 
| 1548 |  |  |  |  |  |  | dbmclose (%H); | 
| 1549 |  |  |  |  |  |  |  | 
| 1550 |  |  |  |  |  |  |  | 
| 1551 |  |  |  |  |  |  | Notice how this allows you to crunch a possibly very large | 
| 1552 |  |  |  |  |  |  | configuration file into a hash, for very rapid loading at run time. | 
| 1553 |  |  |  |  |  |  |  | 
| 1554 |  |  |  |  |  |  | Another useful way to use EPerlE is to set environment | 
| 1555 |  |  |  |  |  |  | variables (particularly C<$PATH>). | 
| 1556 |  |  |  |  |  |  |  | 
| 1557 |  |  |  |  |  |  |  | 
| 1558 |  |  |  |  |  |  | $ENV{PATH} = "/usr/local/bin:$ENV{PATH}" | 
| 1559 |  |  |  |  |  |  |  | 
| 1560 |  |  |  |  |  |  |  | 
| 1561 |  |  |  |  |  |  | HereE<39>s yet another wonderful way to use EPerlE. | 
| 1562 |  |  |  |  |  |  | Look in C for a list of site commands | 
| 1563 |  |  |  |  |  |  | and load each one: | 
| 1564 |  |  |  |  |  |  |  | 
| 1565 |  |  |  |  |  |  |  | 
| 1566 |  |  |  |  |  |  |  | 
| 1567 |  |  |  |  |  |  | my @files = glob "/usr/local/lib/ftp/*.pl"; | 
| 1568 |  |  |  |  |  |  | my @site_commands; | 
| 1569 |  |  |  |  |  |  |  | 
| 1570 |  |  |  |  |  |  | foreach (@files) | 
| 1571 |  |  |  |  |  |  | { | 
| 1572 |  |  |  |  |  |  | push @site_commands, "$1 $_" if /([a-z]+)\.pl/; | 
| 1573 |  |  |  |  |  |  | } | 
| 1574 |  |  |  |  |  |  |  | 
| 1575 |  |  |  |  |  |  | $config{'site command'} = \@site_commands; | 
| 1576 |  |  |  |  |  |  |  | 
| 1577 |  |  |  |  |  |  |  | 
| 1578 |  |  |  |  |  |  |  | 
| 1579 |  |  |  |  |  |  | To force a particular version of Net::FTPServer to be | 
| 1580 |  |  |  |  |  |  | used, include the following code in your configuration | 
| 1581 |  |  |  |  |  |  | file: | 
| 1582 |  |  |  |  |  |  |  | 
| 1583 |  |  |  |  |  |  |  | 
| 1584 |  |  |  |  |  |  | die "requires Net::FTPServer version >= 1.025" | 
| 1585 |  |  |  |  |  |  | unless $Net::FTPServer::VERSION !~ /\..*\./ && | 
| 1586 |  |  |  |  |  |  | $Net::FTPServer::VERSION >= 1.025; | 
| 1587 |  |  |  |  |  |  |  | 
| 1588 |  |  |  |  |  |  |  | 
| 1589 |  |  |  |  |  |  | =back | 
| 1590 |  |  |  |  |  |  |  | 
| 1591 |  |  |  |  |  |  | =head2 LOADING CUSTOMIZED SITE COMMANDS | 
| 1592 |  |  |  |  |  |  |  | 
| 1593 |  |  |  |  |  |  | It is very simple to write custom SITE commands. These | 
| 1594 |  |  |  |  |  |  | commands are available to users when they type "SITE XYZ" | 
| 1595 |  |  |  |  |  |  | in a command line FTP client or when they define a custom | 
| 1596 |  |  |  |  |  |  | SITE command in their graphical FTP client. | 
| 1597 |  |  |  |  |  |  |  | 
| 1598 |  |  |  |  |  |  | SITE commands are unregulated by RFCs. You may define any commands and | 
| 1599 |  |  |  |  |  |  | give them any names and any function you wish. However, over time | 
| 1600 |  |  |  |  |  |  | various standard SITE commands have been recognized and implemented | 
| 1601 |  |  |  |  |  |  | in many FTP servers. C also implements these. They | 
| 1602 |  |  |  |  |  |  | are: | 
| 1603 |  |  |  |  |  |  |  | 
| 1604 |  |  |  |  |  |  | SITE VERSION      Display the server software version. | 
| 1605 |  |  |  |  |  |  | SITE EXEC         Execute a shell command on the server (in | 
| 1606 |  |  |  |  |  |  | C this is disabled by default!) | 
| 1607 |  |  |  |  |  |  | SITE ALIAS        Display chdir aliases. | 
| 1608 |  |  |  |  |  |  | SITE CDPATH       Display chdir paths. | 
| 1609 |  |  |  |  |  |  | SITE CHECKMETHOD  Implement checksums. | 
| 1610 |  |  |  |  |  |  | SITE CHECKSUM | 
| 1611 |  |  |  |  |  |  | SITE IDLE         Get or set the idle timeout. | 
| 1612 |  |  |  |  |  |  | SITE SYNC         Synchronize hard disks. | 
| 1613 |  |  |  |  |  |  |  | 
| 1614 |  |  |  |  |  |  | The following commands are found in C, but not currently | 
| 1615 |  |  |  |  |  |  | implemented by C: SITE CHMOD, SITE GPASS, SITE GROUP, | 
| 1616 |  |  |  |  |  |  | SITE GROUPS, SITE INDEX, SITE MINFO, SITE NEWER, SITE UMASK. | 
| 1617 |  |  |  |  |  |  |  | 
| 1618 |  |  |  |  |  |  | So when you are choosing a name for a SITE command, it is probably | 
| 1619 |  |  |  |  |  |  | best not to choose one of the above names, unless you are specifically | 
| 1620 |  |  |  |  |  |  | implementing or overriding that command. | 
| 1621 |  |  |  |  |  |  |  | 
| 1622 |  |  |  |  |  |  | Custom SITE commands have to be written in Perl. However, there | 
| 1623 |  |  |  |  |  |  | is very little you need to understand in order to write these | 
| 1624 |  |  |  |  |  |  | commands -- you will only need a basic knowledge of Perl scripting. | 
| 1625 |  |  |  |  |  |  |  | 
| 1626 |  |  |  |  |  |  | As our first example, we will implement a C command. | 
| 1627 |  |  |  |  |  |  | This command just prints out some standard information. | 
| 1628 |  |  |  |  |  |  |  | 
| 1629 |  |  |  |  |  |  | Firstly create a file called C (you | 
| 1630 |  |  |  |  |  |  | may choose a different path if you want). The file should contain: | 
| 1631 |  |  |  |  |  |  |  | 
| 1632 |  |  |  |  |  |  | sub { | 
| 1633 |  |  |  |  |  |  | my $self = shift; | 
| 1634 |  |  |  |  |  |  | my $cmd = shift; | 
| 1635 |  |  |  |  |  |  | my $rest = shift; | 
| 1636 |  |  |  |  |  |  |  | 
| 1637 |  |  |  |  |  |  | $self->reply (200, | 
| 1638 |  |  |  |  |  |  | "This is the README file for mysite.example.com.", | 
| 1639 |  |  |  |  |  |  | "Mirrors are contained in /pub/mirrors directory.", | 
| 1640 |  |  |  |  |  |  | "       :       :       :       :       :", | 
| 1641 |  |  |  |  |  |  | "End of the README file."); | 
| 1642 |  |  |  |  |  |  | } | 
| 1643 |  |  |  |  |  |  |  | 
| 1644 |  |  |  |  |  |  | Edit C and add the following command: | 
| 1645 |  |  |  |  |  |  |  | 
| 1646 |  |  |  |  |  |  | site command: readme /usr/local/lib/site_readme.pl | 
| 1647 |  |  |  |  |  |  |  | 
| 1648 |  |  |  |  |  |  | and restart the FTP server (check your system log [/var/log/messages] | 
| 1649 |  |  |  |  |  |  | for any syntax errors or other problems). Here is an example of a | 
| 1650 |  |  |  |  |  |  | user running the SITE README command: | 
| 1651 |  |  |  |  |  |  |  | 
| 1652 |  |  |  |  |  |  | ftp> quote help site | 
| 1653 |  |  |  |  |  |  | 214-The following commands are recognized: | 
| 1654 |  |  |  |  |  |  | 214-    ALIAS   CHECKMETHOD     EXEC    README | 
| 1655 |  |  |  |  |  |  | 214-    CDPATH  CHECKSUM        IDLE    VERSION | 
| 1656 |  |  |  |  |  |  | 214 You can also use HELP to list general commands. | 
| 1657 |  |  |  |  |  |  | ftp> site readme | 
| 1658 |  |  |  |  |  |  | 200-This is the README file for mysite.example.com. | 
| 1659 |  |  |  |  |  |  | 200-Mirrors are contained in /pub/mirrors directory. | 
| 1660 |  |  |  |  |  |  | 200-       :       :       :       :       : | 
| 1661 |  |  |  |  |  |  | 200 End of the README file. | 
| 1662 |  |  |  |  |  |  |  | 
| 1663 |  |  |  |  |  |  | Our second example demonstrates how to use parameters | 
| 1664 |  |  |  |  |  |  | (the C<$rest> argument). This is the C command. | 
| 1665 |  |  |  |  |  |  |  | 
| 1666 |  |  |  |  |  |  | sub { | 
| 1667 |  |  |  |  |  |  | my $self = shift; | 
| 1668 |  |  |  |  |  |  | my $cmd = shift; | 
| 1669 |  |  |  |  |  |  | my $rest = shift; | 
| 1670 |  |  |  |  |  |  |  | 
| 1671 |  |  |  |  |  |  | # Split the parameters up. | 
| 1672 |  |  |  |  |  |  | my @params = split /\s+/, $rest; | 
| 1673 |  |  |  |  |  |  |  | 
| 1674 |  |  |  |  |  |  | # Quote each parameter. | 
| 1675 |  |  |  |  |  |  | my $reply = join ", ", map { "'$_'" } @params; | 
| 1676 |  |  |  |  |  |  |  | 
| 1677 |  |  |  |  |  |  | $self->reply (200, "You said: $reply"); | 
| 1678 |  |  |  |  |  |  | } | 
| 1679 |  |  |  |  |  |  |  | 
| 1680 |  |  |  |  |  |  | Here is the C command in use: | 
| 1681 |  |  |  |  |  |  |  | 
| 1682 |  |  |  |  |  |  | ftp> quote help site | 
| 1683 |  |  |  |  |  |  | 214-The following commands are recognized: | 
| 1684 |  |  |  |  |  |  | 214-    ALIAS   CHECKMETHOD     ECHO    IDLE | 
| 1685 |  |  |  |  |  |  | 214-    CDPATH  CHECKSUM        EXEC    VERSION | 
| 1686 |  |  |  |  |  |  | 214 You can also use HELP to list general commands. | 
| 1687 |  |  |  |  |  |  | ftp> site echo hello how are you? | 
| 1688 |  |  |  |  |  |  | 200 You said: 'hello', 'how', 'are', 'you?' | 
| 1689 |  |  |  |  |  |  |  | 
| 1690 |  |  |  |  |  |  | Our third example is more complex and shows how to interact | 
| 1691 |  |  |  |  |  |  | with the virtual filesystem (VFS). The C command | 
| 1692 |  |  |  |  |  |  | will be used to list text files directly (the user normally | 
| 1693 |  |  |  |  |  |  | has to download the file and view it locally). Hence | 
| 1694 |  |  |  |  |  |  | C should print the contents of the | 
| 1695 |  |  |  |  |  |  | C file in the local directory (if it exists). | 
| 1696 |  |  |  |  |  |  |  | 
| 1697 |  |  |  |  |  |  | All file accesses B be done through the VFS, not | 
| 1698 |  |  |  |  |  |  | by directly accessing the disk. If you follow this convention | 
| 1699 |  |  |  |  |  |  | then your commands will be secure and will work correctly | 
| 1700 |  |  |  |  |  |  | with different back-end personalities (in particular when | 
| 1701 |  |  |  |  |  |  | ``files'' are really blobs in a relational database). | 
| 1702 |  |  |  |  |  |  |  | 
| 1703 |  |  |  |  |  |  | sub { | 
| 1704 |  |  |  |  |  |  | my $self = shift; | 
| 1705 |  |  |  |  |  |  | my $cmd = shift; | 
| 1706 |  |  |  |  |  |  | my $rest = shift; | 
| 1707 |  |  |  |  |  |  |  | 
| 1708 |  |  |  |  |  |  | # Get the file handle. | 
| 1709 |  |  |  |  |  |  | my ($dirh, $fileh, $filename) = $self->_get ($rest); | 
| 1710 |  |  |  |  |  |  |  | 
| 1711 |  |  |  |  |  |  | # File doesn't exist or not accessible. Return an error. | 
| 1712 |  |  |  |  |  |  | unless ($fileh) | 
| 1713 |  |  |  |  |  |  | { | 
| 1714 |  |  |  |  |  |  | $self->reply (550, "File or directory not found."); | 
| 1715 |  |  |  |  |  |  | return; | 
| 1716 |  |  |  |  |  |  | } | 
| 1717 |  |  |  |  |  |  |  | 
| 1718 |  |  |  |  |  |  | # Check it's a simple file. | 
| 1719 |  |  |  |  |  |  | my ($mode) = $fileh->status; | 
| 1720 |  |  |  |  |  |  |  | 
| 1721 |  |  |  |  |  |  | unless ($mode eq "f") | 
| 1722 |  |  |  |  |  |  | { | 
| 1723 |  |  |  |  |  |  | $self->reply (550, | 
| 1724 |  |  |  |  |  |  | "SITE SHOW command is only supported on plain files."); | 
| 1725 |  |  |  |  |  |  | return; | 
| 1726 |  |  |  |  |  |  | } | 
| 1727 |  |  |  |  |  |  |  | 
| 1728 |  |  |  |  |  |  | # Try to open the file. | 
| 1729 |  |  |  |  |  |  | my $file = $fileh->open ("r"); | 
| 1730 |  |  |  |  |  |  |  | 
| 1731 |  |  |  |  |  |  | unless ($file) | 
| 1732 |  |  |  |  |  |  | { | 
| 1733 |  |  |  |  |  |  | $self->reply (550, "File or directory not found."); | 
| 1734 |  |  |  |  |  |  | return; | 
| 1735 |  |  |  |  |  |  | } | 
| 1736 |  |  |  |  |  |  |  | 
| 1737 |  |  |  |  |  |  | # Copy data into memory. | 
| 1738 |  |  |  |  |  |  | my @lines = (); | 
| 1739 |  |  |  |  |  |  |  | 
| 1740 |  |  |  |  |  |  | while (defined ($_ = $file->getline)) | 
| 1741 |  |  |  |  |  |  | { | 
| 1742 |  |  |  |  |  |  | # Remove any native line endings. | 
| 1743 |  |  |  |  |  |  | s/[\n\r]+$//; | 
| 1744 |  |  |  |  |  |  |  | 
| 1745 |  |  |  |  |  |  | push @lines, $_; | 
| 1746 |  |  |  |  |  |  | } | 
| 1747 |  |  |  |  |  |  |  | 
| 1748 |  |  |  |  |  |  | # Close the file handle. | 
| 1749 |  |  |  |  |  |  | unless ($file->close) | 
| 1750 |  |  |  |  |  |  | { | 
| 1751 |  |  |  |  |  |  | $self->reply (550, "Close failed: ".$self->system_error_hook()); | 
| 1752 |  |  |  |  |  |  | return; | 
| 1753 |  |  |  |  |  |  | } | 
| 1754 |  |  |  |  |  |  |  | 
| 1755 |  |  |  |  |  |  | # Send the file back to the user. | 
| 1756 |  |  |  |  |  |  | $self->reply (200, "File $filename:", @lines, "End of file."); | 
| 1757 |  |  |  |  |  |  | } | 
| 1758 |  |  |  |  |  |  |  | 
| 1759 |  |  |  |  |  |  | This code is not quite complete. A better implementation would | 
| 1760 |  |  |  |  |  |  | also check the "retrieve rule" (so that people couldnE<39>t | 
| 1761 |  |  |  |  |  |  | use C in order to get around access control limitations | 
| 1762 |  |  |  |  |  |  | which the server administrator has put in place). It would also | 
| 1763 |  |  |  |  |  |  | check the file more closely to make sure it was a text file and | 
| 1764 |  |  |  |  |  |  | would refuse to list very large files. | 
| 1765 |  |  |  |  |  |  |  | 
| 1766 |  |  |  |  |  |  | Here is an example (abbreviated) of a user using the | 
| 1767 |  |  |  |  |  |  | C command: | 
| 1768 |  |  |  |  |  |  |  | 
| 1769 |  |  |  |  |  |  | ftp> site show README | 
| 1770 |  |  |  |  |  |  | 200-File README: | 
| 1771 |  |  |  |  |  |  | 200-README | 
| 1772 |  |  |  |  |  |  | 200-====== | 
| 1773 |  |  |  |  |  |  | 200- | 
| 1774 |  |  |  |  |  |  | 200-Biblio@Tech Net::FTPServer - A full-featured, secure, extensible | 
| 1775 |  |  |  |  |  |  | [...] | 
| 1776 |  |  |  |  |  |  | 200-Copyright (C) 2000-2003 Richard Jones  and other contributors. | 
| 1777 |  |  |  |  |  |  | 200 End of file. | 
| 1778 |  |  |  |  |  |  |  | 
| 1779 |  |  |  |  |  |  | =head2 STANDARD PERSONALITIES | 
| 1780 |  |  |  |  |  |  |  | 
| 1781 |  |  |  |  |  |  | Currently C is supplied with three standard | 
| 1782 |  |  |  |  |  |  | personalities. These are: | 
| 1783 |  |  |  |  |  |  |  | 
| 1784 |  |  |  |  |  |  | Full    The complete read/write anonymous/authenticated FTP | 
| 1785 |  |  |  |  |  |  | server which serves files from a standard Unix filesystem. | 
| 1786 |  |  |  |  |  |  |  | 
| 1787 |  |  |  |  |  |  | RO      A small read-only anonymous-only FTP server similar | 
| 1788 |  |  |  |  |  |  | in functionality to Dan Bernstein's publicfile | 
| 1789 |  |  |  |  |  |  | program. | 
| 1790 |  |  |  |  |  |  |  | 
| 1791 |  |  |  |  |  |  | DBeg1   An example FTP server which serves files to a PostgreSQL | 
| 1792 |  |  |  |  |  |  | database. This supports files and hierarchical | 
| 1793 |  |  |  |  |  |  | directories, multiple users (but not file permissions) | 
| 1794 |  |  |  |  |  |  | and file upload. | 
| 1795 |  |  |  |  |  |  |  | 
| 1796 |  |  |  |  |  |  | The standard B personality will not be explained here. | 
| 1797 |  |  |  |  |  |  |  | 
| 1798 |  |  |  |  |  |  | The B personality is the Full personality with all code | 
| 1799 |  |  |  |  |  |  | related to writing files, creating directories, deleting, etc. | 
| 1800 |  |  |  |  |  |  | removed. The RO personality also only permits anonymous | 
| 1801 |  |  |  |  |  |  | logins and does not contain any code to do ordinary | 
| 1802 |  |  |  |  |  |  | authentication. It is therefore safe to use the RO | 
| 1803 |  |  |  |  |  |  | personality where you are only interested in serving | 
| 1804 |  |  |  |  |  |  | files to anonymous users and do not want to worry about | 
| 1805 |  |  |  |  |  |  | crackers discovering a way to trick the FTP server into | 
| 1806 |  |  |  |  |  |  | writing over a file. | 
| 1807 |  |  |  |  |  |  |  | 
| 1808 |  |  |  |  |  |  | The B personality is a complete read/write | 
| 1809 |  |  |  |  |  |  | FTP server which stores files as BLOBs (Binary Large | 
| 1810 |  |  |  |  |  |  | OBjects) in a PostgreSQL relational database. The | 
| 1811 |  |  |  |  |  |  | personality supports file download and upload and | 
| 1812 |  |  |  |  |  |  | contains code to authenticate users against a C | 
| 1813 |  |  |  |  |  |  | table in the database (database ``users'' are thus | 
| 1814 |  |  |  |  |  |  | completely unrelated to real Unix users). The | 
| 1815 |  |  |  |  |  |  | B is intended only as an example. It does | 
| 1816 |  |  |  |  |  |  | not support advanced features such as file | 
| 1817 |  |  |  |  |  |  | permissions and quotas. As part of the schoolmaster.net | 
| 1818 |  |  |  |  |  |  | project Bibliotech Ltd. have developed an even more | 
| 1819 |  |  |  |  |  |  | advanced database personality which supports users, | 
| 1820 |  |  |  |  |  |  | groups, access control lists, quotas, recursive | 
| 1821 |  |  |  |  |  |  | moves and copies and many other features. However this | 
| 1822 |  |  |  |  |  |  | database personality is not available as source. | 
| 1823 |  |  |  |  |  |  |  | 
| 1824 |  |  |  |  |  |  | To use the DBeg1 personality you must first run a | 
| 1825 |  |  |  |  |  |  | PostgreSQL server (version 6.4 or above) and ensure | 
| 1826 |  |  |  |  |  |  | that you have access to it from your local user account. | 
| 1827 |  |  |  |  |  |  | Use the C, C and C | 
| 1828 |  |  |  |  |  |  | commands to create the appropriate user account and | 
| 1829 |  |  |  |  |  |  | database (please consult the PostgreSQL administrators | 
| 1830 |  |  |  |  |  |  | manual for further information about this -- I do | 
| 1831 |  |  |  |  |  |  | not answer questions about basic PostgreSQL knowledge). | 
| 1832 |  |  |  |  |  |  |  | 
| 1833 |  |  |  |  |  |  | Here is my correctly set up PostgreSQL server, accessed | 
| 1834 |  |  |  |  |  |  | from my local user account ``rich'': | 
| 1835 |  |  |  |  |  |  |  | 
| 1836 |  |  |  |  |  |  | cruiser:~$ psql | 
| 1837 |  |  |  |  |  |  | Welcome to the POSTGRESQL interactive sql monitor: | 
| 1838 |  |  |  |  |  |  | Please read the file COPYRIGHT for copyright terms of POSTGRESQL | 
| 1839 |  |  |  |  |  |  |  | 
| 1840 |  |  |  |  |  |  | type \? for help on slash commands | 
| 1841 |  |  |  |  |  |  | type \q to quit | 
| 1842 |  |  |  |  |  |  | type \g or terminate with semicolon to execute query | 
| 1843 |  |  |  |  |  |  | You are currently connected to the database: rich | 
| 1844 |  |  |  |  |  |  |  | 
| 1845 |  |  |  |  |  |  | rich=> \d | 
| 1846 |  |  |  |  |  |  | Couldn't find any tables, sequences or indices! | 
| 1847 |  |  |  |  |  |  |  | 
| 1848 |  |  |  |  |  |  | You will also need the following Perl modules installed: | 
| 1849 |  |  |  |  |  |  | DBI, DBD::Pg. | 
| 1850 |  |  |  |  |  |  |  | 
| 1851 |  |  |  |  |  |  | Now you will need to create a database called ``ftp'' and | 
| 1852 |  |  |  |  |  |  | populate it with data. This is how to do this: | 
| 1853 |  |  |  |  |  |  |  | 
| 1854 |  |  |  |  |  |  | createdb ftp | 
| 1855 |  |  |  |  |  |  | psql ftp < doc/eg1.sql | 
| 1856 |  |  |  |  |  |  |  | 
| 1857 |  |  |  |  |  |  | Check that no ERRORs are reported by PostgreSQL. | 
| 1858 |  |  |  |  |  |  |  | 
| 1859 |  |  |  |  |  |  | You should now be able to start the FTP server by running | 
| 1860 |  |  |  |  |  |  | the following command (I as root): | 
| 1861 |  |  |  |  |  |  |  | 
| 1862 |  |  |  |  |  |  | ./dbeg1-ftpd -S -p 2000 -C ftpd.conf | 
| 1863 |  |  |  |  |  |  |  | 
| 1864 |  |  |  |  |  |  | If the FTP server doesnE<39>t start correctly, you should | 
| 1865 |  |  |  |  |  |  | check the system log file [/var/log/messages]. | 
| 1866 |  |  |  |  |  |  |  | 
| 1867 |  |  |  |  |  |  | Connect to the FTP server as follows: | 
| 1868 |  |  |  |  |  |  |  | 
| 1869 |  |  |  |  |  |  | ftp localhost 2000 | 
| 1870 |  |  |  |  |  |  |  | 
| 1871 |  |  |  |  |  |  | Log in as either rich/123456 or dan/123456 and then try | 
| 1872 |  |  |  |  |  |  | to move around, upload and download files, create and | 
| 1873 |  |  |  |  |  |  | delete directories, etc. | 
| 1874 |  |  |  |  |  |  |  | 
| 1875 |  |  |  |  |  |  | =head2 SUBCLASSING THE Net::FTPServer CLASSES | 
| 1876 |  |  |  |  |  |  |  | 
| 1877 |  |  |  |  |  |  | By subclassing C, C and/or | 
| 1878 |  |  |  |  |  |  | C you can create custom | 
| 1879 |  |  |  |  |  |  | personalities for the FTP server. | 
| 1880 |  |  |  |  |  |  |  | 
| 1881 |  |  |  |  |  |  | Typically by overriding the hooks in the C class | 
| 1882 |  |  |  |  |  |  | you can change the basic behaviour of the FTP server - turning | 
| 1883 |  |  |  |  |  |  | it into an anonymous read-only server, for example. | 
| 1884 |  |  |  |  |  |  |  | 
| 1885 |  |  |  |  |  |  | By overriding the hooks in C and | 
| 1886 |  |  |  |  |  |  | C you can create virtual filesystems: | 
| 1887 |  |  |  |  |  |  | serving files into and out of a database, for example. | 
| 1888 |  |  |  |  |  |  |  | 
| 1889 |  |  |  |  |  |  | The current manual page contains information about the | 
| 1890 |  |  |  |  |  |  | hooks in C which may be overridden. | 
| 1891 |  |  |  |  |  |  |  | 
| 1892 |  |  |  |  |  |  | See C for information about | 
| 1893 |  |  |  |  |  |  | the methods in C which may be | 
| 1894 |  |  |  |  |  |  | overridden. | 
| 1895 |  |  |  |  |  |  |  | 
| 1896 |  |  |  |  |  |  | See C for information about | 
| 1897 |  |  |  |  |  |  | the methods in C which may be | 
| 1898 |  |  |  |  |  |  | overridden. | 
| 1899 |  |  |  |  |  |  |  | 
| 1900 |  |  |  |  |  |  | The most reasonable way to create your own personality is | 
| 1901 |  |  |  |  |  |  | to extend one of the existing personalities. Choose the | 
| 1902 |  |  |  |  |  |  | one which most closely matches the personality that you | 
| 1903 |  |  |  |  |  |  | want to create. For example, suppose that you want to create | 
| 1904 |  |  |  |  |  |  | another database personality. A good place to start would | 
| 1905 |  |  |  |  |  |  | be by copying C to a new | 
| 1906 |  |  |  |  |  |  | directory C (for example). Now | 
| 1907 |  |  |  |  |  |  | edit these files and substitute "MyDB" for "DBeg1". Then | 
| 1908 |  |  |  |  |  |  | examine each subroutine in these files and modify them, | 
| 1909 |  |  |  |  |  |  | consulting the appropriate manual page if you need to. | 
| 1910 |  |  |  |  |  |  |  | 
| 1911 |  |  |  |  |  |  | =head2 VIRTUAL HOSTS | 
| 1912 |  |  |  |  |  |  |  | 
| 1913 |  |  |  |  |  |  | C is capable of hosting multiple FTP sites on | 
| 1914 |  |  |  |  |  |  | a single machine. Because of the nature of the FTP protocol, | 
| 1915 |  |  |  |  |  |  | virtual hosting is almost always done by allocating a single | 
| 1916 |  |  |  |  |  |  | separate IP address per FTP site. However, C | 
| 1917 |  |  |  |  |  |  | also supports an experimental IP-less virtual hosting | 
| 1918 |  |  |  |  |  |  | system, although this requires modifications to the client. | 
| 1919 |  |  |  |  |  |  |  | 
| 1920 |  |  |  |  |  |  | Normal (IP-based) virtual hosting is carried out as follows: | 
| 1921 |  |  |  |  |  |  |  | 
| 1922 |  |  |  |  |  |  | * For each FTP site, allocate a separate IP address. | 
| 1923 |  |  |  |  |  |  | * Configure IP aliasing on your normal interface so that | 
| 1924 |  |  |  |  |  |  | the single physical interface responds to multiple | 
| 1925 |  |  |  |  |  |  | virtual IP addresses. | 
| 1926 |  |  |  |  |  |  | * Add entries (A records) in DNS mapping each site's | 
| 1927 |  |  |  |  |  |  | name to a separate IP address. | 
| 1928 |  |  |  |  |  |  | * Add reverse entries (PTR records) in DNS mapping each | 
| 1929 |  |  |  |  |  |  | IP address back to the site hostname. It is important | 
| 1930 |  |  |  |  |  |  | that both forward and reverse DNS is set up correctly, | 
| 1931 |  |  |  |  |  |  | else virtual hosting may not work. | 
| 1932 |  |  |  |  |  |  | * In /etc/ftpd.conf you will need to add a virtual host | 
| 1933 |  |  |  |  |  |  | section for each site like this: | 
| 1934 |  |  |  |  |  |  |  | 
| 1935 |  |  |  |  |  |  |  | 
| 1936 |  |  |  |  |  |  |  | 
| 1937 |  |  |  |  |  |  | ip: 1.2.3.4 | 
| 1938 |  |  |  |  |  |  | ... any specific configuration options for this site ... | 
| 1939 |  |  |  |  |  |  |  | 
| 1940 |  |  |  |  |  |  |  | 
| 1941 |  |  |  |  |  |  |  | 
| 1942 |  |  |  |  |  |  | You don't in fact need the "ip:" part assuming that | 
| 1943 |  |  |  |  |  |  | your forward and reverse DNS are set up correctly. | 
| 1944 |  |  |  |  |  |  | * If you want to specify a lot of external sites, or | 
| 1945 |  |  |  |  |  |  | generate the configuration file automatically from a | 
| 1946 |  |  |  |  |  |  | database or a script, you may find the | 
| 1947 |  |  |  |  |  |  | syntax useful. | 
| 1948 |  |  |  |  |  |  |  | 
| 1949 |  |  |  |  |  |  | There are examples in C. Here is how | 
| 1950 |  |  |  |  |  |  | IP-based virtual hosting works: | 
| 1951 |  |  |  |  |  |  |  | 
| 1952 |  |  |  |  |  |  | * The server starts by listening on all interfaces. | 
| 1953 |  |  |  |  |  |  | * A connection arrives at one of the IP addresses and a | 
| 1954 |  |  |  |  |  |  | process is forked off. | 
| 1955 |  |  |  |  |  |  | * The child process finds out which interface the | 
| 1956 |  |  |  |  |  |  | client connected to and reverses the name. | 
| 1957 |  |  |  |  |  |  | * If: | 
| 1958 |  |  |  |  |  |  | the IP address matches one of the "ip:" declarations | 
| 1959 |  |  |  |  |  |  | in any of the "Host" sections, | 
| 1960 |  |  |  |  |  |  | or: | 
| 1961 |  |  |  |  |  |  | there is a reversal for the name, and the name | 
| 1962 |  |  |  |  |  |  | matches one of the "Host" sections in the configuration | 
| 1963 |  |  |  |  |  |  | file, | 
| 1964 |  |  |  |  |  |  | then: | 
| 1965 |  |  |  |  |  |  | configuration options are read from that | 
| 1966 |  |  |  |  |  |  | section of the file and override any global configuration | 
| 1967 |  |  |  |  |  |  | options specified elsewhere in the file. | 
| 1968 |  |  |  |  |  |  | * Otherwise, the global configuration options only | 
| 1969 |  |  |  |  |  |  | are used. | 
| 1970 |  |  |  |  |  |  |  | 
| 1971 |  |  |  |  |  |  | IP-less virtual hosting is an experimental feature. It | 
| 1972 |  |  |  |  |  |  | requires the client to send a C command very early | 
| 1973 |  |  |  |  |  |  | on in the command stream -- before C and C. The | 
| 1974 |  |  |  |  |  |  | C command explicitly gives the hostname that the | 
| 1975 |  |  |  |  |  |  | FTP client is attempting to connect to, and so allows | 
| 1976 |  |  |  |  |  |  | many FTP sites to be multiplexed onto a single IP | 
| 1977 |  |  |  |  |  |  | address. At the present time, I am not aware of I | 
| 1978 |  |  |  |  |  |  | FTP clients which implement the C command, although | 
| 1979 |  |  |  |  |  |  | they will undoubtedly become more common in future. | 
| 1980 |  |  |  |  |  |  |  | 
| 1981 |  |  |  |  |  |  | This is how to set up IP-less virtual hosting: | 
| 1982 |  |  |  |  |  |  |  | 
| 1983 |  |  |  |  |  |  | * Add entries (A or CNAME records) in DNS mapping the | 
| 1984 |  |  |  |  |  |  | name of each site to a single IP address. | 
| 1985 |  |  |  |  |  |  | * In /etc/ftpd.conf you will need to list the same single | 
| 1986 |  |  |  |  |  |  | IP address to which all your sites map: | 
| 1987 |  |  |  |  |  |  |  | 
| 1988 |  |  |  |  |  |  | virtual host multiplex: 1.2.3.4 | 
| 1989 |  |  |  |  |  |  |  | 
| 1990 |  |  |  |  |  |  | * In /etc/ftpd.conf you will need to add a virtual host | 
| 1991 |  |  |  |  |  |  | section for each site like this: | 
| 1992 |  |  |  |  |  |  |  | 
| 1993 |  |  |  |  |  |  |  | 
| 1994 |  |  |  |  |  |  |  | 
| 1995 |  |  |  |  |  |  | ... any specific configuration options for this site ... | 
| 1996 |  |  |  |  |  |  |  | 
| 1997 |  |  |  |  |  |  |  | 
| 1998 |  |  |  |  |  |  |  | 
| 1999 |  |  |  |  |  |  | Here is how IP-less virtual hosting works: | 
| 2000 |  |  |  |  |  |  |  | 
| 2001 |  |  |  |  |  |  | * The server starts by listening on one interface. | 
| 2002 |  |  |  |  |  |  | * A connection arrives at the IP address and a | 
| 2003 |  |  |  |  |  |  | process is forked off. | 
| 2004 |  |  |  |  |  |  | * The IP address matches "virtual host multiplex" | 
| 2005 |  |  |  |  |  |  | and so no IP-based virtual host processing is done. | 
| 2006 |  |  |  |  |  |  | * One of the first commands that the client sends is | 
| 2007 |  |  |  |  |  |  | "HOST" followed by the hostname of the site. | 
| 2008 |  |  |  |  |  |  | * If there is a matching "Host" section in the | 
| 2009 |  |  |  |  |  |  | configuration file, then configuration options are | 
| 2010 |  |  |  |  |  |  | read from that section of the file and override any | 
| 2011 |  |  |  |  |  |  | global configuration options specified elsewhere in | 
| 2012 |  |  |  |  |  |  | the file. | 
| 2013 |  |  |  |  |  |  | * If there is no matching "Host" section then the | 
| 2014 |  |  |  |  |  |  | global configuration options alone are used. | 
| 2015 |  |  |  |  |  |  |  | 
| 2016 |  |  |  |  |  |  | The client is not permitted to issue the C command | 
| 2017 |  |  |  |  |  |  | more than once, and is not permitted to issue it after | 
| 2018 |  |  |  |  |  |  | login. | 
| 2019 |  |  |  |  |  |  |  | 
| 2020 |  |  |  |  |  |  | =head2 VIRTUAL HOSTING AND SECURITY | 
| 2021 |  |  |  |  |  |  |  | 
| 2022 |  |  |  |  |  |  | Only certain configuration options are available inside | 
| 2023 |  |  |  |  |  |  | the EHostE sections of the configuration file. | 
| 2024 |  |  |  |  |  |  | Generally speaking, the only configuration options you | 
| 2025 |  |  |  |  |  |  | can put here are ones which take effect after the | 
| 2026 |  |  |  |  |  |  | site name has been determined -- hence "allow anonymous" | 
| 2027 |  |  |  |  |  |  | is OK (since itE<39>s an option which is parsed after | 
| 2028 |  |  |  |  |  |  | determining the site name and during log in), but | 
| 2029 |  |  |  |  |  |  | "port" is not (since it is parsed long before any | 
| 2030 |  |  |  |  |  |  | clients ever connect). | 
| 2031 |  |  |  |  |  |  |  | 
| 2032 |  |  |  |  |  |  | Make sure your default global configuration is | 
| 2033 |  |  |  |  |  |  | secure. If you are using IP-less virtual hosting, | 
| 2034 |  |  |  |  |  |  | this is particularly important, since if the client | 
| 2035 |  |  |  |  |  |  | never sends a C command, the client gets | 
| 2036 |  |  |  |  |  |  | the global configuration. Even with IP-based virtual | 
| 2037 |  |  |  |  |  |  | hosting it may be possible for clients to sometimes | 
| 2038 |  |  |  |  |  |  | get the global configuration, for example if your | 
| 2039 |  |  |  |  |  |  | local name server fails. | 
| 2040 |  |  |  |  |  |  |  | 
| 2041 |  |  |  |  |  |  | IP-based virtual hosting always takes precedence | 
| 2042 |  |  |  |  |  |  | above IP-less virtual hosting. | 
| 2043 |  |  |  |  |  |  |  | 
| 2044 |  |  |  |  |  |  | With IP-less virtual hosting, access control cannot | 
| 2045 |  |  |  |  |  |  | be performed on a per-site basis. This is because the | 
| 2046 |  |  |  |  |  |  | client has to issue commands (ie. the C command | 
| 2047 |  |  |  |  |  |  | at least) before the site name is known to the server. | 
| 2048 |  |  |  |  |  |  | However you may still have a global "access control rule". | 
| 2049 |  |  |  |  |  |  |  | 
| 2050 |  |  |  |  |  |  | =head2 ARCHIVE MODE | 
| 2051 |  |  |  |  |  |  |  | 
| 2052 |  |  |  |  |  |  | Beginning with version 1.100, C is able | 
| 2053 |  |  |  |  |  |  | to generate certain types of compressed and archived files | 
| 2054 |  |  |  |  |  |  | on the fly. In practice what this means is that if a user | 
| 2055 |  |  |  |  |  |  | requests, say, C and this file does not actually | 
| 2056 |  |  |  |  |  |  | exist (but C I exist), then the server will | 
| 2057 |  |  |  |  |  |  | dynamically generate a gzip-compressed version of C | 
| 2058 |  |  |  |  |  |  | for the user. This also works on directories, so that a | 
| 2059 |  |  |  |  |  |  | user might request C which does not exist | 
| 2060 |  |  |  |  |  |  | (but directory C I exist), and the server tars | 
| 2061 |  |  |  |  |  |  | up and compresses the entire contents of C and | 
| 2062 |  |  |  |  |  |  | presents that back to the user. | 
| 2063 |  |  |  |  |  |  |  | 
| 2064 |  |  |  |  |  |  | Archive mode is enabled by default. However, it will | 
| 2065 |  |  |  |  |  |  | not work unless you substantially increase the per-process | 
| 2066 |  |  |  |  |  |  | memory, processes and files limits. The reason for this | 
| 2067 |  |  |  |  |  |  | is that archive mode works by forking external programs | 
| 2068 |  |  |  |  |  |  | such as C to perform the compression. For the same | 
| 2069 |  |  |  |  |  |  | reason you may also need to ensure that at least | 
| 2070 |  |  |  |  |  |  | C, C, C and C programs | 
| 2071 |  |  |  |  |  |  | are available on the current C<$PATH>, particularly if | 
| 2072 |  |  |  |  |  |  | you are using a chrooted environment. | 
| 2073 |  |  |  |  |  |  |  | 
| 2074 |  |  |  |  |  |  | To disable archive mode put C | 
| 2075 |  |  |  |  |  |  | into the configuration file. | 
| 2076 |  |  |  |  |  |  |  | 
| 2077 |  |  |  |  |  |  | The following file extensions are supported: | 
| 2078 |  |  |  |  |  |  |  | 
| 2079 |  |  |  |  |  |  | .gz      GZip compressed.      Requires gzip program on PATH. | 
| 2080 |  |  |  |  |  |  | .Z       Unix compressed.      Requires compress program on PATH. | 
| 2081 |  |  |  |  |  |  | .bz2     BZip2 compressed.     Requires bzip2 program on PATH. | 
| 2082 |  |  |  |  |  |  | .uue     UU-encoded.           Requires uuencode program on PATH. | 
| 2083 |  |  |  |  |  |  | .tar     Tar archive.          Requires Perl Archive::Tar module. | 
| 2084 |  |  |  |  |  |  | .zip     DOS ZIP archive.      Requires Perl Archive::Zip module. | 
| 2085 |  |  |  |  |  |  | .list    Return a list of all the files in this directory. | 
| 2086 |  |  |  |  |  |  |  | 
| 2087 |  |  |  |  |  |  | File extensions may be combined. Hence C<.tar.gz>, | 
| 2088 |  |  |  |  |  |  | C<.tar.bz2> and even C<.tar.gz.uue> will all work | 
| 2089 |  |  |  |  |  |  | as you expect. | 
| 2090 |  |  |  |  |  |  |  | 
| 2091 |  |  |  |  |  |  | Archive mode is, of course, extensible. It is particularly | 
| 2092 |  |  |  |  |  |  | simple to add another compression / filter format. In | 
| 2093 |  |  |  |  |  |  | your personality (or in a EPerlE section in the configuration | 
| 2094 |  |  |  |  |  |  | file) you need to add another key to the C | 
| 2095 |  |  |  |  |  |  | hash. | 
| 2096 |  |  |  |  |  |  |  | 
| 2097 |  |  |  |  |  |  | $ftps->{archive_filters}{".foo"} = &_foo_filter; | 
| 2098 |  |  |  |  |  |  |  | 
| 2099 |  |  |  |  |  |  | The value of this key should be a function as defined below: | 
| 2100 |  |  |  |  |  |  |  | 
| 2101 |  |  |  |  |  |  | \%filter = _foo_filter ($ftps, $sock); | 
| 2102 |  |  |  |  |  |  |  | 
| 2103 |  |  |  |  |  |  | The filter should return a hash reference (undef if it fails). | 
| 2104 |  |  |  |  |  |  | The hash should contain the following keys: | 
| 2105 |  |  |  |  |  |  |  | 
| 2106 |  |  |  |  |  |  | sock      Newly opened socket. | 
| 2107 |  |  |  |  |  |  | pid       PID of filter program. | 
| 2108 |  |  |  |  |  |  |  | 
| 2109 |  |  |  |  |  |  | The C<_foo_filter> function takes the existing socket and | 
| 2110 |  |  |  |  |  |  | filters it, providing a new socket which the FTP server will | 
| 2111 |  |  |  |  |  |  | write to (for the data connection back to the client). If | 
| 2112 |  |  |  |  |  |  | your filter is a Unix program, then the simplest thing is | 
| 2113 |  |  |  |  |  |  | just to define C<_foo_filter> as: | 
| 2114 |  |  |  |  |  |  |  | 
| 2115 |  |  |  |  |  |  | sub _foo_filter | 
| 2116 |  |  |  |  |  |  | { | 
| 2117 |  |  |  |  |  |  | return $_[0]->archive_filter_external ($_[1], "foo" [, args ...]); | 
| 2118 |  |  |  |  |  |  | } | 
| 2119 |  |  |  |  |  |  |  | 
| 2120 |  |  |  |  |  |  | The C function takes care of the | 
| 2121 |  |  |  |  |  |  | tricky bits for you. | 
| 2122 |  |  |  |  |  |  |  | 
| 2123 |  |  |  |  |  |  | Adding new I (akin to the existing tar and ZIP) | 
| 2124 |  |  |  |  |  |  | is more tricky. I suggest you look closely at the code and | 
| 2125 |  |  |  |  |  |  | consult the author for more information. | 
| 2126 |  |  |  |  |  |  |  | 
| 2127 |  |  |  |  |  |  | =head1 METHODS | 
| 2128 |  |  |  |  |  |  |  | 
| 2129 |  |  |  |  |  |  | =cut | 
| 2130 |  |  |  |  |  |  |  | 
| 2131 |  |  |  |  |  |  | package Net::FTPServer; | 
| 2132 |  |  |  |  |  |  |  | 
| 2133 | 75 |  |  | 75 |  | 47383 | use 5.005; | 
|  | 75 |  |  |  |  | 242 |  | 
| 2134 |  |  |  |  |  |  |  | 
| 2135 | 75 |  |  | 75 |  | 339 | use strict; | 
|  | 75 |  |  |  |  | 137 |  | 
|  | 75 |  |  |  |  | 1713 |  | 
| 2136 |  |  |  |  |  |  |  | 
| 2137 | 75 |  |  | 75 |  | 343 | use vars qw($VERSION $RELEASE); | 
|  | 75 |  |  |  |  | 146 |  | 
|  | 75 |  |  |  |  | 3845 |  | 
| 2138 |  |  |  |  |  |  |  | 
| 2139 |  |  |  |  |  |  | $VERSION = '1.125'; | 
| 2140 |  |  |  |  |  |  | $RELEASE = 1; | 
| 2141 |  |  |  |  |  |  |  | 
| 2142 |  |  |  |  |  |  | # Non-optional modules. | 
| 2143 | 75 |  |  | 75 |  | 396 | use Config; | 
|  | 75 |  |  |  |  | 249 |  | 
|  | 75 |  |  |  |  | 2812 |  | 
| 2144 | 75 |  |  | 75 |  | 32977 | use Getopt::Long qw(GetOptions); | 
|  | 75 |  |  |  |  | 736382 |  | 
|  | 75 |  |  |  |  | 373 |  | 
| 2145 | 75 |  |  | 75 |  | 25776 | use Sys::Hostname; | 
|  | 75 |  |  |  |  | 51365 |  | 
|  | 75 |  |  |  |  | 3371 |  | 
| 2146 | 75 |  |  | 75 |  | 23473 | use Socket; | 
|  | 75 |  |  |  |  | 195907 |  | 
|  | 75 |  |  |  |  | 27194 |  | 
| 2147 | 75 |  |  | 75 |  | 804 | use FileHandle; | 
|  | 75 |  |  |  |  | 6952 |  | 
|  | 75 |  |  |  |  | 486 |  | 
| 2148 | 75 |  |  | 75 |  | 36538 | use IO::Socket; | 
|  | 75 |  |  |  |  | 553582 |  | 
|  | 75 |  |  |  |  | 300 |  | 
| 2149 | 75 |  |  | 75 |  | 46451 | use IO::File; | 
|  | 75 |  |  |  |  | 164 |  | 
|  | 75 |  |  |  |  | 12986 |  | 
| 2150 | 75 |  |  | 75 |  | 19897 | use IO::Select; | 
|  | 75 |  |  |  |  | 93870 |  | 
|  | 75 |  |  |  |  | 3397 |  | 
| 2151 | 75 |  |  | 75 |  | 21378 | use IO::Scalar; | 
|  | 75 |  |  |  |  | 214925 |  | 
|  | 75 |  |  |  |  | 2866 |  | 
| 2152 | 75 |  |  | 75 |  | 511 | use IO::Seekable; | 
|  | 75 |  |  |  |  | 166 |  | 
|  | 75 |  |  |  |  | 4412 |  | 
| 2153 | 75 |  |  | 75 |  | 18188 | use IPC::Open2; | 
|  | 75 |  |  |  |  | 191601 |  | 
|  | 75 |  |  |  |  | 3602 |  | 
| 2154 | 75 |  |  | 75 |  | 474 | use Carp; | 
|  | 75 |  |  |  |  | 157 |  | 
|  | 75 |  |  |  |  | 2974 |  | 
| 2155 | 75 |  |  | 75 |  | 17012 | use Carp::Heavy ; | 
|  | 75 |  |  |  |  | 9132 |  | 
|  | 75 |  |  |  |  | 2258 |  | 
| 2156 | 75 |  |  | 75 |  | 676 | use POSIX qw(setsid dup dup2 ceil strftime WNOHANG); | 
|  | 75 |  |  |  |  | 5040 |  | 
|  | 75 |  |  |  |  | 554 |  | 
| 2157 | 75 |  |  | 75 |  | 8875 | use Fcntl qw(F_SETOWN F_SETFD FD_CLOEXEC); | 
|  | 75 |  |  |  |  | 143 |  | 
|  | 75 |  |  |  |  | 4770 |  | 
| 2158 | 75 |  |  | 75 |  | 406 | use Errno qw(EADDRINUSE) ; | 
|  | 75 |  |  |  |  | 140 |  | 
|  | 75 |  |  |  |  | 6653 |  | 
| 2159 |  |  |  |  |  |  |  | 
| 2160 | 75 |  |  | 75 |  | 20044 | use Net::FTPServer::FileHandle; | 
|  | 75 |  |  |  |  | 160 |  | 
|  | 75 |  |  |  |  | 1948 |  | 
| 2161 | 75 |  |  | 75 |  | 18936 | use Net::FTPServer::DirHandle; | 
|  | 75 |  |  |  |  | 159 |  | 
|  | 75 |  |  |  |  | 2694 |  | 
| 2162 |  |  |  |  |  |  |  | 
| 2163 |  |  |  |  |  |  | # We require this to suppress warning messages from going to the client | 
| 2164 |  |  |  |  |  |  | # when it starts up, eg. Constant subroutine __need___va_list undefined ... | 
| 2165 |  |  |  |  |  |  | # (Thanks to Rob Brown for this fix.) | 
| 2166 |  |  |  |  |  |  |  | 
| 2167 |  |  |  |  |  |  | BEGIN { | 
| 2168 | 75 |  |  | 75 |  | 563 | local $^W = 0; | 
| 2169 | 75 |  |  |  |  | 25205 | require Sys::Syslog; | 
| 2170 |  |  |  |  |  |  | } | 
| 2171 |  |  |  |  |  |  |  | 
| 2172 |  |  |  |  |  |  | # The following modules are optional, and therefore we need | 
| 2173 |  |  |  |  |  |  | # to eval the require/use statements. Before using the features | 
| 2174 |  |  |  |  |  |  | # of an optional module, make sure it exists first by checking | 
| 2175 |  |  |  |  |  |  | # ``exists $INC{"Module/Name.pm"}'' (see below for examples). | 
| 2176 |  |  |  |  |  |  | #eval "use Archive::Tar;"; | 
| 2177 | 75 |  |  | 75 |  | 6628 | eval "use Archive::Zip;"; | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 2178 | 75 |  |  | 75 |  | 23194 | eval "use BSD::Resource;"; | 
|  | 75 |  |  |  |  | 201912 |  | 
|  | 75 |  |  |  |  | 303 |  | 
| 2179 | 75 |  |  | 75 |  | 475 | eval "use Digest::MD5;"; | 
|  | 75 |  |  |  |  | 141 |  | 
|  | 75 |  |  |  |  | 1687 |  | 
| 2180 | 75 |  |  | 75 |  | 4901 | eval "use File::Sync;"; | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 2181 |  |  |  |  |  |  |  | 
| 2182 |  |  |  |  |  |  | # Global variables and constants. | 
| 2183 | 75 |  |  |  |  | 9745 | use vars qw(@_default_commands | 
| 2184 |  |  |  |  |  |  | @_default_site_commands | 
| 2185 |  |  |  |  |  |  | @_supported_mlst_facts | 
| 2186 | 75 |  |  | 75 |  | 484556 | $_default_timeout); | 
|  | 75 |  |  |  |  | 169 |  | 
| 2187 |  |  |  |  |  |  |  | 
| 2188 |  |  |  |  |  |  | @_default_commands | 
| 2189 |  |  |  |  |  |  | = ( | 
| 2190 |  |  |  |  |  |  | # Standard commands from RFC 959. | 
| 2191 |  |  |  |  |  |  | "USER", "PASS", "ACCT", "CWD", "CDUP", "SMNT", | 
| 2192 |  |  |  |  |  |  | "REIN", "QUIT", "PORT", "PASV", "TYPE", "STRU", | 
| 2193 |  |  |  |  |  |  | "MODE", "RETR", "STOR", "STOU", "APPE", "ALLO", | 
| 2194 |  |  |  |  |  |  | "REST", "RNFR", "RNTO", "ABOR", "DELE", "RMD", | 
| 2195 |  |  |  |  |  |  | "MKD", "PWD", "LIST", "NLST", "SITE", "SYST", | 
| 2196 |  |  |  |  |  |  | "STAT", "HELP", "NOOP", | 
| 2197 |  |  |  |  |  |  | # RFC 1123 section 4.1.3.1 recommends implementing these. | 
| 2198 |  |  |  |  |  |  | "XMKD", "XRMD", "XPWD", "XCUP", "XCWD", | 
| 2199 |  |  |  |  |  |  | # From RFC 2389. | 
| 2200 |  |  |  |  |  |  | "FEAT", "OPTS", | 
| 2201 |  |  |  |  |  |  | # From ftpexts Internet Draft. | 
| 2202 |  |  |  |  |  |  | "SIZE", "MDTM", "MLST", "MLSD", | 
| 2203 |  |  |  |  |  |  | # Mail handling commands from obsolete RFC 765. | 
| 2204 |  |  |  |  |  |  | "MLFL", "MAIL", "MSND", "MSOM", "MSAM", "MRSQ", | 
| 2205 |  |  |  |  |  |  | "MRCP", | 
| 2206 |  |  |  |  |  |  | # I18N support from RFC 2640. | 
| 2207 |  |  |  |  |  |  | "LANG", | 
| 2208 |  |  |  |  |  |  | # NcFTP sends the CLNT command, I know not from what RFC. | 
| 2209 |  |  |  |  |  |  | "CLNT", | 
| 2210 |  |  |  |  |  |  | # Experimental IP-less virtual hosting. | 
| 2211 |  |  |  |  |  |  | "HOST", | 
| 2212 |  |  |  |  |  |  | ); | 
| 2213 |  |  |  |  |  |  |  | 
| 2214 |  |  |  |  |  |  | @_default_site_commands | 
| 2215 |  |  |  |  |  |  | = ( | 
| 2216 |  |  |  |  |  |  | # Common extensions. | 
| 2217 |  |  |  |  |  |  | "EXEC", "VERSION", | 
| 2218 |  |  |  |  |  |  | # Wu-FTPD compatible extensions. | 
| 2219 |  |  |  |  |  |  | "ALIAS", "CDPATH", "CHECKMETHOD", "CHECKSUM", | 
| 2220 |  |  |  |  |  |  | "IDLE", | 
| 2221 |  |  |  |  |  |  | # Net::FTPServer compatible extensions. | 
| 2222 |  |  |  |  |  |  | "SYNC", "ARCHIVE", | 
| 2223 |  |  |  |  |  |  | ); | 
| 2224 |  |  |  |  |  |  |  | 
| 2225 |  |  |  |  |  |  | @_supported_mlst_facts | 
| 2226 |  |  |  |  |  |  | = ( | 
| 2227 |  |  |  |  |  |  | "TYPE", "SIZE", "MODIFY", "PERM", "UNIX.MODE" | 
| 2228 |  |  |  |  |  |  | ); | 
| 2229 |  |  |  |  |  |  |  | 
| 2230 |  |  |  |  |  |  | $_default_timeout = 900; | 
| 2231 |  |  |  |  |  |  |  | 
| 2232 |  |  |  |  |  |  | # Allocate and initialize signal flags | 
| 2233 | 75 |  |  | 75 |  | 433 | use vars qw($GOT_SIGURG $GOT_SIGCHLD $GOT_SIGHUP $GOT_SIGTERM); | 
|  | 75 |  |  |  |  | 147 |  | 
|  | 75 |  |  |  |  | 1177055 |  | 
| 2234 |  |  |  |  |  |  | $GOT_SIGURG  = 0; | 
| 2235 |  |  |  |  |  |  | $GOT_SIGCHLD = 0; | 
| 2236 |  |  |  |  |  |  | $GOT_SIGHUP  = 0; | 
| 2237 |  |  |  |  |  |  | $GOT_SIGTERM = 0; | 
| 2238 |  |  |  |  |  |  |  | 
| 2239 |  |  |  |  |  |  | =pod | 
| 2240 |  |  |  |  |  |  |  | 
| 2241 |  |  |  |  |  |  | =over 4 | 
| 2242 |  |  |  |  |  |  |  | 
| 2243 |  |  |  |  |  |  | =item Net::FTPServer->run ([\@ARGV]); | 
| 2244 |  |  |  |  |  |  |  | 
| 2245 |  |  |  |  |  |  | This is the main entry point into the FTP server. It starts the | 
| 2246 |  |  |  |  |  |  | FTP server running. This function never normally returns. | 
| 2247 |  |  |  |  |  |  |  | 
| 2248 |  |  |  |  |  |  | If no arguments are given, then command line arguments are taken | 
| 2249 |  |  |  |  |  |  | from the global C<@ARGV> array. | 
| 2250 |  |  |  |  |  |  |  | 
| 2251 |  |  |  |  |  |  | =cut | 
| 2252 |  |  |  |  |  |  |  | 
| 2253 |  |  |  |  |  |  | sub run | 
| 2254 |  |  |  |  |  |  | { | 
| 2255 | 41 |  |  | 41 | 1 | 52836 | my $class = shift; | 
| 2256 | 41 |  | 50 |  |  | 888 | my $args = shift || [@ARGV]; | 
| 2257 |  |  |  |  |  |  |  | 
| 2258 |  |  |  |  |  |  | # Clean up the environment to allow tainting to work. | 
| 2259 | 41 |  |  |  |  | 1313 | $ENV{PATH} = "/usr/bin:/bin"; | 
| 2260 | 41 |  |  |  |  | 1228 | $ENV{SHELL} = "/bin/sh"; | 
| 2261 | 41 |  |  |  |  | 1715 | delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; | 
| 2262 |  |  |  |  |  |  |  | 
| 2263 |  |  |  |  |  |  | # Create Net::FTPServer object. | 
| 2264 | 41 |  |  |  |  | 609 | my $self = {}; | 
| 2265 | 41 |  |  |  |  | 346 | bless $self, $class; | 
| 2266 |  |  |  |  |  |  |  | 
| 2267 |  |  |  |  |  |  | # Construct version string. | 
| 2268 |  |  |  |  |  |  | $self->{version_string} | 
| 2269 | 41 |  |  |  |  | 1077 | = "Net::FTPServer/" . | 
| 2270 |  |  |  |  |  |  | $Net::FTPServer::VERSION . "-" . | 
| 2271 |  |  |  |  |  |  | $Net::FTPServer::RELEASE; | 
| 2272 |  |  |  |  |  |  |  | 
| 2273 |  |  |  |  |  |  | # Save the hostname. | 
| 2274 | 41 |  |  |  |  | 1100 | $self->{hostname} = hostname; | 
| 2275 | 41 | 50 |  |  |  | 3088 | $self->{hostname} = $1 if $self->{hostname} =~ /^([\w\-\.]+)$/; | 
| 2276 |  |  |  |  |  |  |  | 
| 2277 |  |  |  |  |  |  | # Construct a table of commands to subroutines. | 
| 2278 | 41 |  |  |  |  | 303 | $self->{command_table} = {}; | 
| 2279 | 41 |  |  |  |  | 600 | foreach (@_default_commands) { | 
| 2280 | 2214 |  |  |  |  | 4457 | my $subname = "_${_}_command"; | 
| 2281 | 2214 |  |  |  |  | 13441 | $self->{command_table}{$_} = \&$subname; | 
| 2282 |  |  |  |  |  |  | } | 
| 2283 |  |  |  |  |  |  |  | 
| 2284 |  |  |  |  |  |  | # Construct a list of SITE commands. | 
| 2285 | 41 |  |  |  |  | 193 | $self->{site_command_table} = {}; | 
| 2286 | 41 |  |  |  |  | 195 | foreach (@_default_site_commands) { | 
| 2287 | 369 |  |  |  |  | 796 | my $subname = "_SITE_${_}_command"; | 
| 2288 | 369 |  |  |  |  | 1886 | $self->{site_command_table}{$_} = \&$subname; | 
| 2289 |  |  |  |  |  |  | } | 
| 2290 |  |  |  |  |  |  |  | 
| 2291 |  |  |  |  |  |  | # Construct a list of supported features (for FEAT command). | 
| 2292 |  |  |  |  |  |  | $self->{features} = { | 
| 2293 |  |  |  |  |  |  | SIZE => undef, | 
| 2294 |  |  |  |  |  |  | REST => "STREAM", | 
| 2295 |  |  |  |  |  |  | MDTM => undef, | 
| 2296 |  |  |  |  |  |  | TVFS => undef, | 
| 2297 |  |  |  |  |  |  | UTF8 => undef, | 
| 2298 |  |  |  |  |  |  | MLST => join ("", | 
| 2299 | 41 |  |  |  |  | 264 | map { "$_*;" } @_supported_mlst_facts), | 
|  | 205 |  |  |  |  | 992 |  | 
| 2300 |  |  |  |  |  |  | LANG => "EN*", | 
| 2301 |  |  |  |  |  |  | HOST => undef, | 
| 2302 |  |  |  |  |  |  | }; | 
| 2303 |  |  |  |  |  |  |  | 
| 2304 |  |  |  |  |  |  | # Construct a list of supported options (for OPTS command). | 
| 2305 |  |  |  |  |  |  | $self->{options} = { | 
| 2306 | 41 |  |  |  |  | 342 | MLST => \&_OPTS_MLST_command, | 
| 2307 |  |  |  |  |  |  | }; | 
| 2308 |  |  |  |  |  |  |  | 
| 2309 | 41 |  |  |  |  | 614 | $self->pre_configuration_hook; | 
| 2310 |  |  |  |  |  |  |  | 
| 2311 |  |  |  |  |  |  | # Global configuration. | 
| 2312 | 41 |  |  |  |  | 295 | $self->{debug} = 0; | 
| 2313 | 41 |  |  |  |  | 250 | $self->{_config_file} = "/etc/ftpd.conf"; | 
| 2314 |  |  |  |  |  |  |  | 
| 2315 | 41 |  |  |  |  | 890 | $self->options_hook ($args); | 
| 2316 | 41 |  |  |  |  | 738 | $self->_get_configuration ($args); | 
| 2317 |  |  |  |  |  |  |  | 
| 2318 | 41 |  |  |  |  | 536 | $self->post_configuration_hook; | 
| 2319 |  |  |  |  |  |  |  | 
| 2320 |  |  |  |  |  |  | # Initialize Max Clients Settings | 
| 2321 |  |  |  |  |  |  | $self->{_max_clients} = | 
| 2322 | 41 |  | 50 |  |  | 541 | $self->config ("max clients") || 255; | 
| 2323 |  |  |  |  |  |  | $self->{_max_clients_message} = | 
| 2324 | 41 |  | 50 |  |  | 221 | $self->config ("max clients message") || | 
| 2325 |  |  |  |  |  |  | "Maximum connections reached"; | 
| 2326 |  |  |  |  |  |  |  | 
| 2327 |  |  |  |  |  |  | # Open syslog. | 
| 2328 |  |  |  |  |  |  | $self->{_enable_syslog} = | 
| 2329 |  |  |  |  |  |  | (!defined $self->config ("enable syslog") || | 
| 2330 |  |  |  |  |  |  | $self->config ("enable syslog")) && | 
| 2331 | 41 |  | 33 |  |  | 182 | !$self->{_test_mode}; | 
| 2332 |  |  |  |  |  |  |  | 
| 2333 | 41 | 50 |  |  |  | 184 | if ($self->{_enable_syslog}) | 
| 2334 |  |  |  |  |  |  | { | 
| 2335 | 0 | 0 |  |  |  | 0 | if (defined $self->config ("log socket type")) { | 
| 2336 | 0 |  |  |  |  | 0 | Sys::Syslog::setlogsock $self->config ("log socket type") | 
| 2337 |  |  |  |  |  |  | } else { | 
| 2338 | 0 |  |  |  |  | 0 | Sys::Syslog::setlogsock "unix"; | 
| 2339 |  |  |  |  |  |  | } | 
| 2340 |  |  |  |  |  |  |  | 
| 2341 | 0 |  |  |  |  | 0 | Sys::Syslog::openlog "ftpd", "pid", "daemon"; | 
| 2342 |  |  |  |  |  |  | } | 
| 2343 |  |  |  |  |  |  |  | 
| 2344 |  |  |  |  |  |  | # Handle error and warning messages. If error log is set (which | 
| 2345 |  |  |  |  |  |  | # is highly recommended BTW), these are appended directly to | 
| 2346 |  |  |  |  |  |  | # that file. If error log is not set, then we use a hack which | 
| 2347 |  |  |  |  |  |  | # directs those messages to syslog. | 
| 2348 |  |  |  |  |  |  |  | 
| 2349 | 41 | 50 |  |  |  | 353 | if (defined $self->config ("error log")) | 
| 2350 |  |  |  |  |  |  | { | 
| 2351 | 0 |  |  |  |  | 0 | $self->_open_error_log ; | 
| 2352 |  |  |  |  |  |  |  | 
| 2353 |  |  |  |  |  |  | $SIG{__DIE__} = sub { | 
| 2354 | 0 |  |  | 0 |  | 0 | $self->log ("err", $_[0]); | 
| 2355 | 0 |  |  |  |  | 0 | confess $_[0]; | 
| 2356 | 0 |  |  |  |  | 0 | }; | 
| 2357 |  |  |  |  |  |  | } | 
| 2358 |  |  |  |  |  |  | else | 
| 2359 |  |  |  |  |  |  | { | 
| 2360 |  |  |  |  |  |  | # Set up a hook for warn and die so that these cause messages to | 
| 2361 |  |  |  |  |  |  | # be echoed to the syslog. | 
| 2362 |  |  |  |  |  |  | $SIG{__WARN__} = sub { | 
| 2363 | 0 |  |  | 0 |  | 0 | $self->log ("warning", $_[0]); | 
| 2364 | 0 |  |  |  |  | 0 | warn $_[0]; | 
| 2365 | 41 |  |  |  |  | 1592 | }; | 
| 2366 |  |  |  |  |  |  | $SIG{__DIE__} = sub { | 
| 2367 | 0 |  |  | 0 |  | 0 | $self->log ("err", $_[0]); | 
| 2368 | 0 |  |  |  |  | 0 | confess $_[0]; | 
| 2369 | 41 |  |  |  |  | 653 | }; | 
| 2370 |  |  |  |  |  |  | } | 
| 2371 |  |  |  |  |  |  |  | 
| 2372 |  |  |  |  |  |  | # Just set a flag in order to be "signal safe" | 
| 2373 | 41 |  |  | 2 |  | 2272 | $SIG{URG}  = sub { $GOT_SIGURG  = 1; }; | 
|  | 2 |  |  |  |  | 146 |  | 
| 2374 | 41 |  |  | 0 |  | 559 | $SIG{CHLD} = sub { $GOT_SIGCHLD = 1; }; | 
|  | 0 |  |  |  |  | 0 |  | 
| 2375 | 41 |  |  | 0 |  | 429 | $SIG{HUP}  = sub { $GOT_SIGHUP  = 1; }; | 
|  | 0 |  |  |  |  | 0 |  | 
| 2376 | 41 |  |  | 0 |  | 408 | $SIG{TERM} = sub { $GOT_SIGTERM = 1; }; | 
|  | 0 |  |  |  |  | 0 |  | 
| 2377 |  |  |  |  |  |  |  | 
| 2378 |  |  |  |  |  |  | # The following signal handlers can be handled by Perl, since | 
| 2379 |  |  |  |  |  |  | # all they are going to do is exit anyway. | 
| 2380 |  |  |  |  |  |  | $SIG{PIPE} = sub { | 
| 2381 | 0 | 0 |  | 0 |  | 0 | $self->log ("info", "client closed connection abruptly") if $self; | 
| 2382 | 0 |  |  |  |  | 0 | exit; | 
| 2383 | 41 |  |  |  |  | 380 | }; | 
| 2384 |  |  |  |  |  |  | $SIG{INT} = sub { | 
| 2385 | 0 |  |  | 0 |  | 0 | $self->log ("info", "exiting on keyboard INT signal"); | 
| 2386 | 0 |  |  |  |  | 0 | exit; | 
| 2387 | 41 |  |  |  |  | 390 | }; | 
| 2388 |  |  |  |  |  |  | $SIG{QUIT} = sub { | 
| 2389 | 0 |  |  | 0 |  | 0 | $self->log ("info", "exiting on keyboard QUIT signal"); | 
| 2390 | 0 |  |  |  |  | 0 | exit; | 
| 2391 | 41 |  |  |  |  | 380 | }; | 
| 2392 |  |  |  |  |  |  | $SIG{ALRM} = sub { | 
| 2393 | 1 |  |  | 1 |  | 36 | $self->log ("info", "exiting on ALRM signal"); | 
| 2394 | 1 |  |  |  |  | 18 | print "421 Server closed the connection after idle timeout.\r\n"; | 
| 2395 | 1 |  |  |  |  | 9 | $self->_log_line ("[TIMED OUT!]"); | 
| 2396 | 1 |  |  |  |  | 105 | exit; | 
| 2397 | 41 |  |  |  |  | 432 | }; | 
| 2398 |  |  |  |  |  |  |  | 
| 2399 |  |  |  |  |  |  | # Setup Client Logging. | 
| 2400 | 41 |  |  |  |  | 678 | $self->_open_client_log ; | 
| 2401 |  |  |  |  |  |  |  | 
| 2402 |  |  |  |  |  |  | # Setup xfer Logging. | 
| 2403 | 41 |  |  |  |  | 410 | $self->_open_xfer_log ; | 
| 2404 |  |  |  |  |  |  |  | 
| 2405 |  |  |  |  |  |  | # Convert FTP Data port service name to port number, if necessary. | 
| 2406 | 41 | 50 |  |  |  | 210 | if (my $ftpdata = $self->config ("ftp data port")) | 
| 2407 |  |  |  |  |  |  | { | 
| 2408 | 0 | 0 |  |  |  | 0 | my $ftp_data_port = | 
| 2409 |  |  |  |  |  |  | $ftpdata =~ /^\d+$/ | 
| 2410 |  |  |  |  |  |  | ? $ftpdata | 
| 2411 |  |  |  |  |  |  | : scalar (getservbyname ($ftpdata, 'tcp')); | 
| 2412 | 0 | 0 |  |  |  | 0 | die "Unable to locate '$ftpdata' service" | 
| 2413 |  |  |  |  |  |  | unless defined $ftp_data_port; | 
| 2414 | 0 |  |  |  |  | 0 | $self->{ftp_data_port} = $ftp_data_port; | 
| 2415 |  |  |  |  |  |  | } | 
| 2416 |  |  |  |  |  |  |  | 
| 2417 |  |  |  |  |  |  | # Load customized SITE commands. | 
| 2418 | 41 |  |  |  |  | 175 | my @custom_site_commands = $self->config ("site command"); | 
| 2419 | 41 |  |  |  |  | 148 | foreach (@custom_site_commands) | 
| 2420 |  |  |  |  |  |  | { | 
| 2421 | 0 |  |  |  |  | 0 | my ($cmdname, $filename) = split /\s+/, $_; | 
| 2422 | 0 |  |  |  |  | 0 | my $sub = do $filename; | 
| 2423 | 0 | 0 |  |  |  | 0 | if ($sub) | 
| 2424 |  |  |  |  |  |  | { | 
| 2425 | 0 | 0 |  |  |  | 0 | if (ref $sub eq "CODE") { | 
| 2426 | 0 |  |  |  |  | 0 | $self->{site_command_table}{uc $cmdname} = $sub; | 
| 2427 |  |  |  |  |  |  | } else { | 
| 2428 | 0 |  |  |  |  | 0 | $self->log ("err", "site command: $filename: must return an anonymous subroutine when evaluated (skipping)"); | 
| 2429 |  |  |  |  |  |  | } | 
| 2430 |  |  |  |  |  |  | } | 
| 2431 |  |  |  |  |  |  | else | 
| 2432 |  |  |  |  |  |  | { | 
| 2433 | 0 | 0 |  |  |  | 0 | if ($!) { | 
| 2434 | 0 |  |  |  |  | 0 | $self->log ("err", "site command: $filename: $! (ignored)") | 
| 2435 |  |  |  |  |  |  | } else { | 
| 2436 | 0 |  |  |  |  | 0 | $self->log ("err", "site command: $filename: $@ (ignored)") | 
| 2437 |  |  |  |  |  |  | } | 
| 2438 |  |  |  |  |  |  | } | 
| 2439 |  |  |  |  |  |  | } | 
| 2440 |  |  |  |  |  |  |  | 
| 2441 | 41 |  |  |  |  | 182 | my $daemon_mode = $self->config ("daemon mode"); | 
| 2442 | 41 |  |  |  |  | 151 | my $run_in_background = $self->config ("run in background"); | 
| 2443 |  |  |  |  |  |  |  | 
| 2444 |  |  |  |  |  |  | # Display start-up string in syslog. | 
| 2445 |  |  |  |  |  |  | $self->log ("info", | 
| 2446 | 41 | 50 |  |  |  | 374 | $self->{version_string} . " running" . | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
| 2447 |  |  |  |  |  |  | ($daemon_mode ? " daemon" : "") . | 
| 2448 |  |  |  |  |  |  | ($run_in_background ? " background" : "") . | 
| 2449 |  |  |  |  |  |  | ($self->config ("port") ? " on port " . $self->config ("port") | 
| 2450 |  |  |  |  |  |  | : "")); | 
| 2451 |  |  |  |  |  |  |  | 
| 2452 |  |  |  |  |  |  | # Daemon mode? | 
| 2453 | 41 | 50 |  |  |  | 148 | if ($daemon_mode) | 
| 2454 |  |  |  |  |  |  | { | 
| 2455 |  |  |  |  |  |  | # Fork into the background? | 
| 2456 | 0 | 0 |  |  |  | 0 | $self->_fork_into_background if $run_in_background; | 
| 2457 |  |  |  |  |  |  |  | 
| 2458 | 0 |  |  |  |  | 0 | $self->_save_pid; | 
| 2459 |  |  |  |  |  |  |  | 
| 2460 |  |  |  |  |  |  | # Run as a daemon. | 
| 2461 | 0 |  |  |  |  | 0 | $self->_be_daemon; | 
| 2462 |  |  |  |  |  |  | } | 
| 2463 |  |  |  |  |  |  |  | 
| 2464 | 41 |  |  |  |  | 222 | $| = 1; | 
| 2465 |  |  |  |  |  |  |  | 
| 2466 | 41 | 50 |  |  |  | 278 | $self->log ("info", "in post accept stage") if $self->{debug}; | 
| 2467 |  |  |  |  |  |  |  | 
| 2468 |  |  |  |  |  |  | # Hook just after accepting the connection. | 
| 2469 | 41 |  |  |  |  | 347 | $self->post_accept_hook; | 
| 2470 |  |  |  |  |  |  |  | 
| 2471 |  |  |  |  |  |  | # Get the sockname of the socket so we know which interface | 
| 2472 |  |  |  |  |  |  | # the client is bound to. | 
| 2473 | 41 |  |  |  |  | 129 | my ($sockname, $sockport, $sockaddr, $sockaddrstring); | 
| 2474 |  |  |  |  |  |  |  | 
| 2475 | 41 | 50 |  |  |  | 187 | unless ($self->{_test_mode}) | 
| 2476 |  |  |  |  |  |  | { | 
| 2477 | 0 | 0 |  |  |  | 0 | $self->log ("info", "get socket name") if $self->{debug}; | 
| 2478 |  |  |  |  |  |  |  | 
| 2479 | 0 |  |  |  |  | 0 | $sockname = getsockname STDIN; | 
| 2480 | 0 | 0 |  |  |  | 0 | if (!defined $sockname) | 
| 2481 |  |  |  |  |  |  | { | 
| 2482 | 0 |  |  |  |  | 0 | $self->reply(500, "inet mode requires a socket - use '$0 -S' for standalone."); | 
| 2483 | 0 |  |  |  |  | 0 | exit; | 
| 2484 |  |  |  |  |  |  | } | 
| 2485 | 0 |  |  |  |  | 0 | ($sockport, $sockaddr) = unpack_sockaddr_in ($sockname); | 
| 2486 | 0 |  |  |  |  | 0 | $sockaddrstring = inet_ntoa ($sockaddr); | 
| 2487 |  |  |  |  |  |  |  | 
| 2488 |  |  |  |  |  |  | # Added 21 Feb 2001 by Rob Brown | 
| 2489 |  |  |  |  |  |  | # If MSG_OOB data arrives on STDIN send it inline and trigger SIGURG | 
| 2490 | 0 | 0 |  |  |  | 0 | setsockopt (STDIN, SOL_SOCKET, SO_OOBINLINE, pack ("l", 1)) | 
| 2491 |  |  |  |  |  |  | or warn "setsockopt: SO_OOBINLINE: $!"; | 
| 2492 |  |  |  |  |  |  |  | 
| 2493 |  |  |  |  |  |  | # Note by RWMJ: The following code always generates an error, so | 
| 2494 |  |  |  |  |  |  | # I have commented it out for the present. | 
| 2495 |  |  |  |  |  |  | #my $pid = pack ("l", $$); | 
| 2496 |  |  |  |  |  |  | #fcntl (STDIN, F_SETOWN, $pid) | 
| 2497 |  |  |  |  |  |  | #  or warn "fcntl: F_SETOWN $$: $!"; | 
| 2498 |  |  |  |  |  |  | } | 
| 2499 |  |  |  |  |  |  |  | 
| 2500 |  |  |  |  |  |  | # Virtual hosts. | 
| 2501 | 41 |  |  |  |  | 158 | my $sitename; | 
| 2502 |  |  |  |  |  |  |  | 
| 2503 | 41 | 50 |  |  |  | 172 | if ($self->config ("enable virtual hosts")) | 
| 2504 |  |  |  |  |  |  | { | 
| 2505 | 0 | 0 |  |  |  | 0 | $self->log ("info", "virtual host configuration") if $self->{debug}; | 
| 2506 |  |  |  |  |  |  |  | 
| 2507 | 0 |  |  |  |  | 0 | my $virtual_host_multiplex = $self->config ("virtual host multiplex"); | 
| 2508 |  |  |  |  |  |  |  | 
| 2509 |  |  |  |  |  |  | # IP-based virtual hosting? | 
| 2510 | 0 | 0 | 0 |  |  | 0 | unless ($virtual_host_multiplex && | 
| 2511 |  |  |  |  |  |  | $virtual_host_multiplex eq $sockaddrstring) | 
| 2512 |  |  |  |  |  |  | { | 
| 2513 |  |  |  |  |  |  | # Look for a matching "ip:" configuration option in | 
| 2514 |  |  |  |  |  |  | # a  section. | 
| 2515 | 0 |  |  |  |  | 0 | $sitename = $self->ip_host_config ($sockaddrstring); | 
| 2516 |  |  |  |  |  |  |  | 
| 2517 | 0 | 0 |  |  |  | 0 | unless ($sitename) | 
| 2518 |  |  |  |  |  |  | { | 
| 2519 |  |  |  |  |  |  | # Try reversing the IP address in DNS instead. | 
| 2520 | 0 |  |  |  |  | 0 | $sitename = gethostbyaddr ($sockaddr, AF_INET); | 
| 2521 |  |  |  |  |  |  | } | 
| 2522 |  |  |  |  |  |  |  | 
| 2523 | 0 | 0 |  |  |  | 0 | if ($self->{debug}) | 
| 2524 |  |  |  |  |  |  | { | 
| 2525 | 0 | 0 |  |  |  | 0 | if ($sitename) | 
| 2526 |  |  |  |  |  |  | { | 
| 2527 | 0 |  |  |  |  | 0 | $self->log ("info", | 
| 2528 |  |  |  |  |  |  | "IP-based virtual hosts: ". | 
| 2529 |  |  |  |  |  |  | "set site to $sitename"); | 
| 2530 |  |  |  |  |  |  | } | 
| 2531 |  |  |  |  |  |  | else | 
| 2532 |  |  |  |  |  |  | { | 
| 2533 | 0 |  |  |  |  | 0 | $self->log ("info", | 
| 2534 |  |  |  |  |  |  | "IP-based virtual hosts: ". | 
| 2535 |  |  |  |  |  |  | "no site found"); | 
| 2536 |  |  |  |  |  |  | } | 
| 2537 |  |  |  |  |  |  | } | 
| 2538 |  |  |  |  |  |  | } | 
| 2539 |  |  |  |  |  |  | } | 
| 2540 |  |  |  |  |  |  |  | 
| 2541 | 41 | 50 |  |  |  | 264 | $self->log ("info", "get peer name") if $self->{debug}; | 
| 2542 |  |  |  |  |  |  |  | 
| 2543 |  |  |  |  |  |  | # Get the peername and other details of this socket. | 
| 2544 | 41 |  |  |  |  | 117 | my ($peername, $peerport, $peeraddr, $peeraddrstring); | 
| 2545 |  |  |  |  |  |  |  | 
| 2546 | 41 | 50 |  |  |  | 492 | if ( $peername = getpeername STDIN ) | 
| 2547 |  |  |  |  |  |  | { | 
| 2548 | 0 |  |  |  |  | 0 | ($peerport, $peeraddr) = unpack_sockaddr_in ($peername); | 
| 2549 | 0 |  |  |  |  | 0 | $peeraddrstring = inet_ntoa ($peeraddr); | 
| 2550 |  |  |  |  |  |  | } | 
| 2551 |  |  |  |  |  |  | else | 
| 2552 |  |  |  |  |  |  | { | 
| 2553 | 41 |  |  |  |  | 109 | $peerport = 0; | 
| 2554 | 41 |  |  |  |  | 1043 | $peeraddr = inet_aton ( $peeraddrstring = "127.0.0.1" ); | 
| 2555 |  |  |  |  |  |  | } | 
| 2556 |  |  |  |  |  |  |  | 
| 2557 | 41 |  |  |  |  | 508 | $self->_log_line ("[CONNECTION FROM $peeraddrstring:$peerport] \#". | 
| 2558 |  |  |  |  |  |  | (1 + $self->concurrent_connections)); | 
| 2559 |  |  |  |  |  |  |  | 
| 2560 |  |  |  |  |  |  | # Resolve the address. | 
| 2561 | 41 |  |  |  |  | 107 | my $peerhostname; | 
| 2562 | 41 | 50 |  |  |  | 179 | if ($self->config ("resolve addresses")) | 
| 2563 |  |  |  |  |  |  | { | 
| 2564 | 0 |  |  |  |  | 0 | my $hostname = gethostbyaddr ($peeraddr, AF_INET); | 
| 2565 |  |  |  |  |  |  |  | 
| 2566 | 0 | 0 |  |  |  | 0 | if ($hostname) | 
| 2567 |  |  |  |  |  |  | { | 
| 2568 | 0 |  |  |  |  | 0 | my $ipaddr = gethostbyname ($hostname); | 
| 2569 |  |  |  |  |  |  |  | 
| 2570 | 0 | 0 | 0 |  |  | 0 | if ($ipaddr && inet_ntoa ($ipaddr) eq $peeraddrstring) | 
| 2571 |  |  |  |  |  |  | { | 
| 2572 | 0 |  |  |  |  | 0 | $peerhostname = $hostname; | 
| 2573 |  |  |  |  |  |  | } | 
| 2574 |  |  |  |  |  |  | } | 
| 2575 |  |  |  |  |  |  |  | 
| 2576 | 0 | 0 | 0 |  |  | 0 | if ($self->config ("require resolved addresses") && !$peerhostname) | 
| 2577 |  |  |  |  |  |  | { | 
| 2578 | 0 |  |  |  |  | 0 | $self->log ("err", | 
| 2579 |  |  |  |  |  |  | "cannot resolve address for connection from " . | 
| 2580 |  |  |  |  |  |  | "$peeraddrstring:$peerport"); | 
| 2581 | 0 |  |  |  |  | 0 | exit 0; | 
| 2582 |  |  |  |  |  |  | } | 
| 2583 |  |  |  |  |  |  | } | 
| 2584 |  |  |  |  |  |  |  | 
| 2585 |  |  |  |  |  |  | # Set up request information. | 
| 2586 | 41 |  |  |  |  | 412 | $self->{sockname} = $sockname; | 
| 2587 | 41 |  |  |  |  | 333 | $self->{sockport} = $sockport; | 
| 2588 | 41 |  |  |  |  | 247 | $self->{sockaddr} = $sockaddr; | 
| 2589 | 41 |  |  |  |  | 257 | $self->{sockaddrstring} = $sockaddrstring; | 
| 2590 | 41 |  |  |  |  | 120 | $self->{sitename} = $sitename; | 
| 2591 | 41 |  |  |  |  | 240 | $self->{peername} = $peername; | 
| 2592 | 41 |  |  |  |  | 265 | $self->{peerport} = $peerport; | 
| 2593 | 41 |  |  |  |  | 249 | $self->{peeraddr} = $peeraddr; | 
| 2594 | 41 |  |  |  |  | 168 | $self->{peeraddrstring} = $peeraddrstring; | 
| 2595 | 41 |  |  |  |  | 127 | $self->{peerhostname} = $peerhostname; | 
| 2596 | 41 |  |  |  |  | 119 | $self->{authenticated} = 0; | 
| 2597 | 41 |  |  |  |  | 101 | $self->{loginattempts} = 0; | 
| 2598 |  |  |  |  |  |  |  | 
| 2599 |  |  |  |  |  |  | # Default port information, used if no PORT command is issued. This | 
| 2600 |  |  |  |  |  |  | # is used by the open_data_connection function. See RFC 959 section 3.2. | 
| 2601 | 41 |  |  |  |  | 112 | $self->{_hostport} = $peerport; | 
| 2602 | 41 |  |  |  |  | 122 | $self->{_hostaddr} = $peeraddr; | 
| 2603 | 41 |  |  |  |  | 116 | $self->{_hostaddrstring} = $peeraddrstring; | 
| 2604 |  |  |  |  |  |  |  | 
| 2605 |  |  |  |  |  |  | # Default mode is active. Issuing the PASV command switches the | 
| 2606 |  |  |  |  |  |  | # server into passive mode. | 
| 2607 | 41 |  |  |  |  | 105 | $self->{_passive} = 0; | 
| 2608 |  |  |  |  |  |  |  | 
| 2609 |  |  |  |  |  |  | # Set up default connection state. | 
| 2610 | 41 |  |  |  |  | 261 | $self->{type} = 'A'; | 
| 2611 | 41 |  |  |  |  | 193 | $self->{form} = 'N'; | 
| 2612 | 41 |  |  |  |  | 256 | $self->{mode} = 'S'; | 
| 2613 | 41 |  |  |  |  | 127 | $self->{stru} = 'F'; | 
| 2614 |  |  |  |  |  |  |  | 
| 2615 |  |  |  |  |  |  | # Other per-connection state. | 
| 2616 | 41 |  |  |  |  | 155 | $self->{_mlst_facts} = \@_supported_mlst_facts; | 
| 2617 | 41 |  |  |  |  | 158 | $self->{_checksum_method} = "MD5"; | 
| 2618 | 41 |  | 33 |  |  | 197 | $self->{_idle_timeout} = $self->config ("timeout") || $_default_timeout; | 
| 2619 |  |  |  |  |  |  | $self->{maintainer_email} | 
| 2620 | 41 | 100 |  |  |  | 150 | = defined $self->config ("maintainer email") ? | 
| 2621 |  |  |  |  |  |  | $self->config ("maintainer email") : | 
| 2622 |  |  |  |  |  |  | "root\@$self->{hostname}"; | 
| 2623 | 41 |  |  |  |  | 160 | $self->{_chdir_message_cache} = {}; | 
| 2624 |  |  |  |  |  |  |  | 
| 2625 |  |  |  |  |  |  | # Support for archive mode. | 
| 2626 |  |  |  |  |  |  | $self->{archive_mode} = | 
| 2627 | 41 |  | 33 |  |  | 158 | !defined $self->config ("enable archive mode") || | 
| 2628 |  |  |  |  |  |  | $self->config ("enable archive mode"); | 
| 2629 | 41 | 50 |  |  |  | 264 | $self->{archive_filters} = {} unless exists $self->{archive_filters}; | 
| 2630 | 41 | 50 |  |  |  | 216 | $self->{archive_generators} = {} unless exists $self->{archive_generators}; | 
| 2631 | 41 | 50 |  |  |  | 241 | if ($self->{archive_mode}) | 
| 2632 |  |  |  |  |  |  | { | 
| 2633 |  |  |  |  |  |  | # NB. Extension matching is case insensitive. | 
| 2634 | 41 | 50 |  |  |  | 392 | $self->{archive_filters}{".z"} = \&_archive_filter_Z | 
| 2635 |  |  |  |  |  |  | if $self->_find_prog ("compress"); | 
| 2636 | 41 | 50 |  |  |  | 188 | $self->{archive_filters}{".gz"} = \&_archive_filter_gz | 
| 2637 |  |  |  |  |  |  | if $self->_find_prog ("gzip"); | 
| 2638 | 41 | 50 |  |  |  | 194 | $self->{archive_filters}{".bz2"} = \&_archive_filter_bz2 | 
| 2639 |  |  |  |  |  |  | if $self->_find_prog ("bzip2"); | 
| 2640 | 41 | 50 |  |  |  | 235 | $self->{archive_filters}{".uue"} = \&_archive_filter_uue | 
| 2641 |  |  |  |  |  |  | if $self->_find_prog ("uuencode"); | 
| 2642 |  |  |  |  |  |  |  | 
| 2643 |  |  |  |  |  |  | $self->{archive_generators}{".zip"} = \&_archive_generator_zip | 
| 2644 | 41 | 50 |  |  |  | 207 | if exists $INC{"Archive/Zip.pm"}; | 
| 2645 |  |  |  |  |  |  | #	$self->{archive_generators}{".tar"} = \&_archive_generator_tar | 
| 2646 |  |  |  |  |  |  | #	  if exists $INC{"Archive/Tar.pm"}; | 
| 2647 | 41 |  |  |  |  | 188 | $self->{archive_generators}{".list"} = \&_archive_generator_list; | 
| 2648 |  |  |  |  |  |  |  | 
| 2649 | 41 | 50 |  |  |  | 173 | if ($self->{debug}) | 
| 2650 |  |  |  |  |  |  | { | 
| 2651 |  |  |  |  |  |  | $self->log ("info", | 
| 2652 |  |  |  |  |  |  | "archive mode enabled [%s]", | 
| 2653 |  |  |  |  |  |  | join (", ", | 
| 2654 | 41 |  |  |  |  | 203 | keys %{$self->{archive_filters}}, | 
| 2655 | 41 |  |  |  |  | 128 | keys %{$self->{archive_generators}})); | 
|  | 41 |  |  |  |  | 345 |  | 
| 2656 |  |  |  |  |  |  | } | 
| 2657 |  |  |  |  |  |  | } | 
| 2658 |  |  |  |  |  |  |  | 
| 2659 | 41 | 50 |  |  |  | 602 | $self->log ("info", "in access control stage") if $self->{debug}; | 
| 2660 |  |  |  |  |  |  |  | 
| 2661 | 41 |  |  |  |  | 806 | my $r = $self->access_control_hook; | 
| 2662 | 41 | 50 |  |  |  | 229 | exit if $r == -1; | 
| 2663 |  |  |  |  |  |  |  | 
| 2664 |  |  |  |  |  |  | # Perform normal access control. | 
| 2665 | 41 | 50 |  |  |  | 170 | if ($r == 0) | 
| 2666 |  |  |  |  |  |  | { | 
| 2667 | 41 | 50 |  |  |  | 1229 | unless ($self->_eval_rule ("access control rule")) | 
| 2668 |  |  |  |  |  |  | { | 
| 2669 | 0 |  |  |  |  | 0 | $self->reply (421, "Client denied by server configuration. Goodbye."); | 
| 2670 | 0 |  |  |  |  | 0 | exit; | 
| 2671 |  |  |  |  |  |  | } | 
| 2672 |  |  |  |  |  |  | } | 
| 2673 |  |  |  |  |  |  |  | 
| 2674 |  |  |  |  |  |  | # Install per-process limits. | 
| 2675 | 41 | 50 |  |  |  | 274 | $self->log ("info", "in process limits stage") if $self->{debug}; | 
| 2676 |  |  |  |  |  |  |  | 
| 2677 | 41 |  |  |  |  | 385 | $r = $self->process_limits_hook; | 
| 2678 | 41 | 50 |  |  |  | 249 | exit if $r == -1; | 
| 2679 |  |  |  |  |  |  |  | 
| 2680 |  |  |  |  |  |  | # Perform normal per-process limits. | 
| 2681 | 41 | 50 |  |  |  | 157 | if ($r == 0) | 
| 2682 |  |  |  |  |  |  | { | 
| 2683 | 41 |  | 100 |  |  | 169 | my $limit = 1024 * ($self->config ("limit memory") || 16384); | 
| 2684 | 41 | 100 |  |  |  | 443 | $self->_set_rlimit ("RLIMIT_DATA", $limit) if $limit >= 0; | 
| 2685 |  |  |  |  |  |  |  | 
| 2686 | 41 |  | 100 |  |  | 26809 | $limit = $self->config ("limit nr processes") || 10; | 
| 2687 | 41 | 100 |  |  |  | 298 | $self->_set_rlimit ("RLIMIT_NPROC", $limit) if $limit >= 0; | 
| 2688 |  |  |  |  |  |  |  | 
| 2689 | 41 |  | 100 |  |  | 4391 | $limit = $self->config ("limit nr files") || 20; | 
| 2690 | 41 | 100 |  |  |  | 222 | $self->_set_rlimit ("RLIMIT_NOFILE", $limit) if $limit >= 0; | 
| 2691 |  |  |  |  |  |  | } | 
| 2692 |  |  |  |  |  |  |  | 
| 2693 | 41 | 50 |  |  |  | 4188 | unless ($self->{_test_mode}) | 
| 2694 |  |  |  |  |  |  | { | 
| 2695 |  |  |  |  |  |  | # Log the connection information available. | 
| 2696 | 0 | 0 |  |  |  | 0 | my $peerinfodpy | 
| 2697 |  |  |  |  |  |  | = $peerhostname ? | 
| 2698 |  |  |  |  |  |  | "$peerhostname:$peerport ($peeraddrstring:$peerport)" : | 
| 2699 |  |  |  |  |  |  | "$peeraddrstring:$peerport"; | 
| 2700 |  |  |  |  |  |  |  | 
| 2701 | 0 |  |  |  |  | 0 | $self->log ("info", "connection from $peerinfodpy"); | 
| 2702 |  |  |  |  |  |  |  | 
| 2703 |  |  |  |  |  |  | # Change name of process in process listing. | 
| 2704 | 0 | 0 | 0 |  |  | 0 | unless (defined $self->config ("change process name") && | 
| 2705 |  |  |  |  |  |  | !$self->config ("change process name")) | 
| 2706 |  |  |  |  |  |  | { | 
| 2707 | 0 |  |  |  |  | 0 | $0 = "ftpd $peerinfodpy"; | 
| 2708 |  |  |  |  |  |  | } | 
| 2709 |  |  |  |  |  |  | } | 
| 2710 |  |  |  |  |  |  |  | 
| 2711 |  |  |  |  |  |  | # Send the greeting. | 
| 2712 | 41 |  | 100 |  |  | 416 | my $greeting_type = $self->config ("greeting type") || "full"; | 
| 2713 |  |  |  |  |  |  |  | 
| 2714 | 41 | 100 |  |  |  | 204 | if ($greeting_type eq "full") | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 2715 |  |  |  |  |  |  | { | 
| 2716 | 38 |  |  |  |  | 390 | $self->reply (220, "$self->{hostname} FTP server ($self->{version_string}) ready."); | 
| 2717 |  |  |  |  |  |  | } | 
| 2718 |  |  |  |  |  |  | elsif ($greeting_type eq "brief") | 
| 2719 |  |  |  |  |  |  | { | 
| 2720 | 1 |  |  |  |  | 7 | $self->reply (220, "$self->{hostname} FTP server ready."); | 
| 2721 |  |  |  |  |  |  | } | 
| 2722 |  |  |  |  |  |  | elsif ($greeting_type eq "terse") | 
| 2723 |  |  |  |  |  |  | { | 
| 2724 | 1 |  |  |  |  | 9 | $self->reply (220, "FTP server ready."); | 
| 2725 |  |  |  |  |  |  | } | 
| 2726 |  |  |  |  |  |  | elsif ($greeting_type eq "text") | 
| 2727 |  |  |  |  |  |  | { | 
| 2728 | 1 | 50 |  |  |  | 7 | my $greeting_text = $self->config ("greeting text") | 
| 2729 |  |  |  |  |  |  | or die "greeting type is text, but no greeting text configuration value"; | 
| 2730 | 1 |  |  |  |  | 7 | $self->reply (220, $greeting_text); | 
| 2731 |  |  |  |  |  |  | } | 
| 2732 |  |  |  |  |  |  | else | 
| 2733 |  |  |  |  |  |  | { | 
| 2734 | 0 |  |  |  |  | 0 | die "unknown greeting type: ${greeting_type}"; | 
| 2735 |  |  |  |  |  |  | } | 
| 2736 |  |  |  |  |  |  |  | 
| 2737 |  |  |  |  |  |  | # Implement Identification Protocol as explained in RFC 1413. | 
| 2738 |  |  |  |  |  |  | # Some firewalls block the auth port which could make this | 
| 2739 |  |  |  |  |  |  | # operation slow.  Wait until after the greeting is sent to the | 
| 2740 |  |  |  |  |  |  | # client to signify that it is okay for commands to be sent while | 
| 2741 |  |  |  |  |  |  | # the ident authentication is taking place.  This timeout is used | 
| 2742 |  |  |  |  |  |  | # for both the connection and the "patience" desired for the | 
| 2743 |  |  |  |  |  |  | # remote ident response.  Having a timeout also helps to avoid a | 
| 2744 |  |  |  |  |  |  | # possible DoS on the FTP server.  There is no way to specify an | 
| 2745 |  |  |  |  |  |  | # infinite timeout.  The directive "ident timeout: 0" will disable | 
| 2746 |  |  |  |  |  |  | # this feature. | 
| 2747 |  |  |  |  |  |  |  | 
| 2748 | 41 |  |  |  |  | 255 | my $ident_timeout = $self->config ("ident timeout"); | 
| 2749 | 41 | 0 | 33 |  |  | 223 | if (defined $ident_timeout && $ident_timeout > 0 && | 
|  |  |  | 33 |  |  |  |  | 
|  |  |  | 0 |  |  |  |  | 
|  |  |  | 0 |  |  |  |  | 
| 2750 |  |  |  |  |  |  | defined $self->{peerport} && defined $self->{sockport} && | 
| 2751 |  |  |  |  |  |  | defined $self->{peeraddrstring}) | 
| 2752 |  |  |  |  |  |  | { | 
| 2753 | 0 |  |  |  |  | 0 | my $got_bored = 0; | 
| 2754 | 0 |  |  |  |  | 0 | my $ident; | 
| 2755 |  |  |  |  |  |  | eval | 
| 2756 | 0 |  |  |  |  | 0 | { | 
| 2757 | 0 |  |  |  |  | 0 | local $SIG{__WARN__} = 'DEFAULT'; | 
| 2758 | 0 |  |  |  |  | 0 | local $SIG{__DIE__}  = 'DEFAULT'; | 
| 2759 | 0 |  |  | 0 |  | 0 | local $SIG{ALRM} = sub { $got_bored = 1; die "timed out"; }; | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 2760 | 0 |  |  |  |  | 0 | alarm $ident_timeout; | 
| 2761 | 0 |  |  |  |  | 0 | "0" =~ /(0)/; # Perl 5.7 / IO::Socket::INET bug workaround. | 
| 2762 |  |  |  |  |  |  | $ident = new IO::Socket::INET | 
| 2763 |  |  |  |  |  |  | (PeerAddr  => $self->{peeraddrstring}, | 
| 2764 | 0 |  |  |  |  | 0 | PeerPort  => "auth"); | 
| 2765 |  |  |  |  |  |  | }; | 
| 2766 |  |  |  |  |  |  |  | 
| 2767 | 0 | 0 |  |  |  | 0 | if ($got_bored) | 
| 2768 |  |  |  |  |  |  | { | 
| 2769 |  |  |  |  |  |  | # Took too long to connect to remote auth port | 
| 2770 |  |  |  |  |  |  | # (probably because of a client-side firewall). | 
| 2771 | 0 |  |  |  |  | 0 | $self->_log_line ("[Ident auth failed: connection timed out]"); | 
| 2772 | 0 |  |  |  |  | 0 | $self->log ("warning", "ident auth failed for $self->{peeraddrstring}: connection timed out"); | 
| 2773 |  |  |  |  |  |  | } | 
| 2774 |  |  |  |  |  |  | else | 
| 2775 |  |  |  |  |  |  | { | 
| 2776 | 0 | 0 |  |  |  | 0 | if (defined $ident) | 
| 2777 |  |  |  |  |  |  | { | 
| 2778 | 0 |  |  |  |  | 0 | my $response; | 
| 2779 |  |  |  |  |  |  | eval | 
| 2780 | 0 |  |  |  |  | 0 | { | 
| 2781 | 0 |  |  |  |  | 0 | local $SIG{__WARN__} = 'DEFAULT'; | 
| 2782 | 0 |  |  |  |  | 0 | local $SIG{__DIE__}  = 'DEFAULT'; | 
| 2783 |  |  |  |  |  |  | local $SIG{ALRM} | 
| 2784 | 0 |  |  | 0 |  | 0 | = sub { $got_bored = 1; die "timed out"; }; | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 2785 | 0 |  |  |  |  | 0 | alarm $ident_timeout; | 
| 2786 | 0 |  |  |  |  | 0 | $ident->print ("$self->{peerport} , ", | 
| 2787 |  |  |  |  |  |  | "$self->{sockport}\r\n"); | 
| 2788 | 0 |  |  |  |  | 0 | $response = $ident->getline; | 
| 2789 |  |  |  |  |  |  | }; | 
| 2790 | 0 |  |  |  |  | 0 | $ident->close; | 
| 2791 |  |  |  |  |  |  |  | 
| 2792 |  |  |  |  |  |  | # Took too long to respond? | 
| 2793 | 0 | 0 |  |  |  | 0 | if ($got_bored) | 
| 2794 |  |  |  |  |  |  | { | 
| 2795 | 0 |  |  |  |  | 0 | $self->_log_line ("[Ident auth failed: response timed out]"); | 
| 2796 | 0 |  |  |  |  | 0 | $self->log ("warning", "ident auth failed for $self->{peeraddrstring}: response timed out"); | 
| 2797 |  |  |  |  |  |  | } | 
| 2798 |  |  |  |  |  |  | else | 
| 2799 |  |  |  |  |  |  | { | 
| 2800 | 0 | 0 |  |  |  | 0 | if ($response =~ /:\s*USERID\s*:\s*OTHER\s*:\s*(\S+)/) | 
| 2801 |  |  |  |  |  |  | { | 
| 2802 | 0 |  |  |  |  | 0 | $self->{auth} = $1; | 
| 2803 | 0 |  |  |  |  | 0 | $self->_log_line ("[IDENT AUTH VERIFIED: $self->{auth}\@$self->{peeraddrstring}]"); | 
| 2804 | 0 |  |  |  |  | 0 | $self->log ("info", "ident auth: $self->{auth}\@$self->{peeraddrstring}"); | 
| 2805 |  |  |  |  |  |  | } | 
| 2806 |  |  |  |  |  |  | else | 
| 2807 |  |  |  |  |  |  | { | 
| 2808 | 0 |  |  |  |  | 0 | $self->_log_line ("[Ident auth failed: invalid response]"); | 
| 2809 | 0 |  |  |  |  | 0 | $self->log ("warning", "ident auth failed for $self->{peeraddrstring}: invalid response"); | 
| 2810 |  |  |  |  |  |  | } | 
| 2811 |  |  |  |  |  |  | } | 
| 2812 |  |  |  |  |  |  | } | 
| 2813 |  |  |  |  |  |  | else | 
| 2814 |  |  |  |  |  |  | { | 
| 2815 | 0 |  |  |  |  | 0 | $self->_log_line ("[Ident auth failed: Connection refused]"); | 
| 2816 | 0 |  |  |  |  | 0 | $self->log ("warning", "ident auth failed for $self->{peeraddrstring}: Connection refused"); | 
| 2817 |  |  |  |  |  |  | } | 
| 2818 |  |  |  |  |  |  | } | 
| 2819 |  |  |  |  |  |  | } | 
| 2820 |  |  |  |  |  |  |  | 
| 2821 |  |  |  |  |  |  | # Get command filter, if set. | 
| 2822 | 41 |  |  |  |  | 275 | my $cmd_filter = $self->config ("command filter"); | 
| 2823 |  |  |  |  |  |  |  | 
| 2824 |  |  |  |  |  |  | # Get restrict commands, if set, and parse them into a simpler format. | 
| 2825 | 41 |  |  |  |  | 154 | my @restrict_commands = $self->config ("restrict command"); | 
| 2826 |  |  |  |  |  |  |  | 
| 2827 | 41 |  |  |  |  | 154 | foreach (@restrict_commands) | 
| 2828 |  |  |  |  |  |  | { | 
| 2829 | 1 | 50 |  |  |  | 10 | unless (/^"([a-zA-Z\s]+)"\s+(.*)/) | 
| 2830 |  |  |  |  |  |  | { | 
| 2831 | 0 |  |  |  |  | 0 | die "bad restrict command directive: restrict command: $_"; | 
| 2832 |  |  |  |  |  |  | } | 
| 2833 |  |  |  |  |  |  |  | 
| 2834 | 1 |  |  |  |  | 4 | my $pattern = uc $1; | 
| 2835 | 1 |  |  |  |  | 3 | my $code = $2; | 
| 2836 |  |  |  |  |  |  |  | 
| 2837 |  |  |  |  |  |  | # The pattern is something like "SITE WHO". Turn this into | 
| 2838 |  |  |  |  |  |  | # a real regular expression "^SITE\s+WHO\b". | 
| 2839 | 1 |  |  |  |  | 7 | $pattern =~ s/\s+/\\s+/g; | 
| 2840 | 1 |  |  |  |  | 4 | $pattern = "^$pattern\\b"; | 
| 2841 |  |  |  |  |  |  |  | 
| 2842 | 1 |  |  |  |  | 14 | $_ = { pattern => $pattern, code => $code }; | 
| 2843 |  |  |  |  |  |  | } | 
| 2844 |  |  |  |  |  |  |  | 
| 2845 |  |  |  |  |  |  | # Command the commands permitted when not authenticated. | 
| 2846 | 41 |  |  |  |  | 149 | my %no_authentication_commands = (); | 
| 2847 |  |  |  |  |  |  |  | 
| 2848 | 41 | 50 |  |  |  | 157 | if (defined $self->config ("no authentication commands")) | 
| 2849 |  |  |  |  |  |  | { | 
| 2850 | 0 |  |  |  |  | 0 | my @c = split /\s+/, $self->config ("no authentication commands"); | 
| 2851 |  |  |  |  |  |  |  | 
| 2852 | 0 |  |  |  |  | 0 | foreach (@c) { $no_authentication_commands{$_} = 1; } | 
|  | 0 |  |  |  |  | 0 |  | 
| 2853 |  |  |  |  |  |  | } | 
| 2854 |  |  |  |  |  |  | else | 
| 2855 |  |  |  |  |  |  | { | 
| 2856 | 41 |  |  |  |  | 320 | %no_authentication_commands = | 
| 2857 |  |  |  |  |  |  | ("USER" => 1, "PASS" => 1, "LANG" => 1, "FEAT" => 1, | 
| 2858 |  |  |  |  |  |  | "HELP" => 1, "QUIT" => 1, "HOST" => 1); | 
| 2859 |  |  |  |  |  |  | } | 
| 2860 |  |  |  |  |  |  |  | 
| 2861 |  |  |  |  |  |  | # Start reading commands from the client. | 
| 2862 |  |  |  |  |  |  | COMMAND: | 
| 2863 | 41 |  |  |  |  | 101 | for (;;) | 
| 2864 |  |  |  |  |  |  | { | 
| 2865 |  |  |  |  |  |  | # Pre-command hook. | 
| 2866 | 327 |  |  |  |  | 1207 | $self->pre_command_hook; | 
| 2867 |  |  |  |  |  |  |  | 
| 2868 |  |  |  |  |  |  | # Set an alarm to go off after so many seconds of idleness. | 
| 2869 | 327 |  |  |  |  | 1712 | alarm $self->{_idle_timeout}; | 
| 2870 |  |  |  |  |  |  |  | 
| 2871 |  |  |  |  |  |  | # Get next line of input from the client. | 
| 2872 |  |  |  |  |  |  | # XXX This does not comply properly with RFC 2640 section 3.1 - | 
| 2873 |  |  |  |  |  |  | # We should translate  to  and treat ONLY | 
| 2874 |  |  |  |  |  |  | # as a line ending character. | 
| 2875 | 327 | 100 |  |  |  | 16625554 | last unless defined ($_ = ); | 
| 2876 |  |  |  |  |  |  |  | 
| 2877 | 311 |  |  |  |  | 2133 | $self->_check_signals; | 
| 2878 |  |  |  |  |  |  |  | 
| 2879 |  |  |  |  |  |  | # Immediately terminate if the parent died. | 
| 2880 |  |  |  |  |  |  | # In standalone mode, this means the main daemon has terminated. | 
| 2881 |  |  |  |  |  |  | # In inet mode, this means that inetd itself has terminated. | 
| 2882 |  |  |  |  |  |  | # In either case, the system administrator may have new | 
| 2883 |  |  |  |  |  |  | # configuration settings that need to be loaded so any current | 
| 2884 |  |  |  |  |  |  | # FTP clients should not be able to run any new commands on the | 
| 2885 |  |  |  |  |  |  | # old configuration for security reasons. | 
| 2886 | 311 | 50 |  |  |  | 1421 | if (getppid == 1) | 
| 2887 |  |  |  |  |  |  | { | 
| 2888 | 0 |  |  |  |  | 0 | $self->reply (421, "Manual Server Shutdown. Reconnect required."); | 
| 2889 | 0 |  |  |  |  | 0 | exit; | 
| 2890 |  |  |  |  |  |  | } | 
| 2891 |  |  |  |  |  |  |  | 
| 2892 |  |  |  |  |  |  | # Restart alarm clock timer. | 
| 2893 | 311 |  |  |  |  | 1226 | alarm $self->{_idle_timeout}; | 
| 2894 |  |  |  |  |  |  |  | 
| 2895 |  |  |  |  |  |  | # When out-of-band data arrives (eg. when the client performs | 
| 2896 |  |  |  |  |  |  | # an ABOR command), the client will send several telnet control | 
| 2897 |  |  |  |  |  |  | # characters before the actual command. Drop those bytes now. | 
| 2898 | 311 |  |  |  |  | 1241 | s/^\377.// while m/^\377./; | 
| 2899 |  |  |  |  |  |  |  | 
| 2900 |  |  |  |  |  |  | # Log client command if logging is enabled. | 
| 2901 | 311 | 50 | 66 |  |  | 2307 | $self->_log_line ($_) | 
| 2902 |  |  |  |  |  |  | unless /^PASS /i && $self->config ("hide passwords in client log"); | 
| 2903 |  |  |  |  |  |  |  | 
| 2904 |  |  |  |  |  |  | # Go slow? | 
| 2905 | 311 | 50 |  |  |  | 871 | sleep $self->config ("command wait") | 
| 2906 |  |  |  |  |  |  | if $self->config ("command wait"); | 
| 2907 |  |  |  |  |  |  |  | 
| 2908 |  |  |  |  |  |  | # Remove trailing CRLF. | 
| 2909 | 311 |  |  |  |  | 2105 | s/[\n\r]+$//; | 
| 2910 |  |  |  |  |  |  |  | 
| 2911 |  |  |  |  |  |  | # Command filter hook. | 
| 2912 | 311 |  |  |  |  | 1055 | $r = $self->command_filter_hook ($_); | 
| 2913 | 311 | 50 |  |  |  | 757 | next if $r == -1; | 
| 2914 |  |  |  |  |  |  |  | 
| 2915 |  |  |  |  |  |  | # Command filter. | 
| 2916 | 311 | 50 |  |  |  | 741 | if ($r == 0) | 
| 2917 |  |  |  |  |  |  | { | 
| 2918 | 311 | 100 |  |  |  | 679 | if (defined $cmd_filter) | 
| 2919 |  |  |  |  |  |  | { | 
| 2920 | 15 | 100 |  |  |  | 104 | unless ($_ =~ m/$cmd_filter/) | 
| 2921 |  |  |  |  |  |  | { | 
| 2922 | 2 |  |  |  |  | 5 | $self->reply (500, | 
| 2923 |  |  |  |  |  |  | "Command does not match command filter."); | 
| 2924 | 2 |  |  |  |  | 4 | next; | 
| 2925 |  |  |  |  |  |  | } | 
| 2926 |  |  |  |  |  |  | } | 
| 2927 |  |  |  |  |  |  |  | 
| 2928 | 309 |  |  |  |  | 832 | foreach my $rc (@restrict_commands) | 
| 2929 |  |  |  |  |  |  | { | 
| 2930 | 13 | 100 |  |  |  | 78 | if ($_ =~ /$rc->{pattern}/i) | 
| 2931 |  |  |  |  |  |  | { | 
| 2932 |  |  |  |  |  |  | # Set up the variables. | 
| 2933 | 10 |  |  |  |  | 23 | my $hostname = $self->{peerhostname}; | 
| 2934 | 10 |  |  |  |  | 21 | my $ip = $self->{peeraddrstring}; | 
| 2935 | 10 |  |  |  |  | 20 | my $user = $self->{user}; | 
| 2936 | 10 |  |  |  |  | 18 | my $class = $self->{class}; | 
| 2937 | 10 |  |  |  |  | 18 | my $user_is_anonymous = $self->{user_is_anonymous}; | 
| 2938 | 10 |  |  |  |  | 18 | my $type = $self->{type}; | 
| 2939 | 10 |  |  |  |  | 17 | my $form = $self->{form}; | 
| 2940 | 10 |  |  |  |  | 15 | my $mode = $self->{mode}; | 
| 2941 | 10 |  |  |  |  | 16 | my $stru = $self->{stru}; | 
| 2942 |  |  |  |  |  |  |  | 
| 2943 | 10 |  |  |  |  | 568 | my $rv = eval $rc->{code}; | 
| 2944 | 10 | 50 |  |  |  | 38 | die if $@; | 
| 2945 |  |  |  |  |  |  |  | 
| 2946 | 10 | 100 |  |  |  | 24 | unless ($rv) | 
| 2947 |  |  |  |  |  |  | { | 
| 2948 | 7 |  |  |  |  | 20 | $self->reply (500, | 
| 2949 |  |  |  |  |  |  | "Command restricted by site administrator."); | 
| 2950 | 7 |  |  |  |  | 27 | next COMMAND; | 
| 2951 |  |  |  |  |  |  | } | 
| 2952 |  |  |  |  |  |  | } | 
| 2953 |  |  |  |  |  |  | } | 
| 2954 |  |  |  |  |  |  | } | 
| 2955 |  |  |  |  |  |  |  | 
| 2956 |  |  |  |  |  |  | # Get the command. | 
| 2957 |  |  |  |  |  |  | # See also RFC 2640 section 3.1. | 
| 2958 | 302 | 50 |  |  |  | 1393 | unless (m/^([A-Z]{3,4})\s?(.*)/i) | 
| 2959 |  |  |  |  |  |  | { | 
| 2960 | 0 |  |  |  |  | 0 | $self->log ("err", | 
| 2961 |  |  |  |  |  |  | "badly formed command received: %s", _escape ($_)); | 
| 2962 | 0 |  |  |  |  | 0 | $self->_log_line ("[Badly formed command]", _escape ($_)); | 
| 2963 | 0 |  |  |  |  | 0 | exit 0; | 
| 2964 |  |  |  |  |  |  | } | 
| 2965 |  |  |  |  |  |  |  | 
| 2966 |  |  |  |  |  |  | # The following strange 'eval' is necessary to work around a | 
| 2967 |  |  |  |  |  |  | # very odd bug in Perl 5.6.0. The following assignment to | 
| 2968 |  |  |  |  |  |  | # $cmd will fail in some cases unless you use $1 in some sort | 
| 2969 |  |  |  |  |  |  | # of an expression beforehand. | 
| 2970 |  |  |  |  |  |  | # - RWMJ 2002-07-05. | 
| 2971 | 302 |  |  |  |  | 20866 | eval '$1 eq $1'; | 
| 2972 |  |  |  |  |  |  |  | 
| 2973 | 302 |  |  |  |  | 1780 | my ($cmd, $rest) = (uc $1, $2); | 
| 2974 |  |  |  |  |  |  |  | 
| 2975 |  |  |  |  |  |  | $self->log ("info", "command: (%s, %s)", | 
| 2976 |  |  |  |  |  |  | _escape ($cmd), _escape ($rest)) | 
| 2977 | 302 | 50 |  |  |  | 1466 | if $self->{debug}; | 
| 2978 |  |  |  |  |  |  |  | 
| 2979 |  |  |  |  |  |  | # Command requires user to be authenticated? | 
| 2980 | 302 | 50 | 66 |  |  | 1151 | unless ($self->{authenticated} || | 
| 2981 |  |  |  |  |  |  | exists $no_authentication_commands{$cmd}) | 
| 2982 |  |  |  |  |  |  | { | 
| 2983 | 0 |  |  |  |  | 0 | $self->reply (530, "Not logged in."); | 
| 2984 | 0 |  |  |  |  | 0 | next; | 
| 2985 |  |  |  |  |  |  | } | 
| 2986 |  |  |  |  |  |  |  | 
| 2987 |  |  |  |  |  |  | # Handle the QUIT command specially. | 
| 2988 | 302 | 100 |  |  |  | 920 | if ($cmd eq "QUIT") | 
| 2989 |  |  |  |  |  |  | { | 
| 2990 | 25 |  |  |  |  | 113 | $self->reply (221, "Goodbye. Service closing connection."); | 
| 2991 | 25 |  |  |  |  | 83 | last; | 
| 2992 |  |  |  |  |  |  | } | 
| 2993 |  |  |  |  |  |  |  | 
| 2994 |  |  |  |  |  |  | # Got a command which matches in the table? | 
| 2995 | 277 | 50 |  |  |  | 794 | unless (exists $self->{command_table}{$cmd}) | 
| 2996 |  |  |  |  |  |  | { | 
| 2997 | 0 |  |  |  |  | 0 | $self->reply (500, "Unrecognized command."); | 
| 2998 | 0 |  |  |  |  | 0 | $self->log ("err", | 
| 2999 |  |  |  |  |  |  | "unknown command received: %s", _escape ($_)); | 
| 3000 | 0 |  |  |  |  | 0 | next; | 
| 3001 |  |  |  |  |  |  | } | 
| 3002 |  |  |  |  |  |  |  | 
| 3003 |  |  |  |  |  |  | # Run the command. | 
| 3004 | 277 |  |  |  |  | 462 | &{$self->{command_table}{$cmd}} ($self, $cmd, $rest); | 
|  | 277 |  |  |  |  | 1428 |  | 
| 3005 |  |  |  |  |  |  |  | 
| 3006 |  |  |  |  |  |  | # Post-command hook. | 
| 3007 | 277 |  |  |  |  | 4790 | $self->post_command_hook ($cmd, $rest); | 
| 3008 |  |  |  |  |  |  |  | 
| 3009 |  |  |  |  |  |  | # Write out any xferlog that may have built up from the command | 
| 3010 | 277 | 50 |  |  |  | 782 | $self->xfer_flush if $self->{_xferlog}; | 
| 3011 |  |  |  |  |  |  | } | 
| 3012 |  |  |  |  |  |  |  | 
| 3013 | 40 |  |  |  |  | 616 | $self->quit_hook (); | 
| 3014 |  |  |  |  |  |  |  | 
| 3015 | 40 | 50 |  |  |  | 262 | unless ($self->{_test_mode}) | 
| 3016 |  |  |  |  |  |  | { | 
| 3017 | 0 |  |  |  |  | 0 | $self->_log_line ("[ENDED BY CLIENT $self->{peeraddrstring}:$self->{peerport}]"); | 
| 3018 | 0 |  |  |  |  | 0 | $self->log ("info", "connection terminated normally"); | 
| 3019 |  |  |  |  |  |  | } | 
| 3020 |  |  |  |  |  |  |  | 
| 3021 |  |  |  |  |  |  | # The return value is used by the test scripts. | 
| 3022 | 40 |  |  |  |  | 486 | $self; | 
| 3023 |  |  |  |  |  |  | } | 
| 3024 |  |  |  |  |  |  |  | 
| 3025 |  |  |  |  |  |  | # Signals are handled synchronously to get around the problem | 
| 3026 |  |  |  |  |  |  | # with unsafe signals which exists in Perl < 5.7.2. Call the | 
| 3027 |  |  |  |  |  |  | # following function periodically to check signals. | 
| 3028 |  |  |  |  |  |  | sub _check_signals | 
| 3029 |  |  |  |  |  |  | { | 
| 3030 | 5048 |  |  | 5048 |  | 7388 | my $self = shift; | 
| 3031 |  |  |  |  |  |  |  | 
| 3032 | 5048 | 100 |  |  |  | 8427 | if ($GOT_SIGURG) | 
| 3033 |  |  |  |  |  |  | { | 
| 3034 | 2 |  |  |  |  | 5 | $GOT_SIGURG  = 0; | 
| 3035 | 2 |  |  |  |  | 23 | $self->_handle_sigurg; | 
| 3036 |  |  |  |  |  |  | } | 
| 3037 |  |  |  |  |  |  |  | 
| 3038 | 5048 | 50 |  |  |  | 7678 | if ($GOT_SIGCHLD) | 
| 3039 |  |  |  |  |  |  | { | 
| 3040 | 0 |  |  |  |  | 0 | $GOT_SIGCHLD = 0; | 
| 3041 | 0 |  |  |  |  | 0 | $self->_handle_sigchld; | 
| 3042 |  |  |  |  |  |  | } | 
| 3043 |  |  |  |  |  |  |  | 
| 3044 | 5048 | 50 |  |  |  | 7963 | if ($GOT_SIGHUP) | 
| 3045 |  |  |  |  |  |  | { | 
| 3046 | 0 |  |  |  |  | 0 | $GOT_SIGHUP  = 0; | 
| 3047 | 0 |  |  |  |  | 0 | $self->_handle_sighup; | 
| 3048 |  |  |  |  |  |  | } | 
| 3049 |  |  |  |  |  |  |  | 
| 3050 | 5048 | 50 |  |  |  | 8399 | if ($GOT_SIGTERM) | 
| 3051 |  |  |  |  |  |  | { | 
| 3052 | 0 |  |  |  |  | 0 | $GOT_SIGTERM = 0; | 
| 3053 | 0 |  |  |  |  | 0 | $self->_handle_sigterm; | 
| 3054 |  |  |  |  |  |  | } | 
| 3055 |  |  |  |  |  |  |  | 
| 3056 |  |  |  |  |  |  | } | 
| 3057 |  |  |  |  |  |  |  | 
| 3058 |  |  |  |  |  |  | # Handle SIGURG signal in the parent process. | 
| 3059 |  |  |  |  |  |  | sub _handle_sigurg | 
| 3060 |  |  |  |  |  |  | { | 
| 3061 | 2 |  |  | 2 |  | 4 | my $self = shift; | 
| 3062 |  |  |  |  |  |  |  | 
| 3063 | 2 |  |  |  |  | 5 | $self->{_urgent} = 1; | 
| 3064 |  |  |  |  |  |  | } | 
| 3065 |  |  |  |  |  |  |  | 
| 3066 |  |  |  |  |  |  | # Handle SIGCHLD signal in the parent process. | 
| 3067 |  |  |  |  |  |  | sub _handle_sigchld | 
| 3068 |  |  |  |  |  |  | { | 
| 3069 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 3070 |  |  |  |  |  |  |  | 
| 3071 |  |  |  |  |  |  | # Clear up any zombie processes. | 
| 3072 | 0 |  |  |  |  | 0 | while ((my $pid = waitpid (-1, WNOHANG)) > 0) | 
| 3073 |  |  |  |  |  |  | { | 
| 3074 |  |  |  |  |  |  | # Remove this PID from the children hash. | 
| 3075 | 0 |  |  |  |  | 0 | delete $self->{_children}->{$pid}; | 
| 3076 |  |  |  |  |  |  | } | 
| 3077 |  |  |  |  |  |  | } | 
| 3078 |  |  |  |  |  |  |  | 
| 3079 |  |  |  |  |  |  | # Handle SIGHUP signal synchronously in the parent process. | 
| 3080 |  |  |  |  |  |  | # This code mostly by Rob, rewritten and simplified by Rich for | 
| 3081 |  |  |  |  |  |  | # the new synchronous signal handling code. Note that this function | 
| 3082 |  |  |  |  |  |  | # has to be called synchronously (not from a signal handler, even | 
| 3083 |  |  |  |  |  |  | # in Perl >= 5.7.2) because otherwise the exec will happen with | 
| 3084 |  |  |  |  |  |  | # most signals blocked. | 
| 3085 |  |  |  |  |  |  | sub _handle_sighup | 
| 3086 |  |  |  |  |  |  | { | 
| 3087 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 3088 |  |  |  |  |  |  |  | 
| 3089 |  |  |  |  |  |  | # Clear FD_CLOEXEC bit on the listening socket because we are | 
| 3090 |  |  |  |  |  |  | # intending to pass that socket to our exec'd child process. | 
| 3091 | 0 |  |  |  |  | 0 | $self->{_ctrl_sock}->fcntl (F_SETFD, my $flags = ""); | 
| 3092 |  |  |  |  |  |  |  | 
| 3093 |  |  |  |  |  |  | # Make the socket available to the child process in the environment. | 
| 3094 | 0 |  |  |  |  | 0 | $ENV{BIND} = $self->{_ctrl_sock}->fileno; | 
| 3095 |  |  |  |  |  |  |  | 
| 3096 |  |  |  |  |  |  | # Print a message to syslog. | 
| 3097 | 0 |  |  |  |  | 0 | $self->log ("info", "received SIGHUP, reloading"); | 
| 3098 | 0 |  |  |  |  | 0 | $self->_log_line ("[DAEMON Reloading]"); | 
| 3099 |  |  |  |  |  |  |  | 
| 3100 |  |  |  |  |  |  | # Restart self. | 
| 3101 | 0 | 0 |  |  |  | 0 | exec ($0, @ARGV) or die "hup exec failed: $!"; | 
| 3102 |  |  |  |  |  |  | } | 
| 3103 |  |  |  |  |  |  |  | 
| 3104 |  |  |  |  |  |  | # Handle SIGTERM signal in the parent process. | 
| 3105 |  |  |  |  |  |  | sub _handle_sigterm | 
| 3106 |  |  |  |  |  |  | { | 
| 3107 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 3108 |  |  |  |  |  |  |  | 
| 3109 | 0 |  |  |  |  | 0 | $self->log ("info", "shutting down daemon"); | 
| 3110 | 0 |  |  |  |  | 0 | $self->_log_line ("[DAEMON Shutdown]"); | 
| 3111 | 0 |  |  |  |  | 0 | exit; | 
| 3112 |  |  |  |  |  |  | } | 
| 3113 |  |  |  |  |  |  |  | 
| 3114 |  |  |  |  |  |  | # Added 20 Oct 2003 by Yair Lenga | 
| 3115 |  |  |  |  |  |  | # Rotating Log files - allow stftime '%' in the file name | 
| 3116 |  |  |  |  |  |  |  | 
| 3117 |  |  |  |  |  |  | sub _rotate_log | 
| 3118 |  |  |  |  |  |  | { | 
| 3119 | 0 |  |  | 0 |  | 0 | my $self = shift ; | 
| 3120 | 0 |  |  |  |  | 0 | my $prop = "rotate log files"; | 
| 3121 |  |  |  |  |  |  |  | 
| 3122 | 0 | 0 |  |  |  | 0 | if (defined ($self->config($prop)) ? $self->config($prop) : 0) | 
|  |  | 0 |  |  |  |  |  | 
| 3123 |  |  |  |  |  |  | { | 
| 3124 | 0 |  |  |  |  | 0 | $self->_open_error_log ; | 
| 3125 | 0 |  |  |  |  | 0 | $self->_open_client_log ; | 
| 3126 | 0 |  |  |  |  | 0 | $self->_open_xfer_log ; | 
| 3127 |  |  |  |  |  |  | } | 
| 3128 |  |  |  |  |  |  | } | 
| 3129 |  |  |  |  |  |  |  | 
| 3130 |  |  |  |  |  |  | sub _open_error_log | 
| 3131 |  |  |  |  |  |  | { | 
| 3132 | 0 |  |  | 0 |  | 0 | my $self = shift ; | 
| 3133 |  |  |  |  |  |  |  | 
| 3134 |  |  |  |  |  |  | # Check for new error log (remember open log file in in _error_file) | 
| 3135 |  |  |  |  |  |  |  | 
| 3136 | 0 | 0 |  |  |  | 0 | if ( my $log_file = $self->config("error log") ) { | 
| 3137 | 0 |  |  |  |  | 0 | $log_file = $self->resolve_log_file_name($log_file) ; | 
| 3138 | 0 | 0 | 0 |  |  | 0 | if (!defined $self->{_error_file} || | 
| 3139 |  |  |  |  |  |  | $log_file ne $self->{_error_file}) { | 
| 3140 | 0 |  |  |  |  | 0 | $self->log( 'notice', "Switch error log to $log_file") ; | 
| 3141 | 0 | 0 |  |  |  | 0 | open STDERR, ">>$log_file" | 
| 3142 |  |  |  |  |  |  | or die "cannot append: $log_file: $!"; | 
| 3143 | 0 |  |  |  |  | 0 | $self->{_error_file} = $log_file; | 
| 3144 |  |  |  |  |  |  | } | 
| 3145 |  |  |  |  |  |  | } | 
| 3146 | 0 |  |  |  |  | 0 | return 1 | 
| 3147 |  |  |  |  |  |  | } | 
| 3148 |  |  |  |  |  |  |  | 
| 3149 |  |  |  |  |  |  | sub _open_xfer_log | 
| 3150 |  |  |  |  |  |  | { | 
| 3151 | 41 |  |  | 41 |  | 132 | my $self = shift ; | 
| 3152 | 41 | 50 |  |  |  | 144 | if ( my $log_file = $self->config("xfer logging") ) { | 
| 3153 | 0 |  |  |  |  | 0 | $log_file = $self->resolve_log_file_name($log_file) ; | 
| 3154 | 0 | 0 | 0 |  |  | 0 | if ( !defined $self->{_xfer_file} || | 
| 3155 |  |  |  |  |  |  | $log_file ne $self->{_xfer_file} ) { | 
| 3156 | 0 | 0 |  |  |  | 0 | if ( my $io = $self->{_xferlog} ) { | 
| 3157 | 0 |  |  |  |  | 0 | $io->close ; | 
| 3158 | 0 |  |  |  |  | 0 | delete $self->{_xferlog} ; | 
| 3159 |  |  |  |  |  |  | } ; | 
| 3160 | 0 |  |  |  |  | 0 | $self->{_xfer_file} = $log_file; | 
| 3161 | 0 |  |  |  |  | 0 | my $io = new IO::File $log_file, "a"; | 
| 3162 | 0 | 0 |  |  |  | 0 | if (defined $io) { | 
| 3163 | 0 |  |  |  |  | 0 | $io->autoflush (1); | 
| 3164 | 0 |  |  |  |  | 0 | $self->{_xferlog} = $io; | 
| 3165 | 0 |  |  |  |  | 0 | $self->log( 'notice', "Using xfer log: $log_file") ; | 
| 3166 |  |  |  |  |  |  | } else { | 
| 3167 | 0 |  |  |  |  | 0 | die "cannot append: $log_file: $!"; | 
| 3168 |  |  |  |  |  |  | } | 
| 3169 |  |  |  |  |  |  | } | 
| 3170 |  |  |  |  |  |  | } | 
| 3171 | 41 |  |  |  |  | 126 | return 1 | 
| 3172 |  |  |  |  |  |  | } | 
| 3173 |  |  |  |  |  |  |  | 
| 3174 |  |  |  |  |  |  | sub _open_client_log | 
| 3175 |  |  |  |  |  |  | { | 
| 3176 | 41 |  |  | 41 |  | 135 | my $self = shift ; | 
| 3177 | 41 | 50 |  |  |  | 150 | if ( my $log_file = $self->config("client logging") ) { | 
| 3178 | 0 |  |  |  |  | 0 | $log_file = $self->resolve_log_file_name($log_file) ; | 
| 3179 | 0 | 0 | 0 |  |  | 0 | if (!defined $self->{_client_file} || | 
| 3180 |  |  |  |  |  |  | $log_file ne $self->{_client_file} ) { | 
| 3181 | 0 | 0 |  |  |  | 0 | if ( my $io = $self->{_client_log} ) { | 
| 3182 | 0 |  |  |  |  | 0 | $io->close ; | 
| 3183 | 0 |  |  |  |  | 0 | delete $self->{_client_log} ; | 
| 3184 |  |  |  |  |  |  | } ; | 
| 3185 | 0 |  |  |  |  | 0 | $self->{_client_file} = $log_file; | 
| 3186 | 0 |  |  |  |  | 0 | my $io = new IO::File $log_file, "a"; | 
| 3187 | 0 | 0 |  |  |  | 0 | if (defined $io) { | 
| 3188 | 0 |  |  |  |  | 0 | $io->autoflush (1); | 
| 3189 | 0 |  |  |  |  | 0 | $self->{_client_log} = $io; | 
| 3190 | 0 |  |  |  |  | 0 | $self->log( 'notice', "Starting client log: $log_file") ; | 
| 3191 |  |  |  |  |  |  | } else { | 
| 3192 | 0 |  |  |  |  | 0 | die "cannot append: $log_file: $!"; | 
| 3193 |  |  |  |  |  |  | } | 
| 3194 |  |  |  |  |  |  | } | 
| 3195 |  |  |  |  |  |  | } | 
| 3196 |  |  |  |  |  |  | } | 
| 3197 |  |  |  |  |  |  |  | 
| 3198 |  |  |  |  |  |  | sub resolve_log_file_name | 
| 3199 |  |  |  |  |  |  | { | 
| 3200 | 0 |  |  | 0 | 0 | 0 | my ($self, $log_file) = @_ ; | 
| 3201 |  |  |  |  |  |  |  | 
| 3202 | 0 | 0 |  |  |  | 0 | $log_file =~ s/\$(\w+)/$self->{$1}/g | 
| 3203 |  |  |  |  |  |  | if $log_file =~ /\$/ ; | 
| 3204 | 0 | 0 |  |  |  | 0 | $log_file = strftime($log_file, localtime()) | 
| 3205 |  |  |  |  |  |  | if $log_file =~ /\%/ ; | 
| 3206 | 0 |  |  |  |  | 0 | return $log_file; | 
| 3207 |  |  |  |  |  |  | } | 
| 3208 |  |  |  |  |  |  |  | 
| 3209 |  |  |  |  |  |  | # Added 21 Feb 2001 by Rob Brown | 
| 3210 |  |  |  |  |  |  | # Client command logging | 
| 3211 |  |  |  |  |  |  | sub _log_line | 
| 3212 |  |  |  |  |  |  | { | 
| 3213 | 353 |  |  | 353 |  | 639 | my $self = shift; | 
| 3214 | 353 | 50 |  |  |  | 1100 | return unless exists $self->{_client_log}; | 
| 3215 | 0 |  |  |  |  | 0 | my $message = join ("",@_); | 
| 3216 | 0 |  |  |  |  | 0 | my $io = $self->{_client_log}; | 
| 3217 | 0 |  |  |  |  | 0 | my $time = scalar localtime; | 
| 3218 | 0 | 0 |  |  |  | 0 | my $authenticated = $self->{authenticated} ? $self->{user} : "-"; | 
| 3219 | 0 |  |  |  |  | 0 | $message =~ s/\n*$/\n/; | 
| 3220 | 0 |  |  |  |  | 0 | $io->print ("[$time][$$:$authenticated]$message"); | 
| 3221 |  |  |  |  |  |  | } | 
| 3222 |  |  |  |  |  |  |  | 
| 3223 |  |  |  |  |  |  | # Added 08 Feb 2001 by Rob Brown | 
| 3224 |  |  |  |  |  |  | # Safely saves the process id to the specified pidfile. | 
| 3225 |  |  |  |  |  |  | # If no pidfile is specified, nothing happens. | 
| 3226 |  |  |  |  |  |  | sub _save_pid | 
| 3227 |  |  |  |  |  |  | { | 
| 3228 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 3229 |  |  |  |  |  |  |  | 
| 3230 |  |  |  |  |  |  | # Store pid into pidfile? | 
| 3231 | 0 |  |  |  |  | 0 | $self->{_pidfile} = $self->config ("pidfile"); | 
| 3232 |  |  |  |  |  |  |  | 
| 3233 | 0 | 0 |  |  |  | 0 | if (defined $self->{_pidfile}) | 
| 3234 |  |  |  |  |  |  | { | 
| 3235 | 0 |  |  |  |  | 0 | my $pidfile = $self->{_pidfile}; | 
| 3236 |  |  |  |  |  |  |  | 
| 3237 |  |  |  |  |  |  | # Swap $VARIABLE with corresponding attribute (i.e., $hostname) | 
| 3238 | 0 |  |  |  |  | 0 | $pidfile =~ s/\$(\w+)/$self->{$1}/g; | 
| 3239 | 0 | 0 |  |  |  | 0 | if ($pidfile =~ m%^([/\w\-\.]+)$%) | 
| 3240 |  |  |  |  |  |  | { | 
| 3241 | 0 |  |  |  |  | 0 | $self->{_pidfile} = $1; | 
| 3242 | 0 | 0 |  |  |  | 0 | open (PID, ">$self->{_pidfile}") | 
| 3243 |  |  |  |  |  |  | or die "cannot write $pidfile: $!"; | 
| 3244 | 0 |  |  |  |  | 0 | print PID "$$\n"; | 
| 3245 | 0 |  |  |  |  | 0 | close PID; | 
| 3246 | 0 |  |  |  |  | 0 | eval "END {unlink('$1') if \$\$ == $$;}"; | 
| 3247 |  |  |  |  |  |  | } | 
| 3248 |  |  |  |  |  |  | else | 
| 3249 |  |  |  |  |  |  | { | 
| 3250 | 0 |  |  |  |  | 0 | die "Refusing to create weird looking pidfile: $pidfile"; | 
| 3251 |  |  |  |  |  |  | } | 
| 3252 |  |  |  |  |  |  | } | 
| 3253 |  |  |  |  |  |  | } | 
| 3254 |  |  |  |  |  |  |  | 
| 3255 |  |  |  |  |  |  | # Set a resource limit, by using the BSD::Resource module, if available. | 
| 3256 |  |  |  |  |  |  |  | 
| 3257 |  |  |  |  |  |  | sub _set_rlimit | 
| 3258 |  |  |  |  |  |  | { | 
| 3259 | 120 |  |  | 120 |  | 227 | my $self = shift; | 
| 3260 | 120 |  |  |  |  | 210 | my $name = shift; | 
| 3261 | 120 |  |  |  |  | 185 | my $value = shift; | 
| 3262 |  |  |  |  |  |  |  | 
| 3263 |  |  |  |  |  |  | # The BSD::Resource module is optional, and may not be available. | 
| 3264 | 120 | 50 | 33 |  |  | 2791 | if (exists $INC{"BSD/Resource.pm"} && | 
|  |  | 0 |  |  |  |  |  | 
| 3265 |  |  |  |  |  |  | exists get_rlimits()->{$name}) | 
| 3266 |  |  |  |  |  |  | { | 
| 3267 | 120 | 50 |  |  |  | 29790 | setrlimit (&{$ {BSD::Resource::}{$name}}, $value, $value) | 
|  | 120 |  |  |  |  | 951 |  | 
| 3268 |  |  |  |  |  |  | or die "setrlimit: $!"; | 
| 3269 |  |  |  |  |  |  | } | 
| 3270 |  |  |  |  |  |  | elsif (not $ENV{NET_FTPSERVER_NO_BSD_RESOURCE_WARNING}) | 
| 3271 |  |  |  |  |  |  | { | 
| 3272 | 0 |  |  |  |  | 0 | warn | 
| 3273 |  |  |  |  |  |  | "Resource limit $name cannot be set. This may be because ", | 
| 3274 |  |  |  |  |  |  | "the BSD::Resource module is not available on your ", | 
| 3275 |  |  |  |  |  |  | "system, or it may be because your operating system ", | 
| 3276 |  |  |  |  |  |  | "does not support $name. Without resource limits, the ", | 
| 3277 |  |  |  |  |  |  | "FTP server may be open to denial of service (DoS) ", | 
| 3278 |  |  |  |  |  |  | "attacks. The real error was: $@"; | 
| 3279 |  |  |  |  |  |  | } | 
| 3280 |  |  |  |  |  |  | } | 
| 3281 |  |  |  |  |  |  |  | 
| 3282 |  |  |  |  |  |  | # Check for an external program (eg. "gzip"). This test is not | 
| 3283 |  |  |  |  |  |  | # bulletproof: In particular, it requires $PATH to be set correctly | 
| 3284 |  |  |  |  |  |  | # at the top of this file or in the config file. | 
| 3285 |  |  |  |  |  |  |  | 
| 3286 |  |  |  |  |  |  | sub _find_prog | 
| 3287 |  |  |  |  |  |  | { | 
| 3288 | 164 |  |  | 164 |  | 335 | my $self = shift; | 
| 3289 | 164 |  |  |  |  | 312 | my $prog = shift; | 
| 3290 |  |  |  |  |  |  |  | 
| 3291 | 164 |  |  |  |  | 516 | my @paths = split /:/, $ENV{PATH}; | 
| 3292 | 164 |  |  |  |  | 449 | foreach (@paths) | 
| 3293 |  |  |  |  |  |  | { | 
| 3294 | 328 | 100 |  |  |  | 3204 | return 1 if -x "$_/$prog"; | 
| 3295 |  |  |  |  |  |  | } | 
| 3296 | 82 |  |  |  |  | 407 | return 0; | 
| 3297 |  |  |  |  |  |  | } | 
| 3298 |  |  |  |  |  |  |  | 
| 3299 |  |  |  |  |  |  | # This subroutine loads the command line options and configuration file | 
| 3300 |  |  |  |  |  |  | # and resolves conflicts. Command line options have priority over | 
| 3301 |  |  |  |  |  |  | # certain things in the configuration file. | 
| 3302 |  |  |  |  |  |  |  | 
| 3303 |  |  |  |  |  |  | sub _get_configuration | 
| 3304 |  |  |  |  |  |  | { | 
| 3305 | 41 |  |  | 41 |  | 128 | my $self = shift; | 
| 3306 | 41 |  |  |  |  | 129 | my $args = shift; | 
| 3307 | 41 |  |  |  |  | 250 | local @ARGV = @$args; | 
| 3308 |  |  |  |  |  |  |  | 
| 3309 | 41 |  |  |  |  | 155 | my ($debug, $help, $port, $s_option, $S_option, | 
| 3310 |  |  |  |  |  |  | $pidfile, $show_version, @overrides); | 
| 3311 |  |  |  |  |  |  |  | 
| 3312 | 41 |  |  |  |  | 767 | Getopt::Long::Configure ("no_ignore_case"); | 
| 3313 | 41 |  |  |  |  | 3274 | Getopt::Long::Configure ("pass_through"); | 
| 3314 |  |  |  |  |  |  |  | 
| 3315 |  |  |  |  |  |  | GetOptions ( | 
| 3316 |  |  |  |  |  |  | "C=s" => \$self->{_config_file}, | 
| 3317 |  |  |  |  |  |  | "d+" => \$debug, | 
| 3318 |  |  |  |  |  |  | "help|?" => \$help, | 
| 3319 |  |  |  |  |  |  | "o=s" => \@overrides, | 
| 3320 |  |  |  |  |  |  | "p=i" => \$port, | 
| 3321 |  |  |  |  |  |  | "P=s" => \$pidfile, | 
| 3322 |  |  |  |  |  |  | "s" => \$s_option, | 
| 3323 |  |  |  |  |  |  | "S" => \$S_option, | 
| 3324 |  |  |  |  |  |  | "test" => \$self->{_test_mode}, | 
| 3325 | 41 |  |  |  |  | 1885 | "v+" => \$debug, | 
| 3326 |  |  |  |  |  |  | "V" => \$show_version, | 
| 3327 |  |  |  |  |  |  | ); | 
| 3328 |  |  |  |  |  |  |  | 
| 3329 |  |  |  |  |  |  | # Show version and exit? | 
| 3330 | 41 | 50 |  |  |  | 57125 | if ($show_version) | 
| 3331 |  |  |  |  |  |  | { | 
| 3332 | 0 |  |  |  |  | 0 | print $self->{version_string}, "\n"; | 
| 3333 | 0 |  |  |  |  | 0 | exit 0; | 
| 3334 |  |  |  |  |  |  | } | 
| 3335 |  |  |  |  |  |  |  | 
| 3336 |  |  |  |  |  |  | # Show help and exit? | 
| 3337 | 41 | 50 |  |  |  | 230 | if ($help) | 
| 3338 |  |  |  |  |  |  | { | 
| 3339 | 0 |  |  |  |  | 0 | my $name = $0; | 
| 3340 | 0 |  |  |  |  | 0 | $name =~ s,.*/,,; | 
| 3341 |  |  |  |  |  |  |  | 
| 3342 | 0 |  |  |  |  | 0 | print < | 
| 3343 |  |  |  |  |  |  | $name: $self->{version_string} | 
| 3344 |  |  |  |  |  |  |  | 
| 3345 |  |  |  |  |  |  | Usage: | 
| 3346 |  |  |  |  |  |  | $name [-options] | 
| 3347 |  |  |  |  |  |  |  | 
| 3348 |  |  |  |  |  |  | Options: | 
| 3349 |  |  |  |  |  |  | -?, --help            Print this help text and exit. | 
| 3350 |  |  |  |  |  |  | -d, -v                Debug mode on. | 
| 3351 |  |  |  |  |  |  | -p port               Specify listening port (defaults to FTP port, 21). | 
| 3352 |  |  |  |  |  |  | -s                    Run in daemon mode (default: run from inetd). | 
| 3353 |  |  |  |  |  |  | -S                    Run in background and in daemon mode. | 
| 3354 |  |  |  |  |  |  | -V                    Show version information and exit. | 
| 3355 |  |  |  |  |  |  | -C config_file        Specify configuration file (default: /etc/ftpd.conf). | 
| 3356 |  |  |  |  |  |  | -P pidfile            Save process ID into pidfile. | 
| 3357 |  |  |  |  |  |  | -o option=value       Override configuration file options. | 
| 3358 |  |  |  |  |  |  |  | 
| 3359 |  |  |  |  |  |  | Normal standalone usage: | 
| 3360 |  |  |  |  |  |  |  | 
| 3361 |  |  |  |  |  |  | $name -S | 
| 3362 |  |  |  |  |  |  |  | 
| 3363 |  |  |  |  |  |  | Normal usage from inetd: | 
| 3364 |  |  |  |  |  |  |  | 
| 3365 |  |  |  |  |  |  | ftp stream tcp nowait root /usr/sbin/tcpd $name | 
| 3366 |  |  |  |  |  |  |  | 
| 3367 |  |  |  |  |  |  | For further information, please read the full documentation in the | 
| 3368 |  |  |  |  |  |  | Net::FTPServer(3) manual page. | 
| 3369 |  |  |  |  |  |  | EOT | 
| 3370 | 0 |  |  |  |  | 0 | exit 0; | 
| 3371 |  |  |  |  |  |  | } | 
| 3372 |  |  |  |  |  |  |  | 
| 3373 |  |  |  |  |  |  | # Read the configuration file. | 
| 3374 | 41 |  |  |  |  | 156 | $self->{_config} = {}; | 
| 3375 | 41 |  |  |  |  | 153 | $self->{_config_ip_host} = {}; | 
| 3376 | 41 |  |  |  |  | 851 | $self->_open_config_file ($self->{_config_file}); | 
| 3377 |  |  |  |  |  |  |  | 
| 3378 |  |  |  |  |  |  | # Magically update configuration values with command line | 
| 3379 |  |  |  |  |  |  | # argument values. Thus configuration entered on the command | 
| 3380 |  |  |  |  |  |  | # line will override those present in the configuration file. | 
| 3381 | 41 | 100 |  |  |  | 2719 | if ($port) | 
| 3382 |  |  |  |  |  |  | { | 
| 3383 | 2 |  |  |  |  | 10 | $self->_set_config ("port", $port, splat => 1); | 
| 3384 |  |  |  |  |  |  | } | 
| 3385 | 41 | 50 |  |  |  | 172 | if ($s_option) | 
| 3386 |  |  |  |  |  |  | { | 
| 3387 | 0 |  |  |  |  | 0 | $self->_set_config ("daemon mode", 1, splat => 1); | 
| 3388 |  |  |  |  |  |  | } | 
| 3389 | 41 | 50 |  |  |  | 182 | if ($S_option) | 
| 3390 |  |  |  |  |  |  | { | 
| 3391 | 0 |  |  |  |  | 0 | $self->_set_config ("daemon mode", 1, splat => 1); | 
| 3392 | 0 |  |  |  |  | 0 | $self->_set_config ("run in background", 1, splat => 1); | 
| 3393 |  |  |  |  |  |  | } | 
| 3394 | 41 | 50 |  |  |  | 165 | if ($pidfile) | 
| 3395 |  |  |  |  |  |  | { | 
| 3396 | 0 |  |  |  |  | 0 | $self->_set_config ("pidfile", $pidfile, splat => 1); | 
| 3397 |  |  |  |  |  |  | } | 
| 3398 |  |  |  |  |  |  |  | 
| 3399 |  |  |  |  |  |  | # Override other configuration file options. | 
| 3400 | 41 |  |  |  |  | 148 | foreach (@overrides) | 
| 3401 |  |  |  |  |  |  | { | 
| 3402 | 39 |  |  |  |  | 163 | my ($key, $value) = split /=/, $_, 2; | 
| 3403 | 39 |  |  |  |  | 363 | $self->_set_config ($key, $value, splat => 1); | 
| 3404 |  |  |  |  |  |  | } | 
| 3405 |  |  |  |  |  |  |  | 
| 3406 |  |  |  |  |  |  | # Set debugging state. | 
| 3407 | 41 | 50 |  |  |  | 189 | if (defined $debug) { | 
|  |  | 0 |  |  |  |  |  | 
| 3408 | 41 |  |  |  |  | 173 | $self->{debug} = 1 | 
| 3409 |  |  |  |  |  |  | } elsif (defined $self->config ("debug")) { | 
| 3410 | 0 |  |  |  |  | 0 | $self->{debug} = $self->config ("debug") | 
| 3411 |  |  |  |  |  |  | } | 
| 3412 |  |  |  |  |  |  | } | 
| 3413 |  |  |  |  |  |  |  | 
| 3414 |  |  |  |  |  |  | # Fork into the background (command line -S option). | 
| 3415 |  |  |  |  |  |  |  | 
| 3416 |  |  |  |  |  |  | sub _fork_into_background | 
| 3417 |  |  |  |  |  |  | { | 
| 3418 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 3419 |  |  |  |  |  |  |  | 
| 3420 | 0 |  |  |  |  | 0 | my $pid = fork; | 
| 3421 | 0 | 0 |  |  |  | 0 | die "fork: $!" unless defined $pid; | 
| 3422 |  |  |  |  |  |  |  | 
| 3423 |  |  |  |  |  |  | # Parent process ends here. | 
| 3424 | 0 | 0 |  |  |  | 0 | exit if $pid > 0; | 
| 3425 |  |  |  |  |  |  |  | 
| 3426 |  |  |  |  |  |  | # Start a new session. | 
| 3427 | 0 |  |  |  |  | 0 | setsid; | 
| 3428 |  |  |  |  |  |  |  | 
| 3429 |  |  |  |  |  |  | # Close connection to tty and reopen 0, 1 as /dev/null. | 
| 3430 |  |  |  |  |  |  | # Note that 2 points to the error log. | 
| 3431 | 0 |  |  |  |  | 0 | open STDIN, " | 
| 3432 | 0 |  |  |  |  | 0 | open STDOUT, ">>/dev/null"; | 
| 3433 |  |  |  |  |  |  |  | 
| 3434 |  |  |  |  |  |  | #   $self->log ("info", "forked into background"); | 
| 3435 |  |  |  |  |  |  | } | 
| 3436 |  |  |  |  |  |  |  | 
| 3437 |  |  |  |  |  |  | # Be a daemon (command line -s option). | 
| 3438 |  |  |  |  |  |  |  | 
| 3439 |  |  |  |  |  |  | sub _be_daemon | 
| 3440 |  |  |  |  |  |  | { | 
| 3441 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 3442 |  |  |  |  |  |  |  | 
| 3443 |  |  |  |  |  |  | #   $self->log ("info", "operating in daemon mode"); | 
| 3444 | 0 |  |  |  |  | 0 | $self->_log_line ("[DAEMON Started]"); | 
| 3445 |  |  |  |  |  |  |  | 
| 3446 |  |  |  |  |  |  | # Jump to a safe place because this is a deamon | 
| 3447 | 0 |  |  |  |  | 0 | chdir "/"; | 
| 3448 |  |  |  |  |  |  |  | 
| 3449 |  |  |  |  |  |  | # If the process receives SIGHUP, then it passes in the socket | 
| 3450 |  |  |  |  |  |  | # fd here through the BIND environment variable. Check for this, | 
| 3451 |  |  |  |  |  |  | # because if so we don't need to open a new listening socket. | 
| 3452 | 0 | 0 | 0 |  |  | 0 | if (exists $ENV{BIND} && $ENV{BIND} =~ /^(\d+)$/) | 
| 3453 |  |  |  |  |  |  | { | 
| 3454 | 0 |  |  |  |  | 0 | my $bind_fd = $1; | 
| 3455 | 0 |  |  |  |  | 0 | "0" =~ /(0)/; # Perl 5.7 / IO::Socket::INET bug workaround. | 
| 3456 | 0 |  |  |  |  | 0 | $self->{_ctrl_sock} = new IO::Socket::INET; | 
| 3457 | 0 | 0 |  |  |  | 0 | $self->{_ctrl_sock}->fdopen ($bind_fd, "w") | 
| 3458 |  |  |  |  |  |  | or die "socket: $!"; | 
| 3459 |  |  |  |  |  |  | } | 
| 3460 |  |  |  |  |  |  | # Otherwise do open a new listening socket. | 
| 3461 |  |  |  |  |  |  | else | 
| 3462 |  |  |  |  |  |  | { | 
| 3463 |  |  |  |  |  |  | # Discover the default FTP port from /etc/services or equivalent. | 
| 3464 | 0 |  | 0 |  |  | 0 | my $default_port = getservbyname ("ftp", "tcp") || 21; | 
| 3465 |  |  |  |  |  |  |  | 
| 3466 |  |  |  |  |  |  | # Construct argument list to socket. | 
| 3467 | 0 | 0 |  |  |  | 0 | my @args = (Reuse => 1, | 
| 3468 |  |  |  |  |  |  | Proto => "tcp", | 
| 3469 |  |  |  |  |  |  | Type => SOCK_STREAM, | 
| 3470 |  |  |  |  |  |  | LocalPort => | 
| 3471 |  |  |  |  |  |  | (defined $self->config ("port") | 
| 3472 |  |  |  |  |  |  | ? $self->config ("port") | 
| 3473 |  |  |  |  |  |  | : $default_port)); | 
| 3474 |  |  |  |  |  |  |  | 
| 3475 |  |  |  |  |  |  | # Get length of listen queue. | 
| 3476 | 0 | 0 |  |  |  | 0 | if (defined $self->config ("listen queue")) { | 
| 3477 | 0 |  |  |  |  | 0 | push @args, Listen => $self->config ("listen queue"); | 
| 3478 |  |  |  |  |  |  | } else { | 
| 3479 | 0 |  |  |  |  | 0 | push @args, Listen => 10; | 
| 3480 |  |  |  |  |  |  | } | 
| 3481 |  |  |  |  |  |  |  | 
| 3482 |  |  |  |  |  |  | # Get the local bind address. | 
| 3483 | 0 | 0 |  |  |  | 0 | if (defined $self->config ("local address")) { | 
| 3484 | 0 |  |  |  |  | 0 | push @args, LocalAddr => $self->config ("local address") | 
| 3485 |  |  |  |  |  |  | } | 
| 3486 |  |  |  |  |  |  |  | 
| 3487 |  |  |  |  |  |  | # Open a socket on the control port. | 
| 3488 | 0 |  |  |  |  | 0 | "0" =~ /(0)/; # Perl 5.7 / IO::Socket::INET bug workaround. | 
| 3489 |  |  |  |  |  |  | $self->{_ctrl_sock} = | 
| 3490 | 0 | 0 |  |  |  | 0 | new IO::Socket::INET (@args) | 
| 3491 |  |  |  |  |  |  | or die "socket: $!"; | 
| 3492 |  |  |  |  |  |  | } | 
| 3493 |  |  |  |  |  |  |  | 
| 3494 |  |  |  |  |  |  | # Set TCP keepalive? | 
| 3495 | 0 | 0 |  |  |  | 0 | if (defined $self->config ("tcp keepalive")) | 
| 3496 |  |  |  |  |  |  | { | 
| 3497 | 0 | 0 |  |  |  | 0 | $self->{_ctrl_sock}->sockopt (SO_KEEPALIVE, 1) | 
| 3498 |  |  |  |  |  |  | or warn "setsockopt: SO_KEEPALIVE: $!"; | 
| 3499 |  |  |  |  |  |  | } | 
| 3500 |  |  |  |  |  |  |  | 
| 3501 |  |  |  |  |  |  | # Initialize the children hash ref for max clients enforcement | 
| 3502 | 0 |  |  |  |  | 0 | $self->{_children} = {}; | 
| 3503 |  |  |  |  |  |  |  | 
| 3504 | 0 |  |  |  |  | 0 | $self->post_bind_hook; | 
| 3505 |  |  |  |  |  |  |  | 
| 3506 |  |  |  |  |  |  | # Accept new connections and fork off new process to handle it. | 
| 3507 | 0 |  |  |  |  | 0 | for (;;) | 
| 3508 |  |  |  |  |  |  | { | 
| 3509 |  |  |  |  |  |  | # Possibly rotate the log files to a new name. | 
| 3510 | 0 |  |  |  |  | 0 | $self->_rotate_log ; | 
| 3511 |  |  |  |  |  |  |  | 
| 3512 | 0 |  |  |  |  | 0 | $self->pre_accept_hook; | 
| 3513 | 0 | 0 |  |  |  | 0 | if (!$self->{_ctrl_sock}->opened) | 
| 3514 |  |  |  |  |  |  | { | 
| 3515 | 0 |  |  |  |  | 0 | die "control socket crashed somehow"; | 
| 3516 |  |  |  |  |  |  | } | 
| 3517 |  |  |  |  |  |  |  | 
| 3518 |  |  |  |  |  |  | # ACCEPT may be undefined if, for example, the TCP-level 3-way | 
| 3519 |  |  |  |  |  |  | # handshake is not completed. If this happens, all we really want | 
| 3520 |  |  |  |  |  |  | # to do is to retry the accept, not die. Thanks to | 
| 3521 |  |  |  |  |  |  | # Rob Brown for pointing this one out :-) | 
| 3522 |  |  |  |  |  |  |  | 
| 3523 |  |  |  |  |  |  | # Because we are now handling signals synchronously, and because | 
| 3524 |  |  |  |  |  |  | # signals are restartable, we want to periodically check for | 
| 3525 |  |  |  |  |  |  | # signals. Thus the following code swaps between blocking on the | 
| 3526 |  |  |  |  |  |  | # accept for 3 seconds and checking signals. The load on the | 
| 3527 |  |  |  |  |  |  | # processor is insignificant (if you're worried about the load, | 
| 3528 |  |  |  |  |  |  | # perhaps you should be using inetd?). | 
| 3529 |  |  |  |  |  |  |  | 
| 3530 | 0 |  |  |  |  | 0 | my $sock; | 
| 3531 |  |  |  |  |  |  |  | 
| 3532 | 0 |  |  |  |  | 0 | my $selector = new IO::Select; | 
| 3533 | 0 |  |  |  |  | 0 | $selector->add ($self->{_ctrl_sock}); | 
| 3534 |  |  |  |  |  |  |  | 
| 3535 | 0 |  |  |  |  | 0 | until (defined $sock) | 
| 3536 |  |  |  |  |  |  | { | 
| 3537 | 0 |  |  |  |  | 0 | my @ready = $selector->can_read (3); | 
| 3538 |  |  |  |  |  |  |  | 
| 3539 | 0 |  |  |  |  | 0 | $self->_check_signals; | 
| 3540 |  |  |  |  |  |  |  | 
| 3541 | 0 | 0 |  |  |  | 0 | if (@ready > 0) | 
| 3542 |  |  |  |  |  |  | { | 
| 3543 | 0 |  |  |  |  | 0 | $sock = $self->{_ctrl_sock}->accept; | 
| 3544 | 0 | 0 |  |  |  | 0 | warn "accept: $!" unless defined $sock; | 
| 3545 |  |  |  |  |  |  | } | 
| 3546 |  |  |  |  |  |  | } | 
| 3547 |  |  |  |  |  |  |  | 
| 3548 |  |  |  |  |  |  | # Possibly rotate the log files to a new name. | 
| 3549 | 0 |  |  |  |  | 0 | $self->_rotate_log ; | 
| 3550 |  |  |  |  |  |  |  | 
| 3551 | 0 | 0 |  |  |  | 0 | if ($self->concurrent_connections >= $self->{_max_clients}) | 
| 3552 |  |  |  |  |  |  | { | 
| 3553 |  |  |  |  |  |  | $sock->print ("500 ". | 
| 3554 | 0 |  |  |  |  | 0 | $self->_percent_substitutions ($self->{_max_clients_message}). | 
| 3555 |  |  |  |  |  |  | "\r\n"); | 
| 3556 | 0 |  |  |  |  | 0 | $sock->close; | 
| 3557 | 0 |  |  |  |  | 0 | warn "Max connections $self->{_max_clients} reached!"; | 
| 3558 | 0 |  |  |  |  | 0 | $self->_log_line ("[Max connections $self->{_max_clients} reached]"); | 
| 3559 | 0 |  |  |  |  | 0 | next; | 
| 3560 |  |  |  |  |  |  | } | 
| 3561 |  |  |  |  |  |  |  | 
| 3562 |  |  |  |  |  |  | # Fork off a process to handle this connection. | 
| 3563 | 0 |  |  |  |  | 0 | my $pid = fork; | 
| 3564 | 0 | 0 |  |  |  | 0 | if (defined $pid) | 
| 3565 |  |  |  |  |  |  | { | 
| 3566 | 0 | 0 |  |  |  | 0 | if ($pid == 0)		# Child process. | 
| 3567 |  |  |  |  |  |  | { | 
| 3568 |  |  |  |  |  |  | $self->log ("info", "starting child process") | 
| 3569 | 0 | 0 |  |  |  | 0 | if $self->{debug}; | 
| 3570 |  |  |  |  |  |  |  | 
| 3571 |  |  |  |  |  |  | # Don't handle SIGCHLD in the child process, in case the | 
| 3572 |  |  |  |  |  |  | # personality tries to launch subprocesses. | 
| 3573 | 0 |  |  |  |  | 0 | $SIG{CHLD} = "DEFAULT"; | 
| 3574 |  |  |  |  |  |  |  | 
| 3575 |  |  |  |  |  |  | # SIGHUP in the child process exits immediately. | 
| 3576 |  |  |  |  |  |  | $SIG{HUP} = sub { | 
| 3577 | 0 |  |  | 0 |  | 0 | $self->log ("info", "exiting on HUP signal"); | 
| 3578 | 0 |  |  |  |  | 0 | exit; | 
| 3579 | 0 |  |  |  |  | 0 | }; | 
| 3580 |  |  |  |  |  |  |  | 
| 3581 |  |  |  |  |  |  | $SIG{TERM} = sub { | 
| 3582 | 0 |  |  | 0 |  | 0 | $self->log ("info", "exiting on TERM signal"); | 
| 3583 | 0 |  |  |  |  | 0 | $self->reply (421, "Manual shutdown from server"); | 
| 3584 | 0 |  |  |  |  | 0 | $self->_log_line ("[TERM RECEIVED]"); | 
| 3585 | 0 |  |  |  |  | 0 | exit; | 
| 3586 | 0 |  |  |  |  | 0 | }; | 
| 3587 |  |  |  |  |  |  |  | 
| 3588 |  |  |  |  |  |  | # Wipe the hash within the child process to save memory | 
| 3589 | 0 |  |  |  |  | 0 | $self->{_children} = $self->concurrent_connections; | 
| 3590 |  |  |  |  |  |  |  | 
| 3591 |  |  |  |  |  |  | # Shutdown accepting file descriptor to allow successful | 
| 3592 |  |  |  |  |  |  | # port bind() in case of a future daemon restart | 
| 3593 | 0 |  |  |  |  | 0 | $self->{_ctrl_sock}->close; | 
| 3594 |  |  |  |  |  |  |  | 
| 3595 |  |  |  |  |  |  | # Duplicate the socket so it looks like we were called | 
| 3596 |  |  |  |  |  |  | # from inetd. | 
| 3597 | 0 |  |  |  |  | 0 | dup2 ($sock->fileno, 0); | 
| 3598 | 0 |  |  |  |  | 0 | dup2 ($sock->fileno, 1); | 
| 3599 |  |  |  |  |  |  |  | 
| 3600 |  |  |  |  |  |  | # Return to the main process to handle the rest of | 
| 3601 |  |  |  |  |  |  | # the connection. | 
| 3602 | 0 |  |  |  |  | 0 | return; | 
| 3603 |  |  |  |  |  |  | }			# End of child process. | 
| 3604 |  |  |  |  |  |  | } | 
| 3605 |  |  |  |  |  |  | else			# Error during fork(2). | 
| 3606 |  |  |  |  |  |  | { | 
| 3607 | 0 |  |  |  |  | 0 | warn "fork: $!"; | 
| 3608 | 0 |  |  |  |  | 0 | sleep 5;		# Back off in case system is overloaded. | 
| 3609 |  |  |  |  |  |  | } | 
| 3610 |  |  |  |  |  |  |  | 
| 3611 |  |  |  |  |  |  | # A child has been successfully spawned. | 
| 3612 |  |  |  |  |  |  | # So don't forget the kid's birthday! | 
| 3613 | 0 |  |  |  |  | 0 | $self->{_children}->{$pid} = time; | 
| 3614 |  |  |  |  |  |  | }				# End of for (;;) loop in ftpd parent process. | 
| 3615 |  |  |  |  |  |  | } | 
| 3616 |  |  |  |  |  |  |  | 
| 3617 |  |  |  |  |  |  | sub concurrent_connections | 
| 3618 |  |  |  |  |  |  | { | 
| 3619 | 41 |  |  | 41 | 0 | 112 | my $self = shift; | 
| 3620 |  |  |  |  |  |  |  | 
| 3621 | 41 | 50 |  |  |  | 209 | if (exists $self->{_children}) | 
| 3622 |  |  |  |  |  |  | { | 
| 3623 | 0 | 0 |  |  |  | 0 | if (ref $self->{_children}) | 
| 3624 |  |  |  |  |  |  | { | 
| 3625 |  |  |  |  |  |  | # Main Parent Server (exactly accurate) | 
| 3626 | 0 |  |  |  |  | 0 | return scalar keys %{$self->{_children}}; | 
|  | 0 |  |  |  |  | 0 |  | 
| 3627 |  |  |  |  |  |  | } | 
| 3628 |  |  |  |  |  |  | else | 
| 3629 |  |  |  |  |  |  | { | 
| 3630 |  |  |  |  |  |  | # Child Process (slightly outdated count) | 
| 3631 | 0 |  |  |  |  | 0 | return $self->{_children}; | 
| 3632 |  |  |  |  |  |  | } | 
| 3633 |  |  |  |  |  |  | } | 
| 3634 |  |  |  |  |  |  | else | 
| 3635 |  |  |  |  |  |  | { | 
| 3636 |  |  |  |  |  |  | # Not running as a daemon (eg. running from inetd). We don't | 
| 3637 |  |  |  |  |  |  | # know the number of connections, but it's not likely to be | 
| 3638 |  |  |  |  |  |  | # high, so just return 1. | 
| 3639 | 41 |  |  |  |  | 485 | return 1; | 
| 3640 |  |  |  |  |  |  | } | 
| 3641 |  |  |  |  |  |  | } | 
| 3642 |  |  |  |  |  |  |  | 
| 3643 |  |  |  |  |  |  | # Open configuration file and prepare to read configuration. | 
| 3644 |  |  |  |  |  |  |  | 
| 3645 |  |  |  |  |  |  | sub _open_config_file | 
| 3646 |  |  |  |  |  |  | { | 
| 3647 | 47 |  |  | 47 |  | 155 | my $self = shift; | 
| 3648 | 47 |  |  |  |  | 139 | my $config_file = shift; | 
| 3649 |  |  |  |  |  |  |  | 
| 3650 | 47 |  |  |  |  | 965 | my $config = new IO::File "<$config_file"; | 
| 3651 | 47 | 50 |  |  |  | 7307 | unless ($config) | 
| 3652 |  |  |  |  |  |  | { | 
| 3653 | 0 |  |  |  |  | 0 | die "cannot open configuration file: $config_file: $!"; | 
| 3654 |  |  |  |  |  |  | } | 
| 3655 |  |  |  |  |  |  |  | 
| 3656 | 47 |  |  |  |  | 131 | my $lineno = 0; | 
| 3657 | 47 |  |  |  |  | 127 | my $sitename; | 
| 3658 |  |  |  |  |  |  |  | 
| 3659 |  |  |  |  |  |  | # Read in the configuration options from the file. | 
| 3660 | 47 |  |  |  |  | 4000 | while (defined ($_ = $config->getline)) | 
| 3661 |  |  |  |  |  |  | { | 
| 3662 | 38 |  |  |  |  | 951 | $lineno++; | 
| 3663 |  |  |  |  |  |  |  | 
| 3664 |  |  |  |  |  |  | # Remove trailing \n and \r. | 
| 3665 | 38 |  |  |  |  | 154 | s/[\n\r]+$//; | 
| 3666 |  |  |  |  |  |  |  | 
| 3667 |  |  |  |  |  |  | # Ignore blank lines and comments. | 
| 3668 | 38 | 100 |  |  |  | 138 | next if /^\s*\#/; | 
| 3669 | 37 | 50 |  |  |  | 94 | next if /^\s*$/; | 
| 3670 |  |  |  |  |  |  |  | 
| 3671 |  |  |  |  |  |  | # More lines? | 
| 3672 | 37 |  |  |  |  | 84 | while (/\\$/) | 
| 3673 |  |  |  |  |  |  | { | 
| 3674 | 2 |  |  |  |  | 6 | $_ =~ s/\\$//; | 
| 3675 | 2 |  |  |  |  | 56 | my $nextline = $config->getline; | 
| 3676 | 2 |  |  |  |  | 40 | $nextline =~ s/^\s+//; | 
| 3677 | 2 |  |  |  |  | 6 | $nextline =~ s/[\n\r]+$//; | 
| 3678 | 2 |  |  |  |  | 5 | $_ .= $nextline; | 
| 3679 | 2 |  |  |  |  | 6 | $lineno++; | 
| 3680 |  |  |  |  |  |  | } | 
| 3681 |  |  |  |  |  |  |  | 
| 3682 |  |  |  |  |  |  | # Special treatment:  files. | 
| 3683 | 37 | 100 |  |  |  | 77 | if (/^\s*\s*$/i) | 
| 3684 |  |  |  |  |  |  | { | 
| 3685 | 3 | 50 |  |  |  | 7 | if ($sitename) | 
| 3686 |  |  |  |  |  |  | { | 
| 3687 | 0 |  |  |  |  | 0 | die "$config_file:$lineno: cannot use  inside a  section. It will not do what you expect. See the Net::FTPServer(3) manual page for information."; | 
| 3688 |  |  |  |  |  |  | } | 
| 3689 |  |  |  |  |  |  |  | 
| 3690 | 3 |  |  |  |  | 18 | $self->_open_config_file ($1); | 
| 3691 | 3 |  |  |  |  | 111 | next; | 
| 3692 |  |  |  |  |  |  | } | 
| 3693 |  |  |  |  |  |  |  | 
| 3694 |  |  |  |  |  |  | # Special treatment:  files. | 
| 3695 | 34 | 100 |  |  |  | 62 | if (/^\s*\s*$/i) | 
| 3696 |  |  |  |  |  |  | { | 
| 3697 | 1 | 50 |  |  |  | 3 | if ($sitename) | 
| 3698 |  |  |  |  |  |  | { | 
| 3699 | 0 |  |  |  |  | 0 | die "$config_file:$lineno: cannot use  inside a  section. It will not do what you expect. See the Net::FTPServer(3) manual page for information."; | 
| 3700 |  |  |  |  |  |  | } | 
| 3701 |  |  |  |  |  |  |  | 
| 3702 | 1 |  |  |  |  | 85 | my @files = sort glob $1; | 
| 3703 | 1 |  |  |  |  | 5 | foreach (@files) | 
| 3704 |  |  |  |  |  |  | { | 
| 3705 | 3 |  |  |  |  | 60 | $self->_open_config_file ($_); | 
| 3706 |  |  |  |  |  |  | } | 
| 3707 | 1 |  |  |  |  | 40 | next; | 
| 3708 |  |  |  |  |  |  | } | 
| 3709 |  |  |  |  |  |  |  | 
| 3710 |  |  |  |  |  |  | # Special treatment:  sections. | 
| 3711 | 33 | 100 |  |  |  | 68 | if (/^\s*\s*$/i) | 
| 3712 |  |  |  |  |  |  | { | 
| 3713 | 1 | 50 |  |  |  | 3 | if ($sitename) | 
| 3714 |  |  |  |  |  |  | { | 
| 3715 | 0 |  |  |  |  | 0 | die "$config_file:$lineno: unfinished  section"; | 
| 3716 |  |  |  |  |  |  | } | 
| 3717 |  |  |  |  |  |  |  | 
| 3718 | 1 |  |  |  |  | 2 | $sitename = $1; | 
| 3719 | 1 |  |  |  |  | 15 | next; | 
| 3720 |  |  |  |  |  |  | } | 
| 3721 |  |  |  |  |  |  |  | 
| 3722 | 32 | 100 |  |  |  | 79 | if (/^\s*<\/Host>\s*$/i) | 
| 3723 |  |  |  |  |  |  | { | 
| 3724 | 1 | 50 |  |  |  | 4 | unless ($sitename) | 
| 3725 |  |  |  |  |  |  | { | 
| 3726 | 0 |  |  |  |  | 0 | die "$config_file:$lineno: unmatched "; | 
| 3727 |  |  |  |  |  |  | } | 
| 3728 |  |  |  |  |  |  |  | 
| 3729 | 1 |  |  |  |  | 2 | $sitename = undef; | 
| 3730 | 1 |  |  |  |  | 15 | next; | 
| 3731 |  |  |  |  |  |  | } | 
| 3732 |  |  |  |  |  |  |  | 
| 3733 |  |  |  |  |  |  | # Special treatment:  sections. | 
| 3734 | 31 | 100 |  |  |  | 69 | if (/^\s*\s*$/i) | 
| 3735 |  |  |  |  |  |  | { | 
| 3736 | 1 | 50 |  |  |  | 3 | if ($sitename) | 
| 3737 |  |  |  |  |  |  | { | 
| 3738 | 0 |  |  |  |  | 0 | die "$config_file:$lineno: cannot use  inside a  section. It will not do what you expect. See the Net::FTPServer(3) manual page for information on the %host_config variable."; | 
| 3739 |  |  |  |  |  |  | } | 
| 3740 |  |  |  |  |  |  |  | 
| 3741 |  |  |  |  |  |  | # Suck in lines verbatim until we reach the end of this section. | 
| 3742 | 1 |  |  |  |  | 2 | my $perl_code = ""; | 
| 3743 |  |  |  |  |  |  |  | 
| 3744 | 1 |  |  |  |  | 17 | while (defined ($_ = $config->getline)) | 
| 3745 |  |  |  |  |  |  | { | 
| 3746 | 5 |  |  |  |  | 87 | $lineno++; | 
| 3747 | 5 | 100 |  |  |  | 13 | last if /^\s*<\/Perl>\s*$/i; | 
| 3748 | 4 |  |  |  |  | 52 | $perl_code .= $_; | 
| 3749 |  |  |  |  |  |  | } | 
| 3750 |  |  |  |  |  |  |  | 
| 3751 | 1 | 50 |  |  |  | 2 | unless ($_) | 
| 3752 |  |  |  |  |  |  | { | 
| 3753 | 0 |  |  |  |  | 0 | die "$config_file:$lineno: unfinished  section"; | 
| 3754 |  |  |  |  |  |  | } | 
| 3755 |  |  |  |  |  |  |  | 
| 3756 |  |  |  |  |  |  | # Untaint this code: it comes from a trusted source, namely | 
| 3757 |  |  |  |  |  |  | # the configuration file. | 
| 3758 | 1 |  |  |  |  | 2 | $perl_code =~ /(.*)/s; | 
| 3759 | 1 |  |  |  |  | 3 | $perl_code = $1; | 
| 3760 |  |  |  |  |  |  |  | 
| 3761 |  |  |  |  |  |  | #	    warn "executing perl code:\n$perl_code\n"; | 
| 3762 |  |  |  |  |  |  |  | 
| 3763 |  |  |  |  |  |  | # Run it. It will write into local variables %config and | 
| 3764 |  |  |  |  |  |  | # %host_config. | 
| 3765 | 1 |  |  |  |  | 2 | my %config; | 
| 3766 |  |  |  |  |  |  | my %host_config; | 
| 3767 |  |  |  |  |  |  |  | 
| 3768 | 1 |  |  |  |  | 75 | eval $perl_code; | 
| 3769 | 1 | 50 |  |  |  | 5 | if ($@) | 
| 3770 |  |  |  |  |  |  | { | 
| 3771 | 0 |  |  |  |  | 0 | die "$config_file:$lineno: $@"; | 
| 3772 |  |  |  |  |  |  | } | 
| 3773 |  |  |  |  |  |  |  | 
| 3774 |  |  |  |  |  |  | # Examine what it's written into %config and %host_config | 
| 3775 |  |  |  |  |  |  | # and add those to the configuration. | 
| 3776 | 1 |  |  |  |  | 4 | foreach (keys %config) | 
| 3777 |  |  |  |  |  |  | { | 
| 3778 | 2 |  |  |  |  | 3 | my $value = $config{$_}; | 
| 3779 |  |  |  |  |  |  |  | 
| 3780 | 2 | 100 |  |  |  | 5 | unless (ref $value) { | 
| 3781 | 1 |  |  |  |  | 2 | $self->_set_config ($_, $value, | 
| 3782 |  |  |  |  |  |  | file => $config_file, line => $lineno); | 
| 3783 |  |  |  |  |  |  | } else { | 
| 3784 | 1 |  |  |  |  | 2 | foreach my $v (@$value) { | 
| 3785 | 2 |  |  |  |  | 5 | $self->_set_config ($_, $v, | 
| 3786 |  |  |  |  |  |  | file => $config_file, line =>$lineno); | 
| 3787 |  |  |  |  |  |  | } | 
| 3788 |  |  |  |  |  |  | } | 
| 3789 |  |  |  |  |  |  | } | 
| 3790 |  |  |  |  |  |  |  | 
| 3791 | 1 |  |  |  |  | 3 | my $host; | 
| 3792 | 1 |  |  |  |  | 3 | foreach $host (keys %host_config) | 
| 3793 |  |  |  |  |  |  | { | 
| 3794 | 1 |  |  |  |  | 2 | foreach (keys %{$host_config{$host}}) | 
|  | 1 |  |  |  |  | 3 |  | 
| 3795 |  |  |  |  |  |  | { | 
| 3796 | 1 |  |  |  |  | 2 | my $value = $host_config{$host}{$_}; | 
| 3797 |  |  |  |  |  |  |  | 
| 3798 | 1 | 50 |  |  |  | 3 | unless (ref $value) { | 
| 3799 | 1 |  |  |  |  | 3 | $self->_set_config ($_, $value, | 
| 3800 |  |  |  |  |  |  | sitename => $host, | 
| 3801 |  |  |  |  |  |  | file => $config_file, | 
| 3802 |  |  |  |  |  |  | line => $lineno); | 
| 3803 |  |  |  |  |  |  | } else { | 
| 3804 | 0 |  |  |  |  | 0 | foreach my $v (@$value) { | 
| 3805 | 0 |  |  |  |  | 0 | $self->_set_config ($_, $v, | 
| 3806 |  |  |  |  |  |  | sitename => $host, | 
| 3807 |  |  |  |  |  |  | file => $config_file, | 
| 3808 |  |  |  |  |  |  | line => $lineno); | 
| 3809 |  |  |  |  |  |  | } | 
| 3810 |  |  |  |  |  |  | } | 
| 3811 |  |  |  |  |  |  | } | 
| 3812 |  |  |  |  |  |  | } | 
| 3813 |  |  |  |  |  |  |  | 
| 3814 | 1 |  |  |  |  | 20 | next; | 
| 3815 |  |  |  |  |  |  | } | 
| 3816 |  |  |  |  |  |  |  | 
| 3817 | 30 | 50 |  |  |  | 62 | if (/^\s*<\/Perl>\s*$/i) | 
| 3818 |  |  |  |  |  |  | { | 
| 3819 | 0 |  |  |  |  | 0 | die "$config_file:$lineno: unmatched "; | 
| 3820 |  |  |  |  |  |  | } | 
| 3821 |  |  |  |  |  |  |  | 
| 3822 |  |  |  |  |  |  | # Split the line on the first : character. | 
| 3823 | 30 | 50 |  |  |  | 96 | unless (/^(.*?):(.*)$/) | 
| 3824 |  |  |  |  |  |  | { | 
| 3825 | 0 |  |  |  |  | 0 | die "$config_file:$lineno: syntax error in configuration file"; | 
| 3826 |  |  |  |  |  |  | } | 
| 3827 |  |  |  |  |  |  |  | 
| 3828 | 30 |  |  |  |  | 63 | my $key = $1; | 
| 3829 | 30 |  |  |  |  | 50 | my $value = $2; | 
| 3830 |  |  |  |  |  |  |  | 
| 3831 | 30 |  |  |  |  | 53 | $key =~ s/^\s+//; | 
| 3832 | 30 |  |  |  |  | 62 | $key =~ s/\s+$//; | 
| 3833 |  |  |  |  |  |  |  | 
| 3834 | 30 |  |  |  |  | 68 | $value =~ s/^\s+//; | 
| 3835 | 30 |  |  |  |  | 60 | $value =~ s/\s+$//; | 
| 3836 |  |  |  |  |  |  |  | 
| 3837 | 30 |  |  |  |  | 103 | $self->_set_config ($key, $value, | 
| 3838 |  |  |  |  |  |  | sitename => $sitename, | 
| 3839 |  |  |  |  |  |  | file => $config_file, | 
| 3840 |  |  |  |  |  |  | line => $lineno); | 
| 3841 |  |  |  |  |  |  | } | 
| 3842 |  |  |  |  |  |  | } | 
| 3843 |  |  |  |  |  |  |  | 
| 3844 |  |  |  |  |  |  | sub _set_config | 
| 3845 |  |  |  |  |  |  | { | 
| 3846 | 75 |  |  | 75 |  | 138 | my $self = shift; | 
| 3847 | 75 |  |  |  |  | 129 | my $key = shift; | 
| 3848 | 75 |  |  |  |  | 127 | my $value = shift; | 
| 3849 | 75 |  |  |  |  | 272 | my %params = @_; | 
| 3850 |  |  |  |  |  |  |  | 
| 3851 | 75 |  |  |  |  | 148 | my $sitename = $params{sitename}; | 
| 3852 | 75 |  | 100 |  |  | 402 | my $config_file = $params{file} || "no file"; | 
| 3853 | 75 |  | 100 |  |  | 251 | my $lineno = $params{line} || "0"; | 
| 3854 | 75 |  |  |  |  | 125 | my $splat = $params{splat}; | 
| 3855 |  |  |  |  |  |  |  | 
| 3856 |  |  |  |  |  |  | # Convert the key to standard form so that small errors in the | 
| 3857 |  |  |  |  |  |  | # FTP config file won't matter too much. | 
| 3858 | 75 |  |  |  |  | 154 | $key = lc ($key); | 
| 3859 | 75 |  |  |  |  | 172 | $key =~ tr/ / /s; | 
| 3860 |  |  |  |  |  |  |  | 
| 3861 |  |  |  |  |  |  | # If the key is ``ip:'' then we treat it specially - adding it | 
| 3862 |  |  |  |  |  |  | # to a hash from IP addresses to sites. | 
| 3863 | 75 | 50 |  |  |  | 186 | if ($key eq "ip") | 
| 3864 |  |  |  |  |  |  | { | 
| 3865 | 0 | 0 |  |  |  | 0 | unless ($sitename) | 
| 3866 |  |  |  |  |  |  | { | 
| 3867 | 0 |  |  |  |  | 0 | print STDERR "$config_file:$lineno: ``ip:'' must only appear inside a  section. See the Net::FTPServer(3) manual page for more information.\n"; | 
| 3868 | 0 |  |  |  |  | 0 | exit 1; | 
| 3869 |  |  |  |  |  |  | } | 
| 3870 |  |  |  |  |  |  |  | 
| 3871 | 0 |  |  |  |  | 0 | $self->{_config_ip_host}{$value} = $sitename; | 
| 3872 |  |  |  |  |  |  | } | 
| 3873 |  |  |  |  |  |  |  | 
| 3874 |  |  |  |  |  |  | # Prefix the sitename, if defined. | 
| 3875 | 75 | 100 |  |  |  | 167 | $key = "$sitename:$key" if $sitename; | 
| 3876 |  |  |  |  |  |  |  | 
| 3877 |  |  |  |  |  |  | #    warn "configuration ($key, $value)"; | 
| 3878 |  |  |  |  |  |  |  | 
| 3879 |  |  |  |  |  |  | # Save this. | 
| 3880 | 75 | 100 | 100 |  |  | 349 | $self->{_config}{$key} = [] if $splat || ! exists $self->{_config}{$key}; | 
| 3881 | 75 |  |  |  |  | 136 | push @{$self->{_config}{$key}}, $value; | 
|  | 75 |  |  |  |  | 799 |  | 
| 3882 |  |  |  |  |  |  | } | 
| 3883 |  |  |  |  |  |  |  | 
| 3884 |  |  |  |  |  |  | # Before printing something received from the user to syslog, escape | 
| 3885 |  |  |  |  |  |  | # any strange characters using this function. | 
| 3886 |  |  |  |  |  |  |  | 
| 3887 |  |  |  |  |  |  | sub _escape | 
| 3888 |  |  |  |  |  |  | { | 
| 3889 | 604 |  |  | 604 |  | 1237 | local $_ = shift; | 
| 3890 | 604 |  |  |  |  | 1287 | s/([^ -~])/sprintf ("\\x%02x", ord ($1))/ge; | 
|  | 0 |  |  |  |  | 0 |  | 
| 3891 | 604 |  |  |  |  | 1832 | $_; | 
| 3892 |  |  |  |  |  |  | } | 
| 3893 |  |  |  |  |  |  |  | 
| 3894 |  |  |  |  |  |  | =item $regex = $ftps->wildcard_to_regex ($wildcard) | 
| 3895 |  |  |  |  |  |  |  | 
| 3896 |  |  |  |  |  |  | This is a general library function shared between many of | 
| 3897 |  |  |  |  |  |  | the back-end database personalities. It converts a general | 
| 3898 |  |  |  |  |  |  | wildcard (eg. *.c) into a regular expression (eg. ^.*\.c$ ). | 
| 3899 |  |  |  |  |  |  |  | 
| 3900 |  |  |  |  |  |  | Thanks to: Terrence Monroe Brannon Eterrence.brannon@oracle.comE. | 
| 3901 |  |  |  |  |  |  |  | 
| 3902 |  |  |  |  |  |  | =cut | 
| 3903 |  |  |  |  |  |  |  | 
| 3904 |  |  |  |  |  |  | sub wildcard_to_regex | 
| 3905 |  |  |  |  |  |  | { | 
| 3906 | 2 |  |  | 2 | 1 | 5 | my $self = shift; | 
| 3907 | 2 |  |  |  |  | 3 | my $wildcard = shift; | 
| 3908 |  |  |  |  |  |  |  | 
| 3909 | 2 |  |  |  |  | 7 | $wildcard =~ s,([^?*a-zA-Z0-9]),\\$1,g; # Escape punctuation. | 
| 3910 | 2 |  |  |  |  | 13 | $wildcard =~ s,\*,.*,g; # Turn * into .* | 
| 3911 | 2 |  |  |  |  | 4 | $wildcard =~ s,\?,.,g;  # Turn ? into . | 
| 3912 | 2 |  |  |  |  | 6 | $wildcard = "^$wildcard\$"; # Bracket it. | 
| 3913 |  |  |  |  |  |  |  | 
| 3914 | 2 |  |  |  |  | 5 | $wildcard; | 
| 3915 |  |  |  |  |  |  | } | 
| 3916 |  |  |  |  |  |  |  | 
| 3917 |  |  |  |  |  |  | =item $regex = $ftps->wildcard_to_sql_like ($wildcard) | 
| 3918 |  |  |  |  |  |  |  | 
| 3919 |  |  |  |  |  |  | This is a general library function shared between many of | 
| 3920 |  |  |  |  |  |  | the back-end database personalities. It converts a general | 
| 3921 |  |  |  |  |  |  | wildcard (eg. *.c) into the strange wildcardish format | 
| 3922 |  |  |  |  |  |  | used by SQL LIKE operator (eg. %.c). | 
| 3923 |  |  |  |  |  |  |  | 
| 3924 |  |  |  |  |  |  | =cut | 
| 3925 |  |  |  |  |  |  |  | 
| 3926 |  |  |  |  |  |  | sub wildcard_to_sql_like | 
| 3927 |  |  |  |  |  |  | { | 
| 3928 | 0 |  |  | 0 | 1 | 0 | my $self = shift; | 
| 3929 | 0 |  |  |  |  | 0 | my $wildcard = shift; | 
| 3930 |  |  |  |  |  |  |  | 
| 3931 | 0 |  |  |  |  | 0 | $wildcard =~ s/%/\\%/g;     # Escape any existing % and _. | 
| 3932 | 0 |  |  |  |  | 0 | $wildcard =~ s/_/\\_/g; | 
| 3933 | 0 |  |  |  |  | 0 | $wildcard =~ tr/*?/%_/;     # Translate to wierdo format. | 
| 3934 |  |  |  |  |  |  |  | 
| 3935 | 0 |  |  |  |  | 0 | $wildcard; | 
| 3936 |  |  |  |  |  |  | } | 
| 3937 |  |  |  |  |  |  |  | 
| 3938 |  |  |  |  |  |  | =item $ftps->reply ($code, $line, [$line, ...]) | 
| 3939 |  |  |  |  |  |  |  | 
| 3940 |  |  |  |  |  |  | This function sends a standard single line or multi-line FTP | 
| 3941 |  |  |  |  |  |  | server reply to the client. The C<$code> should be one of the | 
| 3942 |  |  |  |  |  |  | standard reply codes listed in RFC 959. The one or more | 
| 3943 |  |  |  |  |  |  | C<$line> arguments are the (free text) of the reply. Do | 
| 3944 |  |  |  |  |  |  | I include carriage returns at the end of each C<$line>. | 
| 3945 |  |  |  |  |  |  | This function adds the correct line ending format as specified | 
| 3946 |  |  |  |  |  |  | in the RFC. | 
| 3947 |  |  |  |  |  |  |  | 
| 3948 |  |  |  |  |  |  | =cut | 
| 3949 |  |  |  |  |  |  |  | 
| 3950 |  |  |  |  |  |  | sub reply | 
| 3951 |  |  |  |  |  |  | { | 
| 3952 | 466 |  |  | 466 | 1 | 821 | my $self = shift; | 
| 3953 |  |  |  |  |  |  |  | 
| 3954 | 466 |  |  |  |  | 770 | my $code = shift; | 
| 3955 | 466 | 50 |  |  |  | 2448 | die "response code $code is not in RFC 959 format" | 
| 3956 |  |  |  |  |  |  | unless $code =~ /^[1-5][0-5][0-9]$/; | 
| 3957 |  |  |  |  |  |  |  | 
| 3958 | 466 | 50 |  |  |  | 1138 | die "reply must contain one or more lines of text" | 
| 3959 |  |  |  |  |  |  | unless @_ > 0; | 
| 3960 |  |  |  |  |  |  |  | 
| 3961 | 466 | 100 |  |  |  | 973 | if (@_ == 1)		# Single-line response. | 
| 3962 |  |  |  |  |  |  | { | 
| 3963 | 459 |  |  |  |  | 11108 | print $code, " ", $_[0], "\r\n"; | 
| 3964 |  |  |  |  |  |  | } | 
| 3965 |  |  |  |  |  |  | else			# Multi-line response. | 
| 3966 |  |  |  |  |  |  | { | 
| 3967 | 7 |  |  |  |  | 27 | for (my $i = 0; $i < @_-1; ++$i) | 
| 3968 |  |  |  |  |  |  | { | 
| 3969 | 39 |  |  |  |  | 306 | print $code, "-", $_[$i], "\r\n"; | 
| 3970 |  |  |  |  |  |  | } | 
| 3971 | 7 |  |  |  |  | 34 | print $code, " ", $_[@_-1], "\r\n"; | 
| 3972 |  |  |  |  |  |  | } | 
| 3973 |  |  |  |  |  |  |  | 
| 3974 | 466 | 50 |  |  |  | 3002 | $self->log ("info", "reply: $code") if $self->{debug}; | 
| 3975 |  |  |  |  |  |  | } | 
| 3976 |  |  |  |  |  |  |  | 
| 3977 |  |  |  |  |  |  | =item $ftps->log ($level, $message, ...); | 
| 3978 |  |  |  |  |  |  |  | 
| 3979 |  |  |  |  |  |  | This function is identical to the normal C function | 
| 3980 |  |  |  |  |  |  | to be found in C. However, it only uses syslog | 
| 3981 |  |  |  |  |  |  | if the C configuration option is set to true. | 
| 3982 |  |  |  |  |  |  |  | 
| 3983 |  |  |  |  |  |  | Use this function instead of calling C directly. | 
| 3984 |  |  |  |  |  |  |  | 
| 3985 |  |  |  |  |  |  | =cut | 
| 3986 |  |  |  |  |  |  |  | 
| 3987 |  |  |  |  |  |  | sub log | 
| 3988 |  |  |  |  |  |  | { | 
| 3989 | 1015 |  |  | 1015 | 1 | 1757 | my $self = shift; | 
| 3990 |  |  |  |  |  |  |  | 
| 3991 | 1015 | 50 |  |  |  | 3671 | Sys::Syslog::syslog @_ if $self->{_enable_syslog}; | 
| 3992 |  |  |  |  |  |  | } | 
| 3993 |  |  |  |  |  |  |  | 
| 3994 |  |  |  |  |  |  | =pod | 
| 3995 |  |  |  |  |  |  |  | 
| 3996 |  |  |  |  |  |  | =item $ftps->config ($name); | 
| 3997 |  |  |  |  |  |  |  | 
| 3998 |  |  |  |  |  |  | Read configuration option C<$name> from the configuration file. | 
| 3999 |  |  |  |  |  |  |  | 
| 4000 |  |  |  |  |  |  | =cut | 
| 4001 |  |  |  |  |  |  |  | 
| 4002 |  |  |  |  |  |  | sub config | 
| 4003 |  |  |  |  |  |  | { | 
| 4004 | 1969 |  |  | 1969 | 1 | 3954 | my $self = shift; | 
| 4005 | 1969 |  |  |  |  | 3237 | my $key = shift; | 
| 4006 |  |  |  |  |  |  |  | 
| 4007 |  |  |  |  |  |  | # Convert the key to standard form. | 
| 4008 | 1969 |  |  |  |  | 3275 | $key = lc ($key); | 
| 4009 | 1969 |  |  |  |  | 3738 | $key =~ tr/ / /s; | 
| 4010 |  |  |  |  |  |  |  | 
| 4011 |  |  |  |  |  |  | # Try site-specific configuration option. | 
| 4012 | 1969 | 100 | 100 |  |  | 4904 | if ($self->{sitename} && | 
| 4013 |  |  |  |  |  |  | exists $self->{_config}{"$self->{sitename}:$key"}) | 
| 4014 |  |  |  |  |  |  | { | 
| 4015 | 2 | 50 |  |  |  | 7 | unless (wantarray) | 
| 4016 |  |  |  |  |  |  | { | 
| 4017 |  |  |  |  |  |  | # Return scalar value, but warn if there are many values | 
| 4018 |  |  |  |  |  |  | # for this configuration operation. | 
| 4019 | 2 | 50 |  |  |  | 4 | if (@{$self->{_config}{"$self->{sitename}:$key"}} > 1) | 
|  | 2 |  |  |  |  | 13 |  | 
| 4020 |  |  |  |  |  |  | { | 
| 4021 | 0 |  |  |  |  | 0 | warn "called config in scalar context for an array valued key: $key"; | 
| 4022 |  |  |  |  |  |  | } | 
| 4023 |  |  |  |  |  |  |  | 
| 4024 | 2 |  |  |  |  | 9 | return $self->{_config}{"$self->{sitename}:$key"}[0]; | 
| 4025 |  |  |  |  |  |  | } | 
| 4026 |  |  |  |  |  |  | else | 
| 4027 |  |  |  |  |  |  | { | 
| 4028 | 0 |  |  |  |  | 0 | return @{$self->{_config}{"$self->{sitename}:$key"}}; | 
|  | 0 |  |  |  |  | 0 |  | 
| 4029 |  |  |  |  |  |  | } | 
| 4030 |  |  |  |  |  |  | } | 
| 4031 |  |  |  |  |  |  |  | 
| 4032 |  |  |  |  |  |  | # Try global configuration option. | 
| 4033 | 1967 | 100 |  |  |  | 3912 | if (exists $self->{_config}{$key}) | 
| 4034 |  |  |  |  |  |  | { | 
| 4035 | 79 | 100 |  |  |  | 167 | unless (wantarray) | 
| 4036 |  |  |  |  |  |  | { | 
| 4037 |  |  |  |  |  |  | # Return scalar value, but warn if there are many values | 
| 4038 |  |  |  |  |  |  | # for this configuration operation. | 
| 4039 | 74 | 50 |  |  |  | 105 | if (@{$self->{_config}{$key}} > 1) | 
|  | 74 |  |  |  |  | 187 |  | 
| 4040 |  |  |  |  |  |  | { | 
| 4041 | 0 |  |  |  |  | 0 | warn "called config in scalar context for an array valued key: $key"; | 
| 4042 |  |  |  |  |  |  | } | 
| 4043 |  |  |  |  |  |  |  | 
| 4044 | 74 |  |  |  |  | 275 | return $self->{_config}{$key}[0]; | 
| 4045 |  |  |  |  |  |  | } | 
| 4046 |  |  |  |  |  |  | else | 
| 4047 |  |  |  |  |  |  | { | 
| 4048 | 5 |  |  |  |  | 9 | return @{$self->{_config}{$key}}; | 
|  | 5 |  |  |  |  | 29 |  | 
| 4049 |  |  |  |  |  |  | } | 
| 4050 |  |  |  |  |  |  | } | 
| 4051 |  |  |  |  |  |  |  | 
| 4052 |  |  |  |  |  |  | # Nothing found. | 
| 4053 | 1888 | 100 |  |  |  | 3534 | unless (wantarray) { return undef } else { return () } | 
|  | 105 |  |  |  |  | 304 |  | 
|  | 1783 |  |  |  |  | 8291 |  | 
| 4054 |  |  |  |  |  |  | } | 
| 4055 |  |  |  |  |  |  |  | 
| 4056 |  |  |  |  |  |  | =pod | 
| 4057 |  |  |  |  |  |  |  | 
| 4058 |  |  |  |  |  |  | =item $ftps->ip_host_config ($ip_addr); | 
| 4059 |  |  |  |  |  |  |  | 
| 4060 |  |  |  |  |  |  | Look for a EHostE section which contains "ip: $ip_addr". | 
| 4061 |  |  |  |  |  |  | If one is found, return the site name of the Host section. Otherwise | 
| 4062 |  |  |  |  |  |  | return undef. | 
| 4063 |  |  |  |  |  |  |  | 
| 4064 |  |  |  |  |  |  | =cut | 
| 4065 |  |  |  |  |  |  |  | 
| 4066 |  |  |  |  |  |  | sub ip_host_config | 
| 4067 |  |  |  |  |  |  | { | 
| 4068 | 0 |  |  | 0 | 1 | 0 | my $self = shift; | 
| 4069 | 0 |  |  |  |  | 0 | my $ip_addr = shift; | 
| 4070 |  |  |  |  |  |  |  | 
| 4071 | 0 | 0 |  |  |  | 0 | if (exists $self->{_config_ip_host}{$ip_addr}) | 
| 4072 |  |  |  |  |  |  | { | 
| 4073 | 0 |  |  |  |  | 0 | return $self->{_config_ip_host}{$ip_addr}; | 
| 4074 |  |  |  |  |  |  | } | 
| 4075 |  |  |  |  |  |  |  | 
| 4076 | 0 |  |  |  |  | 0 | return undef; | 
| 4077 |  |  |  |  |  |  | } | 
| 4078 |  |  |  |  |  |  |  | 
| 4079 |  |  |  |  |  |  | sub _archive_filter_Z | 
| 4080 |  |  |  |  |  |  | { | 
| 4081 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 4082 | 0 |  |  |  |  | 0 | my $sock = shift; | 
| 4083 |  |  |  |  |  |  |  | 
| 4084 | 0 |  |  |  |  | 0 | return archive_filter_external ($self, $sock, "compress"); | 
| 4085 |  |  |  |  |  |  | } | 
| 4086 |  |  |  |  |  |  |  | 
| 4087 |  |  |  |  |  |  | sub _archive_filter_gz | 
| 4088 |  |  |  |  |  |  | { | 
| 4089 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 4090 | 0 |  |  |  |  | 0 | my $sock = shift; | 
| 4091 |  |  |  |  |  |  |  | 
| 4092 | 0 |  |  |  |  | 0 | return archive_filter_external ($self, $sock, "gzip"); | 
| 4093 |  |  |  |  |  |  | } | 
| 4094 |  |  |  |  |  |  |  | 
| 4095 |  |  |  |  |  |  | sub _archive_filter_bz2 | 
| 4096 |  |  |  |  |  |  | { | 
| 4097 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 4098 | 0 |  |  |  |  | 0 | my $sock = shift; | 
| 4099 |  |  |  |  |  |  |  | 
| 4100 | 0 |  |  |  |  | 0 | return archive_filter_external ($self, $sock, "bzip2"); | 
| 4101 |  |  |  |  |  |  | } | 
| 4102 |  |  |  |  |  |  |  | 
| 4103 |  |  |  |  |  |  | sub _archive_filter_uue | 
| 4104 |  |  |  |  |  |  | { | 
| 4105 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 4106 | 0 |  |  |  |  | 0 | my $sock = shift; | 
| 4107 |  |  |  |  |  |  |  | 
| 4108 | 0 |  |  |  |  | 0 | return archive_filter_external ($self, $sock, "uuencode", "file"); | 
| 4109 |  |  |  |  |  |  | } | 
| 4110 |  |  |  |  |  |  |  | 
| 4111 |  |  |  |  |  |  | =pod | 
| 4112 |  |  |  |  |  |  |  | 
| 4113 |  |  |  |  |  |  | =item $filter = $ftps->archive_filter_external ($sock, $cmd [, $args]); | 
| 4114 |  |  |  |  |  |  |  | 
| 4115 |  |  |  |  |  |  | Apply C<$cmd> as a filter to socket C<$sock>. Returns a hash reference | 
| 4116 |  |  |  |  |  |  | which contains the following keys: | 
| 4117 |  |  |  |  |  |  |  | 
| 4118 |  |  |  |  |  |  | sock      Newly opened socket. | 
| 4119 |  |  |  |  |  |  | pid       PID of filter program. | 
| 4120 |  |  |  |  |  |  |  | 
| 4121 |  |  |  |  |  |  | If it fails, returns C. | 
| 4122 |  |  |  |  |  |  |  | 
| 4123 |  |  |  |  |  |  | See section ARCHIVE MODE elsewhere in this manual for more information. | 
| 4124 |  |  |  |  |  |  |  | 
| 4125 |  |  |  |  |  |  | =cut | 
| 4126 |  |  |  |  |  |  |  | 
| 4127 |  |  |  |  |  |  | sub archive_filter_external | 
| 4128 |  |  |  |  |  |  | { | 
| 4129 | 0 |  |  | 0 | 1 | 0 | my $self = shift; | 
| 4130 | 0 |  |  |  |  | 0 | my $sock = shift; | 
| 4131 |  |  |  |  |  |  |  | 
| 4132 | 0 |  |  |  |  | 0 | my ($new_sock, $pid) = (FileHandle->new); | 
| 4133 |  |  |  |  |  |  |  | 
| 4134 |  |  |  |  |  |  | # Perl is forcing me to go through unnecessary hoops here ... | 
| 4135 | 0 | 0 |  |  |  | 0 | open AFE_SOCK, ">&" . fileno ($sock) or die "dup: $!"; | 
| 4136 | 0 |  |  |  |  | 0 | close $sock; | 
| 4137 |  |  |  |  |  |  |  | 
| 4138 | 0 |  |  |  |  | 0 | eval { | 
| 4139 | 0 |  |  |  |  | 0 | $pid = open2 (">&AFE_SOCK", $new_sock, @_); | 
| 4140 |  |  |  |  |  |  | }; | 
| 4141 | 0 | 0 |  |  |  | 0 | if ($@) | 
| 4142 |  |  |  |  |  |  | { | 
| 4143 | 0 | 0 |  |  |  | 0 | if ($@ =~ /^open2:/) | 
| 4144 |  |  |  |  |  |  | { | 
| 4145 | 0 |  |  |  |  | 0 | warn (join (" ", @_), ": ", $@); | 
| 4146 | 0 |  |  |  |  | 0 | return undef; | 
| 4147 |  |  |  |  |  |  | } | 
| 4148 | 0 |  |  |  |  | 0 | die; | 
| 4149 |  |  |  |  |  |  | } | 
| 4150 |  |  |  |  |  |  |  | 
| 4151 |  |  |  |  |  |  | # According to the open2 documentation, it should close AFE_SOCK | 
| 4152 |  |  |  |  |  |  | # for me. Apparently not, so I'll close it myself. | 
| 4153 | 0 |  |  |  |  | 0 | close AFE_SOCK; | 
| 4154 |  |  |  |  |  |  |  | 
| 4155 | 0 |  |  |  |  | 0 | my %filter_object = (sock => $new_sock, pid => $pid); | 
| 4156 |  |  |  |  |  |  |  | 
| 4157 | 0 |  |  |  |  | 0 | return \%filter_object; | 
| 4158 |  |  |  |  |  |  | } | 
| 4159 |  |  |  |  |  |  |  | 
| 4160 |  |  |  |  |  |  | sub _archive_generator_list | 
| 4161 |  |  |  |  |  |  | { | 
| 4162 | 1 |  |  | 1 |  | 3 | my $self = shift; | 
| 4163 | 1 |  |  |  |  | 3 | my $dirh = shift; | 
| 4164 |  |  |  |  |  |  |  | 
| 4165 | 1 |  |  |  |  | 4 | my @files = (); | 
| 4166 |  |  |  |  |  |  |  | 
| 4167 |  |  |  |  |  |  | # Recursively visit all files and directories contained in $dirh. | 
| 4168 |  |  |  |  |  |  | $self->visit | 
| 4169 |  |  |  |  |  |  | ($dirh, | 
| 4170 |  |  |  |  |  |  | { 'f' => | 
| 4171 |  |  |  |  |  |  | sub { | 
| 4172 | 2 |  |  | 2 |  | 30 | push @files, $_->pathname; | 
| 4173 |  |  |  |  |  |  | }, | 
| 4174 |  |  |  |  |  |  | 'd' => | 
| 4175 |  |  |  |  |  |  | sub { | 
| 4176 | 4 |  |  | 4 |  | 18 | my $pathname = $_->pathname; | 
| 4177 |  |  |  |  |  |  |  | 
| 4178 | 4 |  |  |  |  | 14 | push @files, $pathname; | 
| 4179 |  |  |  |  |  |  |  | 
| 4180 |  |  |  |  |  |  | # Only visit a directory if we are allowed to by the list rule. | 
| 4181 |  |  |  |  |  |  | # Otherwise this could be used as a backdoor way to list | 
| 4182 |  |  |  |  |  |  | # forbidden directories. | 
| 4183 | 4 |  |  |  |  | 17 | return $self->_eval_rule ("list rule", | 
| 4184 |  |  |  |  |  |  | undef, undef, $pathname); | 
| 4185 |  |  |  |  |  |  | } | 
| 4186 |  |  |  |  |  |  | } | 
| 4187 | 1 |  |  |  |  | 42 | ); | 
| 4188 |  |  |  |  |  |  |  | 
| 4189 | 1 |  |  |  |  | 15 | my $str = join ("\n", @files) . "\n"; | 
| 4190 |  |  |  |  |  |  |  | 
| 4191 | 1 |  |  |  |  | 29 | return new IO::Scalar \$str; | 
| 4192 |  |  |  |  |  |  | } | 
| 4193 |  |  |  |  |  |  |  | 
| 4194 |  |  |  |  |  |  | sub _archive_generator_zip | 
| 4195 |  |  |  |  |  |  | { | 
| 4196 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 4197 | 0 |  |  |  |  | 0 | my $dirh = shift; | 
| 4198 |  |  |  |  |  |  |  | 
| 4199 |  |  |  |  |  |  | # Create the zip file. | 
| 4200 | 0 |  |  |  |  | 0 | my $zip = Archive::Zip->new (); | 
| 4201 |  |  |  |  |  |  |  | 
| 4202 |  |  |  |  |  |  | # Recursively visit all files and directories contained in $dirh. | 
| 4203 |  |  |  |  |  |  | $self->visit | 
| 4204 |  |  |  |  |  |  | ($dirh, | 
| 4205 |  |  |  |  |  |  | { 'f' => | 
| 4206 |  |  |  |  |  |  | sub { | 
| 4207 | 0 |  |  | 0 |  | 0 | my $fileh = $_; | 
| 4208 |  |  |  |  |  |  |  | 
| 4209 | 0 | 0 |  |  |  | 0 | if ($self->_eval_rule ("retrieve rule", | 
| 4210 |  |  |  |  |  |  | $fileh->pathname, | 
| 4211 |  |  |  |  |  |  | $fileh->filename, | 
| 4212 |  |  |  |  |  |  | $fileh->dirname)) | 
| 4213 |  |  |  |  |  |  | { | 
| 4214 |  |  |  |  |  |  | # Add file to archive. Archive::Zip has a nice | 
| 4215 |  |  |  |  |  |  | # extensible "Member" concept. We create our own | 
| 4216 |  |  |  |  |  |  | # member type (Net::FTPServer::ZipMember) which understands | 
| 4217 |  |  |  |  |  |  | # our own file handles and serves them back to the | 
| 4218 |  |  |  |  |  |  | # main Archive::Zip program on demand. This means | 
| 4219 |  |  |  |  |  |  | # that at most only a small part of the file is | 
| 4220 |  |  |  |  |  |  | # held in memory at any one time. | 
| 4221 | 0 |  |  |  |  | 0 | my $memb | 
| 4222 |  |  |  |  |  |  | = Net::FTPServer::ZipMember->_newFromFileHandle ($fileh); | 
| 4223 |  |  |  |  |  |  |  | 
| 4224 | 0 | 0 |  |  |  | 0 | unless ($memb) | 
| 4225 |  |  |  |  |  |  | { | 
| 4226 | 0 |  |  |  |  | 0 | warn "zip: error reading ", $fileh->filename, ": ", | 
| 4227 |  |  |  |  |  |  | $self->system_error_hook, " (ignored)"; | 
| 4228 | 0 |  |  |  |  | 0 | return; | 
| 4229 |  |  |  |  |  |  | } | 
| 4230 |  |  |  |  |  |  |  | 
| 4231 | 0 |  |  |  |  | 0 | $zip->addMember ($memb); | 
| 4232 | 0 |  |  |  |  | 0 | $memb->desiredCompressionMethod | 
| 4233 |  |  |  |  |  |  | (&Archive::Zip::COMPRESSION_DEFLATED); | 
| 4234 | 0 |  |  |  |  | 0 | $memb->desiredCompressionLevel (9); | 
| 4235 |  |  |  |  |  |  | } | 
| 4236 |  |  |  |  |  |  | }, | 
| 4237 |  |  |  |  |  |  | 'd' => | 
| 4238 |  |  |  |  |  |  | sub { | 
| 4239 |  |  |  |  |  |  | # Only visit a directory if we are allowed to by the list rule. | 
| 4240 |  |  |  |  |  |  | # Otherwise this could be used as a backdoor way to list | 
| 4241 |  |  |  |  |  |  | # forbidden directories. | 
| 4242 | 0 |  |  | 0 |  | 0 | return $self->_eval_rule ("list rule", undef, undef, $_->pathname); | 
| 4243 |  |  |  |  |  |  | } | 
| 4244 |  |  |  |  |  |  | } | 
| 4245 | 0 |  |  |  |  | 0 | ); | 
| 4246 |  |  |  |  |  |  |  | 
| 4247 |  |  |  |  |  |  | # Is a temporary directory available? Is it writable? If so, dump | 
| 4248 |  |  |  |  |  |  | # the ZIP file there. Otherwise, write it to an IO::Scalar (ie. in | 
| 4249 |  |  |  |  |  |  | # memory). | 
| 4250 | 0 | 0 |  |  |  | 0 | my $tmpdir = | 
| 4251 |  |  |  |  |  |  | defined $self->config ("archive zip temporaries") | 
| 4252 |  |  |  |  |  |  | ? $self->config ("archive zip temporaries") | 
| 4253 |  |  |  |  |  |  | : "/tmp"; | 
| 4254 |  |  |  |  |  |  |  | 
| 4255 | 0 |  |  |  |  | 0 | my $file; | 
| 4256 |  |  |  |  |  |  |  | 
| 4257 | 0 | 0 |  |  |  | 0 | if ($tmpdir) | 
| 4258 |  |  |  |  |  |  | { | 
| 4259 | 0 |  |  |  |  | 0 | my $tmpname = "$tmpdir/ftps.az.tmp.$$"; | 
| 4260 | 0 |  |  |  |  | 0 | $file = new IO::File ($tmpname, "w+"); | 
| 4261 |  |  |  |  |  |  |  | 
| 4262 | 0 | 0 |  |  |  | 0 | if ($file) | 
| 4263 |  |  |  |  |  |  | { | 
| 4264 | 0 |  |  |  |  | 0 | unlink $tmpname; | 
| 4265 | 0 | 0 |  |  |  | 0 | $zip->writeToFileHandle ($file, 1) == &Archive::Zip::AZ_OK | 
| 4266 |  |  |  |  |  |  | or die "failed to write to zip file: $!"; | 
| 4267 | 0 |  |  |  |  | 0 | $file->seek (0, 0); | 
| 4268 |  |  |  |  |  |  | } | 
| 4269 |  |  |  |  |  |  | } | 
| 4270 |  |  |  |  |  |  |  | 
| 4271 | 0 | 0 |  |  |  | 0 | unless ($file) | 
| 4272 |  |  |  |  |  |  | { | 
| 4273 | 0 |  |  |  |  | 0 | $file = new IO::Scalar; | 
| 4274 | 0 | 0 |  |  |  | 0 | $zip->writeToFileHandle ($file, 1) == &Archive::Zip::AZ_OK | 
| 4275 |  |  |  |  |  |  | or die "failed to write to zip file: $!"; | 
| 4276 | 0 |  |  |  |  | 0 | $file->seek (0, 0); | 
| 4277 |  |  |  |  |  |  | } | 
| 4278 |  |  |  |  |  |  |  | 
| 4279 | 0 |  |  |  |  | 0 | return $file; | 
| 4280 |  |  |  |  |  |  | } | 
| 4281 |  |  |  |  |  |  |  | 
| 4282 |  |  |  |  |  |  | =pod | 
| 4283 |  |  |  |  |  |  |  | 
| 4284 |  |  |  |  |  |  | =item $ftps->visit ($dirh, \%functions); | 
| 4285 |  |  |  |  |  |  |  | 
| 4286 |  |  |  |  |  |  | The C function recursively "visits" every file and directory | 
| 4287 |  |  |  |  |  |  | contained in C<$dirh> (which must be a directory handle). | 
| 4288 |  |  |  |  |  |  |  | 
| 4289 |  |  |  |  |  |  | C<\%functions> is a reference to a hash of file types to functions. | 
| 4290 |  |  |  |  |  |  | For example: | 
| 4291 |  |  |  |  |  |  |  | 
| 4292 |  |  |  |  |  |  | 'f' => \&visit_file, | 
| 4293 |  |  |  |  |  |  | 'd' => \&visit_directory, | 
| 4294 |  |  |  |  |  |  | 'l' => \&visit_symlink, | 
| 4295 |  |  |  |  |  |  | &c. | 
| 4296 |  |  |  |  |  |  |  | 
| 4297 |  |  |  |  |  |  | When a file of the known type is encountered, the appropriate | 
| 4298 |  |  |  |  |  |  | function is called with C<$_> set to the file handle. (All functions | 
| 4299 |  |  |  |  |  |  | are optional: if C encounters a file with a type not listed | 
| 4300 |  |  |  |  |  |  | in the C<%functions> hash, then that file is just ignored). | 
| 4301 |  |  |  |  |  |  |  | 
| 4302 |  |  |  |  |  |  | The return value from functions is ignored, I for the | 
| 4303 |  |  |  |  |  |  | return value from the directory ('d') function. The directory | 
| 4304 |  |  |  |  |  |  | function should return 1 to indicate that C should recurse | 
| 4305 |  |  |  |  |  |  | into that directory. If the directory function returns 0, then | 
| 4306 |  |  |  |  |  |  | C will skip that directory. | 
| 4307 |  |  |  |  |  |  |  | 
| 4308 |  |  |  |  |  |  | C will call the directory function once for C<$dirh>. | 
| 4309 |  |  |  |  |  |  |  | 
| 4310 |  |  |  |  |  |  | =cut | 
| 4311 |  |  |  |  |  |  |  | 
| 4312 |  |  |  |  |  |  | sub visit | 
| 4313 |  |  |  |  |  |  | { | 
| 4314 | 4 |  |  | 4 | 1 | 11 | my $self = shift; | 
| 4315 | 4 |  |  |  |  | 9 | my $dirh = shift; | 
| 4316 | 4 |  |  |  |  | 9 | my $functions = shift; | 
| 4317 |  |  |  |  |  |  |  | 
| 4318 | 4 |  |  |  |  | 9 | my $recurse = 1; | 
| 4319 |  |  |  |  |  |  |  | 
| 4320 | 4 | 50 |  |  |  | 14 | if (exists $functions->{d}) | 
| 4321 |  |  |  |  |  |  | { | 
| 4322 | 4 |  |  |  |  | 12 | local $_ = $dirh; | 
| 4323 | 4 |  |  |  |  | 8 | $recurse = &{$functions->{d}} (); | 
|  | 4 |  |  |  |  | 15 |  | 
| 4324 |  |  |  |  |  |  | } | 
| 4325 |  |  |  |  |  |  |  | 
| 4326 | 4 | 50 |  |  |  | 16 | if ($recurse) | 
| 4327 |  |  |  |  |  |  | { | 
| 4328 | 4 |  |  |  |  | 23 | my $files = $dirh->list_status (); | 
| 4329 |  |  |  |  |  |  |  | 
| 4330 | 4 |  |  |  |  | 10 | my $file; | 
| 4331 | 4 |  |  |  |  | 30 | foreach $file (@$files) | 
| 4332 |  |  |  |  |  |  | { | 
| 4333 | 5 |  |  |  |  | 16 | my $mode = $file->[2][0]; | 
| 4334 | 5 |  |  |  |  | 11 | my $fileh = $file->[1]; | 
| 4335 |  |  |  |  |  |  |  | 
| 4336 | 5 | 100 |  |  |  | 26 | if ($mode eq 'd') | 
|  |  | 50 |  |  |  |  |  | 
| 4337 |  |  |  |  |  |  | { | 
| 4338 | 3 |  |  |  |  | 27 | $self->visit ($fileh, $functions); | 
| 4339 |  |  |  |  |  |  | } | 
| 4340 |  |  |  |  |  |  | elsif (exists $functions->{$mode}) | 
| 4341 |  |  |  |  |  |  | { | 
| 4342 | 2 |  |  |  |  | 5 | local $_ = $fileh; | 
| 4343 | 2 |  |  |  |  | 6 | &{$functions->{$mode}} (); | 
|  | 2 |  |  |  |  | 8 |  | 
| 4344 |  |  |  |  |  |  | } | 
| 4345 |  |  |  |  |  |  | } | 
| 4346 |  |  |  |  |  |  | } | 
| 4347 |  |  |  |  |  |  | } | 
| 4348 |  |  |  |  |  |  |  | 
| 4349 |  |  |  |  |  |  | sub _HOST_command | 
| 4350 |  |  |  |  |  |  | { | 
| 4351 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 4352 | 0 |  |  |  |  | 0 | my $cmd = shift; | 
| 4353 | 0 |  |  |  |  | 0 | my $rest = shift; | 
| 4354 |  |  |  |  |  |  |  | 
| 4355 |  |  |  |  |  |  | # HOST with no parameters just prints out the current site name. | 
| 4356 | 0 | 0 |  |  |  | 0 | if ($rest eq "") | 
| 4357 |  |  |  |  |  |  | { | 
| 4358 | 0 | 0 |  |  |  | 0 | if ($self->{sitename}) { | 
| 4359 | 0 |  |  |  |  | 0 | $self->reply (200, "HOST is set to $self->{sitename}."); | 
| 4360 |  |  |  |  |  |  | } else { | 
| 4361 | 0 |  |  |  |  | 0 | $self->reply (200, "HOST is not set."); | 
| 4362 |  |  |  |  |  |  | } | 
| 4363 | 0 |  |  |  |  | 0 | return; | 
| 4364 |  |  |  |  |  |  | } | 
| 4365 |  |  |  |  |  |  |  | 
| 4366 |  |  |  |  |  |  | # The user may only issue HOST before log in. | 
| 4367 | 0 | 0 |  |  |  | 0 | if ($self->{authenticated}) | 
| 4368 |  |  |  |  |  |  | { | 
| 4369 | 0 |  |  |  |  | 0 | $self->reply (501, "Cannot issue HOST command after logging in."); | 
| 4370 | 0 |  |  |  |  | 0 | return; | 
| 4371 |  |  |  |  |  |  | } | 
| 4372 |  |  |  |  |  |  |  | 
| 4373 |  |  |  |  |  |  | # You cannot change HOST. | 
| 4374 | 0 | 0 | 0 |  |  | 0 | if ($self->{sitename} && $self->{sitename} ne $rest) | 
| 4375 |  |  |  |  |  |  | { | 
| 4376 | 0 |  |  |  |  | 0 | $self->reply (501, "HOST already set to $self->{sitename}."); | 
| 4377 | 0 |  |  |  |  | 0 | return; | 
| 4378 |  |  |  |  |  |  | } | 
| 4379 |  |  |  |  |  |  |  | 
| 4380 |  |  |  |  |  |  | # Check that the name is reasonable. | 
| 4381 | 0 | 0 |  |  |  | 0 | unless ($rest =~ /^[-a-z0-9.]+$/i) | 
| 4382 |  |  |  |  |  |  | { | 
| 4383 | 0 |  |  |  |  | 0 | $self->reply (501, "HOST syntax error."); | 
| 4384 | 0 |  |  |  |  | 0 | return; | 
| 4385 |  |  |  |  |  |  | } | 
| 4386 |  |  |  |  |  |  |  | 
| 4387 |  |  |  |  |  |  | # Allow the change. | 
| 4388 | 0 |  |  |  |  | 0 | $self->{sitename} = $rest; | 
| 4389 | 0 |  |  |  |  | 0 | $self->reply (200, "HOST set to $self->{sitename}."); | 
| 4390 |  |  |  |  |  |  | } | 
| 4391 |  |  |  |  |  |  |  | 
| 4392 |  |  |  |  |  |  | sub _USER_command | 
| 4393 |  |  |  |  |  |  | { | 
| 4394 | 27 |  |  | 27 |  | 61 | my $self = shift; | 
| 4395 | 27 |  |  |  |  | 53 | my $cmd = shift; | 
| 4396 | 27 |  |  |  |  | 66 | my $rest = shift; | 
| 4397 |  |  |  |  |  |  |  | 
| 4398 |  |  |  |  |  |  | # If the user issues this command when logged in, generate an error. | 
| 4399 |  |  |  |  |  |  | # We have to do this basically because of chroot and setuid stuff we | 
| 4400 |  |  |  |  |  |  | # can't ``relogin'' as a different user. | 
| 4401 | 27 | 50 |  |  |  | 136 | if ($self->{authenticated}) | 
| 4402 |  |  |  |  |  |  | { | 
| 4403 | 0 |  |  |  |  | 0 | $self->reply (503, "You are already logged in."); | 
| 4404 | 0 |  |  |  |  | 0 | return; | 
| 4405 |  |  |  |  |  |  | } | 
| 4406 |  |  |  |  |  |  |  | 
| 4407 |  |  |  |  |  |  | # Just save the username for now. | 
| 4408 | 27 |  |  |  |  | 94 | $self->{user} = $rest; | 
| 4409 |  |  |  |  |  |  |  | 
| 4410 |  |  |  |  |  |  | # Tried to log in anonymously? | 
| 4411 | 27 | 100 | 66 |  |  | 255 | if ($rest eq "ftp" || $rest eq "anonymous") | 
| 4412 |  |  |  |  |  |  | { | 
| 4413 | 8 | 50 |  |  |  | 32 | unless ($self->config ("allow anonymous")) | 
| 4414 |  |  |  |  |  |  | { | 
| 4415 | 0 |  |  |  |  | 0 | $self->reply (421, "Anonymous logins not permitted."); | 
| 4416 | 0 |  |  |  |  | 0 | $self->_log_line ("[No anonymous allowed]"); | 
| 4417 | 0 |  |  |  |  | 0 | exit 0; | 
| 4418 |  |  |  |  |  |  | } | 
| 4419 |  |  |  |  |  |  |  | 
| 4420 | 8 |  |  |  |  | 62 | $self->{user_is_anonymous} = 1; | 
| 4421 |  |  |  |  |  |  | } | 
| 4422 |  |  |  |  |  |  | else | 
| 4423 |  |  |  |  |  |  | { | 
| 4424 | 19 |  |  |  |  | 67 | delete $self->{user_is_anonymous}; | 
| 4425 |  |  |  |  |  |  | } | 
| 4426 |  |  |  |  |  |  |  | 
| 4427 | 27 | 100 |  |  |  | 148 | unless ($self->{user_is_anonymous}) | 
| 4428 |  |  |  |  |  |  | { | 
| 4429 | 19 |  |  |  |  | 78 | $self->reply (331, "Username OK, please send password."); | 
| 4430 |  |  |  |  |  |  | } | 
| 4431 |  |  |  |  |  |  | else | 
| 4432 |  |  |  |  |  |  | { | 
| 4433 | 8 |  |  |  |  | 33 | $self->reply (331, "Anonymous login OK, please send your email address as password."); | 
| 4434 |  |  |  |  |  |  | } | 
| 4435 |  |  |  |  |  |  | } | 
| 4436 |  |  |  |  |  |  |  | 
| 4437 |  |  |  |  |  |  | sub _PASS_command | 
| 4438 |  |  |  |  |  |  | { | 
| 4439 | 27 |  |  | 27 |  | 85 | my $self = shift; | 
| 4440 | 27 |  |  |  |  | 75 | my $cmd = shift; | 
| 4441 | 27 |  |  |  |  | 73 | my $rest = shift; | 
| 4442 |  |  |  |  |  |  |  | 
| 4443 |  |  |  |  |  |  | # If the user issues this command when logged in, generate an error. | 
| 4444 | 27 | 50 |  |  |  | 151 | if ($self->{authenticated}) | 
| 4445 |  |  |  |  |  |  | { | 
| 4446 | 0 |  |  |  |  | 0 | $self->reply (503, "You are already logged in."); | 
| 4447 | 0 |  |  |  |  | 0 | return; | 
| 4448 |  |  |  |  |  |  | } | 
| 4449 |  |  |  |  |  |  |  | 
| 4450 |  |  |  |  |  |  | # Have we received a username? | 
| 4451 | 27 | 50 |  |  |  | 124 | unless ($self->{user}) | 
| 4452 |  |  |  |  |  |  | { | 
| 4453 | 0 |  |  |  |  | 0 | $self->reply (503, "Please send your username first."); | 
| 4454 | 0 |  |  |  |  | 0 | return; | 
| 4455 |  |  |  |  |  |  | } | 
| 4456 |  |  |  |  |  |  |  | 
| 4457 |  |  |  |  |  |  | # If this is an anonymous login, check that the password conforms. | 
| 4458 | 27 |  |  |  |  | 130 | my @anon_passwd_warning = (); | 
| 4459 |  |  |  |  |  |  |  | 
| 4460 | 27 | 100 |  |  |  | 117 | if ($self->{user_is_anonymous}) | 
| 4461 |  |  |  |  |  |  | { | 
| 4462 | 8 |  | 100 |  |  | 30 | my $cktype = $self->config ("anonymous password check") || "none"; | 
| 4463 | 8 |  | 100 |  |  | 43 | my $enforce = $self->config ("anonymous password enforce") || 0; | 
| 4464 |  |  |  |  |  |  |  | 
| 4465 |  |  |  |  |  |  | # If the password ends with @, append hostname. | 
| 4466 |  |  |  |  |  |  | my $hostname | 
| 4467 |  |  |  |  |  |  | = $self->{peerhostname} ? | 
| 4468 |  |  |  |  |  |  | $self->{peerhostname} : | 
| 4469 | 8 | 50 |  |  |  | 35 | $self->{peeraddrstring}; | 
| 4470 |  |  |  |  |  |  |  | 
| 4471 | 8 | 100 |  |  |  | 73 | $rest .= $hostname if $rest =~ /\@$/; | 
| 4472 |  |  |  |  |  |  |  | 
| 4473 | 8 | 100 |  |  |  | 28 | if ($cktype ne "none") | 
| 4474 |  |  |  |  |  |  | { | 
| 4475 | 6 |  |  |  |  | 14 | my $valid; | 
| 4476 |  |  |  |  |  |  |  | 
| 4477 | 6 | 100 |  |  |  | 30 | if ($cktype eq "rfc822") | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 4478 |  |  |  |  |  |  | { | 
| 4479 | 2 |  |  |  |  | 31 | $valid = $self->_anon_passwd_validate_rfc822 ($rest); | 
| 4480 |  |  |  |  |  |  | } | 
| 4481 |  |  |  |  |  |  | elsif ($cktype eq "nobrowser") | 
| 4482 |  |  |  |  |  |  | { | 
| 4483 | 2 |  |  |  |  | 27 | $valid = $self->_anon_passwd_validate_nobrowser ($rest); | 
| 4484 |  |  |  |  |  |  | } | 
| 4485 |  |  |  |  |  |  | elsif ($cktype eq "trivial") | 
| 4486 |  |  |  |  |  |  | { | 
| 4487 | 2 |  |  |  |  | 31 | $valid = $self->_anon_passwd_validate_trivial ($rest); | 
| 4488 |  |  |  |  |  |  | } | 
| 4489 |  |  |  |  |  |  | else | 
| 4490 |  |  |  |  |  |  | { | 
| 4491 | 0 |  |  |  |  | 0 | die "unknown password check type: $cktype"; | 
| 4492 |  |  |  |  |  |  | } | 
| 4493 |  |  |  |  |  |  |  | 
| 4494 |  |  |  |  |  |  | # Defer the warning until later on in the function. | 
| 4495 | 6 | 100 |  |  |  | 29 | unless ($valid) | 
| 4496 |  |  |  |  |  |  | { | 
| 4497 | 3 |  |  |  |  | 25 | push @anon_passwd_warning, | 
| 4498 |  |  |  |  |  |  | "The response \"$rest\" is not valid.", | 
| 4499 |  |  |  |  |  |  | "Please use your email address as your password.", | 
| 4500 |  |  |  |  |  |  | "  For example: joe\@$hostname", | 
| 4501 |  |  |  |  |  |  | "($hostname will be added if password ends with \@)."; | 
| 4502 |  |  |  |  |  |  | } | 
| 4503 |  |  |  |  |  |  |  | 
| 4504 |  |  |  |  |  |  | # ... unless we have been told to enforce it now. | 
| 4505 | 6 | 100 | 66 |  |  | 52 | if ($enforce && !$valid) | 
| 4506 |  |  |  |  |  |  | { | 
| 4507 | 3 |  |  |  |  | 14 | $self->reply (530, @anon_passwd_warning); | 
| 4508 | 3 |  |  |  |  | 13 | return; | 
| 4509 |  |  |  |  |  |  | } | 
| 4510 |  |  |  |  |  |  | } | 
| 4511 |  |  |  |  |  |  | } | 
| 4512 |  |  |  |  |  |  |  | 
| 4513 |  |  |  |  |  |  | # OK, now the real authentication check. | 
| 4514 |  |  |  |  |  |  | my $fail_code = | 
| 4515 |  |  |  |  |  |  | $self->authentication_hook ($self->{user}, $rest, | 
| 4516 | 24 |  |  |  |  | 216 | $self->{user_is_anonymous}) ; | 
| 4517 |  |  |  |  |  |  |  | 
| 4518 | 24 | 100 |  |  |  | 139 | if ( $fail_code < 0 ) | 
| 4519 |  |  |  |  |  |  | { | 
| 4520 |  |  |  |  |  |  | # See RFC 2577 section 5. | 
| 4521 | 1 | 50 |  |  |  | 5000145 | sleep 5 unless $fail_code == -2 ; | 
| 4522 |  |  |  |  |  |  |  | 
| 4523 |  |  |  |  |  |  | # Login failed. | 
| 4524 | 1 |  |  |  |  | 27 | $self->{loginattempts}++; | 
| 4525 |  |  |  |  |  |  |  | 
| 4526 | 1 | 50 | 50 |  |  | 21 | if ($self->{loginattempts} >= | 
| 4527 |  |  |  |  |  |  | ($self->config ("max login attempts") || 3)) | 
| 4528 |  |  |  |  |  |  | { | 
| 4529 |  |  |  |  |  |  | $self->log ("notice", "repeated login attempts from %s:%d", | 
| 4530 |  |  |  |  |  |  | $self->{peeraddrstring}, | 
| 4531 | 0 |  |  |  |  | 0 | $self->{peerport}); | 
| 4532 |  |  |  |  |  |  |  | 
| 4533 |  |  |  |  |  |  | # See RFC 2577 section 5. | 
| 4534 | 0 |  |  |  |  | 0 | $self->reply (421, "Too many login attempts. Goodbye."); | 
| 4535 | 0 |  |  |  |  | 0 | $self->_log_line ("[Max logins reached]"); | 
| 4536 | 0 |  |  |  |  | 0 | exit 0; | 
| 4537 |  |  |  |  |  |  | } | 
| 4538 |  |  |  |  |  |  |  | 
| 4539 | 1 |  |  |  |  | 12 | $self->reply (530, "Login failed."); | 
| 4540 | 1 |  |  |  |  | 5 | return; | 
| 4541 |  |  |  |  |  |  | } | 
| 4542 |  |  |  |  |  |  |  | 
| 4543 |  |  |  |  |  |  | # Perform user access control step. | 
| 4544 | 23 | 50 |  |  |  | 184 | unless ($self->_eval_rule ("user access control rule")) | 
| 4545 |  |  |  |  |  |  | { | 
| 4546 | 0 |  |  |  |  | 0 | $self->reply (421, "User denied by server configuration. Goodbye."); | 
| 4547 | 0 |  |  |  |  | 0 | $self->_log_line ("[Client denied]"); | 
| 4548 | 0 |  |  |  |  | 0 | exit; | 
| 4549 |  |  |  |  |  |  | } | 
| 4550 |  |  |  |  |  |  |  | 
| 4551 |  |  |  |  |  |  | # Login was officially OK. | 
| 4552 | 23 |  |  |  |  | 75 | $self->{authenticated} = 1; | 
| 4553 |  |  |  |  |  |  |  | 
| 4554 |  |  |  |  |  |  | # Compute user's class. | 
| 4555 |  |  |  |  |  |  | $self->{class} = | 
| 4556 | 23 |  |  |  |  | 455 | $self->_username_to_class ($rest, $self->{user_is_anonymous}); | 
| 4557 |  |  |  |  |  |  |  | 
| 4558 |  |  |  |  |  |  | # Compute home directory. We may need it when we display the | 
| 4559 |  |  |  |  |  |  | # welcome message. | 
| 4560 | 23 | 100 |  |  |  | 106 | unless ($self->{user_is_anonymous}) | 
| 4561 |  |  |  |  |  |  | { | 
| 4562 | 18 | 50 |  |  |  | 66 | if (defined $self->config ("home directory")) | 
| 4563 |  |  |  |  |  |  | { | 
| 4564 | 0 |  |  |  |  | 0 | $self->{home_directory} = $self->config ("home directory"); | 
| 4565 |  |  |  |  |  |  |  | 
| 4566 | 0 |  |  |  |  | 0 | $self->{home_directory} =~ s/%m/(getpwnam $self->{user})[7]/ge; | 
|  | 0 |  |  |  |  | 0 |  | 
| 4567 | 0 |  |  |  |  | 0 | $self->{home_directory} =~ s/%U/$self->{user}/ge; | 
|  | 0 |  |  |  |  | 0 |  | 
| 4568 | 0 |  |  |  |  | 0 | $self->{home_directory} =~ s/%%/%/g; | 
| 4569 |  |  |  |  |  |  | } | 
| 4570 |  |  |  |  |  |  | else | 
| 4571 |  |  |  |  |  |  | { | 
| 4572 | 18 |  | 50 |  |  | 9781 | $self->{home_directory} = (getpwnam $self->{user})[7] || "/"; | 
| 4573 |  |  |  |  |  |  | } | 
| 4574 |  |  |  |  |  |  | } | 
| 4575 |  |  |  |  |  |  | else | 
| 4576 |  |  |  |  |  |  | { | 
| 4577 |  |  |  |  |  |  | # Anonymous users always get "/" as their home directory. | 
| 4578 | 5 |  |  |  |  | 28 | $self->{home_directory} = "/"; | 
| 4579 |  |  |  |  |  |  | } | 
| 4580 |  |  |  |  |  |  |  | 
| 4581 |  |  |  |  |  |  | # Send a welcome message -- before the chroot since we may | 
| 4582 |  |  |  |  |  |  | # need to read a file in the real root. | 
| 4583 | 23 |  | 100 |  |  | 147 | my $welcome_type = $self->config ("welcome type") || "normal"; | 
| 4584 |  |  |  |  |  |  |  | 
| 4585 | 23 | 100 |  |  |  | 115 | if ($welcome_type eq "normal") | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 4586 |  |  |  |  |  |  | { | 
| 4587 | 21 | 100 |  |  |  | 83 | if (! $self->{user_is_anonymous}) | 
| 4588 |  |  |  |  |  |  | { | 
| 4589 |  |  |  |  |  |  | $self->reply (230, | 
| 4590 |  |  |  |  |  |  | @anon_passwd_warning, | 
| 4591 | 16 |  |  |  |  | 94 | "Welcome " . $self->{user} . "."); | 
| 4592 |  |  |  |  |  |  | } | 
| 4593 |  |  |  |  |  |  | else | 
| 4594 |  |  |  |  |  |  | { | 
| 4595 | 5 |  |  |  |  | 33 | $self->reply (230, | 
| 4596 |  |  |  |  |  |  | @anon_passwd_warning, | 
| 4597 |  |  |  |  |  |  | "Welcome $rest."); | 
| 4598 |  |  |  |  |  |  | } | 
| 4599 |  |  |  |  |  |  | } | 
| 4600 |  |  |  |  |  |  | elsif ($welcome_type eq "text") | 
| 4601 |  |  |  |  |  |  | { | 
| 4602 | 1 | 50 |  |  |  | 3 | my $welcome_text = $self->config ("welcome text") | 
| 4603 |  |  |  |  |  |  | or die "welcome type is text, but no welcome text configuration value"; | 
| 4604 |  |  |  |  |  |  |  | 
| 4605 | 1 |  |  |  |  | 29 | $welcome_text = $self->_percent_substitutions ($welcome_text); | 
| 4606 |  |  |  |  |  |  |  | 
| 4607 | 1 |  |  |  |  | 4 | $self->reply (230, | 
| 4608 |  |  |  |  |  |  | @anon_passwd_warning, | 
| 4609 |  |  |  |  |  |  | $welcome_text); | 
| 4610 |  |  |  |  |  |  | } | 
| 4611 |  |  |  |  |  |  | elsif ($welcome_type eq "file") | 
| 4612 |  |  |  |  |  |  | { | 
| 4613 | 1 | 50 |  |  |  | 5 | my $welcome_file = $self->config ("welcome file") | 
| 4614 |  |  |  |  |  |  | or die "welcome type is file, but no welcome file configuration value"; | 
| 4615 |  |  |  |  |  |  |  | 
| 4616 | 1 |  |  |  |  | 4 | my @lines = (); | 
| 4617 |  |  |  |  |  |  |  | 
| 4618 | 1 | 50 |  |  |  | 6 | if (my $io = new IO::File $welcome_file, "r") | 
| 4619 |  |  |  |  |  |  | { | 
| 4620 | 1 |  |  |  |  | 111 | while (<$io>) { | 
| 4621 | 1 |  |  |  |  | 12 | s/[\n\r]+$//; | 
| 4622 | 1 |  |  |  |  | 11 | push @lines, $self->_percent_substitutions ($_); | 
| 4623 |  |  |  |  |  |  | } | 
| 4624 | 1 |  |  |  |  | 16 | $io->close; | 
| 4625 |  |  |  |  |  |  | } | 
| 4626 |  |  |  |  |  |  | else | 
| 4627 |  |  |  |  |  |  | { | 
| 4628 | 0 |  |  |  |  | 0 | @lines = | 
| 4629 |  |  |  |  |  |  | ( "The server administrator has configured a welcome file,", | 
| 4630 |  |  |  |  |  |  | "but the file is missing." ); | 
| 4631 |  |  |  |  |  |  | } | 
| 4632 |  |  |  |  |  |  |  | 
| 4633 | 1 |  |  |  |  | 13 | $self->reply (230, @anon_passwd_warning, @lines); | 
| 4634 |  |  |  |  |  |  | } | 
| 4635 |  |  |  |  |  |  | else | 
| 4636 |  |  |  |  |  |  | { | 
| 4637 | 0 |  |  |  |  | 0 | die "unknown welcome type: $welcome_type"; | 
| 4638 |  |  |  |  |  |  | } | 
| 4639 |  |  |  |  |  |  |  | 
| 4640 |  |  |  |  |  |  | # Set the timezone for responses. | 
| 4641 | 23 | 50 |  |  |  | 106 | $ENV{TZ} = defined $self->config ("time zone") | 
| 4642 |  |  |  |  |  |  | ? $self->config ("time zone") | 
| 4643 |  |  |  |  |  |  | : "GMT"; | 
| 4644 |  |  |  |  |  |  |  | 
| 4645 |  |  |  |  |  |  | # Patch fom John Jetmore .  The following | 
| 4646 |  |  |  |  |  |  | # line is necessary to open /etc/localtime in the chroot environment. | 
| 4647 | 23 |  |  |  |  | 1100 | scalar (localtime (time)); | 
| 4648 |  |  |  |  |  |  |  | 
| 4649 |  |  |  |  |  |  | # Open /etc/protocols etc., in case we chroot. And yes, doing the | 
| 4650 |  |  |  |  |  |  | # setprotoent _twice_ is necessary to work around a bug in Perl or | 
| 4651 |  |  |  |  |  |  | # glibc (thanks Abraham Ingersoll ). Jamie Hill | 
| 4652 |  |  |  |  |  |  | #  says that the getprotobyname ("tcp") call | 
| 4653 |  |  |  |  |  |  | # is necessary for Solaris too. | 
| 4654 | 23 |  |  |  |  | 3302 | setprotoent 1; | 
| 4655 | 23 |  |  |  |  | 328 | setprotoent 1; | 
| 4656 | 23 |  |  |  |  | 733 | $_ = getprotobyname ("tcp"); | 
| 4657 | 23 |  |  |  |  | 678 | sethostent 1; | 
| 4658 | 23 |  |  |  |  | 565 | setnetent 1; | 
| 4659 | 23 |  |  |  |  | 263 | setservent 1; | 
| 4660 | 23 |  |  |  |  | 2231 | setpwent; | 
| 4661 | 23 |  |  |  |  | 373 | setgrent; | 
| 4662 |  |  |  |  |  |  |  | 
| 4663 |  |  |  |  |  |  | # Perform chroot, etc., as required. | 
| 4664 |  |  |  |  |  |  | $self->user_login_hook ($self->{user}, | 
| 4665 | 23 |  |  |  |  | 207 | $self->{user_is_anonymous}); | 
| 4666 |  |  |  |  |  |  |  | 
| 4667 |  |  |  |  |  |  | # Set CWD to /. | 
| 4668 | 23 |  |  |  |  | 118 | $self->{cwd} = $self->root_directory_hook; | 
| 4669 |  |  |  |  |  |  |  | 
| 4670 |  |  |  |  |  |  | # Move to home directory. | 
| 4671 | 23 |  |  |  |  | 60 | my $new_cwd; | 
| 4672 |  |  |  |  |  |  |  | 
| 4673 | 23 | 50 |  |  |  | 194 | if ($new_cwd = $self->_chdir ($self->{cwd}, $self->{home_directory})) | 
| 4674 |  |  |  |  |  |  | { | 
| 4675 | 23 |  |  |  |  | 468 | $self->{cwd} = $new_cwd; | 
| 4676 |  |  |  |  |  |  | } | 
| 4677 |  |  |  |  |  |  | else | 
| 4678 |  |  |  |  |  |  | { | 
| 4679 | 0 |  |  |  |  | 0 | $self->log ("warning", | 
| 4680 |  |  |  |  |  |  | "no home directory for user: $self->{user}"); | 
| 4681 |  |  |  |  |  |  | } | 
| 4682 |  |  |  |  |  |  |  | 
| 4683 |  |  |  |  |  |  | } | 
| 4684 |  |  |  |  |  |  |  | 
| 4685 |  |  |  |  |  |  | # Convert a username to a class by using the class directives | 
| 4686 |  |  |  |  |  |  | # in the configuration file. | 
| 4687 |  |  |  |  |  |  |  | 
| 4688 |  |  |  |  |  |  | sub _username_to_class | 
| 4689 |  |  |  |  |  |  | { | 
| 4690 | 23 |  |  | 23 |  | 78 | my $self = shift; | 
| 4691 | 23 |  |  |  |  | 58 | my $username = shift; | 
| 4692 | 23 |  |  |  |  | 66 | my $user_is_anonymous = shift; | 
| 4693 |  |  |  |  |  |  |  | 
| 4694 | 23 |  |  |  |  | 83 | my @classes = $self->config ("class"); | 
| 4695 |  |  |  |  |  |  |  | 
| 4696 | 23 |  |  |  |  | 79 | local $_; | 
| 4697 |  |  |  |  |  |  |  | 
| 4698 | 23 |  |  |  |  | 147 | foreach my $class (@classes) | 
| 4699 |  |  |  |  |  |  | { | 
| 4700 |  |  |  |  |  |  | # class: CLASSNAME { perl code ... } | 
| 4701 | 0 | 0 |  |  |  | 0 | if ($class =~ /^(\w+)\s+\{(.*)\}\s*$/) | 
|  |  | 0 |  |  |  |  |  | 
| 4702 |  |  |  |  |  |  | { | 
| 4703 | 0 |  |  |  |  | 0 | my $classname = $1; | 
| 4704 | 0 |  |  |  |  | 0 | my $code = $2; | 
| 4705 |  |  |  |  |  |  |  | 
| 4706 | 0 |  |  |  |  | 0 | $_ = $username; | 
| 4707 |  |  |  |  |  |  |  | 
| 4708 | 0 |  |  |  |  | 0 | my $rv = eval $code; | 
| 4709 | 0 | 0 |  |  |  | 0 | die if $@; | 
| 4710 |  |  |  |  |  |  |  | 
| 4711 | 0 | 0 |  |  |  | 0 | return $classname if $rv; | 
| 4712 |  |  |  |  |  |  | } | 
| 4713 |  |  |  |  |  |  | # class: CLASSNAME USERNAME[,USERNAME[,...]] | 
| 4714 |  |  |  |  |  |  | elsif ($class =~ /^(\w*)\s+(.*)/) | 
| 4715 |  |  |  |  |  |  | { | 
| 4716 | 0 |  |  |  |  | 0 | my $classname = $1; | 
| 4717 | 0 |  |  |  |  | 0 | my @users = split /[,\s]+/, $2; | 
| 4718 |  |  |  |  |  |  |  | 
| 4719 | 0 |  |  |  |  | 0 | foreach (@users) | 
| 4720 |  |  |  |  |  |  | { | 
| 4721 | 0 | 0 |  |  |  | 0 | return $classname if $_ eq $username; | 
| 4722 |  |  |  |  |  |  | } | 
| 4723 |  |  |  |  |  |  | } | 
| 4724 |  |  |  |  |  |  | else | 
| 4725 |  |  |  |  |  |  | { | 
| 4726 | 0 |  |  |  |  | 0 | die "bad class directive: class: $_"; | 
| 4727 |  |  |  |  |  |  | } | 
| 4728 |  |  |  |  |  |  | } | 
| 4729 |  |  |  |  |  |  |  | 
| 4730 |  |  |  |  |  |  | # Default cases. | 
| 4731 | 23 | 100 |  |  |  | 117 | return "anonymous" if $user_is_anonymous; | 
| 4732 | 18 |  |  |  |  | 155 | return "users"; | 
| 4733 |  |  |  |  |  |  | } | 
| 4734 |  |  |  |  |  |  |  | 
| 4735 |  |  |  |  |  |  | sub _percent_substitutions | 
| 4736 |  |  |  |  |  |  | { | 
| 4737 | 2 |  |  | 2 |  | 7 | my $self = shift; | 
| 4738 | 2 |  |  |  |  | 15 | local $_ = shift; | 
| 4739 |  |  |  |  |  |  |  | 
| 4740 |  |  |  |  |  |  | # See CONFIGURATION section on ``welcome text'' for a list of | 
| 4741 |  |  |  |  |  |  | # the substitutions available. | 
| 4742 | 2 |  |  |  |  | 21 | s/%C/$self->{cwd}->pathname/ge; | 
|  | 0 |  |  |  |  | 0 |  | 
| 4743 | 2 |  |  |  |  | 12 | s/%E/$self->{maintainer_email}/ge; | 
|  | 2 |  |  |  |  | 9 |  | 
| 4744 | 2 |  |  |  |  | 7 | s/%G/gmtime/ge; | 
|  | 0 |  |  |  |  | 0 |  | 
| 4745 | 2 | 0 |  |  |  | 4 | s/%R/$self->{peerhostname} ? $self->{peerhostname} : $self->{peeraddrstring}/ge; | 
|  | 0 |  |  |  |  | 0 |  | 
| 4746 | 2 |  |  |  |  | 8 | s/%L/$self->{hostname}/ge; | 
|  | 2 |  |  |  |  | 7 |  | 
| 4747 | 2 |  |  |  |  | 5 | s/%m/$self->{home_directory}/ge; | 
|  | 0 |  |  |  |  | 0 |  | 
| 4748 | 2 |  |  |  |  | 5 | s/%T/localtime/ge; | 
|  | 0 |  |  |  |  | 0 |  | 
| 4749 | 2 |  |  |  |  | 8 | s/%U/$self->{user}/ge; | 
|  | 2 |  |  |  |  | 7 |  | 
| 4750 | 2 |  |  |  |  | 7 | s/%u/$self->{user}/ge; | 
|  | 0 |  |  |  |  | 0 |  | 
| 4751 | 2 |  |  |  |  | 3 | s/%x/$self->{_max_clients}/ge; | 
|  | 0 |  |  |  |  | 0 |  | 
| 4752 | 2 |  |  |  |  | 11 | s/%%/%/g; | 
| 4753 |  |  |  |  |  |  |  | 
| 4754 | 2 |  |  |  |  | 12 | return $_; | 
| 4755 |  |  |  |  |  |  | } | 
| 4756 |  |  |  |  |  |  |  | 
| 4757 |  |  |  |  |  |  | sub _anon_passwd_validate_rfc822 | 
| 4758 |  |  |  |  |  |  | { | 
| 4759 | 4 |  |  | 4 |  | 10 | my $self = shift; | 
| 4760 | 4 |  |  |  |  | 8 | my $pass = shift; | 
| 4761 |  |  |  |  |  |  |  | 
| 4762 |  |  |  |  |  |  | # RFC 822 section 6.1, ``addr-spec''. | 
| 4763 |  |  |  |  |  |  | # But in fact this is not very careful about checking | 
| 4764 |  |  |  |  |  |  | # the address. There's probably a Perl library I should | 
| 4765 |  |  |  |  |  |  | # be using here ... XXX | 
| 4766 | 4 |  |  |  |  | 79 | return $pass =~ /^\S+\@\S+\.\S+$/; | 
| 4767 |  |  |  |  |  |  | } | 
| 4768 |  |  |  |  |  |  |  | 
| 4769 |  |  |  |  |  |  | sub _anon_passwd_validate_nobrowser | 
| 4770 |  |  |  |  |  |  | { | 
| 4771 | 2 |  |  | 2 |  | 5 | my $self = shift; | 
| 4772 | 2 |  |  |  |  | 4 | my $pass = shift; | 
| 4773 |  |  |  |  |  |  |  | 
| 4774 |  |  |  |  |  |  | return | 
| 4775 | 2 |  | 66 |  |  | 15 | $self->_anon_passwd_validate_rfc822 ($pass) && | 
| 4776 |  |  |  |  |  |  | $pass !~ /^mozilla@/ && | 
| 4777 |  |  |  |  |  |  | $pass !~ /^IE[0-9]+User@/ && | 
| 4778 |  |  |  |  |  |  | $pass !~ /^nobody@/; | 
| 4779 |  |  |  |  |  |  | } | 
| 4780 |  |  |  |  |  |  |  | 
| 4781 |  |  |  |  |  |  | sub _anon_passwd_validate_trivial | 
| 4782 |  |  |  |  |  |  | { | 
| 4783 | 2 |  |  | 2 |  | 4 | my $self = shift; | 
| 4784 | 2 |  |  |  |  | 4 | my $pass = shift; | 
| 4785 |  |  |  |  |  |  |  | 
| 4786 | 2 |  |  |  |  | 18 | return $pass =~ /\@/; | 
| 4787 |  |  |  |  |  |  | } | 
| 4788 |  |  |  |  |  |  |  | 
| 4789 |  |  |  |  |  |  | # Assuming we are running as root, drop privileges and change | 
| 4790 |  |  |  |  |  |  | # to user called $username who has uid $uid and gid $gid. There | 
| 4791 |  |  |  |  |  |  | # is no interface to initgroups, so we have to do that by | 
| 4792 |  |  |  |  |  |  | # hand -- yuck. | 
| 4793 |  |  |  |  |  |  | sub _drop_privs | 
| 4794 |  |  |  |  |  |  | { | 
| 4795 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 4796 | 0 |  |  |  |  | 0 | my $uid = shift; | 
| 4797 | 0 |  |  |  |  | 0 | my $gid = shift; | 
| 4798 | 0 |  |  |  |  | 0 | my $username = shift; | 
| 4799 |  |  |  |  |  |  |  | 
| 4800 |  |  |  |  |  |  | # Get the list of extra groups to pass to setgroups(2). | 
| 4801 | 0 |  |  |  |  | 0 | my @groups = (); | 
| 4802 |  |  |  |  |  |  |  | 
| 4803 | 0 |  |  |  |  | 0 | my @g; | 
| 4804 | 0 |  |  |  |  | 0 | while (@g = getgrent) | 
| 4805 |  |  |  |  |  |  | { | 
| 4806 | 0 |  |  |  |  | 0 | my ($gr_name, $gr_passwd, $gr_gid, $gr_members) = @g; | 
| 4807 | 0 |  |  |  |  | 0 | my @members = split /\s+/, $gr_members; | 
| 4808 |  |  |  |  |  |  |  | 
| 4809 | 0 |  |  |  |  | 0 | foreach (@members) | 
| 4810 |  |  |  |  |  |  | { | 
| 4811 | 0 | 0 |  |  |  | 0 | push @groups, $gr_gid if $_ eq $username; | 
| 4812 |  |  |  |  |  |  | } | 
| 4813 |  |  |  |  |  |  | } | 
| 4814 |  |  |  |  |  |  |  | 
| 4815 | 0 |  |  |  |  | 0 | setgrent;			# Rewind the pointer. | 
| 4816 |  |  |  |  |  |  |  | 
| 4817 |  |  |  |  |  |  | # Set the effective GID/UID. | 
| 4818 | 0 |  |  |  |  | 0 | $) = join (" ", $gid, $gid, @groups); | 
| 4819 | 0 |  |  |  |  | 0 | $> = $uid; | 
| 4820 |  |  |  |  |  |  |  | 
| 4821 |  |  |  |  |  |  | # set the real GID/UID if we are going to use non-priv port | 
| 4822 |  |  |  |  |  |  | # Otherwise, keep root access so we can bind to the port | 
| 4823 | 0 | 0 |  |  |  | 0 | if (my $ftpdata = $self->{ftp_data_port}) | 
| 4824 |  |  |  |  |  |  | { | 
| 4825 | 0 | 0 |  |  |  | 0 | if ( $ftpdata >= 1024 ) | 
| 4826 |  |  |  |  |  |  | { | 
| 4827 | 0 |  |  |  |  | 0 | $( = $gid; | 
| 4828 | 0 |  |  |  |  | 0 | $< = $uid; | 
| 4829 |  |  |  |  |  |  | } | 
| 4830 |  |  |  |  |  |  | } | 
| 4831 |  |  |  |  |  |  | } | 
| 4832 |  |  |  |  |  |  |  | 
| 4833 |  |  |  |  |  |  | sub _ACCT_command | 
| 4834 |  |  |  |  |  |  | { | 
| 4835 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 4836 | 0 |  |  |  |  | 0 | my $cmd = shift; | 
| 4837 | 0 |  |  |  |  | 0 | my $rest = shift; | 
| 4838 |  |  |  |  |  |  |  | 
| 4839 |  |  |  |  |  |  | # Not likely that the ACCT command will ever be implemented, | 
| 4840 |  |  |  |  |  |  | # unless there is some strange login method that needs to be | 
| 4841 |  |  |  |  |  |  | # supported. | 
| 4842 | 0 |  |  |  |  | 0 | $self->reply (500, "Command not implemented."); | 
| 4843 |  |  |  |  |  |  | } | 
| 4844 |  |  |  |  |  |  |  | 
| 4845 |  |  |  |  |  |  | sub _CWD_command | 
| 4846 |  |  |  |  |  |  | { | 
| 4847 | 7 |  |  | 7 |  | 15 | my $self = shift; | 
| 4848 | 7 |  |  |  |  | 16 | my $cmd = shift; | 
| 4849 | 7 |  |  |  |  | 14 | my $rest = shift; | 
| 4850 |  |  |  |  |  |  |  | 
| 4851 | 7 |  |  |  |  | 15 | my $new_cwd; | 
| 4852 |  |  |  |  |  |  |  | 
| 4853 |  |  |  |  |  |  | # Look relative to the current directory first. | 
| 4854 | 7 | 100 |  |  |  | 28 | if ($new_cwd = $self->_chdir ($self->{cwd}, $rest)) | 
| 4855 |  |  |  |  |  |  | { | 
| 4856 |  |  |  |  |  |  | # Access control | 
| 4857 | 6 | 50 |  |  |  | 27 | unless ($self->_eval_rule ("chdir rule", | 
| 4858 |  |  |  |  |  |  | $new_cwd->pathname, $new_cwd->filename, | 
| 4859 |  |  |  |  |  |  | $new_cwd->pathname)) | 
| 4860 |  |  |  |  |  |  | { | 
| 4861 | 0 |  |  |  |  | 0 | $self->reply (550, "CWD command denied by server configuration."); | 
| 4862 | 0 |  |  |  |  | 0 | return; | 
| 4863 |  |  |  |  |  |  | } | 
| 4864 |  |  |  |  |  |  |  | 
| 4865 | 6 |  |  |  |  | 28 | $self->{cwd} = $new_cwd; | 
| 4866 | 6 |  |  |  |  | 40 | $self->_chdir_message; | 
| 4867 | 6 |  |  |  |  | 23 | return; | 
| 4868 |  |  |  |  |  |  | } | 
| 4869 |  |  |  |  |  |  |  | 
| 4870 |  |  |  |  |  |  | # Look for an alias called ``$rest''. | 
| 4871 | 1 | 50 |  |  |  | 12 | if ($rest !~ /\//) | 
| 4872 |  |  |  |  |  |  | { | 
| 4873 | 1 |  |  |  |  | 4 | my @aliases = $self->config ("alias"); | 
| 4874 |  |  |  |  |  |  |  | 
| 4875 | 1 |  |  |  |  | 3 | foreach (@aliases) | 
| 4876 |  |  |  |  |  |  | { | 
| 4877 | 0 |  |  |  |  | 0 | my ($name, $dir) = split /\s+/, $_; | 
| 4878 |  |  |  |  |  |  |  | 
| 4879 | 0 | 0 | 0 |  |  | 0 | if ($name eq $rest && | 
| 4880 |  |  |  |  |  |  | ($new_cwd = $self->_chdir ($self->{cwd}, $dir))) | 
| 4881 |  |  |  |  |  |  | { | 
| 4882 | 0 |  |  |  |  | 0 | $self->{cwd} = $new_cwd; | 
| 4883 | 0 |  |  |  |  | 0 | $self->_chdir_message; | 
| 4884 | 0 |  |  |  |  | 0 | return; | 
| 4885 |  |  |  |  |  |  | } | 
| 4886 |  |  |  |  |  |  | } | 
| 4887 |  |  |  |  |  |  | } | 
| 4888 |  |  |  |  |  |  |  | 
| 4889 |  |  |  |  |  |  | # Look for a directory on the cdpath. | 
| 4890 | 1 | 50 |  |  |  | 3 | if ($self->config ("cdpath")) | 
| 4891 |  |  |  |  |  |  | { | 
| 4892 | 0 |  |  |  |  | 0 | my @cdpath = split /\s+/, $self->config ("cdpath"); | 
| 4893 |  |  |  |  |  |  |  | 
| 4894 | 0 |  |  |  |  | 0 | foreach (@cdpath) | 
| 4895 |  |  |  |  |  |  | { | 
| 4896 | 0 | 0 | 0 |  |  | 0 | if (($new_cwd = $self->_chdir ($self->{cwd}, $_)) && | 
| 4897 |  |  |  |  |  |  | ($new_cwd = $self->_chdir ($new_cwd, $rest))) | 
| 4898 |  |  |  |  |  |  | { | 
| 4899 | 0 |  |  |  |  | 0 | $self->{cwd} = $new_cwd; | 
| 4900 | 0 |  |  |  |  | 0 | $self->_chdir_message; | 
| 4901 | 0 |  |  |  |  | 0 | return; | 
| 4902 |  |  |  |  |  |  | } | 
| 4903 |  |  |  |  |  |  | } | 
| 4904 |  |  |  |  |  |  | } | 
| 4905 |  |  |  |  |  |  |  | 
| 4906 |  |  |  |  |  |  | # All change directory methods failed. | 
| 4907 | 1 |  |  |  |  | 6 | $self->reply (550, "Directory not found."); | 
| 4908 |  |  |  |  |  |  | } | 
| 4909 |  |  |  |  |  |  |  | 
| 4910 |  |  |  |  |  |  | sub _CDUP_command | 
| 4911 |  |  |  |  |  |  | { | 
| 4912 | 2 |  |  | 2 |  | 3 | my $self = shift; | 
| 4913 | 2 |  |  |  |  | 4 | my $cmd = shift; | 
| 4914 | 2 |  |  |  |  | 3 | my $rest = shift; | 
| 4915 |  |  |  |  |  |  |  | 
| 4916 | 2 | 50 |  |  |  | 5 | if (my $new_cwd = $self->_chdir ($self->{cwd}, "..")) | 
| 4917 |  |  |  |  |  |  | { | 
| 4918 |  |  |  |  |  |  | # Access control | 
| 4919 | 2 | 50 |  |  |  | 6 | unless ($self->_eval_rule ("chdir rule", | 
| 4920 |  |  |  |  |  |  | $new_cwd->pathname, $new_cwd->filename, | 
| 4921 |  |  |  |  |  |  | $new_cwd->pathname)) | 
| 4922 |  |  |  |  |  |  | { | 
| 4923 | 0 |  |  |  |  | 0 | $self->reply (550, "CDUP command denied by server configuration."); | 
| 4924 | 0 |  |  |  |  | 0 | return; | 
| 4925 |  |  |  |  |  |  | } | 
| 4926 |  |  |  |  |  |  |  | 
| 4927 | 2 |  |  |  |  | 6 | $self->{cwd} = $new_cwd; | 
| 4928 | 2 |  |  |  |  | 5 | $self->_chdir_message; | 
| 4929 |  |  |  |  |  |  | } | 
| 4930 |  |  |  |  |  |  | else | 
| 4931 |  |  |  |  |  |  | { | 
| 4932 | 0 |  |  |  |  | 0 | $self->reply (550, "Directory not found."); | 
| 4933 |  |  |  |  |  |  | } | 
| 4934 |  |  |  |  |  |  | } | 
| 4935 |  |  |  |  |  |  |  | 
| 4936 |  |  |  |  |  |  | # This little function displays the contents of a special | 
| 4937 |  |  |  |  |  |  | # message file the first time a user visits a directory, | 
| 4938 |  |  |  |  |  |  | # if this capability has been configured in. | 
| 4939 |  |  |  |  |  |  |  | 
| 4940 |  |  |  |  |  |  | sub _chdir_message | 
| 4941 |  |  |  |  |  |  | { | 
| 4942 | 8 |  |  | 8 |  | 19 | my $self = shift; | 
| 4943 |  |  |  |  |  |  |  | 
| 4944 | 8 |  |  |  |  | 19 | my $filename = $self->config ("chdir message file"); | 
| 4945 | 8 |  |  |  |  | 15 | my $file; | 
| 4946 |  |  |  |  |  |  |  | 
| 4947 | 8 | 50 | 33 |  |  | 31 | if ($filename && | 
|  |  |  | 33 |  |  |  |  | 
| 4948 |  |  |  |  |  |  | ! exists $self->{_chdir_message_cache}{$self->{cwd}->pathname} && | 
| 4949 |  |  |  |  |  |  | ($file = $self->{cwd}->open ($filename, "r"))) | 
| 4950 |  |  |  |  |  |  | { | 
| 4951 | 0 |  |  |  |  | 0 | my @lines = (); | 
| 4952 | 0 |  |  |  |  | 0 | local $_; | 
| 4953 |  |  |  |  |  |  |  | 
| 4954 |  |  |  |  |  |  | # Read the file into memory and perform % escaping. | 
| 4955 | 0 |  |  |  |  | 0 | while (defined ($_ = $file->getline)) | 
| 4956 |  |  |  |  |  |  | { | 
| 4957 | 0 |  |  |  |  | 0 | s/[\n\r]+$//; | 
| 4958 | 0 |  |  |  |  | 0 | push @lines, $self->_percent_substitutions ($_); | 
| 4959 |  |  |  |  |  |  | } | 
| 4960 | 0 |  |  |  |  | 0 | $file->close; | 
| 4961 |  |  |  |  |  |  |  | 
| 4962 |  |  |  |  |  |  | # Remember that we've visited this directory once in | 
| 4963 |  |  |  |  |  |  | # this session. | 
| 4964 | 0 |  |  |  |  | 0 | $self->{_chdir_message_cache}{$self->{cwd}->pathname} = 1; | 
| 4965 |  |  |  |  |  |  |  | 
| 4966 | 0 |  |  |  |  | 0 | $self->reply (250, @lines, "Changed directory OK."); | 
| 4967 |  |  |  |  |  |  | } | 
| 4968 |  |  |  |  |  |  | else | 
| 4969 |  |  |  |  |  |  | { | 
| 4970 | 8 |  |  |  |  | 29 | $self->reply (250, "Changed directory OK."); | 
| 4971 |  |  |  |  |  |  | } | 
| 4972 |  |  |  |  |  |  | } | 
| 4973 |  |  |  |  |  |  |  | 
| 4974 |  |  |  |  |  |  | sub _SMNT_command | 
| 4975 |  |  |  |  |  |  | { | 
| 4976 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 4977 | 0 |  |  |  |  | 0 | my $cmd = shift; | 
| 4978 | 0 |  |  |  |  | 0 | my $rest = shift; | 
| 4979 |  |  |  |  |  |  |  | 
| 4980 |  |  |  |  |  |  | # Not a very useful command. | 
| 4981 | 0 |  |  |  |  | 0 | $self->reply (500, "Command not implemented."); | 
| 4982 |  |  |  |  |  |  | } | 
| 4983 |  |  |  |  |  |  |  | 
| 4984 |  |  |  |  |  |  | sub _REIN_command | 
| 4985 |  |  |  |  |  |  | { | 
| 4986 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 4987 | 0 |  |  |  |  | 0 | my $cmd = shift; | 
| 4988 | 0 |  |  |  |  | 0 | my $rest = shift; | 
| 4989 |  |  |  |  |  |  |  | 
| 4990 |  |  |  |  |  |  | # This command is not implemented, because we do not allow a | 
| 4991 |  |  |  |  |  |  | # user to revoke permissions and relogin (without disconnecting | 
| 4992 |  |  |  |  |  |  | # and reconnecting anyway). | 
| 4993 | 0 |  |  |  |  | 0 | $self->reply (500, "The REIN command is not supported. You must QUIT and reconnect."); | 
| 4994 |  |  |  |  |  |  | } | 
| 4995 |  |  |  |  |  |  |  | 
| 4996 |  |  |  |  |  |  | sub _QUIT_command | 
| 4997 |  |  |  |  |  |  | { | 
| 4998 |  |  |  |  |  |  | # This function should never be called. The server main command loop | 
| 4999 |  |  |  |  |  |  | # now deals with the "QUIT" command as a special case. | 
| 5000 | 0 |  |  | 0 |  | 0 | die; | 
| 5001 |  |  |  |  |  |  | } | 
| 5002 |  |  |  |  |  |  |  | 
| 5003 |  |  |  |  |  |  | sub _PORT_command | 
| 5004 |  |  |  |  |  |  | { | 
| 5005 | 6 |  |  | 6 |  | 13 | my $self = shift; | 
| 5006 | 6 |  |  |  |  | 12 | my $cmd = shift; | 
| 5007 | 6 |  |  |  |  | 11 | my $rest = shift; | 
| 5008 |  |  |  |  |  |  |  | 
| 5009 |  |  |  |  |  |  | # The arguments to PORT are a1,a2,a3,a4,p1,p2 where a1 is the | 
| 5010 |  |  |  |  |  |  | # most significant part of the address (eg. 127,0,0,1) and | 
| 5011 |  |  |  |  |  |  | # p1 is the most significant part of the port. | 
| 5012 |  |  |  |  |  |  | # | 
| 5013 |  |  |  |  |  |  | # Some clients (eg. IE 6.0.2600.0000 and IBM mainframes) send | 
| 5014 |  |  |  |  |  |  | # leading zeroes in front of the numbers, and apparently the RFC | 
| 5015 |  |  |  |  |  |  | # doesn't prevent this. So we must use the 'int' function to | 
| 5016 |  |  |  |  |  |  | # remove these leading zeroes. | 
| 5017 | 6 | 50 |  |  |  | 48 | unless ($rest =~ /^\s*(\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})/) | 
| 5018 |  |  |  |  |  |  | { | 
| 5019 | 0 |  |  |  |  | 0 | $self->reply (501, "Syntax error in PORT command."); | 
| 5020 | 0 |  |  |  |  | 0 | return; | 
| 5021 |  |  |  |  |  |  | } | 
| 5022 |  |  |  |  |  |  |  | 
| 5023 | 6 |  |  |  |  | 28 | my $a1 = int ($1); | 
| 5024 | 6 |  |  |  |  | 12 | my $a2 = int ($2); | 
| 5025 | 6 |  |  |  |  | 15 | my $a3 = int ($3); | 
| 5026 | 6 |  |  |  |  | 27 | my $a4 = int ($4); | 
| 5027 | 6 |  |  |  |  | 16 | my $p1 = int ($5); | 
| 5028 | 6 |  |  |  |  | 12 | my $p2 = int ($6); | 
| 5029 |  |  |  |  |  |  |  | 
| 5030 |  |  |  |  |  |  | # Check host address. | 
| 5031 | 6 | 50 | 33 |  |  | 106 | unless ($a1 > 0 && $a1 < 224 && | 
|  |  |  | 33 |  |  |  |  | 
|  |  |  | 33 |  |  |  |  | 
|  |  |  | 33 |  |  |  |  | 
|  |  |  | 33 |  |  |  |  | 
|  |  |  | 33 |  |  |  |  | 
|  |  |  | 33 |  |  |  |  | 
| 5032 |  |  |  |  |  |  | $a2 >= 0 && $a2 < 256 && | 
| 5033 |  |  |  |  |  |  | $a3 >= 0 && $a3 < 256 && | 
| 5034 |  |  |  |  |  |  | $a4 >= 0 && $a4 < 256) | 
| 5035 |  |  |  |  |  |  | { | 
| 5036 | 0 |  |  |  |  | 0 | $self->reply (501, "Invalid host address."); | 
| 5037 | 0 |  |  |  |  | 0 | return; | 
| 5038 |  |  |  |  |  |  | } | 
| 5039 |  |  |  |  |  |  |  | 
| 5040 |  |  |  |  |  |  | # Construct host address. | 
| 5041 | 6 |  |  |  |  | 27 | my $hostaddrstring = "$a1.$a2.$a3.$a4"; | 
| 5042 |  |  |  |  |  |  |  | 
| 5043 |  |  |  |  |  |  | # Are we connecting back to the client? | 
| 5044 | 6 | 50 |  |  |  | 16 | unless ($self->config ("allow proxy ftp")) | 
| 5045 |  |  |  |  |  |  | { | 
| 5046 | 6 | 50 | 33 |  |  | 21 | if (!$self->{_test_mode} && $hostaddrstring ne $self->{peeraddrstring}) | 
| 5047 |  |  |  |  |  |  | { | 
| 5048 |  |  |  |  |  |  | # See RFC 2577 section 3. | 
| 5049 | 0 |  |  |  |  | 0 | $self->reply (504, "Proxy FTP is not allowed on this server."); | 
| 5050 | 0 |  |  |  |  | 0 | return; | 
| 5051 |  |  |  |  |  |  | } | 
| 5052 |  |  |  |  |  |  | } | 
| 5053 |  |  |  |  |  |  |  | 
| 5054 |  |  |  |  |  |  | # Construct port number. | 
| 5055 | 6 |  |  |  |  | 19 | my $hostport = $p1 * 256 + $p2; | 
| 5056 |  |  |  |  |  |  |  | 
| 5057 |  |  |  |  |  |  | # Check port number. | 
| 5058 | 6 | 50 | 33 |  |  | 35 | unless ($hostport > 0 && $hostport < 65536) | 
| 5059 |  |  |  |  |  |  | { | 
| 5060 | 0 |  |  |  |  | 0 | $self->reply (501, "Invalid port number."); | 
| 5061 |  |  |  |  |  |  | } | 
| 5062 |  |  |  |  |  |  |  | 
| 5063 |  |  |  |  |  |  | # Allow connections back to ports < 1024? | 
| 5064 | 6 | 50 |  |  |  | 16 | unless ($self->config ("allow connect low port")) | 
| 5065 |  |  |  |  |  |  | { | 
| 5066 | 6 | 50 |  |  |  | 17 | if ($hostport < 1024) | 
| 5067 |  |  |  |  |  |  | { | 
| 5068 |  |  |  |  |  |  | # See RFC 2577 section 3. | 
| 5069 | 0 |  |  |  |  | 0 | $self->reply (504, "This server will not connect back to ports < 1024."); | 
| 5070 | 0 |  |  |  |  | 0 | return; | 
| 5071 |  |  |  |  |  |  | } | 
| 5072 |  |  |  |  |  |  | } | 
| 5073 |  |  |  |  |  |  |  | 
| 5074 | 6 |  |  |  |  | 14 | $self->{_hostaddrstring} = $hostaddrstring; | 
| 5075 | 6 |  |  |  |  | 30 | $self->{_hostaddr} = inet_aton ($hostaddrstring); | 
| 5076 | 6 |  |  |  |  | 16 | $self->{_hostport} = $hostport; | 
| 5077 | 6 |  |  |  |  | 12 | $self->{_passive} = 0; | 
| 5078 |  |  |  |  |  |  |  | 
| 5079 | 6 |  |  |  |  | 17 | $self->reply (200, "PORT command OK."); | 
| 5080 |  |  |  |  |  |  | } | 
| 5081 |  |  |  |  |  |  |  | 
| 5082 |  |  |  |  |  |  | sub _PASV_command | 
| 5083 |  |  |  |  |  |  | { | 
| 5084 | 12 |  |  | 12 |  | 36 | my $self = shift; | 
| 5085 | 12 |  |  |  |  | 29 | my $cmd = shift; | 
| 5086 | 12 |  |  |  |  | 30 | my $rest = shift; | 
| 5087 |  |  |  |  |  |  |  | 
| 5088 |  |  |  |  |  |  | # Open a listening socket - but don't actually accept on it yet. | 
| 5089 |  |  |  |  |  |  | # RFC 2577 section 8 suggests using random local port numbers. | 
| 5090 |  |  |  |  |  |  | # In order to make firewall rules on FTP servers more sane, make | 
| 5091 |  |  |  |  |  |  | # the range of local port numbers configurable, and default to | 
| 5092 |  |  |  |  |  |  | # only opening ports in the range 49152-65535 (see: | 
| 5093 |  |  |  |  |  |  | # http://www.isi.edu/in-notes/iana/assignments/port-numbers for | 
| 5094 |  |  |  |  |  |  | # rationale). | 
| 5095 | 12 |  |  |  |  | 47 | my $port_range = $self->config ("passive port range"); | 
| 5096 | 12 | 50 |  |  |  | 99 | $port_range = "49152-65535" unless defined $port_range; | 
| 5097 |  |  |  |  |  |  |  | 
| 5098 | 12 |  |  |  |  | 34 | my $sock; | 
| 5099 |  |  |  |  |  |  |  | 
| 5100 | 12 | 50 |  |  |  | 47 | if ($port_range eq "0") | 
| 5101 |  |  |  |  |  |  | { | 
| 5102 |  |  |  |  |  |  | # Use the standard kernel determined ephemeral port | 
| 5103 |  |  |  |  |  |  | # by leaving off LocalPort parameter. | 
| 5104 | 0 |  |  |  |  | 0 | "0" =~ /(0)/; # Perl 5.7 / IO::Socket::INET bug workaround. | 
| 5105 |  |  |  |  |  |  | $sock = IO::Socket::INET->new | 
| 5106 |  |  |  |  |  |  | (Listen => 1, | 
| 5107 |  |  |  |  |  |  | LocalAddr => $self->{sockaddrstring}, | 
| 5108 | 0 |  |  |  |  | 0 | Reuse => 1, | 
| 5109 |  |  |  |  |  |  | Proto => "tcp", | 
| 5110 |  |  |  |  |  |  | Type => SOCK_STREAM); | 
| 5111 |  |  |  |  |  |  | } | 
| 5112 |  |  |  |  |  |  | else | 
| 5113 |  |  |  |  |  |  | { | 
| 5114 |  |  |  |  |  |  | # Parse the $port_range string and assign a port from the | 
| 5115 |  |  |  |  |  |  | # range at random. | 
| 5116 | 12 |  |  |  |  | 74 | my @ranges = split /\s*,\s*/, $port_range; | 
| 5117 | 12 |  |  |  |  | 37 | my $total_width = 0; | 
| 5118 | 12 |  |  |  |  | 38 | foreach (@ranges) | 
| 5119 |  |  |  |  |  |  | { | 
| 5120 | 12 |  |  |  |  | 177 | my ($min, $max) = split /\s*-\s*/, $_; | 
| 5121 | 12 |  |  |  |  | 83 | $_ = [ $min, $max, $max - $min + 1 ]; | 
| 5122 | 12 |  |  |  |  | 42 | $total_width += $_->[2]; | 
| 5123 |  |  |  |  |  |  | } | 
| 5124 |  |  |  |  |  |  |  | 
| 5125 |  |  |  |  |  |  | # XXX We need to use a secure source of random numbers here, otherwise | 
| 5126 |  |  |  |  |  |  | # this is a little bit pointless. | 
| 5127 | 12 |  |  |  |  | 27 | my $count = 100; | 
| 5128 |  |  |  |  |  |  |  | 
| 5129 | 12 |  | 66 |  |  | 125 | until (defined $sock || --$count == 0) | 
| 5130 |  |  |  |  |  |  | { | 
| 5131 | 12 |  |  |  |  | 421 | my $n = int (rand $total_width); | 
| 5132 | 12 |  |  |  |  | 30 | my $port; | 
| 5133 | 12 |  |  |  |  | 34 | foreach (@ranges) | 
| 5134 |  |  |  |  |  |  | { | 
| 5135 | 12 | 50 |  |  |  | 48 | if ($n < $_->[2]) | 
| 5136 |  |  |  |  |  |  | { | 
| 5137 | 12 |  |  |  |  | 30 | $port = $_->[0] + $n; | 
| 5138 | 12 |  |  |  |  | 28 | last; | 
| 5139 |  |  |  |  |  |  | } | 
| 5140 | 0 |  |  |  |  | 0 | $n -= $_->[2]; | 
| 5141 |  |  |  |  |  |  | } | 
| 5142 |  |  |  |  |  |  |  | 
| 5143 | 12 |  |  |  |  | 76 | "0" =~ /(0)/; # Perl 5.7 / IO::Socket::INET bug workaround. | 
| 5144 |  |  |  |  |  |  | $sock = IO::Socket::INET->new | 
| 5145 |  |  |  |  |  |  | (Listen => 1, | 
| 5146 |  |  |  |  |  |  | LocalAddr => $self->{sockaddrstring}, | 
| 5147 | 12 |  |  |  |  | 386 | LocalPort => $port, | 
| 5148 |  |  |  |  |  |  | Reuse => 1, | 
| 5149 |  |  |  |  |  |  | Proto => "tcp", | 
| 5150 |  |  |  |  |  |  | Type => SOCK_STREAM); | 
| 5151 |  |  |  |  |  |  | } | 
| 5152 |  |  |  |  |  |  | } | 
| 5153 |  |  |  |  |  |  |  | 
| 5154 | 12 | 50 |  |  |  | 5932 | unless ($sock) | 
| 5155 |  |  |  |  |  |  | { | 
| 5156 |  |  |  |  |  |  | # Return a code 550 here, even though this is not in the RFC. XXX | 
| 5157 | 0 |  |  |  |  | 0 | $self->reply (550, "Can't open a listening socket."); | 
| 5158 | 0 |  |  |  |  | 0 | return; | 
| 5159 |  |  |  |  |  |  | } | 
| 5160 |  |  |  |  |  |  |  | 
| 5161 | 12 |  |  |  |  | 39 | $self->{_passive} = 1; | 
| 5162 | 12 |  |  |  |  | 89 | $self->{_passive_sock} = $sock; | 
| 5163 |  |  |  |  |  |  |  | 
| 5164 |  |  |  |  |  |  | # Get our port number. | 
| 5165 | 12 |  |  |  |  | 68 | my $sockport = $sock->sockport; | 
| 5166 |  |  |  |  |  |  |  | 
| 5167 |  |  |  |  |  |  | # Split the port number into high and low components. | 
| 5168 | 12 |  |  |  |  | 554 | my $p1 = int ($sockport / 256); | 
| 5169 | 12 |  |  |  |  | 44 | my $p2 = $sockport % 256; | 
| 5170 |  |  |  |  |  |  |  | 
| 5171 | 12 | 50 |  |  |  | 57 | unless ($self->{_test_mode}) | 
| 5172 |  |  |  |  |  |  | { | 
| 5173 | 0 |  |  |  |  | 0 | my $sockaddrstring = $self->{sockaddrstring}; | 
| 5174 |  |  |  |  |  |  |  | 
| 5175 |  |  |  |  |  |  | # We will need to revise this for IPv6 XXX | 
| 5176 | 0 | 0 |  |  |  | 0 | die | 
| 5177 |  |  |  |  |  |  | unless $sockaddrstring =~ /^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$/; | 
| 5178 |  |  |  |  |  |  |  | 
| 5179 |  |  |  |  |  |  | # Be very precise about this error message, since most clients | 
| 5180 |  |  |  |  |  |  | # will have to parse the whole of it. | 
| 5181 | 0 |  |  |  |  | 0 | $self->reply (227, "Entering Passive Mode ($1,$2,$3,$4,$p1,$p2)"); | 
| 5182 |  |  |  |  |  |  | } | 
| 5183 |  |  |  |  |  |  | else | 
| 5184 |  |  |  |  |  |  | { | 
| 5185 |  |  |  |  |  |  | # Test mode: connect back to localhost. | 
| 5186 | 12 |  |  |  |  | 77 | $self->reply (227, "Entering Passive Mode (127,0,0,1,$p1,$p2)"); | 
| 5187 |  |  |  |  |  |  | } | 
| 5188 |  |  |  |  |  |  | } | 
| 5189 |  |  |  |  |  |  |  | 
| 5190 |  |  |  |  |  |  | sub _TYPE_command | 
| 5191 |  |  |  |  |  |  | { | 
| 5192 | 20 |  |  | 20 |  | 53 | my $self = shift; | 
| 5193 | 20 |  |  |  |  | 55 | my $cmd = shift; | 
| 5194 | 20 |  |  |  |  | 43 | my $rest = shift; | 
| 5195 |  |  |  |  |  |  |  | 
| 5196 |  |  |  |  |  |  | # See RFC 959 section 5.3.2. | 
| 5197 | 20 | 100 |  |  |  | 173 | if ($rest =~ /^([AI])$/i) | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 5198 |  |  |  |  |  |  | { | 
| 5199 | 17 |  |  |  |  | 75 | $self->{type} = uc $1; | 
| 5200 |  |  |  |  |  |  | } | 
| 5201 |  |  |  |  |  |  | elsif ($rest =~ /^([AI])\sN$/i) | 
| 5202 |  |  |  |  |  |  | { | 
| 5203 | 2 |  |  |  |  | 5 | $self->{type} = uc $1; | 
| 5204 |  |  |  |  |  |  | } | 
| 5205 |  |  |  |  |  |  | elsif ($rest =~ /^L\s8$/i) | 
| 5206 |  |  |  |  |  |  | { | 
| 5207 | 1 |  |  |  |  | 2 | $self->{type} = 'L8'; | 
| 5208 |  |  |  |  |  |  | } | 
| 5209 |  |  |  |  |  |  | else | 
| 5210 |  |  |  |  |  |  | { | 
| 5211 | 0 |  |  |  |  | 0 | $self->reply (504, "This server does not support TYPE $rest."); | 
| 5212 | 0 |  |  |  |  | 0 | return; | 
| 5213 |  |  |  |  |  |  | } | 
| 5214 |  |  |  |  |  |  |  | 
| 5215 | 20 |  |  |  |  | 112 | $self->reply (200, "TYPE changed to $rest."); | 
| 5216 |  |  |  |  |  |  | } | 
| 5217 |  |  |  |  |  |  |  | 
| 5218 |  |  |  |  |  |  | sub _STRU_command | 
| 5219 |  |  |  |  |  |  | { | 
| 5220 | 1 |  |  | 1 |  | 2 | my $self = shift; | 
| 5221 | 1 |  |  |  |  | 2 | my $cmd = shift; | 
| 5222 | 1 |  |  |  |  | 3 | my $rest = shift; | 
| 5223 |  |  |  |  |  |  |  | 
| 5224 |  |  |  |  |  |  | # See RFC 959 section 5.3.2. | 
| 5225 |  |  |  |  |  |  | # Although this defies the RFC, I'm not going to support | 
| 5226 |  |  |  |  |  |  | # record or page structure. TOPS-20 didn't really take off | 
| 5227 |  |  |  |  |  |  | # as an operating system in the 90s ... | 
| 5228 | 1 | 50 |  |  |  | 4 | if ($rest =~ /^F$/i) | 
| 5229 |  |  |  |  |  |  | { | 
| 5230 | 1 |  |  |  |  | 3 | $self->{stru} = 'F'; | 
| 5231 |  |  |  |  |  |  | } | 
| 5232 |  |  |  |  |  |  | else | 
| 5233 |  |  |  |  |  |  | { | 
| 5234 | 0 |  |  |  |  | 0 | $self->reply (504, "This server does not support STRU $rest."); | 
| 5235 | 0 |  |  |  |  | 0 | return; | 
| 5236 |  |  |  |  |  |  | } | 
| 5237 |  |  |  |  |  |  |  | 
| 5238 | 1 |  |  |  |  | 5 | $self->reply (200, "STRU changed to $rest."); | 
| 5239 |  |  |  |  |  |  | } | 
| 5240 |  |  |  |  |  |  |  | 
| 5241 |  |  |  |  |  |  | sub _MODE_command | 
| 5242 |  |  |  |  |  |  | { | 
| 5243 | 1 |  |  | 1 |  | 2 | my $self = shift; | 
| 5244 | 1 |  |  |  |  | 3 | my $cmd = shift; | 
| 5245 | 1 |  |  |  |  | 2 | my $rest = shift; | 
| 5246 |  |  |  |  |  |  |  | 
| 5247 |  |  |  |  |  |  | # See RFC 959 section 5.3.2. | 
| 5248 | 1 | 50 |  |  |  | 4 | if ($rest =~ /^S$/i) | 
| 5249 |  |  |  |  |  |  | { | 
| 5250 | 1 |  |  |  |  | 3 | $self->{mode} = 'S'; | 
| 5251 |  |  |  |  |  |  | } | 
| 5252 |  |  |  |  |  |  | else | 
| 5253 |  |  |  |  |  |  | { | 
| 5254 | 0 |  |  |  |  | 0 | $self->reply (504, "This server does not support MODE $rest."); | 
| 5255 | 0 |  |  |  |  | 0 | return; | 
| 5256 |  |  |  |  |  |  | } | 
| 5257 |  |  |  |  |  |  |  | 
| 5258 | 1 |  |  |  |  | 6 | $self->reply (200, "MODE changed to $rest."); | 
| 5259 |  |  |  |  |  |  | } | 
| 5260 |  |  |  |  |  |  |  | 
| 5261 |  |  |  |  |  |  | sub _RETR_command | 
| 5262 |  |  |  |  |  |  | { | 
| 5263 | 52 |  |  | 52 |  | 111 | my $self = shift; | 
| 5264 | 52 |  |  |  |  | 91 | my $cmd = shift; | 
| 5265 | 52 |  |  |  |  | 136 | my $rest = shift; | 
| 5266 |  |  |  |  |  |  |  | 
| 5267 |  |  |  |  |  |  | # Find file by name. | 
| 5268 | 52 |  |  |  |  | 193 | my ($dirh, $fileh, $filename) = $self->_get ($rest); | 
| 5269 | 52 |  |  |  |  | 124 | my ($generator, @filters); | 
| 5270 |  |  |  |  |  |  |  | 
| 5271 | 52 | 100 |  |  |  | 173 | unless ($fileh) | 
| 5272 |  |  |  |  |  |  | { | 
| 5273 |  |  |  |  |  |  | # No simple file by that name exists. Perhaps the user is | 
| 5274 |  |  |  |  |  |  | # requesting an automatic archive download? You are not | 
| 5275 |  |  |  |  |  |  | # expected to understand the following code unless you've | 
| 5276 |  |  |  |  |  |  | # read doc/archives.txt. | 
| 5277 |  |  |  |  |  |  |  | 
| 5278 |  |  |  |  |  |  | # Check archive mode is enabled. | 
| 5279 | 1 | 50 |  |  |  | 7 | unless ($self->{archive_mode}) | 
| 5280 |  |  |  |  |  |  | { | 
| 5281 | 0 |  |  |  |  | 0 | $self->reply (550, "File or directory not found."); | 
| 5282 | 0 |  |  |  |  | 0 | return; | 
| 5283 |  |  |  |  |  |  | } | 
| 5284 |  |  |  |  |  |  |  | 
| 5285 |  |  |  |  |  |  | ARCHIVE_CHECK: | 
| 5286 | 1 |  |  |  |  | 4 | for (;;) | 
| 5287 |  |  |  |  |  |  | { | 
| 5288 |  |  |  |  |  |  | # Matches filter extension? | 
| 5289 | 1 |  |  |  |  | 4 | foreach (keys %{$self->{archive_filters}}) | 
|  | 1 |  |  |  |  | 8 |  | 
| 5290 |  |  |  |  |  |  | { | 
| 5291 | 2 | 50 |  |  |  | 13 | if (lc (substr ($rest, -length ($_))) eq lc ($_)) | 
| 5292 |  |  |  |  |  |  | { | 
| 5293 | 0 |  |  |  |  | 0 | substr ($rest, -length ($_), length ($_), ""); | 
| 5294 | 0 |  |  |  |  | 0 | push @filters, $self->{archive_filters}{$_}; | 
| 5295 |  |  |  |  |  |  |  | 
| 5296 |  |  |  |  |  |  | # Does remainder of $rest correspond to a file? | 
| 5297 | 0 |  |  |  |  | 0 | ($dirh, $fileh, $filename) = $self->_get ($rest); | 
| 5298 |  |  |  |  |  |  |  | 
| 5299 | 0 | 0 |  |  |  | 0 | if ($fileh) | 
| 5300 |  |  |  |  |  |  | { | 
| 5301 | 0 |  |  |  |  | 0 | my ($mode) = $fileh->status; | 
| 5302 |  |  |  |  |  |  |  | 
| 5303 | 0 | 0 |  |  |  | 0 | if ($mode eq "f") | 
| 5304 |  |  |  |  |  |  | { | 
| 5305 | 0 |  |  |  |  | 0 | last ARCHIVE_CHECK; | 
| 5306 |  |  |  |  |  |  | } | 
| 5307 |  |  |  |  |  |  | } | 
| 5308 |  |  |  |  |  |  |  | 
| 5309 | 0 |  |  |  |  | 0 | next ARCHIVE_CHECK; | 
| 5310 |  |  |  |  |  |  | } | 
| 5311 |  |  |  |  |  |  | } | 
| 5312 |  |  |  |  |  |  |  | 
| 5313 |  |  |  |  |  |  | # Matches directory + generator extension? | 
| 5314 | 1 |  |  |  |  | 3 | foreach (keys %{$self->{archive_generators}}) | 
|  | 1 |  |  |  |  | 6 |  | 
| 5315 |  |  |  |  |  |  | { | 
| 5316 | 1 | 50 |  |  |  | 9 | if (lc (substr ($rest, -length ($_))) eq lc ($_)) | 
| 5317 |  |  |  |  |  |  | { | 
| 5318 | 1 |  |  |  |  | 5 | my $tmp = substr ($rest, 0, -length ($_)); | 
| 5319 | 1 |  |  |  |  | 5 | my $tmp_gen = $self->{archive_generators}{$_}; | 
| 5320 |  |  |  |  |  |  |  | 
| 5321 | 1 |  |  |  |  | 6 | ($dirh, $fileh, $filename) = $self->_get ($tmp); | 
| 5322 |  |  |  |  |  |  |  | 
| 5323 | 1 | 50 |  |  |  | 7 | if ($fileh) | 
| 5324 |  |  |  |  |  |  | { | 
| 5325 | 1 |  |  |  |  | 9 | my ($mode) = $fileh->status; | 
| 5326 |  |  |  |  |  |  |  | 
| 5327 | 1 | 50 |  |  |  | 8 | if ($mode eq "d") | 
| 5328 |  |  |  |  |  |  | { | 
| 5329 | 1 |  |  |  |  | 4 | $rest = $tmp; | 
| 5330 | 1 |  |  |  |  | 2 | $generator = $tmp_gen; | 
| 5331 | 1 |  |  |  |  | 6 | last ARCHIVE_CHECK; | 
| 5332 |  |  |  |  |  |  | } | 
| 5333 |  |  |  |  |  |  | } | 
| 5334 |  |  |  |  |  |  | } | 
| 5335 |  |  |  |  |  |  | } | 
| 5336 |  |  |  |  |  |  |  | 
| 5337 | 0 |  |  |  |  | 0 | $self->reply (550, "File or directory not found."); | 
| 5338 | 0 |  |  |  |  | 0 | return; | 
| 5339 |  |  |  |  |  |  | } # ARCHIVE_CHECK: for (;;) | 
| 5340 |  |  |  |  |  |  | } # unless ($fileh) | 
| 5341 |  |  |  |  |  |  |  | 
| 5342 |  |  |  |  |  |  | # Check access control. | 
| 5343 | 52 | 50 |  |  |  | 232 | unless ($self->_eval_rule ("retrieve rule", | 
| 5344 |  |  |  |  |  |  | $fileh->pathname, $filename, $dirh->pathname)) | 
| 5345 |  |  |  |  |  |  | { | 
| 5346 | 0 |  |  |  |  | 0 | $self->reply (550, "RETR command denied by server configuration."); | 
| 5347 | 0 |  |  |  |  | 0 | return; | 
| 5348 |  |  |  |  |  |  | } | 
| 5349 |  |  |  |  |  |  |  | 
| 5350 |  |  |  |  |  |  | # Check it's a simple file (unless we're using a generator to archive | 
| 5351 |  |  |  |  |  |  | # a directory, in which case it's OK). | 
| 5352 | 52 | 100 |  |  |  | 144 | unless ($generator) | 
| 5353 |  |  |  |  |  |  | { | 
| 5354 | 51 |  |  |  |  | 206 | my ($mode) = $fileh->status; | 
| 5355 | 51 | 50 |  |  |  | 194 | unless ($mode eq "f") | 
| 5356 |  |  |  |  |  |  | { | 
| 5357 | 0 |  |  |  |  | 0 | $self->reply (550, | 
| 5358 |  |  |  |  |  |  | "RETR command is only supported on plain files."); | 
| 5359 | 0 |  |  |  |  | 0 | return; | 
| 5360 |  |  |  |  |  |  | } | 
| 5361 |  |  |  |  |  |  | } | 
| 5362 |  |  |  |  |  |  |  | 
| 5363 |  |  |  |  |  |  | # Try to open the file. | 
| 5364 | 52 | 100 |  |  |  | 261 | my $file = !$generator ? $fileh->open ("r") : &$generator ($self, $fileh); | 
| 5365 |  |  |  |  |  |  |  | 
| 5366 | 52 | 50 |  |  |  | 2155 | unless ($file) | 
| 5367 |  |  |  |  |  |  | { | 
| 5368 | 0 |  |  |  |  | 0 | $self->reply (550, "File or directory not found."); | 
| 5369 | 0 |  |  |  |  | 0 | return; | 
| 5370 |  |  |  |  |  |  | } | 
| 5371 |  |  |  |  |  |  |  | 
| 5372 |  |  |  |  |  |  | $self->reply (150, | 
| 5373 |  |  |  |  |  |  | "Opening " . | 
| 5374 | 52 | 100 |  |  |  | 600 | ($self->{type} eq 'A' ? "ASCII mode" : "BINARY mode") . | 
| 5375 |  |  |  |  |  |  | " data connection for file $filename."); | 
| 5376 |  |  |  |  |  |  |  | 
| 5377 |  |  |  |  |  |  | # Open a path back to the client. | 
| 5378 | 52 |  |  |  |  | 192 | my $sock = $self->open_data_connection; | 
| 5379 |  |  |  |  |  |  |  | 
| 5380 | 52 | 50 |  |  |  | 186 | unless ($sock) | 
| 5381 |  |  |  |  |  |  | { | 
| 5382 | 0 |  |  |  |  | 0 | $self->reply (425, "Can't open data connection."); | 
| 5383 | 0 |  |  |  |  | 0 | return; | 
| 5384 |  |  |  |  |  |  | } | 
| 5385 |  |  |  |  |  |  |  | 
| 5386 |  |  |  |  |  |  | # If there are any filters to apply, do that now. | 
| 5387 | 52 |  |  |  |  | 106 | my @filter_objects; | 
| 5388 | 52 |  |  |  |  | 126 | foreach (@filters) | 
| 5389 |  |  |  |  |  |  | { | 
| 5390 | 0 |  |  |  |  | 0 | my $filter = &$_ ($self, $sock); | 
| 5391 |  |  |  |  |  |  |  | 
| 5392 | 0 | 0 |  |  |  | 0 | unless ($filter) | 
| 5393 |  |  |  |  |  |  | { | 
| 5394 | 0 |  |  |  |  | 0 | $self->reply (500, "Can't open filter program in archive mode."); | 
| 5395 | 0 |  |  |  |  | 0 | close $sock; | 
| 5396 | 0 |  |  |  |  | 0 | $self->_cleanup_filters (@filter_objects); | 
| 5397 | 0 |  |  |  |  | 0 | return; | 
| 5398 |  |  |  |  |  |  | } | 
| 5399 |  |  |  |  |  |  |  | 
| 5400 | 0 |  |  |  |  | 0 | unshift @filter_objects, $filter; | 
| 5401 | 0 |  |  |  |  | 0 | $sock = $filter->{sock}; | 
| 5402 |  |  |  |  |  |  | } | 
| 5403 |  |  |  |  |  |  |  | 
| 5404 |  |  |  |  |  |  | # Outgoing bandwidth | 
| 5405 | 52 | 50 |  |  |  | 143 | $self->xfer_start ($fileh->pathname, "o") if $self->{_xferlog}; | 
| 5406 |  |  |  |  |  |  |  | 
| 5407 | 52 |  |  |  |  | 82 | my $transfer_hook; | 
| 5408 |  |  |  |  |  |  |  | 
| 5409 |  |  |  |  |  |  | # What mode are we sending this file in? | 
| 5410 | 52 | 100 |  |  |  | 157 | unless ($self->{type} eq 'A') # Binary type. | 
| 5411 |  |  |  |  |  |  | { | 
| 5412 | 42 |  |  |  |  | 85 | my ($r, $buffer, $n, $w); | 
| 5413 |  |  |  |  |  |  |  | 
| 5414 |  |  |  |  |  |  | # Restart the connection from previous point? | 
| 5415 | 42 | 100 |  |  |  | 142 | if ($self->{_restart}) | 
| 5416 |  |  |  |  |  |  | { | 
| 5417 |  |  |  |  |  |  | # VFS seek method only required to support relative forward seeks | 
| 5418 |  |  |  |  |  |  | # | 
| 5419 |  |  |  |  |  |  | # In Perl = 5.00503, SEEK_CUR is exported by IO::Seekable, | 
| 5420 |  |  |  |  |  |  | # in Perl >= 5.6, SEEK_CUR is exported by both IO::Seekable | 
| 5421 |  |  |  |  |  |  | # and Fcntl. Hence we 'use IO::Seekable' at the top of the | 
| 5422 |  |  |  |  |  |  | # file to get this symbol reliably in both cases. | 
| 5423 | 4 |  |  |  |  | 27 | $file->sysseek ($self->{_restart}, SEEK_CUR); | 
| 5424 | 4 |  |  |  |  | 120 | $self->{_restart} = 0; | 
| 5425 |  |  |  |  |  |  | } | 
| 5426 |  |  |  |  |  |  |  | 
| 5427 |  |  |  |  |  |  | # Copy data. | 
| 5428 | 42 |  |  |  |  | 180 | while ($r = $file->sysread ($buffer, 65536)) | 
| 5429 |  |  |  |  |  |  | { | 
| 5430 | 73 | 50 |  |  |  | 2523 | $self->xfer ($r) if $self->{_xferlog}; | 
| 5431 |  |  |  |  |  |  |  | 
| 5432 |  |  |  |  |  |  | # Restart alarm clock timer. | 
| 5433 | 73 |  |  |  |  | 316 | alarm $self->{_idle_timeout}; | 
| 5434 |  |  |  |  |  |  |  | 
| 5435 | 73 | 50 |  |  |  | 216 | if ($transfer_hook | 
| 5436 |  |  |  |  |  |  | = $self->transfer_hook ("r", $file, $sock, \$buffer)) | 
| 5437 |  |  |  |  |  |  | { | 
| 5438 | 0 |  |  |  |  | 0 | close $sock; | 
| 5439 | 0 |  |  |  |  | 0 | $file->close; | 
| 5440 | 0 |  |  |  |  | 0 | $self->_cleanup_filters (@filter_objects); | 
| 5441 | 0 |  |  |  |  | 0 | $self->reply (426, | 
| 5442 |  |  |  |  |  |  | "File retrieval error: $transfer_hook", | 
| 5443 |  |  |  |  |  |  | "Data connection has been closed."); | 
| 5444 | 0 |  |  |  |  | 0 | return; | 
| 5445 |  |  |  |  |  |  | } | 
| 5446 |  |  |  |  |  |  |  | 
| 5447 | 73 |  |  |  |  | 208 | for ($n = 0; $n < $r; ) | 
| 5448 |  |  |  |  |  |  | { | 
| 5449 |  |  |  |  |  |  | #		$w = $sock->syswrite ($buffer, $r - $n, $n); | 
| 5450 | 73 |  |  |  |  | 3240 | $w = syswrite $sock, $buffer, $r - $n, $n; | 
| 5451 |  |  |  |  |  |  |  | 
| 5452 | 73 | 50 |  |  |  | 267 | unless (defined $w) | 
| 5453 |  |  |  |  |  |  | { | 
| 5454 |  |  |  |  |  |  | # There was an error. | 
| 5455 | 0 |  |  |  |  | 0 | my $reason = $self->system_error_hook(); | 
| 5456 |  |  |  |  |  |  |  | 
| 5457 | 0 |  |  |  |  | 0 | close $sock; | 
| 5458 | 0 |  |  |  |  | 0 | $file->close; | 
| 5459 | 0 |  |  |  |  | 0 | $self->_cleanup_filters (@filter_objects); | 
| 5460 | 0 |  |  |  |  | 0 | $self->reply (426, | 
| 5461 |  |  |  |  |  |  | "File retrieval error: $reason", | 
| 5462 |  |  |  |  |  |  | "Data connection has been closed."); | 
| 5463 | 0 |  |  |  |  | 0 | return; | 
| 5464 |  |  |  |  |  |  | } | 
| 5465 |  |  |  |  |  |  |  | 
| 5466 | 73 |  |  |  |  | 187 | $n += $w; | 
| 5467 |  |  |  |  |  |  | } | 
| 5468 |  |  |  |  |  |  |  | 
| 5469 | 73 |  |  |  |  | 226 | $self->_check_signals; | 
| 5470 |  |  |  |  |  |  |  | 
| 5471 |  |  |  |  |  |  | # Transfer aborted by client? | 
| 5472 | 73 | 100 |  |  |  | 302 | if ($self->{_urgent}) | 
| 5473 |  |  |  |  |  |  | { | 
| 5474 | 1 |  |  |  |  | 13 | close $sock; | 
| 5475 | 1 |  |  |  |  | 4 | $file->close; | 
| 5476 | 1 |  |  |  |  | 11 | $self->_cleanup_filters (@filter_objects); | 
| 5477 | 1 |  |  |  |  | 4 | $self->reply (426, "Transfer aborted. Data connection closed."); | 
| 5478 | 1 |  |  |  |  | 2 | $self->{_urgent} = 0; | 
| 5479 | 1 |  |  |  |  | 7 | return; | 
| 5480 |  |  |  |  |  |  | } | 
| 5481 |  |  |  |  |  |  | } | 
| 5482 |  |  |  |  |  |  |  | 
| 5483 | 41 | 50 |  |  |  | 862 | unless (defined $r) | 
| 5484 |  |  |  |  |  |  | { | 
| 5485 |  |  |  |  |  |  | # There was an error. | 
| 5486 | 0 |  |  |  |  | 0 | my $reason = $self->system_error_hook(); | 
| 5487 |  |  |  |  |  |  |  | 
| 5488 | 0 |  |  |  |  | 0 | close $sock; | 
| 5489 | 0 |  |  |  |  | 0 | $file->close; | 
| 5490 | 0 |  |  |  |  | 0 | $self->_cleanup_filters (@filter_objects); | 
| 5491 | 0 |  |  |  |  | 0 | $self->reply (426, | 
| 5492 |  |  |  |  |  |  | "File retrieval error: $reason", | 
| 5493 |  |  |  |  |  |  | "Data connection has been closed."); | 
| 5494 | 0 |  |  |  |  | 0 | return; | 
| 5495 |  |  |  |  |  |  | } | 
| 5496 |  |  |  |  |  |  | } | 
| 5497 |  |  |  |  |  |  | else			# ASCII type. | 
| 5498 |  |  |  |  |  |  | { | 
| 5499 |  |  |  |  |  |  | # Restart the connection from previous point? | 
| 5500 | 10 | 100 |  |  |  | 33 | if ($self->{_restart}) | 
| 5501 |  |  |  |  |  |  | { | 
| 5502 | 1 |  |  |  |  | 56 | for (my $i = 0; $i < $self->{_restart}; ++$i) | 
| 5503 |  |  |  |  |  |  | { | 
| 5504 | 33 |  |  |  |  | 466 | $file->getc; | 
| 5505 |  |  |  |  |  |  | } | 
| 5506 | 1 |  |  |  |  | 12 | $self->{_restart} = 0; | 
| 5507 |  |  |  |  |  |  | } | 
| 5508 |  |  |  |  |  |  |  | 
| 5509 |  |  |  |  |  |  | # Copy data. | 
| 5510 | 10 |  |  |  |  | 60 | while (defined ($_ = $file->getline)) | 
| 5511 |  |  |  |  |  |  | { | 
| 5512 | 4664 | 50 |  |  |  | 465139 | $self->xfer (length $_) if $self->{_xferlog}; | 
| 5513 |  |  |  |  |  |  |  | 
| 5514 |  |  |  |  |  |  | # Remove any native line endings. | 
| 5515 | 4664 |  |  |  |  | 25640 | s/[\n\r]+$//; | 
| 5516 |  |  |  |  |  |  |  | 
| 5517 |  |  |  |  |  |  | # Restart alarm clock timer. | 
| 5518 | 4664 |  |  |  |  | 16483 | alarm $self->{_idle_timeout}; | 
| 5519 |  |  |  |  |  |  |  | 
| 5520 | 4664 | 50 |  |  |  | 10073 | if ($transfer_hook = $self->transfer_hook ("r", $file, $sock, \$_)) | 
| 5521 |  |  |  |  |  |  | { | 
| 5522 | 0 |  |  |  |  | 0 | close $sock; | 
| 5523 | 0 |  |  |  |  | 0 | $file->close; | 
| 5524 | 0 |  |  |  |  | 0 | $self->_cleanup_filters (@filter_objects); | 
| 5525 | 0 |  |  |  |  | 0 | $self->reply (426, | 
| 5526 |  |  |  |  |  |  | "File retrieval error: $transfer_hook", | 
| 5527 |  |  |  |  |  |  | "Data connection has been closed."); | 
| 5528 | 0 |  |  |  |  | 0 | return; | 
| 5529 |  |  |  |  |  |  | } | 
| 5530 |  |  |  |  |  |  |  | 
| 5531 | 4664 |  |  |  |  | 10575 | $self->_check_signals; | 
| 5532 |  |  |  |  |  |  |  | 
| 5533 |  |  |  |  |  |  | # Write the line with telnet-format line endings. | 
| 5534 | 4664 |  |  |  |  | 14148 | $sock->print ("$_\r\n"); | 
| 5535 | 4664 | 100 |  |  |  | 165042 | if ($self->{_urgent}) | 
| 5536 |  |  |  |  |  |  | { | 
| 5537 | 1 |  |  |  |  | 33 | close $sock; | 
| 5538 | 1 |  |  |  |  | 8 | $file->close; | 
| 5539 | 1 |  |  |  |  | 20 | $self->_cleanup_filters (@filter_objects); | 
| 5540 | 1 |  |  |  |  | 6 | $self->reply (426, "Transfer aborted. Data connection closed."); | 
| 5541 | 1 |  |  |  |  | 4 | $self->{_urgent} = 0; | 
| 5542 | 1 |  |  |  |  | 18 | return; | 
| 5543 |  |  |  |  |  |  | } | 
| 5544 |  |  |  |  |  |  | } | 
| 5545 |  |  |  |  |  |  | } | 
| 5546 |  |  |  |  |  |  |  | 
| 5547 | 50 | 50 | 33 |  |  | 2179 | unless (close ($sock) && $file->close) | 
| 5548 |  |  |  |  |  |  | { | 
| 5549 | 0 |  |  |  |  | 0 | my $reason = $self->system_error_hook(); | 
| 5550 | 0 |  |  |  |  | 0 | $self->reply (550, "File retrieval error: $reason"); | 
| 5551 | 0 |  |  |  |  | 0 | return; | 
| 5552 |  |  |  |  |  |  | } | 
| 5553 |  |  |  |  |  |  |  | 
| 5554 |  |  |  |  |  |  | # Clean up any outstanding filter objects. | 
| 5555 | 50 |  |  |  |  | 802 | $self->_cleanup_filters (@filter_objects); | 
| 5556 |  |  |  |  |  |  |  | 
| 5557 | 50 | 50 |  |  |  | 153 | $self->xfer_complete if $self->{_xferlog}; | 
| 5558 | 50 |  |  |  |  | 157 | $self->reply (226, "File retrieval complete. Data connection has been closed."); | 
| 5559 |  |  |  |  |  |  | } | 
| 5560 |  |  |  |  |  |  |  | 
| 5561 |  |  |  |  |  |  | sub _cleanup_filters | 
| 5562 |  |  |  |  |  |  | { | 
| 5563 | 52 |  |  | 52 |  | 101 | my $self = shift; | 
| 5564 |  |  |  |  |  |  |  | 
| 5565 | 52 |  |  |  |  | 194 | foreach (@_) | 
| 5566 |  |  |  |  |  |  | { | 
| 5567 | 0 | 0 |  |  |  | 0 | if (exists $_->{pid}) | 
| 5568 |  |  |  |  |  |  | { | 
| 5569 | 0 |  |  |  |  | 0 | waitpid $_->{pid}, 0; | 
| 5570 |  |  |  |  |  |  | } | 
| 5571 |  |  |  |  |  |  | } | 
| 5572 |  |  |  |  |  |  | } | 
| 5573 |  |  |  |  |  |  |  | 
| 5574 |  |  |  |  |  |  | sub _STOR_command | 
| 5575 |  |  |  |  |  |  | { | 
| 5576 | 46 |  |  | 46 |  | 98 | my $self = shift; | 
| 5577 | 46 |  |  |  |  | 77 | my $cmd = shift; | 
| 5578 | 46 |  |  |  |  | 83 | my $rest = shift; | 
| 5579 |  |  |  |  |  |  |  | 
| 5580 | 46 |  |  |  |  | 328 | $self->_store ($rest); | 
| 5581 |  |  |  |  |  |  | } | 
| 5582 |  |  |  |  |  |  |  | 
| 5583 |  |  |  |  |  |  | sub _STOU_command | 
| 5584 |  |  |  |  |  |  | { | 
| 5585 | 9 |  |  | 9 |  | 15 | my $self = shift; | 
| 5586 | 9 |  |  |  |  | 20 | my $cmd = shift; | 
| 5587 | 9 |  |  |  |  | 18 | my $rest = shift; | 
| 5588 |  |  |  |  |  |  |  | 
| 5589 | 9 |  |  |  |  | 35 | $self->_store ($rest, unique => 1); | 
| 5590 |  |  |  |  |  |  | } | 
| 5591 |  |  |  |  |  |  |  | 
| 5592 |  |  |  |  |  |  | sub _APPE_command | 
| 5593 |  |  |  |  |  |  | { | 
| 5594 | 1 |  |  | 1 |  | 2 | my $self = shift; | 
| 5595 | 1 |  |  |  |  | 2 | my $cmd = shift; | 
| 5596 | 1 |  |  |  |  | 1 | my $rest = shift; | 
| 5597 |  |  |  |  |  |  |  | 
| 5598 | 1 |  |  |  |  | 4 | $self->_store ($rest, append => 1); | 
| 5599 |  |  |  |  |  |  | } | 
| 5600 |  |  |  |  |  |  |  | 
| 5601 |  |  |  |  |  |  | sub _ALLO_command | 
| 5602 |  |  |  |  |  |  | { | 
| 5603 | 1 |  |  | 1 |  | 3 | my $self = shift; | 
| 5604 | 1 |  |  |  |  | 3 | my $cmd = shift; | 
| 5605 | 1 |  |  |  |  | 2 | my $rest = shift; | 
| 5606 |  |  |  |  |  |  |  | 
| 5607 |  |  |  |  |  |  | # RFC 959 Section 4.1.3: Treat this as a NOOP. Note that djb | 
| 5608 |  |  |  |  |  |  | # recommends replying with 202 here [http://cr.yp.to/ftp/stor.html]. | 
| 5609 | 1 |  |  |  |  | 4 | $self->reply (200, "OK"); | 
| 5610 |  |  |  |  |  |  | } | 
| 5611 |  |  |  |  |  |  |  | 
| 5612 |  |  |  |  |  |  | sub _REST_command | 
| 5613 |  |  |  |  |  |  | { | 
| 5614 | 6 |  |  | 6 |  | 10 | my $self = shift; | 
| 5615 | 6 |  |  |  |  | 9 | my $cmd = shift; | 
| 5616 | 6 |  |  |  |  | 11 | my $rest = shift; | 
| 5617 |  |  |  |  |  |  |  | 
| 5618 | 6 | 50 |  |  |  | 27 | unless ($rest =~ /^([1-9][0-9]*|0)$/) | 
| 5619 |  |  |  |  |  |  | { | 
| 5620 | 0 |  |  |  |  | 0 | $self->reply (501, "REST command needs a numeric argument."); | 
| 5621 | 0 |  |  |  |  | 0 | return; | 
| 5622 |  |  |  |  |  |  | } | 
| 5623 |  |  |  |  |  |  |  | 
| 5624 | 6 |  |  |  |  | 21 | $self->{_restart} = $1; | 
| 5625 | 6 |  |  |  |  | 23 | $self->reply (350, "Restarting next transfer at $1."); | 
| 5626 |  |  |  |  |  |  | } | 
| 5627 |  |  |  |  |  |  |  | 
| 5628 |  |  |  |  |  |  | sub _RNFR_command | 
| 5629 |  |  |  |  |  |  | { | 
| 5630 | 1 |  |  | 1 |  | 3 | my $self = shift; | 
| 5631 | 1 |  |  |  |  | 3 | my $cmd = shift; | 
| 5632 | 1 |  |  |  |  | 3 | my $rest = shift; | 
| 5633 |  |  |  |  |  |  |  | 
| 5634 | 1 |  |  |  |  | 6 | my ($dirh, $fileh, $filename) = $self->_get ($rest); | 
| 5635 |  |  |  |  |  |  |  | 
| 5636 | 1 | 50 |  |  |  | 9 | unless ($fileh) | 
| 5637 |  |  |  |  |  |  | { | 
| 5638 | 0 |  |  |  |  | 0 | $self->reply (550, "File or directory not found."); | 
| 5639 | 0 |  |  |  |  | 0 | return; | 
| 5640 |  |  |  |  |  |  | } | 
| 5641 |  |  |  |  |  |  |  | 
| 5642 |  |  |  |  |  |  | # Access control. | 
| 5643 | 1 | 50 |  |  |  | 5 | unless ($self->_eval_rule ("rename rule", | 
| 5644 |  |  |  |  |  |  | $dirh->pathname . $filename, | 
| 5645 |  |  |  |  |  |  | $filename, $dirh->pathname)) | 
| 5646 |  |  |  |  |  |  | { | 
| 5647 | 0 |  |  |  |  | 0 | $self->reply (550, "RNFR command denied by server configuration."); | 
| 5648 | 0 |  |  |  |  | 0 | return; | 
| 5649 |  |  |  |  |  |  | } | 
| 5650 |  |  |  |  |  |  |  | 
| 5651 |  |  |  |  |  |  | # Store the file handle so we can complete the operation. | 
| 5652 | 1 |  |  |  |  | 4 | $self->{_rename_fileh} = $fileh; | 
| 5653 |  |  |  |  |  |  |  | 
| 5654 | 1 |  |  |  |  | 6 | $self->reply (350, "OK. Send RNTO command to complete rename operation."); | 
| 5655 |  |  |  |  |  |  | } | 
| 5656 |  |  |  |  |  |  |  | 
| 5657 |  |  |  |  |  |  | sub _RNTO_command | 
| 5658 |  |  |  |  |  |  | { | 
| 5659 | 1 |  |  | 1 |  | 4 | my $self = shift; | 
| 5660 | 1 |  |  |  |  | 3 | my $cmd = shift; | 
| 5661 | 1 |  |  |  |  | 4 | my $rest = shift; | 
| 5662 |  |  |  |  |  |  |  | 
| 5663 |  |  |  |  |  |  | # Seen a previous RNFR command? | 
| 5664 | 1 | 50 |  |  |  | 10 | unless ($self->{_rename_fileh}) | 
| 5665 |  |  |  |  |  |  | { | 
| 5666 | 0 |  |  |  |  | 0 | $self->reply (503, "Send RNFR command first."); | 
| 5667 | 0 |  |  |  |  | 0 | return; | 
| 5668 |  |  |  |  |  |  | } | 
| 5669 |  |  |  |  |  |  |  | 
| 5670 |  |  |  |  |  |  | # Get the directory name. | 
| 5671 | 1 |  |  |  |  | 5 | my ($dirh, $fileh, $filename) = $self->_get ($rest); | 
| 5672 |  |  |  |  |  |  |  | 
| 5673 | 1 | 50 |  |  |  | 7 | if (!$dirh) | 
| 5674 |  |  |  |  |  |  | { | 
| 5675 | 0 |  |  |  |  | 0 | $self->reply (550, "File or directory not found."); | 
| 5676 | 0 |  |  |  |  | 0 | return; | 
| 5677 |  |  |  |  |  |  | } | 
| 5678 |  |  |  |  |  |  |  | 
| 5679 |  |  |  |  |  |  | # Access control. | 
| 5680 | 1 | 50 |  |  |  | 5 | unless ($self->_eval_rule ("rename rule", | 
| 5681 |  |  |  |  |  |  | $dirh->pathname . $filename, | 
| 5682 |  |  |  |  |  |  | $filename, $dirh->pathname)) | 
| 5683 |  |  |  |  |  |  | { | 
| 5684 | 0 |  |  |  |  | 0 | $self->reply (550, "RNTO command denied by server configuration."); | 
| 5685 | 0 |  |  |  |  | 0 | return; | 
| 5686 |  |  |  |  |  |  | } | 
| 5687 |  |  |  |  |  |  |  | 
| 5688 |  |  |  |  |  |  | # Are we trying to overwrite a previously existing file? | 
| 5689 | 1 | 0 | 33 |  |  | 8 | if (defined $fileh && | 
|  |  |  | 33 |  |  |  |  | 
| 5690 |  |  |  |  |  |  | defined $self->config ("allow rename to overwrite") && | 
| 5691 |  |  |  |  |  |  | ! $self->config ("allow rename to overwrite")) | 
| 5692 |  |  |  |  |  |  | { | 
| 5693 | 0 |  |  |  |  | 0 | $self->reply (550, "Cannot rename file."); | 
| 5694 | 0 |  |  |  |  | 0 | return; | 
| 5695 |  |  |  |  |  |  | } | 
| 5696 |  |  |  |  |  |  |  | 
| 5697 |  |  |  |  |  |  | # Attempt the rename operation. | 
| 5698 | 1 | 50 |  |  |  | 16 | if ($self->{_rename_fileh}->move ($dirh, $filename) < 0) | 
| 5699 |  |  |  |  |  |  | { | 
| 5700 | 0 |  |  |  |  | 0 | $self->reply (550, "Cannot rename file."); | 
| 5701 | 0 |  |  |  |  | 0 | return; | 
| 5702 |  |  |  |  |  |  | } | 
| 5703 |  |  |  |  |  |  |  | 
| 5704 | 1 |  |  |  |  | 25 | delete $self->{_rename_fileh}; | 
| 5705 |  |  |  |  |  |  |  | 
| 5706 | 1 |  |  |  |  | 7 | $self->reply (250, "File has been renamed."); | 
| 5707 |  |  |  |  |  |  | } | 
| 5708 |  |  |  |  |  |  |  | 
| 5709 |  |  |  |  |  |  | sub _ABOR_command | 
| 5710 |  |  |  |  |  |  | { | 
| 5711 | 2 |  |  | 2 |  | 4 | my $self = shift; | 
| 5712 | 2 |  |  |  |  | 3 | my $cmd = shift; | 
| 5713 | 2 |  |  |  |  | 5 | my $rest = shift; | 
| 5714 |  |  |  |  |  |  |  | 
| 5715 | 2 |  |  |  |  | 9 | $self->reply (226, "Command aborted successfully."); | 
| 5716 |  |  |  |  |  |  | } | 
| 5717 |  |  |  |  |  |  |  | 
| 5718 |  |  |  |  |  |  | # Note that in the current implementation, DELE and RMD are synonyms. | 
| 5719 |  |  |  |  |  |  | sub _DELE_command | 
| 5720 |  |  |  |  |  |  | { | 
| 5721 | 9 |  |  | 9 |  | 17 | my $self = shift; | 
| 5722 | 9 |  |  |  |  | 14 | my $cmd = shift; | 
| 5723 | 9 |  |  |  |  | 14 | my $rest = shift; | 
| 5724 |  |  |  |  |  |  |  | 
| 5725 | 9 |  |  |  |  | 22 | my ($dirh, $fileh, $filename) = $self->_get ($rest); | 
| 5726 |  |  |  |  |  |  |  | 
| 5727 | 9 | 50 |  |  |  | 27 | unless ($fileh) | 
| 5728 |  |  |  |  |  |  | { | 
| 5729 | 0 |  |  |  |  | 0 | $self->reply (550, "File or directory not found."); | 
| 5730 | 0 |  |  |  |  | 0 | return; | 
| 5731 |  |  |  |  |  |  | } | 
| 5732 |  |  |  |  |  |  |  | 
| 5733 |  |  |  |  |  |  | # Check access control. | 
| 5734 | 9 | 50 |  |  |  | 27 | unless ($self->_eval_rule ("delete rule", | 
| 5735 |  |  |  |  |  |  | $fileh->pathname, $filename, $dirh->pathname)) | 
| 5736 |  |  |  |  |  |  | { | 
| 5737 | 0 |  |  |  |  | 0 | $self->reply (550, "DELE command denied by server configuration."); | 
| 5738 | 0 |  |  |  |  | 0 | return; | 
| 5739 |  |  |  |  |  |  | } | 
| 5740 |  |  |  |  |  |  |  | 
| 5741 |  |  |  |  |  |  | # Attempt to delete the file. | 
| 5742 | 9 | 50 |  |  |  | 35 | if ($fileh->delete < 0) | 
| 5743 |  |  |  |  |  |  | { | 
| 5744 | 0 |  |  |  |  | 0 | $self->reply (550, "Cannot delete file."); | 
| 5745 | 0 |  |  |  |  | 0 | return; | 
| 5746 |  |  |  |  |  |  | } | 
| 5747 |  |  |  |  |  |  |  | 
| 5748 | 9 |  |  |  |  | 25 | $self->reply (250, "File has been deleted."); | 
| 5749 |  |  |  |  |  |  | } | 
| 5750 |  |  |  |  |  |  |  | 
| 5751 |  |  |  |  |  |  | sub _RMD_command | 
| 5752 |  |  |  |  |  |  | { | 
| 5753 | 1 |  |  | 1 |  | 3 | my $self = shift; | 
| 5754 | 1 |  |  |  |  | 3 | my $cmd = shift; | 
| 5755 | 1 |  |  |  |  | 2 | my $rest = shift; | 
| 5756 |  |  |  |  |  |  |  | 
| 5757 | 1 |  |  |  |  | 9 | my ($dirh, $fileh, $filename) = $self->_get ($rest); | 
| 5758 |  |  |  |  |  |  |  | 
| 5759 | 1 | 50 |  |  |  | 4 | unless ($fileh) | 
| 5760 |  |  |  |  |  |  | { | 
| 5761 | 0 |  |  |  |  | 0 | $self->reply (550, "File or directory not found."); | 
| 5762 | 0 |  |  |  |  | 0 | return; | 
| 5763 |  |  |  |  |  |  | } | 
| 5764 |  |  |  |  |  |  |  | 
| 5765 |  |  |  |  |  |  | # Check access control. | 
| 5766 | 1 | 50 |  |  |  | 4 | unless ($self->_eval_rule ("delete rule", | 
| 5767 |  |  |  |  |  |  | $fileh->pathname, $filename, $dirh->pathname)) | 
| 5768 |  |  |  |  |  |  | { | 
| 5769 | 0 |  |  |  |  | 0 | $self->reply (550, "RMD command denied by server configuration."); | 
| 5770 | 0 |  |  |  |  | 0 | return; | 
| 5771 |  |  |  |  |  |  | } | 
| 5772 |  |  |  |  |  |  |  | 
| 5773 |  |  |  |  |  |  | # Attempt to delete the file. | 
| 5774 | 1 | 50 |  |  |  | 6 | if ($fileh->delete < 0) | 
| 5775 |  |  |  |  |  |  | { | 
| 5776 | 0 |  |  |  |  | 0 | $self->reply (550, "Cannot delete file."); | 
| 5777 | 0 |  |  |  |  | 0 | return; | 
| 5778 |  |  |  |  |  |  | } | 
| 5779 |  |  |  |  |  |  |  | 
| 5780 | 1 |  |  |  |  | 5 | $self->reply (250, "File has been deleted."); | 
| 5781 |  |  |  |  |  |  | } | 
| 5782 |  |  |  |  |  |  |  | 
| 5783 |  |  |  |  |  |  | sub _MKD_command | 
| 5784 |  |  |  |  |  |  | { | 
| 5785 | 11 |  |  | 11 |  | 25 | my $self = shift; | 
| 5786 | 11 |  |  |  |  | 23 | my $cmd = shift; | 
| 5787 | 11 |  |  |  |  | 23 | my $rest = shift; | 
| 5788 |  |  |  |  |  |  |  | 
| 5789 | 11 |  |  |  |  | 61 | my ($dirh, $fileh, $filename) = $self->_get ($rest); | 
| 5790 |  |  |  |  |  |  |  | 
| 5791 | 11 | 50 |  |  |  | 39 | if (!$dirh) | 
| 5792 |  |  |  |  |  |  | { | 
| 5793 | 0 |  |  |  |  | 0 | $self->reply (550, "File or directory not found."); | 
| 5794 | 0 |  |  |  |  | 0 | return; | 
| 5795 |  |  |  |  |  |  | } | 
| 5796 |  |  |  |  |  |  |  | 
| 5797 | 11 | 50 |  |  |  | 27 | if ($fileh) | 
| 5798 |  |  |  |  |  |  | { | 
| 5799 | 0 |  |  |  |  | 0 | $self->reply (550, "File or directory already exists."); | 
| 5800 | 0 |  |  |  |  | 0 | return; | 
| 5801 |  |  |  |  |  |  | } | 
| 5802 |  |  |  |  |  |  |  | 
| 5803 |  |  |  |  |  |  | # Access control. | 
| 5804 | 11 | 50 |  |  |  | 59 | unless ($self->_eval_rule ("mkdir rule", | 
| 5805 |  |  |  |  |  |  | $dirh->pathname . $filename, | 
| 5806 |  |  |  |  |  |  | $filename, $dirh->pathname)) | 
| 5807 |  |  |  |  |  |  | { | 
| 5808 | 0 |  |  |  |  | 0 | $self->reply (550, "MKD command denied by server configuration."); | 
| 5809 | 0 |  |  |  |  | 0 | return; | 
| 5810 |  |  |  |  |  |  | } | 
| 5811 |  |  |  |  |  |  |  | 
| 5812 |  |  |  |  |  |  | # Try to create a subdirectory with the appropriate filename. | 
| 5813 | 11 | 50 |  |  |  | 69 | if ($dirh->mkdir ($filename) < 0) | 
| 5814 |  |  |  |  |  |  | { | 
| 5815 | 0 |  |  |  |  | 0 | $self->reply (550, "Could not create directory."); | 
| 5816 | 0 |  |  |  |  | 0 | return; | 
| 5817 |  |  |  |  |  |  | } | 
| 5818 |  |  |  |  |  |  |  | 
| 5819 | 11 |  |  |  |  | 41 | $self->reply (250, "Directory has been created."); | 
| 5820 |  |  |  |  |  |  | } | 
| 5821 |  |  |  |  |  |  |  | 
| 5822 |  |  |  |  |  |  | sub _PWD_command | 
| 5823 |  |  |  |  |  |  | { | 
| 5824 | 5 |  |  | 5 |  | 10 | my $self = shift; | 
| 5825 | 5 |  |  |  |  | 7 | my $cmd = shift; | 
| 5826 | 5 |  |  |  |  | 7 | my $rest = shift; | 
| 5827 |  |  |  |  |  |  |  | 
| 5828 |  |  |  |  |  |  | # See RFC 959 Appendix II and draft-ietf-ftpext-mlst-11.txt section 6.2.1. | 
| 5829 | 5 |  |  |  |  | 16 | my $pathname = $self->{cwd}->pathname; | 
| 5830 | 5 | 100 |  |  |  | 33 | $pathname =~ s,/+$,, unless $pathname eq "/"; | 
| 5831 | 5 |  |  |  |  | 11 | $pathname =~ tr,/,/,s; | 
| 5832 |  |  |  |  |  |  |  | 
| 5833 | 5 |  |  |  |  | 14 | $self->reply (257, "\"$pathname\""); | 
| 5834 |  |  |  |  |  |  | } | 
| 5835 |  |  |  |  |  |  |  | 
| 5836 |  |  |  |  |  |  | sub _LIST_command | 
| 5837 |  |  |  |  |  |  | { | 
| 5838 | 3 |  |  | 3 |  | 7 | my $self = shift; | 
| 5839 | 3 |  |  |  |  | 6 | my $cmd = shift; | 
| 5840 | 3 |  |  |  |  | 4 | my $rest = shift; | 
| 5841 |  |  |  |  |  |  |  | 
| 5842 |  |  |  |  |  |  | # This is something of a hack. Some clients expect a Unix server | 
| 5843 |  |  |  |  |  |  | # to respond to flags on the 'ls command line'. Remove these flags | 
| 5844 |  |  |  |  |  |  | # and ignore them. This is particularly an issue with ncftp 2.4.3. | 
| 5845 | 3 |  |  |  |  | 6 | $rest =~ s/^-[a-zA-Z0-9]+\s?//; | 
| 5846 |  |  |  |  |  |  |  | 
| 5847 | 3 |  |  |  |  | 21 | my ($dirh, $wildcard, $fileh, $filename) | 
| 5848 |  |  |  |  |  |  | = $self->_list ($rest); | 
| 5849 |  |  |  |  |  |  |  | 
| 5850 | 3 | 50 |  |  |  | 8 | unless ($dirh) | 
| 5851 |  |  |  |  |  |  | { | 
| 5852 | 0 |  |  |  |  | 0 | $self->reply (550, "File or directory not found."); | 
| 5853 | 0 |  |  |  |  | 0 | return; | 
| 5854 |  |  |  |  |  |  | } | 
| 5855 |  |  |  |  |  |  |  | 
| 5856 |  |  |  |  |  |  | # Check access control. | 
| 5857 | 3 | 50 |  |  |  | 12 | unless ($self->_eval_rule ("list rule", | 
| 5858 |  |  |  |  |  |  | undef, undef, $dirh->pathname)) | 
| 5859 |  |  |  |  |  |  | { | 
| 5860 | 0 |  |  |  |  | 0 | $self->reply (550, "LIST command denied by server configuration."); | 
| 5861 | 0 |  |  |  |  | 0 | return; | 
| 5862 |  |  |  |  |  |  | } | 
| 5863 |  |  |  |  |  |  |  | 
| 5864 | 3 |  |  |  |  | 17 | $self->reply (150, "Opening data connection for file listing."); | 
| 5865 |  |  |  |  |  |  |  | 
| 5866 |  |  |  |  |  |  | # Open a path back to the client. | 
| 5867 | 3 |  |  |  |  | 13 | my $sock = $self->open_data_connection; | 
| 5868 |  |  |  |  |  |  |  | 
| 5869 | 3 | 50 |  |  |  | 11 | unless ($sock) | 
| 5870 |  |  |  |  |  |  | { | 
| 5871 | 0 |  |  |  |  | 0 | $self->reply (425, "Can't open data connection."); | 
| 5872 | 0 |  |  |  |  | 0 | return; | 
| 5873 |  |  |  |  |  |  | } | 
| 5874 |  |  |  |  |  |  |  | 
| 5875 |  |  |  |  |  |  | # Outgoing bandwidth | 
| 5876 | 3 | 50 |  |  |  | 16 | $self->xfer_start ($dirh->pathname, "o") if $self->{_xferlog}; | 
| 5877 |  |  |  |  |  |  |  | 
| 5878 |  |  |  |  |  |  | # If the path ($rest) contains a directory name, extract it so that | 
| 5879 |  |  |  |  |  |  | # we can prefix it to every filename listed. Thanks Rob Brown | 
| 5880 |  |  |  |  |  |  | # for pointing this problem out. | 
| 5881 | 3 | 50 | 66 |  |  | 43 | my $prefix = (($fileh || $wildcard) && $rest =~ /(.*\/).*/) ? $1 : ""; | 
| 5882 |  |  |  |  |  |  |  | 
| 5883 |  |  |  |  |  |  | # OK, we're either listing a full directory, listing a single | 
| 5884 |  |  |  |  |  |  | # file or listing a wildcard. | 
| 5885 | 3 | 50 |  |  |  | 8 | if ($fileh)			# Single file in $dirh. | 
| 5886 |  |  |  |  |  |  | { | 
| 5887 | 0 |  |  |  |  | 0 | $self->_list_file ($sock, $fileh, $prefix . $filename); | 
| 5888 |  |  |  |  |  |  | } | 
| 5889 |  |  |  |  |  |  | else			# Wildcard or full directory $dirh. | 
| 5890 |  |  |  |  |  |  | { | 
| 5891 | 3 | 100 |  |  |  | 6 | unless ($wildcard) | 
| 5892 |  |  |  |  |  |  | { | 
| 5893 |  |  |  |  |  |  | # Synthesize "total" field. | 
| 5894 | 1 |  |  |  |  | 2 | my $header = "total 1\r\n"; | 
| 5895 | 1 |  |  |  |  | 21 | $self->xfer (length $header); | 
| 5896 | 1 |  |  |  |  | 12 | $sock->print ($header); | 
| 5897 |  |  |  |  |  |  | } | 
| 5898 |  |  |  |  |  |  |  | 
| 5899 | 3 |  |  |  |  | 69 | my $r = $dirh->_list_status ($wildcard); | 
| 5900 |  |  |  |  |  |  |  | 
| 5901 | 3 |  |  |  |  | 9 | foreach (@$r) | 
| 5902 |  |  |  |  |  |  | { | 
| 5903 | 23 |  |  |  |  | 701 | my $filename = $_->[0]; | 
| 5904 | 23 |  |  |  |  | 32 | my $handle = $_->[1]; | 
| 5905 | 23 |  |  |  |  | 32 | my $statusref = $_->[2]; | 
| 5906 |  |  |  |  |  |  |  | 
| 5907 | 23 |  |  |  |  | 77 | $self->_list_file ($sock, $handle, $prefix . $filename, $statusref); | 
| 5908 |  |  |  |  |  |  | } | 
| 5909 |  |  |  |  |  |  | } | 
| 5910 |  |  |  |  |  |  |  | 
| 5911 | 3 | 50 |  |  |  | 162 | unless ($sock->close) | 
| 5912 |  |  |  |  |  |  | { | 
| 5913 | 0 |  |  |  |  | 0 | $self->reply (550, "Error closing data connection: $!"); | 
| 5914 | 0 |  |  |  |  | 0 | return; | 
| 5915 |  |  |  |  |  |  | } | 
| 5916 |  |  |  |  |  |  |  | 
| 5917 | 3 | 50 |  |  |  | 158 | $self->xfer_complete if $self->{_xferlog}; | 
| 5918 | 3 |  |  |  |  | 12 | $self->reply (226, "Listing complete. Data connection has been closed."); | 
| 5919 |  |  |  |  |  |  | } | 
| 5920 |  |  |  |  |  |  |  | 
| 5921 |  |  |  |  |  |  | sub _NLST_command | 
| 5922 |  |  |  |  |  |  | { | 
| 5923 | 3 |  |  | 3 |  | 8 | my $self = shift; | 
| 5924 | 3 |  |  |  |  | 5 | my $cmd = shift; | 
| 5925 | 3 |  |  |  |  | 6 | my $rest = shift; | 
| 5926 |  |  |  |  |  |  |  | 
| 5927 |  |  |  |  |  |  | # This is something of a hack. Some clients expect a Unix server | 
| 5928 |  |  |  |  |  |  | # to respond to flags on the 'ls command line'. | 
| 5929 |  |  |  |  |  |  | # Handle the "-l" flag by just calling LIST instead of NLST. | 
| 5930 |  |  |  |  |  |  | # This is particularly an issue with ncftp 2.4.3, | 
| 5931 |  |  |  |  |  |  | # emacs / Ange-ftp, commandline "ftp" on Windows Platform, | 
| 5932 |  |  |  |  |  |  | # netftp, and some old versions of WSFTP.  I would think that if | 
| 5933 |  |  |  |  |  |  | # the client wants a nice pretty listing, that they should use | 
| 5934 |  |  |  |  |  |  | # the LIST command, but for some reasons they insist on trying | 
| 5935 |  |  |  |  |  |  | # to pass arguments to NLST and expect them to work. | 
| 5936 |  |  |  |  |  |  | # Examples: | 
| 5937 |  |  |  |  |  |  | # NLST -al /. | 
| 5938 |  |  |  |  |  |  | # NLST -AL *.htm | 
| 5939 | 3 | 50 |  |  |  | 10 | return $self->_LIST_command ($cmd, $rest) if $rest =~ /^\-\w*l/i; | 
| 5940 | 3 |  |  |  |  | 4 | $rest =~ s/^-\w+\s?//; | 
| 5941 |  |  |  |  |  |  |  | 
| 5942 | 3 |  |  |  |  | 13 | my ($dirh, $wildcard, $fileh, $filename) | 
| 5943 |  |  |  |  |  |  | = $self->_list ($rest); | 
| 5944 |  |  |  |  |  |  |  | 
| 5945 | 3 | 50 |  |  |  | 14 | unless ($dirh) | 
| 5946 |  |  |  |  |  |  | { | 
| 5947 | 0 |  |  |  |  | 0 | $self->reply (550, "File or directory not found."); | 
| 5948 | 0 |  |  |  |  | 0 | return; | 
| 5949 |  |  |  |  |  |  | } | 
| 5950 |  |  |  |  |  |  |  | 
| 5951 |  |  |  |  |  |  | # Check access control. | 
| 5952 | 3 | 50 |  |  |  | 17 | unless ($self->_eval_rule ("list rule", | 
| 5953 |  |  |  |  |  |  | undef, undef, $dirh->pathname)) | 
| 5954 |  |  |  |  |  |  | { | 
| 5955 | 0 |  |  |  |  | 0 | $self->reply (550, "NLST command denied by server configuration."); | 
| 5956 | 0 |  |  |  |  | 0 | return; | 
| 5957 |  |  |  |  |  |  | } | 
| 5958 |  |  |  |  |  |  |  | 
| 5959 | 3 |  |  |  |  | 12 | $self->reply (150, "Opening data connection for file listing."); | 
| 5960 |  |  |  |  |  |  |  | 
| 5961 |  |  |  |  |  |  | # Open a path back to the client. | 
| 5962 | 3 |  |  |  |  | 12 | my $sock = $self->open_data_connection; | 
| 5963 |  |  |  |  |  |  |  | 
| 5964 | 3 | 50 |  |  |  | 12 | unless ($sock) | 
| 5965 |  |  |  |  |  |  | { | 
| 5966 | 0 |  |  |  |  | 0 | $self->reply (425, "Can't open data connection."); | 
| 5967 | 0 |  |  |  |  | 0 | return; | 
| 5968 |  |  |  |  |  |  | } | 
| 5969 |  |  |  |  |  |  |  | 
| 5970 |  |  |  |  |  |  | # Outgoing bandwidth | 
| 5971 | 3 | 50 |  |  |  | 14 | $self->xfer_start ($dirh->pathname, "o") if $self->{_xferlog}; | 
| 5972 |  |  |  |  |  |  |  | 
| 5973 |  |  |  |  |  |  | # If the path ($rest) contains a directory name, extract it so that | 
| 5974 |  |  |  |  |  |  | # we can prefix it to every filename listed. Thanks Rob Brown | 
| 5975 |  |  |  |  |  |  | # for pointing this problem out. | 
| 5976 | 3 | 50 | 66 |  |  | 48 | my $prefix = (($fileh || $wildcard) && $rest =~ /(.*\/).*/) ? $1 : ""; | 
| 5977 |  |  |  |  |  |  |  | 
| 5978 |  |  |  |  |  |  | # OK, we're either listing a full directory, listing a single | 
| 5979 |  |  |  |  |  |  | # file or listing a wildcard. | 
| 5980 | 3 | 50 |  |  |  | 14 | if ($fileh)			# Single file in $dirh. | 
| 5981 |  |  |  |  |  |  | { | 
| 5982 | 0 |  |  |  |  | 0 | $sock->print ($prefix . $filename, "\r\n"); | 
| 5983 |  |  |  |  |  |  | } | 
| 5984 |  |  |  |  |  |  | else			# Wildcard or full directory $dirh. | 
| 5985 |  |  |  |  |  |  | { | 
| 5986 | 3 |  |  |  |  | 26 | my $r = $dirh->list ($wildcard); | 
| 5987 |  |  |  |  |  |  |  | 
| 5988 | 3 |  |  |  |  | 10 | foreach (@$r) | 
| 5989 |  |  |  |  |  |  | { | 
| 5990 | 24 |  |  |  |  | 625 | my $filename = $_->[0]; | 
| 5991 | 24 |  |  |  |  | 41 | my $handle = $_->[1];   # handle not used? | 
| 5992 | 24 |  |  |  |  | 41 | my $line = "$prefix$filename\r\n"; | 
| 5993 | 24 |  |  |  |  | 76 | $self->xfer (length $line); | 
| 5994 | 24 |  |  |  |  | 64 | $sock->print ($line); | 
| 5995 |  |  |  |  |  |  | } | 
| 5996 |  |  |  |  |  |  | } | 
| 5997 |  |  |  |  |  |  |  | 
| 5998 | 3 | 50 |  |  |  | 112 | unless ($sock->close) | 
| 5999 |  |  |  |  |  |  | { | 
| 6000 | 0 |  |  |  |  | 0 | $self->reply (550, "Error closing data connection: $!"); | 
| 6001 | 0 |  |  |  |  | 0 | return; | 
| 6002 |  |  |  |  |  |  | } | 
| 6003 |  |  |  |  |  |  |  | 
| 6004 | 3 | 50 |  |  |  | 160 | $self->xfer_complete if $self->{_xferlog}; | 
| 6005 | 3 |  |  |  |  | 14 | $self->reply (226, "Listing complete. Data connection has been closed."); | 
| 6006 |  |  |  |  |  |  | } | 
| 6007 |  |  |  |  |  |  |  | 
| 6008 |  |  |  |  |  |  | sub _SITE_command | 
| 6009 |  |  |  |  |  |  | { | 
| 6010 | 5 |  |  | 5 |  | 12 | my $self = shift; | 
| 6011 | 5 |  |  |  |  | 11 | my $cmd = shift; | 
| 6012 | 5 |  |  |  |  | 11 | my $rest = shift; | 
| 6013 |  |  |  |  |  |  |  | 
| 6014 |  |  |  |  |  |  | # Find the command. | 
| 6015 |  |  |  |  |  |  | # See also RFC 2640 section 3.1. | 
| 6016 |  |  |  |  |  |  | # "Brian Freeman"  wants to be able to use | 
| 6017 |  |  |  |  |  |  | # non-alpha characters in SITE command names. Fine by me as far as I can | 
| 6018 |  |  |  |  |  |  | # tell. | 
| 6019 | 5 | 50 |  |  |  | 34 | unless ($rest =~ /^(\S{3,})\s?(.*)/i) | 
| 6020 |  |  |  |  |  |  | { | 
| 6021 | 0 |  |  |  |  | 0 | $self->reply (501, "Syntax error in SITE command."); | 
| 6022 | 0 |  |  |  |  | 0 | return; | 
| 6023 |  |  |  |  |  |  | } | 
| 6024 |  |  |  |  |  |  |  | 
| 6025 | 5 |  |  |  |  | 20 | ($cmd, $rest) = (uc $1, $2); | 
| 6026 |  |  |  |  |  |  |  | 
| 6027 |  |  |  |  |  |  | # Find the appropriate command and run it. | 
| 6028 | 5 | 50 |  |  |  | 19 | unless (exists $self->{site_command_table}{$cmd}) | 
| 6029 |  |  |  |  |  |  | { | 
| 6030 | 0 |  |  |  |  | 0 | $self->reply (501, "Unknown SITE command."); | 
| 6031 | 0 |  |  |  |  | 0 | return; | 
| 6032 |  |  |  |  |  |  | } | 
| 6033 |  |  |  |  |  |  |  | 
| 6034 | 5 |  |  |  |  | 11 | &{$self->{site_command_table}{$cmd}} ($self, $cmd, $rest); | 
|  | 5 |  |  |  |  | 24 |  | 
| 6035 |  |  |  |  |  |  | } | 
| 6036 |  |  |  |  |  |  |  | 
| 6037 |  |  |  |  |  |  | sub _SITE_EXEC_command | 
| 6038 |  |  |  |  |  |  | { | 
| 6039 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 6040 | 0 |  |  |  |  | 0 | my $cmd = shift; | 
| 6041 | 0 |  |  |  |  | 0 | my $rest = shift; | 
| 6042 |  |  |  |  |  |  |  | 
| 6043 |  |  |  |  |  |  | # This command is DISABLED by default. | 
| 6044 | 0 | 0 |  |  |  | 0 | unless ($self->config ("allow site exec command")) | 
| 6045 |  |  |  |  |  |  | { | 
| 6046 | 0 |  |  |  |  | 0 | $self->reply (502, "SITE EXEC is disabled at this site."); | 
| 6047 | 0 |  |  |  |  | 0 | return; | 
| 6048 |  |  |  |  |  |  | } | 
| 6049 |  |  |  |  |  |  |  | 
| 6050 |  |  |  |  |  |  | # Don't allow this command for anonymous users. | 
| 6051 | 0 | 0 |  |  |  | 0 | if ($self->{user_is_anonymous}) | 
| 6052 |  |  |  |  |  |  | { | 
| 6053 | 0 |  |  |  |  | 0 | $self->reply (502, "SITE EXEC is not permitted for anonymous logins."); | 
| 6054 | 0 |  |  |  |  | 0 | return; | 
| 6055 |  |  |  |  |  |  | } | 
| 6056 |  |  |  |  |  |  |  | 
| 6057 |  |  |  |  |  |  | # We trust everything the client sends us implicitly. Foolish? Probably. | 
| 6058 | 0 | 0 |  |  |  | 0 | $rest = $1 if $rest =~ /(.*)/; | 
| 6059 |  |  |  |  |  |  |  | 
| 6060 |  |  |  |  |  |  | # Run it and collect the output. | 
| 6061 | 0 | 0 |  |  |  | 0 | unless (open OUTPUT, "$rest |") | 
| 6062 |  |  |  |  |  |  | { | 
| 6063 | 0 |  |  |  |  | 0 | $self->reply (451, "Error running command: $!"); | 
| 6064 | 0 |  |  |  |  | 0 | return; | 
| 6065 |  |  |  |  |  |  | } | 
| 6066 |  |  |  |  |  |  |  | 
| 6067 | 0 |  |  |  |  | 0 | my @result, (); | 
| 6068 |  |  |  |  |  |  |  | 
| 6069 | 0 |  |  |  |  | 0 | while ( | 
| 6070 |  |  |  |  |  |  | { | 
| 6071 |  |  |  |  |  |  | # Remove trailing \n, \r. | 
| 6072 | 0 |  |  |  |  | 0 | s/[\n\r]+$//; | 
| 6073 |  |  |  |  |  |  |  | 
| 6074 | 0 |  |  |  |  | 0 | push @result, $_; | 
| 6075 |  |  |  |  |  |  | } | 
| 6076 |  |  |  |  |  |  |  | 
| 6077 | 0 |  |  |  |  | 0 | close OUTPUT; | 
| 6078 |  |  |  |  |  |  |  | 
| 6079 |  |  |  |  |  |  | # Return the result to the client. | 
| 6080 | 0 |  |  |  |  | 0 | $self->reply (200, "Result from command $rest:", @result); | 
| 6081 |  |  |  |  |  |  | } | 
| 6082 |  |  |  |  |  |  |  | 
| 6083 |  |  |  |  |  |  | sub _SITE_VERSION_command | 
| 6084 |  |  |  |  |  |  | { | 
| 6085 | 4 |  |  | 4 |  | 6 | my $self = shift; | 
| 6086 | 4 |  |  |  |  | 8 | my $cmd = shift; | 
| 6087 | 4 |  |  |  |  | 5 | my $rest = shift; | 
| 6088 |  |  |  |  |  |  |  | 
| 6089 | 4 | 50 |  |  |  | 9 | my $enabled | 
| 6090 |  |  |  |  |  |  | = defined $self->config ("allow site version command") | 
| 6091 |  |  |  |  |  |  | ? $self->config ("allow site version command") : 1; | 
| 6092 |  |  |  |  |  |  |  | 
| 6093 | 4 | 50 |  |  |  | 10 | unless ($enabled) | 
| 6094 |  |  |  |  |  |  | { | 
| 6095 | 0 |  |  |  |  | 0 | $self->reply (502, "SITE VERSION is disabled at this site."); | 
| 6096 | 0 |  |  |  |  | 0 | return; | 
| 6097 |  |  |  |  |  |  | } | 
| 6098 |  |  |  |  |  |  |  | 
| 6099 |  |  |  |  |  |  | # Return the version string. | 
| 6100 | 4 |  |  |  |  | 12 | $self->reply (200, $self->{version_string}); | 
| 6101 |  |  |  |  |  |  | } | 
| 6102 |  |  |  |  |  |  |  | 
| 6103 |  |  |  |  |  |  | sub _SITE_ALIAS_command | 
| 6104 |  |  |  |  |  |  | { | 
| 6105 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 6106 | 0 |  |  |  |  | 0 | my $cmd = shift; | 
| 6107 | 0 |  |  |  |  | 0 | my $rest = shift; | 
| 6108 |  |  |  |  |  |  |  | 
| 6109 | 0 |  |  |  |  | 0 | my @aliases = $self->config ("alias"); | 
| 6110 |  |  |  |  |  |  |  | 
| 6111 |  |  |  |  |  |  | # List out all aliases? | 
| 6112 | 0 | 0 |  |  |  | 0 | if ($rest eq "") | 
| 6113 |  |  |  |  |  |  | { | 
| 6114 | 0 |  |  |  |  | 0 | $self->reply (214, | 
| 6115 |  |  |  |  |  |  | "The following aliases are defined:", | 
| 6116 |  |  |  |  |  |  | @aliases, | 
| 6117 |  |  |  |  |  |  | "End of alias list."); | 
| 6118 | 0 |  |  |  |  | 0 | return; | 
| 6119 |  |  |  |  |  |  | } | 
| 6120 |  |  |  |  |  |  |  | 
| 6121 |  |  |  |  |  |  | # Find a particular alias. | 
| 6122 | 0 |  |  |  |  | 0 | foreach (@aliases) | 
| 6123 |  |  |  |  |  |  | { | 
| 6124 | 0 |  |  |  |  | 0 | my ($name, $dir) = split /\s+/, $_; | 
| 6125 | 0 | 0 |  |  |  | 0 | if ($name eq $rest) | 
| 6126 |  |  |  |  |  |  | { | 
| 6127 | 0 |  |  |  |  | 0 | $self->reply (214, "$name is an alias for $dir."); | 
| 6128 | 0 |  |  |  |  | 0 | return; | 
| 6129 |  |  |  |  |  |  | } | 
| 6130 |  |  |  |  |  |  | } | 
| 6131 |  |  |  |  |  |  |  | 
| 6132 |  |  |  |  |  |  | # No alias found. | 
| 6133 | 0 |  |  |  |  | 0 | $self->reply (502, | 
| 6134 |  |  |  |  |  |  | "Unknown alias $rest. Note that aliases are case sensitive."); | 
| 6135 |  |  |  |  |  |  | } | 
| 6136 |  |  |  |  |  |  |  | 
| 6137 |  |  |  |  |  |  | sub _SITE_CDPATH_command | 
| 6138 |  |  |  |  |  |  | { | 
| 6139 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 6140 | 0 |  |  |  |  | 0 | my $cmd = shift; | 
| 6141 | 0 |  |  |  |  | 0 | my $rest = shift; | 
| 6142 |  |  |  |  |  |  |  | 
| 6143 | 0 |  |  |  |  | 0 | my $cdpath = $self->config ("cdpath"); | 
| 6144 |  |  |  |  |  |  |  | 
| 6145 | 0 | 0 |  |  |  | 0 | unless (defined $cdpath) | 
| 6146 |  |  |  |  |  |  | { | 
| 6147 | 0 |  |  |  |  | 0 | $self->reply (502, "No CDPATH is defined in this server."); | 
| 6148 | 0 |  |  |  |  | 0 | return; | 
| 6149 |  |  |  |  |  |  | } | 
| 6150 |  |  |  |  |  |  |  | 
| 6151 | 0 |  |  |  |  | 0 | my @cdpath = split /\s+/, $cdpath; | 
| 6152 |  |  |  |  |  |  |  | 
| 6153 | 0 |  |  |  |  | 0 | $self->reply (214, "The current CDPATH is:", @cdpath, "End of CDPATH."); | 
| 6154 |  |  |  |  |  |  | } | 
| 6155 |  |  |  |  |  |  |  | 
| 6156 |  |  |  |  |  |  | sub _SITE_CHECKMETHOD_command | 
| 6157 |  |  |  |  |  |  | { | 
| 6158 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 6159 | 0 |  |  |  |  | 0 | my $cmd = shift; | 
| 6160 | 0 |  |  |  |  | 0 | my $rest = shift; | 
| 6161 |  |  |  |  |  |  |  | 
| 6162 | 0 |  |  |  |  | 0 | $rest = uc $rest; | 
| 6163 |  |  |  |  |  |  |  | 
| 6164 | 0 | 0 |  |  |  | 0 | if ($rest eq "MD5") | 
|  |  | 0 |  |  |  |  |  | 
| 6165 |  |  |  |  |  |  | { | 
| 6166 | 0 |  |  |  |  | 0 | $self->{_checksum_method} = $rest; | 
| 6167 | 0 |  |  |  |  | 0 | $self->reply (200, "Checksum method is now: $rest"); | 
| 6168 |  |  |  |  |  |  | } | 
| 6169 |  |  |  |  |  |  | elsif ($rest eq "") | 
| 6170 |  |  |  |  |  |  | { | 
| 6171 | 0 |  |  |  |  | 0 | $self->reply (200, "Checksum method is now: $self->{_checksum_method}"); | 
| 6172 |  |  |  |  |  |  | } | 
| 6173 |  |  |  |  |  |  | else | 
| 6174 |  |  |  |  |  |  | { | 
| 6175 | 0 |  |  |  |  | 0 | $self->reply (500, "Unknown checksum method. I know about MD5."); | 
| 6176 |  |  |  |  |  |  | } | 
| 6177 |  |  |  |  |  |  | } | 
| 6178 |  |  |  |  |  |  |  | 
| 6179 |  |  |  |  |  |  | sub _SITE_CHECKSUM_command | 
| 6180 |  |  |  |  |  |  | { | 
| 6181 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 6182 | 0 |  |  |  |  | 0 | my $cmd = shift; | 
| 6183 | 0 |  |  |  |  | 0 | my $rest = shift; | 
| 6184 |  |  |  |  |  |  |  | 
| 6185 | 0 | 0 |  |  |  | 0 | unless (exists $INC{"Digest/MD5.pm"}) | 
| 6186 |  |  |  |  |  |  | { | 
| 6187 | 0 |  |  |  |  | 0 | $self->reply (500, "SITE CHECKSUM is not supported on this server."); | 
| 6188 | 0 |  |  |  |  | 0 | return; | 
| 6189 |  |  |  |  |  |  | } | 
| 6190 |  |  |  |  |  |  |  | 
| 6191 | 0 |  |  |  |  | 0 | my ($dirh, $fileh, $filename) = $self->_get ($rest); | 
| 6192 |  |  |  |  |  |  |  | 
| 6193 | 0 | 0 |  |  |  | 0 | unless ($fileh) | 
| 6194 |  |  |  |  |  |  | { | 
| 6195 | 0 |  |  |  |  | 0 | $self->reply (550, "File or directory not found."); | 
| 6196 | 0 |  |  |  |  | 0 | return; | 
| 6197 |  |  |  |  |  |  | } | 
| 6198 |  |  |  |  |  |  |  | 
| 6199 | 0 |  |  |  |  | 0 | my ($mode) = $fileh->status; | 
| 6200 |  |  |  |  |  |  |  | 
| 6201 | 0 | 0 |  |  |  | 0 | unless ($mode eq 'f') | 
| 6202 |  |  |  |  |  |  | { | 
| 6203 | 0 |  |  |  |  | 0 | $self->reply (550, "SITE CHECKSUM only works on plain files."); | 
| 6204 | 0 |  |  |  |  | 0 | return; | 
| 6205 |  |  |  |  |  |  | } | 
| 6206 |  |  |  |  |  |  |  | 
| 6207 | 0 |  |  |  |  | 0 | my $file = $fileh->open ("r"); | 
| 6208 |  |  |  |  |  |  |  | 
| 6209 | 0 | 0 |  |  |  | 0 | unless ($file) | 
| 6210 |  |  |  |  |  |  | { | 
| 6211 | 0 |  |  |  |  | 0 | $self->reply (550, "File not found."); | 
| 6212 | 0 |  |  |  |  | 0 | return; | 
| 6213 |  |  |  |  |  |  | } | 
| 6214 |  |  |  |  |  |  |  | 
| 6215 | 0 |  |  |  |  | 0 | my $ctx = "Digest::MD5"->new; | 
| 6216 | 0 |  |  |  |  | 0 | $ctx->addfile ($file);	# IO::Handles are also filehandle globs. | 
| 6217 |  |  |  |  |  |  |  | 
| 6218 | 0 |  |  |  |  | 0 | $self->reply (200, $ctx->hexdigest . " " . $filename); | 
| 6219 |  |  |  |  |  |  | } | 
| 6220 |  |  |  |  |  |  |  | 
| 6221 |  |  |  |  |  |  | sub _SITE_IDLE_command | 
| 6222 |  |  |  |  |  |  | { | 
| 6223 | 1 |  |  | 1 |  | 3 | my $self = shift; | 
| 6224 | 1 |  |  |  |  | 2 | my $cmd = shift; | 
| 6225 | 1 |  |  |  |  | 3 | my $rest = shift; | 
| 6226 |  |  |  |  |  |  |  | 
| 6227 | 1 | 50 |  |  |  | 4 | if ($rest eq "") | 
| 6228 |  |  |  |  |  |  | { | 
| 6229 | 0 |  |  |  |  | 0 | $self->reply (200, "Current idle timeout is $self->{_idle_timeout} seconds."); | 
| 6230 | 0 |  |  |  |  | 0 | return; | 
| 6231 |  |  |  |  |  |  | } | 
| 6232 |  |  |  |  |  |  |  | 
| 6233 |  |  |  |  |  |  | # As with wu-ftpd, we only allow idle timeouts to be set between | 
| 6234 |  |  |  |  |  |  | # 30 seconds and the current maximum set in the configuration file. | 
| 6235 |  |  |  |  |  |  | # In test mode, allow the idle timeout to be set to as small as 1 | 
| 6236 |  |  |  |  |  |  | # second -- useful for testing without having to hang around. | 
| 6237 | 1 | 50 |  |  |  | 4 | my $min_timeout = ! $self->{_test_mode} ? 30 : 1; | 
| 6238 | 1 |  | 33 |  |  | 5 | my $max_timeout = $self->config ("timeout") || $_default_timeout; | 
| 6239 |  |  |  |  |  |  |  | 
| 6240 | 1 | 50 | 33 |  |  | 13 | unless ($rest =~ /^[1-9][0-9]*$/ && | 
|  |  |  | 33 |  |  |  |  | 
| 6241 |  |  |  |  |  |  | $rest >= $min_timeout && $rest <= $max_timeout) | 
| 6242 |  |  |  |  |  |  | { | 
| 6243 | 0 |  |  |  |  | 0 | $self->reply (500, "Idle timeout must be between $min_timeout and $max_timeout seconds."); | 
| 6244 | 0 |  |  |  |  | 0 | return; | 
| 6245 |  |  |  |  |  |  | } | 
| 6246 |  |  |  |  |  |  |  | 
| 6247 | 1 |  |  |  |  | 2 | $self->{_idle_timeout} = $rest; | 
| 6248 |  |  |  |  |  |  |  | 
| 6249 | 1 |  |  |  |  | 5 | $self->reply (200, "Current idle timeout set to $self->{_idle_timeout} seconds."); | 
| 6250 |  |  |  |  |  |  | } | 
| 6251 |  |  |  |  |  |  |  | 
| 6252 |  |  |  |  |  |  | sub _SITE_SYNC_command | 
| 6253 |  |  |  |  |  |  | { | 
| 6254 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 6255 | 0 |  |  |  |  | 0 | my $cmd = shift; | 
| 6256 | 0 |  |  |  |  | 0 | my $rest = shift; | 
| 6257 |  |  |  |  |  |  |  | 
| 6258 | 0 | 0 |  |  |  | 0 | unless (exists $INC{"File/Sync.pm"}) | 
| 6259 |  |  |  |  |  |  | { | 
| 6260 | 0 |  |  |  |  | 0 | $self->reply (500, "Synchronization not available on this server."); | 
| 6261 | 0 |  |  |  |  | 0 | return; | 
| 6262 |  |  |  |  |  |  | } | 
| 6263 |  |  |  |  |  |  |  | 
| 6264 | 0 |  |  |  |  | 0 | File::Sync::sync (); | 
| 6265 |  |  |  |  |  |  |  | 
| 6266 | 0 |  |  |  |  | 0 | $self->reply (200, "Disks synchronized."); | 
| 6267 |  |  |  |  |  |  | } | 
| 6268 |  |  |  |  |  |  |  | 
| 6269 |  |  |  |  |  |  | sub _SITE_ARCHIVE_command | 
| 6270 |  |  |  |  |  |  | { | 
| 6271 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 6272 | 0 |  |  |  |  | 0 | my $cmd = shift; | 
| 6273 | 0 |  |  |  |  | 0 | my $rest = shift; | 
| 6274 |  |  |  |  |  |  |  | 
| 6275 | 0 | 0 | 0 |  |  | 0 | if (defined $self->config ("enable archive mode") && | 
| 6276 |  |  |  |  |  |  | !$self->config ("enable archive mode")) | 
| 6277 |  |  |  |  |  |  | { | 
| 6278 | 0 |  |  |  |  | 0 | $self->reply (500, "Archive mode is not enabled on this server."); | 
| 6279 | 0 |  |  |  |  | 0 | return; | 
| 6280 |  |  |  |  |  |  | } | 
| 6281 |  |  |  |  |  |  |  | 
| 6282 | 0 | 0 |  |  |  | 0 | if (!$rest) | 
| 6283 |  |  |  |  |  |  | { | 
| 6284 |  |  |  |  |  |  | $self->reply (200, | 
| 6285 |  |  |  |  |  |  | "Archive mode is ". | 
| 6286 | 0 | 0 |  |  |  | 0 | ($self->{archive_mode} ? "ON" : "OFF"). "."); | 
| 6287 | 0 |  |  |  |  | 0 | return; | 
| 6288 |  |  |  |  |  |  | } | 
| 6289 |  |  |  |  |  |  |  | 
| 6290 | 0 | 0 |  |  |  | 0 | if (uc ($rest) eq "ON") | 
| 6291 |  |  |  |  |  |  | { | 
| 6292 | 0 |  |  |  |  | 0 | $self->{archive_mode} = 1; | 
| 6293 | 0 |  |  |  |  | 0 | $self->reply (200, "Archive mode turned ON."); | 
| 6294 | 0 |  |  |  |  | 0 | return; | 
| 6295 |  |  |  |  |  |  | } | 
| 6296 |  |  |  |  |  |  |  | 
| 6297 | 0 | 0 |  |  |  | 0 | if (uc ($rest) eq "OFF") | 
| 6298 |  |  |  |  |  |  | { | 
| 6299 | 0 |  |  |  |  | 0 | $self->{archive_mode} = 0; | 
| 6300 | 0 |  |  |  |  | 0 | $self->reply (200, "Archive mode turned OFF."); | 
| 6301 | 0 |  |  |  |  | 0 | return; | 
| 6302 |  |  |  |  |  |  | } | 
| 6303 |  |  |  |  |  |  |  | 
| 6304 | 0 |  |  |  |  | 0 | $self->reply (500, "Usage: SITE ARCHIVE ON|OFF"); | 
| 6305 |  |  |  |  |  |  | } | 
| 6306 |  |  |  |  |  |  |  | 
| 6307 |  |  |  |  |  |  | sub _SYST_command | 
| 6308 |  |  |  |  |  |  | { | 
| 6309 | 1 |  |  | 1 |  | 3 | my $self = shift; | 
| 6310 | 1 |  |  |  |  | 3 | my $cmd = shift; | 
| 6311 | 1 |  |  |  |  | 3 | my $rest = shift; | 
| 6312 |  |  |  |  |  |  |  | 
| 6313 | 1 |  |  |  |  | 3 | $self->reply (215, "UNIX Type: L8"); | 
| 6314 |  |  |  |  |  |  | } | 
| 6315 |  |  |  |  |  |  |  | 
| 6316 |  |  |  |  |  |  | sub _SIZE_command | 
| 6317 |  |  |  |  |  |  | { | 
| 6318 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 6319 | 0 |  |  |  |  | 0 | my $cmd = shift; | 
| 6320 | 0 |  |  |  |  | 0 | my $rest = shift; | 
| 6321 |  |  |  |  |  |  |  | 
| 6322 | 0 |  |  |  |  | 0 | my ($dirh, $fileh, $filename) = $self->_get ($rest); | 
| 6323 |  |  |  |  |  |  |  | 
| 6324 | 0 | 0 |  |  |  | 0 | unless ($fileh) | 
| 6325 |  |  |  |  |  |  | { | 
| 6326 | 0 |  |  |  |  | 0 | $self->reply (550, "File or directory not found."); | 
| 6327 | 0 |  |  |  |  | 0 | return; | 
| 6328 |  |  |  |  |  |  | } | 
| 6329 |  |  |  |  |  |  |  | 
| 6330 |  |  |  |  |  |  | # Get the mode, size etc. Remember to check the mode. | 
| 6331 | 0 |  |  |  |  | 0 | my ($mode, $perms, $nlink, $user, $group, $size, $time) | 
| 6332 |  |  |  |  |  |  | = $fileh->status; | 
| 6333 |  |  |  |  |  |  |  | 
| 6334 | 0 | 0 |  |  |  | 0 | if ($mode ne "f") | 
| 6335 |  |  |  |  |  |  | { | 
| 6336 | 0 |  |  |  |  | 0 | $self->reply (550, "SIZE command is only supported on plain files."); | 
| 6337 | 0 |  |  |  |  | 0 | return; | 
| 6338 |  |  |  |  |  |  | } | 
| 6339 |  |  |  |  |  |  |  | 
| 6340 | 0 | 0 |  |  |  | 0 | if ($self->{type} eq 'A') | 
| 6341 |  |  |  |  |  |  | { | 
| 6342 |  |  |  |  |  |  | # ASCII mode: we have to count the characters by hand. | 
| 6343 | 0 | 0 |  |  |  | 0 | if (my $file = $fileh->open ("r")) | 
| 6344 |  |  |  |  |  |  | { | 
| 6345 | 0 |  |  |  |  | 0 | $size = 0; | 
| 6346 | 0 |  |  |  |  | 0 | $size++ while (defined ($file->getc)); | 
| 6347 | 0 |  |  |  |  | 0 | $file->close; | 
| 6348 |  |  |  |  |  |  | } | 
| 6349 |  |  |  |  |  |  | } | 
| 6350 |  |  |  |  |  |  |  | 
| 6351 | 0 |  |  |  |  | 0 | $self->reply (213, "$size"); | 
| 6352 |  |  |  |  |  |  | } | 
| 6353 |  |  |  |  |  |  |  | 
| 6354 |  |  |  |  |  |  | sub _STAT_command | 
| 6355 |  |  |  |  |  |  | { | 
| 6356 | 3 |  |  | 3 |  | 6 | my $self = shift; | 
| 6357 | 3 |  |  |  |  | 6 | my $cmd = shift; | 
| 6358 | 3 |  |  |  |  | 6 | my $rest = shift; | 
| 6359 |  |  |  |  |  |  |  | 
| 6360 |  |  |  |  |  |  | # STAT is a very strange command. It can either be used to show | 
| 6361 |  |  |  |  |  |  | # general internal information about the server in a free format, | 
| 6362 |  |  |  |  |  |  | # or else it can be used to list a directory over the control | 
| 6363 |  |  |  |  |  |  | # connection. See RFC 959 Section 4.1.3. | 
| 6364 |  |  |  |  |  |  |  | 
| 6365 | 3 | 100 |  |  |  | 9 | if ($rest eq "") | 
| 6366 |  |  |  |  |  |  | { | 
| 6367 |  |  |  |  |  |  | # Internal status. | 
| 6368 | 1 |  |  |  |  | 3 | my %status = (); | 
| 6369 |  |  |  |  |  |  |  | 
| 6370 | 1 | 50 | 33 |  |  | 3 | unless (defined $self->config ("allow site version command") && | 
| 6371 |  |  |  |  |  |  | ! $self->config ("allow site version command")) | 
| 6372 |  |  |  |  |  |  | { | 
| 6373 | 1 |  |  |  |  | 3 | $status{Version} = $self->{version_string}; | 
| 6374 |  |  |  |  |  |  | } | 
| 6375 |  |  |  |  |  |  |  | 
| 6376 | 1 |  |  |  |  | 4 | $status{TYPE} = $self->{type}; | 
| 6377 | 1 |  |  |  |  | 3 | $status{MODE} = $self->{mode}; | 
| 6378 | 1 |  |  |  |  | 4 | $status{FORM} = $self->{form}; | 
| 6379 | 1 |  |  |  |  | 2 | $status{STRUcture} = $self->{stru}; | 
| 6380 |  |  |  |  |  |  |  | 
| 6381 | 1 |  |  |  |  | 10 | $status{"Data Connection"} = "None"; # XXX | 
| 6382 |  |  |  |  |  |  |  | 
| 6383 | 1 | 50 | 33 |  |  | 15 | if ($self->{peeraddrstring} && $self->{peerport}) | 
| 6384 |  |  |  |  |  |  | { | 
| 6385 | 0 |  |  |  |  | 0 | $status{Client} = "$self->{peeraddrstring}:$self->{peerport}"; | 
| 6386 |  |  |  |  |  |  | $status{Client} .= " ($self->{peerhostname}:$self->{peerport})" | 
| 6387 | 0 | 0 |  |  |  | 0 | if $self->{peerhostname}; | 
| 6388 |  |  |  |  |  |  | } | 
| 6389 |  |  |  |  |  |  |  | 
| 6390 | 1 | 50 |  |  |  | 4 | unless ($self->{user_is_anonymous}) | 
| 6391 |  |  |  |  |  |  | { | 
| 6392 | 1 |  |  |  |  | 4 | $status{User} = $self->{user}; | 
| 6393 |  |  |  |  |  |  | } | 
| 6394 |  |  |  |  |  |  | else | 
| 6395 |  |  |  |  |  |  | { | 
| 6396 | 0 |  |  |  |  | 0 | $status{User} = "anonymous"; | 
| 6397 |  |  |  |  |  |  | } | 
| 6398 |  |  |  |  |  |  |  | 
| 6399 | 1 |  |  |  |  | 10 | my @status = map { $_ . ": " . $status{$_} } sort keys %status; | 
|  | 7 |  |  |  |  | 19 |  | 
| 6400 |  |  |  |  |  |  |  | 
| 6401 | 1 |  |  |  |  | 4 | $self->reply (211, "FTP server status:", @status, "End of status"); | 
| 6402 |  |  |  |  |  |  | } | 
| 6403 |  |  |  |  |  |  | else | 
| 6404 |  |  |  |  |  |  | { | 
| 6405 |  |  |  |  |  |  | # Act like the LIST command. | 
| 6406 | 2 |  |  |  |  | 22 | my ($dirh, $wildcard, $fileh, $filename) | 
| 6407 |  |  |  |  |  |  | = $self->_list ($rest); | 
| 6408 |  |  |  |  |  |  |  | 
| 6409 | 2 | 100 |  |  |  | 10 | unless ($dirh) | 
| 6410 |  |  |  |  |  |  | { | 
| 6411 | 1 |  |  |  |  | 3 | $self->reply (550, "File or directory not found."); | 
| 6412 | 1 |  |  |  |  | 4 | return; | 
| 6413 |  |  |  |  |  |  | } | 
| 6414 |  |  |  |  |  |  |  | 
| 6415 | 1 |  |  |  |  | 3 | my @lines = (); | 
| 6416 |  |  |  |  |  |  |  | 
| 6417 |  |  |  |  |  |  | # OK, we're either listing a full directory, listing a single | 
| 6418 |  |  |  |  |  |  | # file or listing a wildcard. | 
| 6419 | 1 | 50 |  |  |  | 6 | if ($fileh)		# Single file in $dirh. | 
| 6420 |  |  |  |  |  |  | { | 
| 6421 | 0 |  |  |  |  | 0 | push @lines, $filename; | 
| 6422 |  |  |  |  |  |  | } | 
| 6423 |  |  |  |  |  |  | else			# Wildcard or full directory $dirh. | 
| 6424 |  |  |  |  |  |  | { | 
| 6425 | 1 |  |  |  |  | 10 | my $r = $dirh->list_status ($wildcard); | 
| 6426 |  |  |  |  |  |  |  | 
| 6427 | 1 |  |  |  |  | 4 | foreach (@$r) | 
| 6428 |  |  |  |  |  |  | { | 
| 6429 | 0 |  |  |  |  | 0 | my $filename = $_->[0]; | 
| 6430 |  |  |  |  |  |  |  | 
| 6431 | 0 |  |  |  |  | 0 | push @lines, $filename; | 
| 6432 |  |  |  |  |  |  | } | 
| 6433 |  |  |  |  |  |  | } | 
| 6434 |  |  |  |  |  |  |  | 
| 6435 |  |  |  |  |  |  | # Send them back to the client. | 
| 6436 | 1 |  |  |  |  | 6 | $self->reply (213, "Status of $rest:", @lines, "End of status"); | 
| 6437 |  |  |  |  |  |  | } | 
| 6438 |  |  |  |  |  |  | } | 
| 6439 |  |  |  |  |  |  |  | 
| 6440 |  |  |  |  |  |  | sub _HELP_command | 
| 6441 |  |  |  |  |  |  | { | 
| 6442 | 2 |  |  | 2 |  | 6 | my $self = shift; | 
| 6443 | 2 |  |  |  |  | 6 | my $cmd = shift; | 
| 6444 | 2 |  |  |  |  | 4 | my $rest = shift; | 
| 6445 |  |  |  |  |  |  |  | 
| 6446 | 2 |  |  |  |  | 5 | my @version_info = (); | 
| 6447 |  |  |  |  |  |  |  | 
| 6448 |  |  |  |  |  |  | # Dan Bernstein recommends sending the server version info here. | 
| 6449 | 2 | 50 | 33 |  |  | 9 | unless (defined $self->config ("allow site version command") && | 
| 6450 |  |  |  |  |  |  | ! $self->config ("allow site version command")) | 
| 6451 |  |  |  |  |  |  | { | 
| 6452 | 2 |  |  |  |  | 6 | @version_info = ( $self->{version_string} ); | 
| 6453 |  |  |  |  |  |  | } | 
| 6454 |  |  |  |  |  |  |  | 
| 6455 |  |  |  |  |  |  | # Without any arguments, return a list of commands supported. | 
| 6456 | 2 | 100 |  |  |  | 8 | if ($rest eq "") | 
|  |  | 50 |  |  |  |  |  | 
| 6457 |  |  |  |  |  |  | { | 
| 6458 | 1 |  |  |  |  | 3 | my @lines = _format_list (sort keys %{$self->{command_table}}); | 
|  | 1 |  |  |  |  | 51 |  | 
| 6459 |  |  |  |  |  |  |  | 
| 6460 | 1 |  |  |  |  | 6 | $self->reply (214, | 
| 6461 |  |  |  |  |  |  | @version_info, | 
| 6462 |  |  |  |  |  |  | "The following commands are recognized:", | 
| 6463 |  |  |  |  |  |  | @lines, | 
| 6464 |  |  |  |  |  |  | "You can also use HELP SITE to list site specific commands."); | 
| 6465 |  |  |  |  |  |  | } | 
| 6466 |  |  |  |  |  |  | # HELP SITE. | 
| 6467 |  |  |  |  |  |  | elsif (uc $rest eq "SITE") | 
| 6468 |  |  |  |  |  |  | { | 
| 6469 | 1 |  |  |  |  | 2 | my @lines = _format_list (sort keys %{$self->{site_command_table}}); | 
|  | 1 |  |  |  |  | 10 |  | 
| 6470 |  |  |  |  |  |  |  | 
| 6471 | 1 |  |  |  |  | 4 | $self->reply (214, | 
| 6472 |  |  |  |  |  |  | @version_info, | 
| 6473 |  |  |  |  |  |  | "The following commands are recognized:", | 
| 6474 |  |  |  |  |  |  | @lines, | 
| 6475 |  |  |  |  |  |  | "You can also use HELP to list general commands."); | 
| 6476 |  |  |  |  |  |  | } | 
| 6477 |  |  |  |  |  |  | # No other form of HELP available right now. | 
| 6478 |  |  |  |  |  |  | else | 
| 6479 |  |  |  |  |  |  | { | 
| 6480 | 0 |  |  |  |  | 0 | $self->reply (214, | 
| 6481 |  |  |  |  |  |  | "No command-specific help is available right now. Use HELP or HELP SITE."); | 
| 6482 |  |  |  |  |  |  | } | 
| 6483 |  |  |  |  |  |  | } | 
| 6484 |  |  |  |  |  |  |  | 
| 6485 |  |  |  |  |  |  | sub _format_list | 
| 6486 |  |  |  |  |  |  | { | 
| 6487 | 2 |  |  | 2 |  | 7 | my @lines = (); | 
| 6488 | 2 |  |  |  |  | 5 | my ($r, $c); | 
| 6489 | 2 |  |  |  |  | 29 | my $rows = int (ceil (@_ / 4.)); | 
| 6490 |  |  |  |  |  |  |  | 
| 6491 | 2 |  |  |  |  | 8 | for ($r = 0; $r < $rows; ++$r) | 
| 6492 |  |  |  |  |  |  | { | 
| 6493 | 17 |  |  |  |  | 39 | my @r = (); | 
| 6494 |  |  |  |  |  |  |  | 
| 6495 | 17 |  |  |  |  | 28 | for ($c = 0; $c < 4; ++$c) | 
| 6496 |  |  |  |  |  |  | { | 
| 6497 | 68 |  |  |  |  | 75 | my $n = $c * $rows + $r; | 
| 6498 |  |  |  |  |  |  |  | 
| 6499 | 68 | 100 |  |  |  | 141 | push @r, $_[$n] if $n < @_; | 
| 6500 |  |  |  |  |  |  | } | 
| 6501 |  |  |  |  |  |  |  | 
| 6502 | 17 |  |  |  |  | 61 | push @lines, "\t" . join "\t", @r; | 
| 6503 |  |  |  |  |  |  | } | 
| 6504 |  |  |  |  |  |  |  | 
| 6505 | 2 |  |  |  |  | 8 | return @lines; | 
| 6506 |  |  |  |  |  |  | } | 
| 6507 |  |  |  |  |  |  |  | 
| 6508 |  |  |  |  |  |  | sub _NOOP_command | 
| 6509 |  |  |  |  |  |  | { | 
| 6510 | 1 |  |  | 1 |  | 3 | my $self = shift; | 
| 6511 | 1 |  |  |  |  | 2 | my $cmd = shift; | 
| 6512 | 1 |  |  |  |  | 2 | my $rest = shift; | 
| 6513 |  |  |  |  |  |  |  | 
| 6514 | 1 |  |  |  |  | 4 | $self->reply (200, "OK"); | 
| 6515 |  |  |  |  |  |  | } | 
| 6516 |  |  |  |  |  |  |  | 
| 6517 |  |  |  |  |  |  | sub _XMKD_command | 
| 6518 |  |  |  |  |  |  | { | 
| 6519 | 0 |  |  | 0 |  | 0 | return shift->_MKD_command (@_); | 
| 6520 |  |  |  |  |  |  | } | 
| 6521 |  |  |  |  |  |  |  | 
| 6522 |  |  |  |  |  |  | sub _XRMD_command | 
| 6523 |  |  |  |  |  |  | { | 
| 6524 | 0 |  |  | 0 |  | 0 | return shift->_RMD_command (@_); | 
| 6525 |  |  |  |  |  |  | } | 
| 6526 |  |  |  |  |  |  |  | 
| 6527 |  |  |  |  |  |  | sub _XPWD_command | 
| 6528 |  |  |  |  |  |  | { | 
| 6529 | 0 |  |  | 0 |  | 0 | return shift->_PWD_command (@_); | 
| 6530 |  |  |  |  |  |  | } | 
| 6531 |  |  |  |  |  |  |  | 
| 6532 |  |  |  |  |  |  | sub _XCUP_command | 
| 6533 |  |  |  |  |  |  | { | 
| 6534 | 0 |  |  | 0 |  | 0 | return shift->_CDUP_command (@_); | 
| 6535 |  |  |  |  |  |  | } | 
| 6536 |  |  |  |  |  |  |  | 
| 6537 |  |  |  |  |  |  | sub _XCWD_command | 
| 6538 |  |  |  |  |  |  | { | 
| 6539 | 0 |  |  | 0 |  | 0 | return shift->_CWD_command (@_); | 
| 6540 |  |  |  |  |  |  | } | 
| 6541 |  |  |  |  |  |  |  | 
| 6542 |  |  |  |  |  |  | sub _FEAT_command | 
| 6543 |  |  |  |  |  |  | { | 
| 6544 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 6545 | 0 |  |  |  |  | 0 | my $cmd = shift; | 
| 6546 | 0 |  |  |  |  | 0 | my $rest = shift; | 
| 6547 |  |  |  |  |  |  |  | 
| 6548 | 0 | 0 |  |  |  | 0 | if ($rest ne "") | 
| 6549 |  |  |  |  |  |  | { | 
| 6550 | 0 |  |  |  |  | 0 | $self->reply (501, "Unexpected parameters to FEAT command."); | 
| 6551 | 0 |  |  |  |  | 0 | return; | 
| 6552 |  |  |  |  |  |  | } | 
| 6553 |  |  |  |  |  |  |  | 
| 6554 |  |  |  |  |  |  | # Print out the extensions supported. Don't use $self->reply, since | 
| 6555 |  |  |  |  |  |  | # it doesn't have the exact guaranteed behaviour (it instead immitates | 
| 6556 |  |  |  |  |  |  | # wu-ftpd by putting the server code in each line). | 
| 6557 |  |  |  |  |  |  | # | 
| 6558 |  |  |  |  |  |  | # See RFC 2389 section 3.2. | 
| 6559 | 0 |  |  |  |  | 0 | print "211-Extensions supported:\r\n"; | 
| 6560 |  |  |  |  |  |  |  | 
| 6561 | 0 |  |  |  |  | 0 | foreach (sort keys %{$self->{features}}) | 
|  | 0 |  |  |  |  | 0 |  | 
| 6562 |  |  |  |  |  |  | { | 
| 6563 | 0 | 0 |  |  |  | 0 | unless ($self->{features}{$_}) | 
| 6564 |  |  |  |  |  |  | { | 
| 6565 | 0 |  |  |  |  | 0 | print " $_\r\n"; | 
| 6566 |  |  |  |  |  |  | } | 
| 6567 |  |  |  |  |  |  | else | 
| 6568 |  |  |  |  |  |  | { | 
| 6569 | 0 |  |  |  |  | 0 | print " $_ ", $self->{features}{$_}, "\r\n"; | 
| 6570 |  |  |  |  |  |  | } | 
| 6571 |  |  |  |  |  |  | } | 
| 6572 |  |  |  |  |  |  |  | 
| 6573 | 0 |  |  |  |  | 0 | print "211 END\r\n"; | 
| 6574 |  |  |  |  |  |  | } | 
| 6575 |  |  |  |  |  |  |  | 
| 6576 |  |  |  |  |  |  | sub _OPTS_command | 
| 6577 |  |  |  |  |  |  | { | 
| 6578 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 6579 | 0 |  |  |  |  | 0 | my $cmd = shift; | 
| 6580 | 0 |  |  |  |  | 0 | my $rest = shift; | 
| 6581 |  |  |  |  |  |  |  | 
| 6582 |  |  |  |  |  |  | # RFC 2389 section 4. | 
| 6583 |  |  |  |  |  |  | # See also RFC 2640 section 3.1. | 
| 6584 | 0 | 0 |  |  |  | 0 | unless ($rest =~ /^([A-Z]{3,4})\s?(.*)/i) | 
| 6585 |  |  |  |  |  |  | { | 
| 6586 | 0 |  |  |  |  | 0 | $self->reply (501, "Syntax error in OPTS command."); | 
| 6587 | 0 |  |  |  |  | 0 | return; | 
| 6588 |  |  |  |  |  |  | } | 
| 6589 |  |  |  |  |  |  |  | 
| 6590 | 0 |  |  |  |  | 0 | ($cmd, $rest) = (uc $1, $2); | 
| 6591 |  |  |  |  |  |  |  | 
| 6592 |  |  |  |  |  |  | # Find the appropriate command. | 
| 6593 | 0 | 0 |  |  |  | 0 | unless (exists $self->{options}{$cmd}) | 
| 6594 |  |  |  |  |  |  | { | 
| 6595 | 0 |  |  |  |  | 0 | $self->reply (501, "Command has no settable options."); | 
| 6596 | 0 |  |  |  |  | 0 | return; | 
| 6597 |  |  |  |  |  |  | } | 
| 6598 |  |  |  |  |  |  |  | 
| 6599 |  |  |  |  |  |  | # The command should print either a 200 or a 451 reply. | 
| 6600 | 0 |  |  |  |  | 0 | &{$self->{options}{$cmd}} ($self, $cmd, $rest); | 
|  | 0 |  |  |  |  | 0 |  | 
| 6601 |  |  |  |  |  |  | } | 
| 6602 |  |  |  |  |  |  |  | 
| 6603 |  |  |  |  |  |  | sub _MSAM_command | 
| 6604 |  |  |  |  |  |  | { | 
| 6605 | 1 |  |  | 1 |  | 4 | my $self = shift; | 
| 6606 | 1 |  |  |  |  | 2 | my $cmd = shift; | 
| 6607 | 1 |  |  |  |  | 3 | my $rest = shift; | 
| 6608 |  |  |  |  |  |  |  | 
| 6609 | 1 |  |  |  |  | 3 | $self->reply (502, "Obsolete RFC 765 mail commands not implemented."); | 
| 6610 |  |  |  |  |  |  | } | 
| 6611 |  |  |  |  |  |  |  | 
| 6612 |  |  |  |  |  |  | sub _MRSQ_command | 
| 6613 |  |  |  |  |  |  | { | 
| 6614 | 1 |  |  | 1 |  | 3 | my $self = shift; | 
| 6615 | 1 |  |  |  |  | 2 | my $cmd = shift; | 
| 6616 | 1 |  |  |  |  | 3 | my $rest = shift; | 
| 6617 |  |  |  |  |  |  |  | 
| 6618 | 1 |  |  |  |  | 3 | $self->reply (502, "Obsolete RFC 765 mail commands not implemented."); | 
| 6619 |  |  |  |  |  |  | } | 
| 6620 |  |  |  |  |  |  |  | 
| 6621 |  |  |  |  |  |  | sub _MLFL_command | 
| 6622 |  |  |  |  |  |  | { | 
| 6623 | 1 |  |  | 1 |  | 4 | my $self = shift; | 
| 6624 | 1 |  |  |  |  | 5 | my $cmd = shift; | 
| 6625 | 1 |  |  |  |  | 4 | my $rest = shift; | 
| 6626 |  |  |  |  |  |  |  | 
| 6627 | 1 |  |  |  |  | 5 | $self->reply (502, "Obsolete RFC 765 mail commands not implemented."); | 
| 6628 |  |  |  |  |  |  | } | 
| 6629 |  |  |  |  |  |  |  | 
| 6630 |  |  |  |  |  |  | sub _MRCP_command | 
| 6631 |  |  |  |  |  |  | { | 
| 6632 | 1 |  |  | 1 |  | 4 | my $self = shift; | 
| 6633 | 1 |  |  |  |  | 2 | my $cmd = shift; | 
| 6634 | 1 |  |  |  |  | 2 | my $rest = shift; | 
| 6635 |  |  |  |  |  |  |  | 
| 6636 | 1 |  |  |  |  | 4 | $self->reply (502, "Obsolete RFC 765 mail commands not implemented."); | 
| 6637 |  |  |  |  |  |  | } | 
| 6638 |  |  |  |  |  |  |  | 
| 6639 |  |  |  |  |  |  | sub _MAIL_command | 
| 6640 |  |  |  |  |  |  | { | 
| 6641 | 1 |  |  | 1 |  | 3 | my $self = shift; | 
| 6642 | 1 |  |  |  |  | 2 | my $cmd = shift; | 
| 6643 | 1 |  |  |  |  | 2 | my $rest = shift; | 
| 6644 |  |  |  |  |  |  |  | 
| 6645 | 1 |  |  |  |  | 5 | $self->reply (502, "Obsolete RFC 765 mail commands not implemented."); | 
| 6646 |  |  |  |  |  |  | } | 
| 6647 |  |  |  |  |  |  |  | 
| 6648 |  |  |  |  |  |  | sub _MSND_command | 
| 6649 |  |  |  |  |  |  | { | 
| 6650 | 1 |  |  | 1 |  | 2 | my $self = shift; | 
| 6651 | 1 |  |  |  |  | 3 | my $cmd = shift; | 
| 6652 | 1 |  |  |  |  | 2 | my $rest = shift; | 
| 6653 |  |  |  |  |  |  |  | 
| 6654 | 1 |  |  |  |  | 3 | $self->reply (502, "Obsolete RFC 765 mail commands not implemented."); | 
| 6655 |  |  |  |  |  |  | } | 
| 6656 |  |  |  |  |  |  |  | 
| 6657 |  |  |  |  |  |  | sub _MSOM_command | 
| 6658 |  |  |  |  |  |  | { | 
| 6659 | 1 |  |  | 1 |  | 2 | my $self = shift; | 
| 6660 | 1 |  |  |  |  | 3 | my $cmd = shift; | 
| 6661 | 1 |  |  |  |  | 2 | my $rest = shift; | 
| 6662 |  |  |  |  |  |  |  | 
| 6663 | 1 |  |  |  |  | 3 | $self->reply (502, "Obsolete RFC 765 mail commands not implemented."); | 
| 6664 |  |  |  |  |  |  | } | 
| 6665 |  |  |  |  |  |  |  | 
| 6666 |  |  |  |  |  |  | sub _LANG_command | 
| 6667 |  |  |  |  |  |  | { | 
| 6668 | 3 |  |  | 3 |  | 5 | my $self = shift; | 
| 6669 | 3 |  |  |  |  | 4 | my $cmd = shift; | 
| 6670 | 3 |  |  |  |  | 4 | my $rest = shift; | 
| 6671 |  |  |  |  |  |  |  | 
| 6672 |  |  |  |  |  |  | # The beginnings of language support. | 
| 6673 |  |  |  |  |  |  | # | 
| 6674 |  |  |  |  |  |  | # XXX To complete language support we need to implement the FEAT | 
| 6675 |  |  |  |  |  |  | # command for language properly, put gettext around all strings | 
| 6676 |  |  |  |  |  |  | # and also arrange for strings to be translated. See RFC 2640. | 
| 6677 |  |  |  |  |  |  |  | 
| 6678 |  |  |  |  |  |  | # If no argument, then we want to find the current language. | 
| 6679 | 3 | 100 |  |  |  | 8 | if ($rest eq "") | 
| 6680 |  |  |  |  |  |  | { | 
| 6681 | 2 |  | 100 |  |  | 16 | my $lang = $ENV{LANGUAGE} || "en"; | 
| 6682 | 2 |  |  |  |  | 11 | $self->reply (200, "Language is $lang."); | 
| 6683 | 2 |  |  |  |  | 5 | return; | 
| 6684 |  |  |  |  |  |  | } | 
| 6685 |  |  |  |  |  |  |  | 
| 6686 |  |  |  |  |  |  | # We limit the whole tag to 8 chars since (a) it's highly unlikely | 
| 6687 |  |  |  |  |  |  | # that any genuine language code would be longer than this and | 
| 6688 |  |  |  |  |  |  | # (b) there are all sorts of possible libc exploits available if | 
| 6689 |  |  |  |  |  |  | # the user is allowed to set this to arbitrary values. | 
| 6690 | 1 | 50 | 33 |  |  | 12 | unless (length ($rest) <= 8 && | 
| 6691 |  |  |  |  |  |  | $rest =~ /^[A-Z]{1,8}(-[A-Z]{1-8})*$/i) | 
| 6692 |  |  |  |  |  |  | { | 
| 6693 | 0 |  |  |  |  | 0 | $self->reply (504, "Incorrect language."); | 
| 6694 | 0 |  |  |  |  | 0 | return; | 
| 6695 |  |  |  |  |  |  | } | 
| 6696 |  |  |  |  |  |  |  | 
| 6697 | 1 |  |  |  |  | 14 | $ENV{LANGUAGE} = $rest; | 
| 6698 | 1 |  |  |  |  | 9 | $self->reply (200, "Language changed to $rest."); | 
| 6699 |  |  |  |  |  |  | } | 
| 6700 |  |  |  |  |  |  |  | 
| 6701 |  |  |  |  |  |  | sub _CLNT_command | 
| 6702 |  |  |  |  |  |  | { | 
| 6703 | 1 |  |  | 1 |  | 3 | my $self = shift; | 
| 6704 | 1 |  |  |  |  | 3 | my $cmd = shift; | 
| 6705 | 1 |  |  |  |  | 3 | my $rest = shift; | 
| 6706 |  |  |  |  |  |  |  | 
| 6707 |  |  |  |  |  |  | # NcFTP sends the CLNT command. I don't know what RFC this | 
| 6708 |  |  |  |  |  |  | # comes from. | 
| 6709 | 1 |  |  |  |  | 6 | $self->reply (200, "Hello $rest."); | 
| 6710 |  |  |  |  |  |  | } | 
| 6711 |  |  |  |  |  |  |  | 
| 6712 |  |  |  |  |  |  | sub _MDTM_command | 
| 6713 |  |  |  |  |  |  | { | 
| 6714 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 6715 | 0 |  |  |  |  | 0 | my $cmd = shift; | 
| 6716 | 0 |  |  |  |  | 0 | my $rest = shift; | 
| 6717 |  |  |  |  |  |  |  | 
| 6718 | 0 |  |  |  |  | 0 | my ($dirh, $fileh, $filename) = $self->_get ($rest); | 
| 6719 |  |  |  |  |  |  |  | 
| 6720 | 0 | 0 |  |  |  | 0 | unless ($fileh) | 
| 6721 |  |  |  |  |  |  | { | 
| 6722 | 0 |  |  |  |  | 0 | $self->reply (550, "File or directory not found."); | 
| 6723 | 0 |  |  |  |  | 0 | return; | 
| 6724 |  |  |  |  |  |  | } | 
| 6725 |  |  |  |  |  |  |  | 
| 6726 |  |  |  |  |  |  | # Get the status. | 
| 6727 | 0 |  |  |  |  | 0 | my ($mode, $perms, $nlink, $user, $group, $size, $time) | 
| 6728 |  |  |  |  |  |  | = $fileh->status; | 
| 6729 |  |  |  |  |  |  |  | 
| 6730 |  |  |  |  |  |  | # Format the modification time. See draft-ietf-ftpext-mlst-11.txt | 
| 6731 |  |  |  |  |  |  | # sections 2.3 and 3.1. | 
| 6732 | 0 |  |  |  |  | 0 | my $fmt_time = strftime "%Y%m%d%H%M%S", gmtime ($time); | 
| 6733 |  |  |  |  |  |  |  | 
| 6734 | 0 |  |  |  |  | 0 | $self->reply (213, $fmt_time); | 
| 6735 |  |  |  |  |  |  | } | 
| 6736 |  |  |  |  |  |  |  | 
| 6737 |  |  |  |  |  |  | sub _MLST_command | 
| 6738 |  |  |  |  |  |  | { | 
| 6739 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 6740 | 0 |  |  |  |  | 0 | my $cmd = shift; | 
| 6741 | 0 |  |  |  |  | 0 | my $rest = shift; | 
| 6742 |  |  |  |  |  |  |  | 
| 6743 |  |  |  |  |  |  | # If not file name is given, then we need to return | 
| 6744 |  |  |  |  |  |  | # status on the current directory. Else we return | 
| 6745 |  |  |  |  |  |  | # status on the file or directory name given. | 
| 6746 | 0 |  |  |  |  | 0 | my $fileh; | 
| 6747 | 0 |  |  |  |  | 0 | my $dirh = $self->{cwd}; | 
| 6748 | 0 |  |  |  |  | 0 | my $filename = "."; | 
| 6749 |  |  |  |  |  |  |  | 
| 6750 | 0 | 0 |  |  |  | 0 | if ($rest ne "") | 
| 6751 |  |  |  |  |  |  | { | 
| 6752 | 0 |  |  |  |  | 0 | ($dirh, $fileh, $filename) = $self->_get ($rest); | 
| 6753 |  |  |  |  |  |  |  | 
| 6754 | 0 | 0 |  |  |  | 0 | unless ($fileh) | 
| 6755 |  |  |  |  |  |  | { | 
| 6756 | 0 |  |  |  |  | 0 | $self->reply (550, "File or directory not found."); | 
| 6757 | 0 |  |  |  |  | 0 | return; | 
| 6758 |  |  |  |  |  |  | } | 
| 6759 |  |  |  |  |  |  | } | 
| 6760 |  |  |  |  |  |  |  | 
| 6761 |  |  |  |  |  |  | # Check access control. | 
| 6762 | 0 | 0 |  |  |  | 0 | unless ($self->_eval_rule ("list rule", | 
| 6763 |  |  |  |  |  |  | undef, undef, $fileh->pathname)) | 
| 6764 |  |  |  |  |  |  | { | 
| 6765 | 0 |  |  |  |  | 0 | $self->reply (550, "LIST command denied by server configuration."); | 
| 6766 | 0 |  |  |  |  | 0 | return; | 
| 6767 |  |  |  |  |  |  | } | 
| 6768 |  |  |  |  |  |  |  | 
| 6769 |  |  |  |  |  |  | # Get the status. | 
| 6770 | 0 |  |  |  |  | 0 | my ($mode, $perms, $nlink, $user, $group, $size, $time) | 
| 6771 |  |  |  |  |  |  | = $fileh->status; | 
| 6772 |  |  |  |  |  |  |  | 
| 6773 |  |  |  |  |  |  | # Return the requested information over the control connection. | 
| 6774 | 0 |  |  |  |  | 0 | my $info = $self->_mlst_format ($filename, $fileh, $dirh); | 
| 6775 |  |  |  |  |  |  |  | 
| 6776 |  |  |  |  |  |  | # Can't use $self->reply since it produces the wrong format. | 
| 6777 | 0 |  |  |  |  | 0 | print "250-Listing of $filename:\r\n"; | 
| 6778 | 0 |  |  |  |  | 0 | print " ", $info, "\r\n"; | 
| 6779 | 0 |  |  |  |  | 0 | print "250 End of listing.\r\n"; | 
| 6780 |  |  |  |  |  |  | } | 
| 6781 |  |  |  |  |  |  |  | 
| 6782 |  |  |  |  |  |  | sub _MLSD_command | 
| 6783 |  |  |  |  |  |  | { | 
| 6784 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 6785 | 0 |  |  |  |  | 0 | my $cmd = shift; | 
| 6786 | 0 |  |  |  |  | 0 | my $rest = shift; | 
| 6787 |  |  |  |  |  |  |  | 
| 6788 |  |  |  |  |  |  | # XXX Note that this is slightly wrong. According to the Internet | 
| 6789 |  |  |  |  |  |  | # Draft we shouldn't handle wildcards in the MLST or MLSD commands. | 
| 6790 | 0 |  |  |  |  | 0 | my ($dirh, $wildcard, $fileh, $filename) | 
| 6791 |  |  |  |  |  |  | = $self->_list ($rest); | 
| 6792 |  |  |  |  |  |  |  | 
| 6793 | 0 | 0 |  |  |  | 0 | unless ($dirh) | 
| 6794 |  |  |  |  |  |  | { | 
| 6795 | 0 |  |  |  |  | 0 | $self->reply (550, "File or directory not found."); | 
| 6796 | 0 |  |  |  |  | 0 | return; | 
| 6797 |  |  |  |  |  |  | } | 
| 6798 |  |  |  |  |  |  |  | 
| 6799 |  |  |  |  |  |  | # Check access control. | 
| 6800 | 0 | 0 |  |  |  | 0 | unless ($self->_eval_rule ("list rule", | 
| 6801 |  |  |  |  |  |  | undef, undef, $dirh->pathname)) | 
| 6802 |  |  |  |  |  |  | { | 
| 6803 | 0 |  |  |  |  | 0 | $self->reply (550, "MLSD command denied by server configuration."); | 
| 6804 | 0 |  |  |  |  | 0 | return; | 
| 6805 |  |  |  |  |  |  | } | 
| 6806 |  |  |  |  |  |  |  | 
| 6807 | 0 |  |  |  |  | 0 | $self->reply (150, "Opening data connection for file listing."); | 
| 6808 |  |  |  |  |  |  |  | 
| 6809 |  |  |  |  |  |  | # Open a path back to the client. | 
| 6810 | 0 |  |  |  |  | 0 | my $sock = $self->open_data_connection; | 
| 6811 |  |  |  |  |  |  |  | 
| 6812 | 0 | 0 |  |  |  | 0 | unless ($sock) | 
| 6813 |  |  |  |  |  |  | { | 
| 6814 | 0 |  |  |  |  | 0 | $self->reply (425, "Can't open data connection."); | 
| 6815 | 0 |  |  |  |  | 0 | return; | 
| 6816 |  |  |  |  |  |  | } | 
| 6817 |  |  |  |  |  |  |  | 
| 6818 |  |  |  |  |  |  | # Outgoing bandwidth | 
| 6819 | 0 | 0 |  |  |  | 0 | $self->xfer_start ($dirh->pathname, "o") if $self->{_xferlog}; | 
| 6820 |  |  |  |  |  |  |  | 
| 6821 |  |  |  |  |  |  | # OK, we're either listing a full directory, listing a single | 
| 6822 |  |  |  |  |  |  | # file or listing a wildcard. | 
| 6823 | 0 | 0 |  |  |  | 0 | if ($fileh)			# Single file in $dirh. | 
| 6824 |  |  |  |  |  |  | { | 
| 6825 |  |  |  |  |  |  | # Do not bother logging xfer of the status of one file | 
| 6826 | 0 |  |  |  |  | 0 | $sock->print ($self->_mlst_format ($filename, $fileh, $dirh), "\r\n"); | 
| 6827 |  |  |  |  |  |  | } | 
| 6828 |  |  |  |  |  |  | else			# Wildcard or full directory $dirh. | 
| 6829 |  |  |  |  |  |  | { | 
| 6830 | 0 |  |  |  |  | 0 | my $r = $dirh->list_status ($wildcard); | 
| 6831 |  |  |  |  |  |  |  | 
| 6832 | 0 |  |  |  |  | 0 | foreach (@$r) | 
| 6833 |  |  |  |  |  |  | { | 
| 6834 | 0 |  |  |  |  | 0 | my $filename = $_->[0]; | 
| 6835 | 0 |  |  |  |  | 0 | my $handle = $_->[1]; | 
| 6836 | 0 |  |  |  |  | 0 | my $statusref = $_->[2]; | 
| 6837 | 0 |  |  |  |  | 0 | my $line = $self->_mlst_format ($filename, | 
| 6838 |  |  |  |  |  |  | $handle, $dirh, $statusref). | 
| 6839 |  |  |  |  |  |  | "\r\n"; | 
| 6840 | 0 | 0 |  |  |  | 0 | $self->xfer (length $line) if $self->{_xferlog}; | 
| 6841 | 0 |  |  |  |  | 0 | $sock->print ($line); | 
| 6842 |  |  |  |  |  |  | } | 
| 6843 |  |  |  |  |  |  | } | 
| 6844 |  |  |  |  |  |  |  | 
| 6845 | 0 | 0 |  |  |  | 0 | unless ($sock->close) | 
| 6846 |  |  |  |  |  |  | { | 
| 6847 | 0 |  |  |  |  | 0 | $self->reply (550, "Error closing data connection: $!"); | 
| 6848 | 0 |  |  |  |  | 0 | return; | 
| 6849 |  |  |  |  |  |  | } | 
| 6850 |  |  |  |  |  |  |  | 
| 6851 | 0 | 0 |  |  |  | 0 | $self->xfer_complete if $self->{_xferlog}; | 
| 6852 | 0 |  |  |  |  | 0 | $self->reply (226, "Listing complete. Data connection has been closed."); | 
| 6853 |  |  |  |  |  |  | } | 
| 6854 |  |  |  |  |  |  |  | 
| 6855 |  |  |  |  |  |  | sub _OPTS_MLST_command | 
| 6856 |  |  |  |  |  |  | { | 
| 6857 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 6858 | 0 |  |  |  |  | 0 | my $cmd = shift; | 
| 6859 | 0 |  |  |  |  | 0 | my $rest = shift; | 
| 6860 |  |  |  |  |  |  |  | 
| 6861 |  |  |  |  |  |  | # Break up the list of facts. | 
| 6862 | 0 |  |  |  |  | 0 | my @facts = split /;/, $rest; | 
| 6863 |  |  |  |  |  |  |  | 
| 6864 | 0 |  |  |  |  | 0 | $self->{_mlst_facts} = []; | 
| 6865 |  |  |  |  |  |  |  | 
| 6866 |  |  |  |  |  |  | # Check that all the facts asked for are supported. | 
| 6867 | 0 |  |  |  |  | 0 | foreach (@facts) | 
| 6868 |  |  |  |  |  |  | { | 
| 6869 | 0 |  |  |  |  | 0 | $_ = uc; | 
| 6870 |  |  |  |  |  |  |  | 
| 6871 | 0 | 0 |  |  |  | 0 | if ($_ ne "") | 
| 6872 |  |  |  |  |  |  | { | 
| 6873 | 0 | 0 |  |  |  | 0 | if ($self->_is_supported_mlst_fact ($_)) | 
| 6874 |  |  |  |  |  |  | { | 
| 6875 | 0 |  |  |  |  | 0 | push @{$self->{_mlst_facts}}, $_; | 
|  | 0 |  |  |  |  | 0 |  | 
| 6876 |  |  |  |  |  |  | } | 
| 6877 |  |  |  |  |  |  | } | 
| 6878 |  |  |  |  |  |  | } | 
| 6879 |  |  |  |  |  |  |  | 
| 6880 |  |  |  |  |  |  | # Return the list of facts enabled. | 
| 6881 |  |  |  |  |  |  | $self->reply (200, | 
| 6882 |  |  |  |  |  |  | "MLST OPTS " . | 
| 6883 |  |  |  |  |  |  | join ("", | 
| 6884 | 0 |  |  |  |  | 0 | map { "$_;" } @{$self->{_mlst_facts}})); | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 6885 |  |  |  |  |  |  |  | 
| 6886 |  |  |  |  |  |  | # Update the FEAT list. | 
| 6887 | 0 |  |  |  |  | 0 | $self->{features}{MLST} = $self->_mlst_features; | 
| 6888 |  |  |  |  |  |  | } | 
| 6889 |  |  |  |  |  |  |  | 
| 6890 |  |  |  |  |  |  | sub _is_supported_mlst_fact | 
| 6891 |  |  |  |  |  |  | { | 
| 6892 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 6893 | 0 |  |  |  |  | 0 | my $fact = shift; | 
| 6894 |  |  |  |  |  |  |  | 
| 6895 | 0 |  |  |  |  | 0 | foreach my $supported_fact (@_supported_mlst_facts) | 
| 6896 |  |  |  |  |  |  | { | 
| 6897 | 0 | 0 |  |  |  | 0 | return 1 if $fact eq $supported_fact; | 
| 6898 |  |  |  |  |  |  | } | 
| 6899 |  |  |  |  |  |  |  | 
| 6900 | 0 |  |  |  |  | 0 | return 0; | 
| 6901 |  |  |  |  |  |  | } | 
| 6902 |  |  |  |  |  |  |  | 
| 6903 |  |  |  |  |  |  | sub _mlst_features | 
| 6904 |  |  |  |  |  |  | { | 
| 6905 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 6906 | 0 |  |  |  |  | 0 | my $out = ""; | 
| 6907 |  |  |  |  |  |  |  | 
| 6908 | 0 |  |  |  |  | 0 | foreach my $supported_fact (@_supported_mlst_facts) | 
| 6909 |  |  |  |  |  |  | { | 
| 6910 | 0 | 0 |  |  |  | 0 | if ($self->_is_enabled_fact ($supported_fact)) { | 
| 6911 | 0 |  |  |  |  | 0 | $out .= "$supported_fact*;" | 
| 6912 |  |  |  |  |  |  | } else { | 
| 6913 | 0 |  |  |  |  | 0 | $out .= "$supported_fact;" | 
| 6914 |  |  |  |  |  |  | } | 
| 6915 |  |  |  |  |  |  | } | 
| 6916 |  |  |  |  |  |  |  | 
| 6917 | 0 |  |  |  |  | 0 | return $out; | 
| 6918 |  |  |  |  |  |  | } | 
| 6919 |  |  |  |  |  |  |  | 
| 6920 |  |  |  |  |  |  | sub _is_enabled_fact | 
| 6921 |  |  |  |  |  |  | { | 
| 6922 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 6923 | 0 |  |  |  |  | 0 | my $fact = shift; | 
| 6924 |  |  |  |  |  |  |  | 
| 6925 | 0 |  |  |  |  | 0 | foreach my $enabled_fact (@{$self->{_mlst_facts}}) | 
|  | 0 |  |  |  |  | 0 |  | 
| 6926 |  |  |  |  |  |  | { | 
| 6927 | 0 | 0 |  |  |  | 0 | return 1 if $fact eq $enabled_fact; | 
| 6928 |  |  |  |  |  |  | } | 
| 6929 | 0 |  |  |  |  | 0 | return 0; | 
| 6930 |  |  |  |  |  |  | } | 
| 6931 |  |  |  |  |  |  |  | 
| 6932 | 75 |  |  | 75 |  | 1026 | use vars qw(%_mode_to_mlst_unix_type); | 
|  | 75 |  |  |  |  | 186 |  | 
|  | 75 |  |  |  |  | 202903 |  | 
| 6933 |  |  |  |  |  |  |  | 
| 6934 |  |  |  |  |  |  | # XXX I made these up. Is there a list anywhere? | 
| 6935 |  |  |  |  |  |  | %_mode_to_mlst_unix_type = ( | 
| 6936 |  |  |  |  |  |  | l => "LINK", | 
| 6937 |  |  |  |  |  |  | p => "PIPE", | 
| 6938 |  |  |  |  |  |  | s => "SOCKET", | 
| 6939 |  |  |  |  |  |  | b => "BLOCK", | 
| 6940 |  |  |  |  |  |  | c => "CHAR", | 
| 6941 |  |  |  |  |  |  | ); | 
| 6942 |  |  |  |  |  |  |  | 
| 6943 |  |  |  |  |  |  | sub _mlst_format | 
| 6944 |  |  |  |  |  |  | { | 
| 6945 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 6946 | 0 |  |  |  |  | 0 | my $filename = shift; | 
| 6947 | 0 |  |  |  |  | 0 | my $fileh = shift; | 
| 6948 | 0 |  |  |  |  | 0 | my $dirh = shift; | 
| 6949 | 0 |  |  |  |  | 0 | my $statusref = shift; | 
| 6950 | 0 |  |  |  |  | 0 | local $_; | 
| 6951 |  |  |  |  |  |  |  | 
| 6952 |  |  |  |  |  |  | # Get the status information. | 
| 6953 | 0 |  |  |  |  | 0 | my @status; | 
| 6954 | 0 | 0 |  |  |  | 0 | if ($statusref) { @status = @$statusref } | 
|  | 0 |  |  |  |  | 0 |  | 
| 6955 | 0 |  |  |  |  | 0 | else            { @status = $fileh->status } | 
| 6956 |  |  |  |  |  |  |  | 
| 6957 |  |  |  |  |  |  | # Break out the fields of the status information. | 
| 6958 | 0 |  |  |  |  | 0 | my ($mode, $perms, $nlink, $user, $group, $size, $mtime) = @status; | 
| 6959 |  |  |  |  |  |  |  | 
| 6960 |  |  |  |  |  |  | # Get the directory status information. | 
| 6961 | 0 |  |  |  |  | 0 | my ($dir_mode, $dir_perms) = ('d', $perms); | 
| 6962 | 0 | 0 |  |  |  | 0 | ($dir_mode, $dir_perms) = $dirh->status if $dirh; | 
| 6963 |  |  |  |  |  |  |  | 
| 6964 |  |  |  |  |  |  | # Return the requested facts. | 
| 6965 | 0 |  |  |  |  | 0 | my @facts = (); | 
| 6966 |  |  |  |  |  |  |  | 
| 6967 | 0 |  |  |  |  | 0 | foreach (@{$self->{_mlst_facts}}) | 
|  | 0 |  |  |  |  | 0 |  | 
| 6968 |  |  |  |  |  |  | { | 
| 6969 | 0 | 0 |  |  |  | 0 | if ($_ eq "TYPE") | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 6970 |  |  |  |  |  |  | { | 
| 6971 | 0 | 0 |  |  |  | 0 | if ($mode eq "f") { | 
|  |  | 0 |  |  |  |  |  | 
| 6972 | 0 |  |  |  |  | 0 | push @facts, "$_=file"; | 
| 6973 |  |  |  |  |  |  | } elsif ($mode eq "d") { | 
| 6974 | 0 | 0 |  |  |  | 0 | if ($filename eq ".") { | 
|  |  | 0 |  |  |  |  |  | 
| 6975 | 0 |  |  |  |  | 0 | push @facts, "$_=cdir"; | 
| 6976 |  |  |  |  |  |  | } elsif ($filename eq "..") { | 
| 6977 | 0 |  |  |  |  | 0 | push @facts, "$_=pdir"; | 
| 6978 |  |  |  |  |  |  | } else { | 
| 6979 | 0 |  |  |  |  | 0 | push @facts, "$_=dir"; | 
| 6980 |  |  |  |  |  |  | } | 
| 6981 |  |  |  |  |  |  | } else { | 
| 6982 | 0 |  |  |  |  | 0 | push @facts, "$_=OS.UNIX=$_mode_to_mlst_unix_type{$mode}"; | 
| 6983 |  |  |  |  |  |  | } | 
| 6984 |  |  |  |  |  |  | } | 
| 6985 |  |  |  |  |  |  | elsif ($_ eq "SIZE") | 
| 6986 |  |  |  |  |  |  | { | 
| 6987 | 0 |  |  |  |  | 0 | push @facts, "$_=$size"; | 
| 6988 |  |  |  |  |  |  | } | 
| 6989 |  |  |  |  |  |  | elsif ($_ eq "MODIFY") | 
| 6990 |  |  |  |  |  |  | { | 
| 6991 | 0 |  |  |  |  | 0 | my $fmt_time = strftime "%Y%m%d%H%M%S", localtime ($mtime); | 
| 6992 | 0 |  |  |  |  | 0 | push @facts, "$_=$fmt_time"; | 
| 6993 |  |  |  |  |  |  | } | 
| 6994 |  |  |  |  |  |  | elsif ($_ eq "PERM") | 
| 6995 |  |  |  |  |  |  | { | 
| 6996 | 0 | 0 |  |  |  | 0 | if ($mode eq "f") | 
|  |  | 0 |  |  |  |  |  | 
| 6997 |  |  |  |  |  |  | { | 
| 6998 | 0 | 0 |  |  |  | 0 | push @facts, | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 6999 |  |  |  |  |  |  | "$_=" . ($perms & 0400     ? "r" : "") . # read | 
| 7000 |  |  |  |  |  |  | ($perms & 0200     ? "w" : "") . # write | 
| 7001 |  |  |  |  |  |  | ($perms & 0200     ? "a" : "") . # append | 
| 7002 |  |  |  |  |  |  | ($dir_perms & 0200 ? "f" : "") . # rename | 
| 7003 |  |  |  |  |  |  | ($dir_perms & 0200 ? "d" : "");	 # delete | 
| 7004 |  |  |  |  |  |  | } | 
| 7005 |  |  |  |  |  |  | elsif ($mode eq "d") | 
| 7006 |  |  |  |  |  |  | { | 
| 7007 | 0 | 0 |  |  |  | 0 | push @facts, | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 7008 |  |  |  |  |  |  | "$_=" . ($perms & 0200     ? "c" : "") . # write | 
| 7009 |  |  |  |  |  |  | ($dir_perms & 0200 ? "d" : "") . # delete | 
| 7010 |  |  |  |  |  |  | ($perms & 0100     ? "e" : "") . # enter | 
| 7011 |  |  |  |  |  |  | ($perms & 0500     ? "l" : "") . # list | 
| 7012 |  |  |  |  |  |  | ($dir_perms & 0200 ? "f" : "") . # rename | 
| 7013 |  |  |  |  |  |  | ($perms & 0200     ? "m" : "");	 # mkdir | 
| 7014 |  |  |  |  |  |  | } | 
| 7015 |  |  |  |  |  |  | else | 
| 7016 |  |  |  |  |  |  | { | 
| 7017 |  |  |  |  |  |  | # Pipes, block specials, etc. | 
| 7018 | 0 | 0 |  |  |  | 0 | push @facts, | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 7019 |  |  |  |  |  |  | "$_=" . ($perms & 0400     ? "r" : "") . # read | 
| 7020 |  |  |  |  |  |  | ($perms & 0200     ? "w" : "") . # write | 
| 7021 |  |  |  |  |  |  | ($dir_perms & 0200 ? "f" : "") . # rename | 
| 7022 |  |  |  |  |  |  | ($dir_perms & 0200 ? "d" : "");  # delete | 
| 7023 |  |  |  |  |  |  | } | 
| 7024 |  |  |  |  |  |  | } | 
| 7025 |  |  |  |  |  |  | elsif ($_ eq "UNIX.MODE") | 
| 7026 |  |  |  |  |  |  | { | 
| 7027 | 0 | 0 |  |  |  | 0 | my $unix_mode = sprintf ("%s%s%s%s%s%s%s%s%s", | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 7028 |  |  |  |  |  |  | ($perms & 0400 ? 'r' : '-'), | 
| 7029 |  |  |  |  |  |  | ($perms & 0200 ? 'w' : '-'), | 
| 7030 |  |  |  |  |  |  | ($perms & 0100 ? 'x' : '-'), | 
| 7031 |  |  |  |  |  |  | ($perms & 040 ? 'r' : '-'), | 
| 7032 |  |  |  |  |  |  | ($perms & 020 ? 'w' : '-'), | 
| 7033 |  |  |  |  |  |  | ($perms & 010 ? 'x' : '-'), | 
| 7034 |  |  |  |  |  |  | ($perms & 04 ? 'r' : '-'), | 
| 7035 |  |  |  |  |  |  | ($perms & 02 ? 'w' : '-'), | 
| 7036 |  |  |  |  |  |  | ($perms & 01 ? 'x' : '-')); | 
| 7037 | 0 |  |  |  |  | 0 | push @facts, "$_=$unix_mode"; | 
| 7038 |  |  |  |  |  |  | } | 
| 7039 |  |  |  |  |  |  | else | 
| 7040 |  |  |  |  |  |  | { | 
| 7041 | 0 |  |  |  |  | 0 | die "unknown MLST fact: $_"; | 
| 7042 |  |  |  |  |  |  | } | 
| 7043 |  |  |  |  |  |  | } | 
| 7044 |  |  |  |  |  |  |  | 
| 7045 |  |  |  |  |  |  | # Return the facts to the user in a string. | 
| 7046 | 0 |  |  |  |  | 0 | return join (";", @facts) . "; " . $filename; | 
| 7047 |  |  |  |  |  |  | } | 
| 7048 |  |  |  |  |  |  |  | 
| 7049 |  |  |  |  |  |  | # Routine: xfer_start | 
| 7050 |  |  |  |  |  |  | # Purpose: Initialize the beginning of a transfer. | 
| 7051 |  |  |  |  |  |  | # PreCond: | 
| 7052 |  |  |  |  |  |  | #   Takes full pathname and direction as arguments. | 
| 7053 |  |  |  |  |  |  | #   _xferlog should be set to a writeable file handle. | 
| 7054 |  |  |  |  |  |  | #   Should not already have xfer_start'ed a transfer | 
| 7055 |  |  |  |  |  |  | #    or already finished it with a xfer_flush call. | 
| 7056 |  |  |  |  |  |  | sub xfer_start | 
| 7057 |  |  |  |  |  |  | { | 
| 7058 | 0 |  |  | 0 | 0 | 0 | my $self = shift; | 
| 7059 |  |  |  |  |  |  | # If old data still exists, write to log | 
| 7060 |  |  |  |  |  |  | # (This should not happen.) | 
| 7061 | 0 | 0 |  |  |  | 0 | $self->xfer_flush if $self->{xfer}; | 
| 7062 |  |  |  |  |  |  | $self->{xfer} = { | 
| 7063 | 0 |  |  |  |  | 0 | status => "i",  # Default to incomplete transfer status | 
| 7064 |  |  |  |  |  |  | start  => time, # Started right now | 
| 7065 |  |  |  |  |  |  | bytes  => 0,    # Nothing transferred yet | 
| 7066 |  |  |  |  |  |  | path   => shift, | 
| 7067 |  |  |  |  |  |  | direct => shift, | 
| 7068 |  |  |  |  |  |  | }; | 
| 7069 |  |  |  |  |  |  | } | 
| 7070 |  |  |  |  |  |  |  | 
| 7071 |  |  |  |  |  |  | # Routine: xfer | 
| 7072 |  |  |  |  |  |  | # Purpose: Log transfer chunk. | 
| 7073 |  |  |  |  |  |  | # PreCond: | 
| 7074 |  |  |  |  |  |  | #   Takes the number of bytes just transferring. | 
| 7075 |  |  |  |  |  |  | #   Should have called xfer_start first. | 
| 7076 |  |  |  |  |  |  | sub xfer | 
| 7077 |  |  |  |  |  |  | { | 
| 7078 | 25 |  |  | 25 | 1 | 38 | my $self = shift; | 
| 7079 | 25 | 50 |  |  |  | 58 | return unless $self->{xfer}; | 
| 7080 | 0 |  |  |  |  | 0 | $self->{xfer}->{bytes} += shift; | 
| 7081 |  |  |  |  |  |  | } | 
| 7082 |  |  |  |  |  |  |  | 
| 7083 |  |  |  |  |  |  | # Routine: xfer_complete | 
| 7084 |  |  |  |  |  |  | # Purpose: Mark that the transfer completed successfully. | 
| 7085 |  |  |  |  |  |  | # PreCond: | 
| 7086 |  |  |  |  |  |  | #   Should have called xfer_start first. | 
| 7087 |  |  |  |  |  |  | sub xfer_complete | 
| 7088 |  |  |  |  |  |  | { | 
| 7089 | 0 |  |  | 0 | 0 | 0 | my $self = shift; | 
| 7090 | 0 | 0 |  |  |  | 0 | return unless $self->{xfer}; | 
| 7091 | 0 |  |  |  |  | 0 | $self->{xfer}->{status} = 'c'; | 
| 7092 | 0 |  |  |  |  | 0 | $self->xfer_flush; | 
| 7093 |  |  |  |  |  |  | } | 
| 7094 |  |  |  |  |  |  |  | 
| 7095 |  |  |  |  |  |  | # Routine: xfer_flush | 
| 7096 |  |  |  |  |  |  | # Purpose: Write to the xferlog and clean up. | 
| 7097 |  |  |  |  |  |  | # PreCond: | 
| 7098 |  |  |  |  |  |  | #   Should have called xfer_start first. | 
| 7099 |  |  |  |  |  |  | sub xfer_flush | 
| 7100 |  |  |  |  |  |  | { | 
| 7101 | 0 |  |  | 0 | 0 | 0 | my $self = shift; | 
| 7102 |  |  |  |  |  |  | # If no xfer ref, then it's already flushed | 
| 7103 | 0 | 0 |  |  |  | 0 | my $xfer = $self->{xfer} or return; | 
| 7104 | 0 | 0 |  |  |  | 0 | return unless $self->{_xferlog}; | 
| 7105 |  |  |  |  |  |  |  | 
| 7106 |  |  |  |  |  |  | # Wipe xfer ref to signify that it's flushed | 
| 7107 | 0 |  |  |  |  | 0 | delete $self->{xfer}; | 
| 7108 |  |  |  |  |  |  |  | 
| 7109 |  |  |  |  |  |  | # Never log if zero bytes transferred | 
| 7110 | 0 | 0 |  |  |  | 0 | return unless $xfer->{bytes}; | 
| 7111 |  |  |  |  |  |  |  | 
| 7112 |  |  |  |  |  |  | # Send information in the right format | 
| 7113 |  |  |  |  |  |  | $self->{_xferlog}->print | 
| 7114 |  |  |  |  |  |  | (join " ", | 
| 7115 |  |  |  |  |  |  | scalar(localtime($xfer->{start})),                    # current-time | 
| 7116 |  |  |  |  |  |  | (time() - $xfer->{start}),                            # transfer-time | 
| 7117 |  |  |  |  |  |  | ($self->{peerhostname} || $self->{peeraddrstring}),   # remote-host | 
| 7118 |  |  |  |  |  |  | $xfer->{bytes},                                       # file-size | 
| 7119 |  |  |  |  |  |  | $xfer->{path},                                        # filename | 
| 7120 |  |  |  |  |  |  | ($self->{type} eq 'A' ? "a" : "b"),                   # transfer-type | 
| 7121 |  |  |  |  |  |  | "_",  # Compression not implemented?                  # special-action-flag | 
| 7122 |  |  |  |  |  |  | $xfer->{direct},                                      # direction | 
| 7123 |  |  |  |  |  |  | ($self->{user_is_anonymous} ? "a" : "r"),             # access-mode | 
| 7124 |  |  |  |  |  |  | $self->{user},                                        # username | 
| 7125 | 0 | 0 | 0 |  |  | 0 | "ftp",                                                # service-name | 
|  |  | 0 |  |  |  |  |  | 
| 7126 |  |  |  |  |  |  | "0",  # RFC931 stuff?                                 # authentication-method | 
| 7127 |  |  |  |  |  |  | "*",  # RFC931 stuff?                                 # authenticated-user-id | 
| 7128 |  |  |  |  |  |  | "$xfer->{status}".                                    # completion-status | 
| 7129 |  |  |  |  |  |  | "\n"); | 
| 7130 | 0 |  |  |  |  | 0 | return; | 
| 7131 |  |  |  |  |  |  | } | 
| 7132 |  |  |  |  |  |  |  | 
| 7133 |  |  |  |  |  |  |  | 
| 7134 |  |  |  |  |  |  | # Evaluate an access control rule from the configuration file. | 
| 7135 |  |  |  |  |  |  |  | 
| 7136 |  |  |  |  |  |  | sub _eval_rule | 
| 7137 |  |  |  |  |  |  | { | 
| 7138 | 213 |  |  | 213 |  | 498 | my $self = shift; | 
| 7139 | 213 |  |  |  |  | 606 | my $rulename = shift; | 
| 7140 | 213 |  |  |  |  | 368 | my $pathname = shift; | 
| 7141 | 213 |  |  |  |  | 407 | my $filename = shift; | 
| 7142 | 213 |  |  |  |  | 745 | my $dirname = shift; | 
| 7143 |  |  |  |  |  |  |  | 
| 7144 | 213 | 50 |  |  |  | 638 | my $rule | 
| 7145 |  |  |  |  |  |  | = defined $self->config ($rulename) ? $self->config ($rulename) : "1"; | 
| 7146 |  |  |  |  |  |  |  | 
| 7147 |  |  |  |  |  |  | # Set up the variables. | 
| 7148 | 213 |  |  |  |  | 473 | my $hostname = $self->{peerhostname}; | 
| 7149 | 213 |  |  |  |  | 440 | my $ip = $self->{peeraddrstring}; | 
| 7150 | 213 |  |  |  |  | 459 | my $user = $self->{user}; | 
| 7151 | 213 |  |  |  |  | 413 | my $class = $self->{class}; | 
| 7152 | 213 |  |  |  |  | 404 | my $user_is_anonymous = $self->{user_is_anonymous}; | 
| 7153 | 213 |  |  |  |  | 399 | my $type = $self->{type}; | 
| 7154 | 213 |  |  |  |  | 398 | my $form = $self->{form}; | 
| 7155 | 213 |  |  |  |  | 386 | my $mode = $self->{mode}; | 
| 7156 | 213 |  |  |  |  | 368 | my $stru = $self->{stru}; | 
| 7157 |  |  |  |  |  |  |  | 
| 7158 | 213 |  |  |  |  | 11247 | my $rv = eval $rule; | 
| 7159 | 213 | 50 |  |  |  | 919 | die if $@; | 
| 7160 |  |  |  |  |  |  |  | 
| 7161 | 213 |  |  |  |  | 827 | return $rv; | 
| 7162 |  |  |  |  |  |  | } | 
| 7163 |  |  |  |  |  |  |  | 
| 7164 |  |  |  |  |  |  | # Move from one directory to another. Return the new directory handle. | 
| 7165 |  |  |  |  |  |  |  | 
| 7166 |  |  |  |  |  |  | sub _chdir | 
| 7167 |  |  |  |  |  |  | { | 
| 7168 | 32 |  |  | 32 |  | 74 | my $self = shift; | 
| 7169 | 32 |  |  |  |  | 57 | my $dirh = shift; | 
| 7170 | 32 |  |  |  |  | 67 | my $path = shift; | 
| 7171 | 32 |  |  |  |  | 63 | local $_; | 
| 7172 |  |  |  |  |  |  |  | 
| 7173 |  |  |  |  |  |  | # If the path starts with a "/" then it's an absolute path. | 
| 7174 | 32 | 100 |  |  |  | 137 | if (substr ($path, 0, 1) eq "/") | 
| 7175 |  |  |  |  |  |  | { | 
| 7176 | 24 |  |  |  |  | 91 | $dirh = $self->root_directory_hook; | 
| 7177 | 24 |  |  |  |  | 356 | $path =~ s,^/+,,; | 
| 7178 |  |  |  |  |  |  | } | 
| 7179 |  |  |  |  |  |  |  | 
| 7180 |  |  |  |  |  |  | # Split the path into its component parts and process each separately. | 
| 7181 | 32 |  |  |  |  | 151 | my @elems = split /\//, $path; | 
| 7182 |  |  |  |  |  |  |  | 
| 7183 | 32 |  |  |  |  | 111 | foreach (@elems) | 
| 7184 |  |  |  |  |  |  | { | 
| 7185 | 8 | 50 | 33 |  |  | 90 | if ($_ eq "" || $_ eq ".") { next } # Ignore these. | 
|  | 0 | 100 |  |  |  | 0 |  | 
| 7186 |  |  |  |  |  |  | elsif ($_ eq "..") | 
| 7187 |  |  |  |  |  |  | { | 
| 7188 |  |  |  |  |  |  | # Go to parent directory. | 
| 7189 | 2 |  |  |  |  | 8 | $dirh = $dirh->parent; | 
| 7190 |  |  |  |  |  |  | } | 
| 7191 |  |  |  |  |  |  | else | 
| 7192 |  |  |  |  |  |  | { | 
| 7193 |  |  |  |  |  |  | # Go into subdirectory, if it exists. | 
| 7194 | 6 |  |  |  |  | 27 | $dirh = $dirh->get ($_); | 
| 7195 |  |  |  |  |  |  |  | 
| 7196 |  |  |  |  |  |  | return undef | 
| 7197 | 6 | 100 | 66 |  |  | 77 | unless $dirh && $dirh->isa ("Net::FTPServer::DirHandle"); | 
| 7198 |  |  |  |  |  |  | } | 
| 7199 |  |  |  |  |  |  | } | 
| 7200 |  |  |  |  |  |  |  | 
| 7201 | 31 |  |  |  |  | 157 | return $dirh; | 
| 7202 |  |  |  |  |  |  | } | 
| 7203 |  |  |  |  |  |  |  | 
| 7204 |  |  |  |  |  |  | # The list command understands the following forms for $path: | 
| 7205 |  |  |  |  |  |  | # | 
| 7206 |  |  |  |  |  |  | #   <>         List current directory. | 
| 7207 |  |  |  |  |  |  | #   file              List single file in cwd. | 
| 7208 |  |  |  |  |  |  | #   wildcard          List files by wildcard in cwd. | 
| 7209 |  |  |  |  |  |  | #   path/to/dir       List contents of directory, relative to cwd. | 
| 7210 |  |  |  |  |  |  | #   /path/to/dir      List contents of directory, absolute. | 
| 7211 |  |  |  |  |  |  | #   path/to/file      List single file, relative to cwd. | 
| 7212 |  |  |  |  |  |  | #   /path/to/file     List single file, absolute. | 
| 7213 |  |  |  |  |  |  | #   path/to/wildcard  List files by wildcard, relative to cwd. | 
| 7214 |  |  |  |  |  |  | #   /path/to/wildcard List files by wildcard, absolute. | 
| 7215 |  |  |  |  |  |  |  | 
| 7216 |  |  |  |  |  |  | sub _list | 
| 7217 |  |  |  |  |  |  | { | 
| 7218 | 8 |  |  | 8 |  | 17 | my $self = shift; | 
| 7219 | 8 |  |  |  |  | 13 | my $path = shift; | 
| 7220 |  |  |  |  |  |  |  | 
| 7221 | 8 |  |  |  |  | 18 | my $dirh = $self->{cwd}; | 
| 7222 |  |  |  |  |  |  |  | 
| 7223 |  |  |  |  |  |  | # Absolute path? | 
| 7224 | 8 | 50 |  |  |  | 36 | if (substr ($path, 0, 1) eq "/") | 
| 7225 |  |  |  |  |  |  | { | 
| 7226 | 0 |  |  |  |  | 0 | $dirh = $self->root_directory_hook; | 
| 7227 | 0 |  |  |  |  | 0 | $path =~ s,^/+,,; | 
| 7228 |  |  |  |  |  |  | } | 
| 7229 |  |  |  |  |  |  |  | 
| 7230 |  |  |  |  |  |  | # Parse the first elements of the path until we find the appropriate | 
| 7231 |  |  |  |  |  |  | # working directory. | 
| 7232 | 8 |  |  |  |  | 26 | my @elems = split /\//, $path; | 
| 7233 | 8 |  |  |  |  | 16 | my ($wildcard, $fileh, $filename); | 
| 7234 | 8 |  |  |  |  | 15 | local $_; | 
| 7235 |  |  |  |  |  |  |  | 
| 7236 | 8 |  |  |  |  | 25 | for (my $i = 0; $i < @elems; ++$i) | 
| 7237 |  |  |  |  |  |  | { | 
| 7238 | 6 |  |  |  |  | 15 | $_ = $elems[$i]; | 
| 7239 | 6 |  |  |  |  | 16 | my $lastelement = $i == @elems-1; | 
| 7240 |  |  |  |  |  |  |  | 
| 7241 | 6 | 100 | 66 |  |  | 63 | if ($_ eq "" || $_ eq ".") { next } # Ignore these. | 
|  | 1 | 50 |  |  |  | 6 |  | 
| 7242 |  |  |  |  |  |  | elsif ($_ eq "..") | 
| 7243 |  |  |  |  |  |  | { | 
| 7244 |  |  |  |  |  |  | # Go to parent directory. | 
| 7245 | 0 |  |  |  |  | 0 | $dirh = $dirh->parent; | 
| 7246 |  |  |  |  |  |  | } | 
| 7247 |  |  |  |  |  |  | else | 
| 7248 |  |  |  |  |  |  | { | 
| 7249 |  |  |  |  |  |  | # What is it? | 
| 7250 | 5 |  |  |  |  | 29 | my $handle = $dirh->get ($_); | 
| 7251 |  |  |  |  |  |  |  | 
| 7252 | 5 | 50 |  |  |  | 18 | if (!$lastelement) | 
| 7253 |  |  |  |  |  |  | { | 
| 7254 | 0 | 0 |  |  |  | 0 | if (!$handle) | 
|  |  | 0 |  |  |  |  |  | 
| 7255 |  |  |  |  |  |  | { | 
| 7256 | 0 |  |  |  |  | 0 | return (); | 
| 7257 |  |  |  |  |  |  | } | 
| 7258 |  |  |  |  |  |  | elsif (!$handle->isa ("Net::FTPServer::DirHandle")) | 
| 7259 |  |  |  |  |  |  | { | 
| 7260 | 0 |  |  |  |  | 0 | return (); | 
| 7261 |  |  |  |  |  |  | } | 
| 7262 |  |  |  |  |  |  | else | 
| 7263 |  |  |  |  |  |  | { | 
| 7264 | 0 |  |  |  |  | 0 | $dirh = $handle; | 
| 7265 |  |  |  |  |  |  | } | 
| 7266 |  |  |  |  |  |  | } | 
| 7267 |  |  |  |  |  |  | else # it's the last element - treat it nicely. | 
| 7268 |  |  |  |  |  |  | { | 
| 7269 | 5 | 50 |  |  |  | 17 | if (!$handle) | 
|  |  | 0 |  |  |  |  |  | 
| 7270 |  |  |  |  |  |  | { | 
| 7271 |  |  |  |  |  |  | # But it could be a wildcard ... | 
| 7272 | 5 | 100 | 66 |  |  | 46 | if (/\*/ || /\?/) | 
| 7273 |  |  |  |  |  |  | { | 
| 7274 | 4 |  |  |  |  | 20 | $wildcard = $_; | 
| 7275 |  |  |  |  |  |  | } | 
| 7276 |  |  |  |  |  |  | else | 
| 7277 |  |  |  |  |  |  | { | 
| 7278 | 1 |  |  |  |  | 4 | return (); | 
| 7279 |  |  |  |  |  |  | } | 
| 7280 |  |  |  |  |  |  | } | 
| 7281 |  |  |  |  |  |  | elsif (!$handle->isa ("Net::FTPServer::DirHandle")) | 
| 7282 |  |  |  |  |  |  | { | 
| 7283 |  |  |  |  |  |  | # So it's a file. | 
| 7284 | 0 |  |  |  |  | 0 | $fileh = $handle; | 
| 7285 | 0 |  |  |  |  | 0 | $filename = $_; | 
| 7286 |  |  |  |  |  |  | } | 
| 7287 |  |  |  |  |  |  | else | 
| 7288 |  |  |  |  |  |  | { | 
| 7289 | 0 |  |  |  |  | 0 | $dirh = $handle; | 
| 7290 |  |  |  |  |  |  | } | 
| 7291 |  |  |  |  |  |  | } | 
| 7292 |  |  |  |  |  |  | } | 
| 7293 |  |  |  |  |  |  | } # for | 
| 7294 |  |  |  |  |  |  |  | 
| 7295 | 7 |  |  |  |  | 36 | return ($dirh, $wildcard, $fileh, $filename); | 
| 7296 |  |  |  |  |  |  | } | 
| 7297 |  |  |  |  |  |  |  | 
| 7298 |  |  |  |  |  |  | # The get command understands the following forms for $path: | 
| 7299 |  |  |  |  |  |  | # | 
| 7300 |  |  |  |  |  |  | #   file              List single file in cwd. | 
| 7301 |  |  |  |  |  |  | #   path/to/file      List single file, relative to cwd. | 
| 7302 |  |  |  |  |  |  | #   /path/to/file     List single file, absolute. | 
| 7303 |  |  |  |  |  |  | # | 
| 7304 |  |  |  |  |  |  | # Returns ($dirh, $fileh, $filename) where: | 
| 7305 |  |  |  |  |  |  | # | 
| 7306 |  |  |  |  |  |  | #   $dirh is set if the directory exists | 
| 7307 |  |  |  |  |  |  | #   $fileh is set if the directory and the file exist | 
| 7308 |  |  |  |  |  |  | #   $filename is just the last component part of the path | 
| 7309 |  |  |  |  |  |  | #     and is always set if $dirh is set. | 
| 7310 |  |  |  |  |  |  |  | 
| 7311 |  |  |  |  |  |  | sub _get | 
| 7312 |  |  |  |  |  |  | { | 
| 7313 | 123 |  |  | 123 |  | 206 | my $self = shift; | 
| 7314 | 123 |  |  |  |  | 219 | my $path = shift; | 
| 7315 |  |  |  |  |  |  |  | 
| 7316 | 123 |  |  |  |  | 245 | my $dirh = $self->{cwd}; | 
| 7317 |  |  |  |  |  |  |  | 
| 7318 |  |  |  |  |  |  | # Absolute path? | 
| 7319 | 123 | 50 |  |  |  | 389 | if (substr ($path, 0, 1) eq "/") | 
| 7320 |  |  |  |  |  |  | { | 
| 7321 | 0 |  |  |  |  | 0 | $dirh = $self->root_directory_hook; | 
| 7322 | 0 |  |  |  |  | 0 | $path =~ s,^/+,,; | 
| 7323 | 0 | 0 |  |  |  | 0 | $path = "." if $path eq ""; | 
| 7324 |  |  |  |  |  |  | } | 
| 7325 |  |  |  |  |  |  |  | 
| 7326 |  |  |  |  |  |  | # Parse the first elements of path until we find the appropriate | 
| 7327 |  |  |  |  |  |  | # working directory. | 
| 7328 | 123 |  |  |  |  | 468 | my @elems = split /\//, $path; | 
| 7329 | 123 |  |  |  |  | 281 | my $filename = pop @elems; | 
| 7330 |  |  |  |  |  |  |  | 
| 7331 | 123 | 50 | 33 |  |  | 697 | unless (defined $filename && length $filename) | 
| 7332 |  |  |  |  |  |  | { | 
| 7333 | 0 |  |  |  |  | 0 | return (); | 
| 7334 |  |  |  |  |  |  | } | 
| 7335 |  |  |  |  |  |  |  | 
| 7336 | 123 |  |  |  |  | 340 | foreach (@elems) | 
| 7337 |  |  |  |  |  |  | { | 
| 7338 | 0 | 0 | 0 |  |  | 0 | if ($_ eq "" || $_ eq ".") { next } # Ignore these. | 
|  | 0 | 0 |  |  |  | 0 |  | 
| 7339 |  |  |  |  |  |  | elsif ($_ eq "..") | 
| 7340 |  |  |  |  |  |  | { | 
| 7341 |  |  |  |  |  |  | # Go to parent directory. | 
| 7342 | 0 |  |  |  |  | 0 | $dirh = $dirh->parent; | 
| 7343 |  |  |  |  |  |  | } | 
| 7344 |  |  |  |  |  |  | else | 
| 7345 |  |  |  |  |  |  | { | 
| 7346 | 0 |  |  |  |  | 0 | my $handle = $dirh->get ($_); | 
| 7347 |  |  |  |  |  |  |  | 
| 7348 | 0 | 0 |  |  |  | 0 | if (!$handle) | 
|  |  | 0 |  |  |  |  |  | 
| 7349 |  |  |  |  |  |  | { | 
| 7350 | 0 |  |  |  |  | 0 | return (); | 
| 7351 |  |  |  |  |  |  | } | 
| 7352 |  |  |  |  |  |  | elsif (!$handle->isa ("Net::FTPServer::DirHandle")) | 
| 7353 |  |  |  |  |  |  | { | 
| 7354 | 0 |  |  |  |  | 0 | return (); | 
| 7355 |  |  |  |  |  |  | } | 
| 7356 |  |  |  |  |  |  | else | 
| 7357 |  |  |  |  |  |  | { | 
| 7358 | 0 |  |  |  |  | 0 | $dirh = $handle; | 
| 7359 |  |  |  |  |  |  | } | 
| 7360 |  |  |  |  |  |  | } | 
| 7361 |  |  |  |  |  |  | } | 
| 7362 |  |  |  |  |  |  |  | 
| 7363 |  |  |  |  |  |  | # Get the file handle. | 
| 7364 | 123 | 50 |  |  |  | 765 | my $fileh = | 
|  |  | 50 |  |  |  |  |  | 
| 7365 |  |  |  |  |  |  | ($filename eq ".") ? $dirh : | 
| 7366 |  |  |  |  |  |  | ($filename eq "..") ? $dirh->parent : | 
| 7367 |  |  |  |  |  |  | $dirh->get($filename); | 
| 7368 |  |  |  |  |  |  |  | 
| 7369 | 123 |  |  |  |  | 472 | return ($dirh, $fileh, $filename); | 
| 7370 |  |  |  |  |  |  | } | 
| 7371 |  |  |  |  |  |  |  | 
| 7372 |  |  |  |  |  |  | =pod | 
| 7373 |  |  |  |  |  |  |  | 
| 7374 |  |  |  |  |  |  | =item $sock = $self->open_data_connection; | 
| 7375 |  |  |  |  |  |  |  | 
| 7376 |  |  |  |  |  |  | Open a data connection. Returns the socket (an instance of C) or undef if it fails for some reason. | 
| 7377 |  |  |  |  |  |  |  | 
| 7378 |  |  |  |  |  |  | =cut | 
| 7379 |  |  |  |  |  |  |  | 
| 7380 |  |  |  |  |  |  | sub open_data_connection | 
| 7381 |  |  |  |  |  |  | { | 
| 7382 | 114 |  |  | 114 | 1 | 201 | my $self = shift; | 
| 7383 | 114 |  |  |  |  | 170 | my $sock; | 
| 7384 |  |  |  |  |  |  |  | 
| 7385 | 114 | 100 |  |  |  | 317 | if (! $self->{_passive}) | 
| 7386 |  |  |  |  |  |  | { | 
| 7387 |  |  |  |  |  |  | # Active mode - connect back to the client. | 
| 7388 | 12 |  |  |  |  | 26 | my $source_addr = $self->{sockaddrstring}; | 
| 7389 | 12 |  |  |  |  | 20 | my $target_addr = $self->{_hostaddrstring}; | 
| 7390 | 12 |  |  |  |  | 20 | my $target_port = $self->{_hostport}; | 
| 7391 | 12 | 50 |  |  |  | 28 | if (my $source_port = $self->{ftp_data_port}) | 
| 7392 |  |  |  |  |  |  | { | 
| 7393 |  |  |  |  |  |  | # Temporarily jump back to super user just | 
| 7394 |  |  |  |  |  |  | # long enough to bind the privileged port. | 
| 7395 | 0 |  |  |  |  | 0 | local $) = 0; | 
| 7396 | 0 |  |  |  |  | 0 | local $> = 0; | 
| 7397 | 0 |  |  |  |  | 0 | for (1..5) { | 
| 7398 | 0 |  |  |  |  | 0 | "0" =~ /(0)/; # Perl 5.7 / IO::Socket::INET bug workaround. | 
| 7399 | 0 | 0 |  |  |  | 0 | $sock = new IO::Socket::INET | 
| 7400 |  |  |  |  |  |  | LocalAddr => $source_addr, | 
| 7401 |  |  |  |  |  |  | LocalPort => $source_port, | 
| 7402 |  |  |  |  |  |  | PeerAddr => $target_addr, | 
| 7403 |  |  |  |  |  |  | PeerPort => $target_port, | 
| 7404 |  |  |  |  |  |  | Proto => "tcp", | 
| 7405 |  |  |  |  |  |  | Type => SOCK_STREAM, | 
| 7406 |  |  |  |  |  |  | Reuse => 1, | 
| 7407 |  |  |  |  |  |  | or warn "PID $$ Failed to bind() ($!)"; | 
| 7408 | 0 | 0 |  |  |  | 0 | last if $sock; | 
| 7409 | 0 | 0 |  |  |  | 0 | print STDERR "    PID $$ Socket [${source_addr}:${source_port}] to [${target_addr}:${target_port}]\n" | 
| 7410 |  |  |  |  |  |  | if $_ == 1; | 
| 7411 | 0 | 0 |  |  |  | 0 | last unless $!{EADDRINUSE}; | 
| 7412 | 0 |  |  |  |  | 0 | print STDERR | 
| 7413 |  |  |  |  |  |  | "    PID $$ Retrying data connection (Attempt $_)\n" ; | 
| 7414 | 0 |  |  |  |  | 0 | sleep 1; | 
| 7415 |  |  |  |  |  |  | } | 
| 7416 | 0 | 0 |  |  |  | 0 | return undef unless $sock ; | 
| 7417 |  |  |  |  |  |  | } | 
| 7418 |  |  |  |  |  |  | else | 
| 7419 |  |  |  |  |  |  | { | 
| 7420 | 12 |  |  |  |  | 55 | "0" =~ /(0)/; # Perl 5.7 / IO::Socket::INET bug workaround. | 
| 7421 |  |  |  |  |  |  | $sock = new IO::Socket::INET | 
| 7422 |  |  |  |  |  |  | LocalAddr => $self->{sockaddrstring}, | 
| 7423 |  |  |  |  |  |  | PeerAddr => $self->{_hostaddrstring}, | 
| 7424 |  |  |  |  |  |  | PeerPort => $self->{_hostport}, | 
| 7425 | 12 | 50 |  |  |  | 149 | Proto => "tcp", | 
| 7426 |  |  |  |  |  |  | Type => SOCK_STREAM, | 
| 7427 |  |  |  |  |  |  | Reuse => 1, | 
| 7428 |  |  |  |  |  |  | or return undef; | 
| 7429 |  |  |  |  |  |  | } | 
| 7430 |  |  |  |  |  |  | } | 
| 7431 |  |  |  |  |  |  | else | 
| 7432 |  |  |  |  |  |  | { | 
| 7433 |  |  |  |  |  |  | # Passive mode - wait for a connection from the client. | 
| 7434 | 102 | 50 |  |  |  | 569 | $sock = $self->{_passive_sock}->accept or return undef; | 
| 7435 |  |  |  |  |  |  |  | 
| 7436 |  |  |  |  |  |  | # Check that the peer address of the connection is the | 
| 7437 |  |  |  |  |  |  | # client's own IP address. | 
| 7438 |  |  |  |  |  |  | # XXX This test is commented out because it causes Netscape 4 | 
| 7439 |  |  |  |  |  |  | # to fail on loopback connections. | 
| 7440 |  |  |  |  |  |  | #	unless ($self->config ("allow proxy ftp")) | 
| 7441 |  |  |  |  |  |  | #	  { | 
| 7442 |  |  |  |  |  |  | #	    my $peeraddrstring = inet_ntoa ($sock->peeraddr); | 
| 7443 |  |  |  |  |  |  |  | 
| 7444 |  |  |  |  |  |  | #	    if ($peeraddrstring ne $self->{peeraddrstring}) | 
| 7445 |  |  |  |  |  |  | #	      { | 
| 7446 |  |  |  |  |  |  | #		$self->reply (504, "Proxy FTP is not allowed on this server."); | 
| 7447 |  |  |  |  |  |  | #		return; | 
| 7448 |  |  |  |  |  |  | #	      } | 
| 7449 |  |  |  |  |  |  | #	  } | 
| 7450 |  |  |  |  |  |  | } | 
| 7451 |  |  |  |  |  |  |  | 
| 7452 |  |  |  |  |  |  | # Set TCP keepalive? | 
| 7453 | 114 | 50 |  |  |  | 82268 | if (defined $self->config ("tcp keepalive")) | 
| 7454 |  |  |  |  |  |  | { | 
| 7455 | 0 | 0 |  |  |  | 0 | $sock->sockopt (SO_KEEPALIVE, 1) | 
| 7456 |  |  |  |  |  |  | or warn "setsockopt: SO_KEEPALIVE: $!"; | 
| 7457 |  |  |  |  |  |  | } | 
| 7458 |  |  |  |  |  |  |  | 
| 7459 |  |  |  |  |  |  | # Set TCP initial window size? | 
| 7460 | 114 | 50 |  |  |  | 286 | if (defined $self->config ("tcp window")) | 
| 7461 |  |  |  |  |  |  | { | 
| 7462 | 0 | 0 |  |  |  | 0 | $sock->sockopt (SO_SNDBUF, $self->config ("tcp window")) | 
| 7463 |  |  |  |  |  |  | or warn "setsockopt: SO_SNDBUF: $!"; | 
| 7464 | 0 | 0 |  |  |  | 0 | $sock->sockopt (SO_RCVBUF, $self->config ("tcp window")) | 
| 7465 |  |  |  |  |  |  | or warn "setsockopt: SO_RCVBUF: $!"; | 
| 7466 |  |  |  |  |  |  | } | 
| 7467 |  |  |  |  |  |  |  | 
| 7468 | 114 |  |  |  |  | 20241 | return $sock; | 
| 7469 |  |  |  |  |  |  | } | 
| 7470 |  |  |  |  |  |  |  | 
| 7471 |  |  |  |  |  |  | # $self->_list_file ($sock, $fileh, [$filename, [$statusref]]); | 
| 7472 |  |  |  |  |  |  | # | 
| 7473 |  |  |  |  |  |  | # List a single file over the data connection $sock. | 
| 7474 |  |  |  |  |  |  |  | 
| 7475 |  |  |  |  |  |  | sub _list_file | 
| 7476 |  |  |  |  |  |  | { | 
| 7477 | 23 |  |  | 23 |  | 33 | my $self = shift; | 
| 7478 | 23 |  |  |  |  | 31 | my $sock = shift; | 
| 7479 | 23 |  |  |  |  | 28 | my $fileh = shift; | 
| 7480 | 23 |  |  |  |  | 30 | my $filename = shift; | 
| 7481 | 23 |  |  |  |  | 26 | my $statusref = shift; | 
| 7482 |  |  |  |  |  |  |  | 
| 7483 | 23 | 50 |  |  |  | 50 | $filename = $fileh->filename | 
| 7484 |  |  |  |  |  |  | if $filename eq ''; | 
| 7485 |  |  |  |  |  |  |  | 
| 7486 |  |  |  |  |  |  | # Get the status information. | 
| 7487 | 23 |  |  |  |  | 29 | my @status; | 
| 7488 | 23 | 100 |  |  |  | 40 | if ($statusref) { @status = @$statusref } | 
|  | 21 |  |  |  |  | 49 |  | 
| 7489 | 2 |  |  |  |  | 6 | else            { @status = $fileh->status } | 
| 7490 |  |  |  |  |  |  |  | 
| 7491 |  |  |  |  |  |  | # Break out the fields of the status information. | 
| 7492 | 23 |  |  |  |  | 50 | my ($mode, $perms, $nlink, $user, $group, $size, $mtime) = @status; | 
| 7493 |  |  |  |  |  |  |  | 
| 7494 |  |  |  |  |  |  | # Generate printable date (this logic is taken from GNU fileutils: | 
| 7495 |  |  |  |  |  |  | # src/ls.c: print_long_format). | 
| 7496 | 23 |  |  |  |  | 36 | my $time = time; | 
| 7497 | 23 |  |  |  |  | 32 | my $fmt; | 
| 7498 | 23 | 50 | 33 |  |  | 58 | if ($time > $mtime + 6 * 30 * 24 * 60 * 60 || $time < $mtime - 60 * 60) | 
| 7499 |  |  |  |  |  |  | { | 
| 7500 | 23 |  |  |  |  | 35 | $fmt = "%b %e  %Y"; | 
| 7501 |  |  |  |  |  |  | } | 
| 7502 |  |  |  |  |  |  | else | 
| 7503 |  |  |  |  |  |  | { | 
| 7504 | 0 |  |  |  |  | 0 | $fmt = "%b %e %H:%M"; | 
| 7505 |  |  |  |  |  |  | } | 
| 7506 |  |  |  |  |  |  |  | 
| 7507 | 23 |  |  |  |  | 213 | my $fmt_time = strftime $fmt, localtime ($mtime); | 
| 7508 |  |  |  |  |  |  |  | 
| 7509 |  |  |  |  |  |  | # Generate printable permissions. | 
| 7510 | 23 | 50 |  |  |  | 146 | my $fmt_perms = join "", | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
| 7511 |  |  |  |  |  |  | ($perms & 0400 ? 'r' : '-'), | 
| 7512 |  |  |  |  |  |  | ($perms & 0200 ? 'w' : '-'), | 
| 7513 |  |  |  |  |  |  | ($perms & 0100 ? 'x' : '-'), | 
| 7514 |  |  |  |  |  |  | ($perms & 040 ? 'r' : '-'), | 
| 7515 |  |  |  |  |  |  | ($perms & 020 ? 'w' : '-'), | 
| 7516 |  |  |  |  |  |  | ($perms & 010 ? 'x' : '-'), | 
| 7517 |  |  |  |  |  |  | ($perms & 04 ? 'r' : '-'), | 
| 7518 |  |  |  |  |  |  | ($perms & 02 ? 'w' : '-'), | 
| 7519 |  |  |  |  |  |  | ($perms & 01 ? 'x' : '-'); | 
| 7520 |  |  |  |  |  |  |  | 
| 7521 |  |  |  |  |  |  | # Printable file type. | 
| 7522 | 23 | 100 |  |  |  | 43 | my $fmt_mode = $mode eq 'f' ? '-' : $mode; | 
| 7523 |  |  |  |  |  |  |  | 
| 7524 |  |  |  |  |  |  | # If it's a symbolic link, display the link. | 
| 7525 | 23 |  |  |  |  | 32 | my $link; | 
| 7526 | 23 | 50 |  |  |  | 41 | if ($mode eq 'l') | 
| 7527 |  |  |  |  |  |  | { | 
| 7528 | 0 |  |  |  |  | 0 | $link = $fileh->readlink; | 
| 7529 | 0 | 0 |  |  |  | 0 | die "readlink: $!" unless defined $link; | 
| 7530 |  |  |  |  |  |  | } | 
| 7531 | 23 | 50 |  |  |  | 45 | my $fmt_link = defined $link ? " -> $link" : ""; | 
| 7532 |  |  |  |  |  |  |  | 
| 7533 |  |  |  |  |  |  | # Display the file. | 
| 7534 | 23 |  |  |  |  | 200 | my $line = sprintf | 
| 7535 |  |  |  |  |  |  | ("%s%s%4d %-8s %-8s %8.0f %s %s%s\r\n", | 
| 7536 |  |  |  |  |  |  | $fmt_mode, | 
| 7537 |  |  |  |  |  |  | $fmt_perms, | 
| 7538 |  |  |  |  |  |  | $nlink, | 
| 7539 |  |  |  |  |  |  | $user, | 
| 7540 |  |  |  |  |  |  | $group, | 
| 7541 |  |  |  |  |  |  | $size, | 
| 7542 |  |  |  |  |  |  | $fmt_time, | 
| 7543 |  |  |  |  |  |  | $filename, | 
| 7544 |  |  |  |  |  |  | $fmt_link); | 
| 7545 | 23 | 50 |  |  |  | 58 | $self->xfer (length $line) if $self->{_xferlog}; | 
| 7546 | 23 |  |  |  |  | 72 | $sock->print ($line); | 
| 7547 |  |  |  |  |  |  | } | 
| 7548 |  |  |  |  |  |  |  | 
| 7549 |  |  |  |  |  |  | # Implement the STOR, STOU (store unique) and APPE (append) commands. | 
| 7550 |  |  |  |  |  |  |  | 
| 7551 |  |  |  |  |  |  | sub _store | 
| 7552 |  |  |  |  |  |  | { | 
| 7553 | 56 |  |  | 56 |  | 133 | my $self = shift; | 
| 7554 | 56 |  |  |  |  | 110 | my $path = shift; | 
| 7555 | 56 |  |  |  |  | 143 | my %params = @_; | 
| 7556 |  |  |  |  |  |  |  | 
| 7557 | 56 |  | 100 |  |  | 333 | my $unique = $params{unique} || 0; | 
| 7558 | 56 |  | 100 |  |  | 256 | my $append = $params{append} || 0; | 
| 7559 |  |  |  |  |  |  |  | 
| 7560 | 56 |  |  |  |  | 110 | my ($dirh, $fileh, $filename, $transfer_hook); | 
| 7561 |  |  |  |  |  |  |  | 
| 7562 | 56 | 100 |  |  |  | 149 | unless ($unique) | 
| 7563 |  |  |  |  |  |  | { | 
| 7564 |  |  |  |  |  |  | # Get the directory. | 
| 7565 | 47 |  |  |  |  | 232 | ($dirh, $fileh, $filename) = $self->_get ($path); | 
| 7566 |  |  |  |  |  |  |  | 
| 7567 | 47 | 50 |  |  |  | 234 | unless ($dirh) | 
| 7568 |  |  |  |  |  |  | { | 
| 7569 | 0 |  |  |  |  | 0 | $self->reply (550, "File or directory not found."); | 
| 7570 | 0 |  |  |  |  | 0 | return; | 
| 7571 |  |  |  |  |  |  | } | 
| 7572 |  |  |  |  |  |  | } | 
| 7573 |  |  |  |  |  |  | else			# STOU command -- ignore any parameters. | 
| 7574 |  |  |  |  |  |  | { | 
| 7575 | 9 |  |  |  |  | 24 | $dirh = $self->{cwd}; | 
| 7576 |  |  |  |  |  |  |  | 
| 7577 |  |  |  |  |  |  | # Choose a unique name for this file. | 
| 7578 | 9 |  |  |  |  | 19 | my $i = 0; | 
| 7579 | 9 |  |  |  |  | 54 | while ($dirh->get ("X$i")) { | 
| 7580 | 36 |  |  |  |  | 178 | $i++; | 
| 7581 |  |  |  |  |  |  | } | 
| 7582 |  |  |  |  |  |  |  | 
| 7583 | 9 |  |  |  |  | 30 | $filename = "X$i"; | 
| 7584 |  |  |  |  |  |  | } | 
| 7585 |  |  |  |  |  |  |  | 
| 7586 |  |  |  |  |  |  | # Check access control. | 
| 7587 | 56 | 50 |  |  |  | 286 | unless ($self->_eval_rule ("store rule", | 
| 7588 |  |  |  |  |  |  | $dirh->pathname . $filename, | 
| 7589 |  |  |  |  |  |  | $filename, $dirh->pathname)) | 
| 7590 |  |  |  |  |  |  | { | 
| 7591 | 0 |  |  |  |  | 0 | $self->reply (550, "Store command denied by server configuration."); | 
| 7592 | 0 |  |  |  |  | 0 | return; | 
| 7593 |  |  |  |  |  |  | } | 
| 7594 |  |  |  |  |  |  |  | 
| 7595 |  |  |  |  |  |  | # Are we trying to overwrite a previously existing file? | 
| 7596 | 56 | 50 | 100 |  |  | 363 | if (! $append && | 
|  |  |  | 66 |  |  |  |  | 
|  |  |  | 33 |  |  |  |  | 
| 7597 |  |  |  |  |  |  | defined $fileh && | 
| 7598 |  |  |  |  |  |  | defined $self->config ("allow store to overwrite") && | 
| 7599 |  |  |  |  |  |  | ! $self->config ("allow store to overwrite")) | 
| 7600 |  |  |  |  |  |  | { | 
| 7601 | 0 |  |  |  |  | 0 | $self->reply (550, "Cannot rename file."); | 
| 7602 | 0 |  |  |  |  | 0 | return; | 
| 7603 |  |  |  |  |  |  | } | 
| 7604 |  |  |  |  |  |  |  | 
| 7605 |  |  |  |  |  |  | # Try to open the file. | 
| 7606 | 56 | 100 |  |  |  | 349 | my $file = $dirh->open ($filename, ($append ? "a" : "w")); | 
| 7607 |  |  |  |  |  |  |  | 
| 7608 | 56 | 50 |  |  |  | 3617 | unless ($file) | 
| 7609 |  |  |  |  |  |  | { | 
| 7610 | 0 |  |  |  |  | 0 | $self->reply (550, "Cannot create file $filename."); | 
| 7611 | 0 |  |  |  |  | 0 | return; | 
| 7612 |  |  |  |  |  |  | } | 
| 7613 |  |  |  |  |  |  |  | 
| 7614 | 56 | 100 |  |  |  | 392 | unless ($unique) | 
| 7615 |  |  |  |  |  |  | { | 
| 7616 |  |  |  |  |  |  | $self->reply (150, | 
| 7617 |  |  |  |  |  |  | "Opening " . | 
| 7618 | 47 | 100 |  |  |  | 304 | ($self->{type} eq 'A' ? "ASCII mode" : "BINARY mode") . | 
| 7619 |  |  |  |  |  |  | " data connection for file $filename."); | 
| 7620 |  |  |  |  |  |  | } | 
| 7621 |  |  |  |  |  |  | else | 
| 7622 |  |  |  |  |  |  | { | 
| 7623 |  |  |  |  |  |  | # RFC 1123 section 4.1.2.9. | 
| 7624 | 9 |  |  |  |  | 39 | $self->reply (150, "FILE: $filename"); | 
| 7625 |  |  |  |  |  |  | } | 
| 7626 |  |  |  |  |  |  |  | 
| 7627 |  |  |  |  |  |  | # Open a path back to the client. | 
| 7628 | 56 |  |  |  |  | 342 | my $sock = $self->open_data_connection; | 
| 7629 |  |  |  |  |  |  |  | 
| 7630 | 56 | 50 |  |  |  | 4986 | unless ($sock) | 
| 7631 |  |  |  |  |  |  | { | 
| 7632 | 0 |  |  |  |  | 0 | $self->reply (425, "Can't open data connection."); | 
| 7633 | 0 |  |  |  |  | 0 | return; | 
| 7634 |  |  |  |  |  |  | } | 
| 7635 |  |  |  |  |  |  |  | 
| 7636 |  |  |  |  |  |  | # Incoming bandwidth | 
| 7637 | 56 | 50 |  |  |  | 201 | $self->xfer_start ($dirh->pathname . $filename, "i") if $self->{_xferlog}; | 
| 7638 |  |  |  |  |  |  |  | 
| 7639 |  |  |  |  |  |  | # What mode are we receiving this file in? | 
| 7640 | 56 | 100 |  |  |  | 186 | unless ($self->{type} eq 'A') # Binary type. | 
| 7641 |  |  |  |  |  |  | { | 
| 7642 | 40 |  |  |  |  | 83 | my ($r, $buffer, $n, $w); | 
| 7643 |  |  |  |  |  |  |  | 
| 7644 |  |  |  |  |  |  | # XXX Do we need to support REST? | 
| 7645 |  |  |  |  |  |  |  | 
| 7646 |  |  |  |  |  |  | # Copy data. | 
| 7647 | 40 |  |  |  |  | 297 | while ($r = $sock->sysread ($buffer, 65536)) | 
| 7648 |  |  |  |  |  |  | { | 
| 7649 | 3603 | 50 |  |  |  | 2327125 | $self->xfer ($r) if $self->{_xferlog}; | 
| 7650 |  |  |  |  |  |  |  | 
| 7651 |  |  |  |  |  |  | # Restart alarm clock timer. | 
| 7652 | 3603 |  |  |  |  | 9359 | alarm $self->{_idle_timeout}; | 
| 7653 |  |  |  |  |  |  |  | 
| 7654 | 3603 | 50 |  |  |  | 8069 | if ($transfer_hook | 
| 7655 |  |  |  |  |  |  | = $self->transfer_hook ("w", $file, $sock, \$buffer)) | 
| 7656 |  |  |  |  |  |  | { | 
| 7657 | 0 |  |  |  |  | 0 | $sock->close; | 
| 7658 | 0 |  |  |  |  | 0 | $file->close; | 
| 7659 | 0 |  |  |  |  | 0 | $self->reply (426, | 
| 7660 |  |  |  |  |  |  | "File store error: $transfer_hook", | 
| 7661 |  |  |  |  |  |  | "Data connection has been closed."); | 
| 7662 | 0 |  |  |  |  | 0 | return; | 
| 7663 |  |  |  |  |  |  | } | 
| 7664 |  |  |  |  |  |  |  | 
| 7665 | 3603 |  |  |  |  | 6496 | for ($n = 0; $n < $r; ) | 
| 7666 |  |  |  |  |  |  | { | 
| 7667 | 3603 |  |  |  |  | 7892 | $w = $file->syswrite ($buffer, $r - $n, $n); | 
| 7668 |  |  |  |  |  |  |  | 
| 7669 | 3603 | 50 |  |  |  | 123235 | unless (defined $w) | 
| 7670 |  |  |  |  |  |  | { | 
| 7671 |  |  |  |  |  |  | # There was an error. | 
| 7672 | 0 |  |  |  |  | 0 | my $reason = $self->system_error_hook(); | 
| 7673 |  |  |  |  |  |  |  | 
| 7674 | 0 |  |  |  |  | 0 | $sock->close; | 
| 7675 | 0 |  |  |  |  | 0 | $file->close; | 
| 7676 | 0 |  |  |  |  | 0 | $self->reply (426, | 
| 7677 |  |  |  |  |  |  | "File store error: $reason", | 
| 7678 |  |  |  |  |  |  | "Data connection has been closed."); | 
| 7679 | 0 |  |  |  |  | 0 | return; | 
| 7680 |  |  |  |  |  |  | } | 
| 7681 |  |  |  |  |  |  |  | 
| 7682 | 3603 |  |  |  |  | 10293 | $n += $w; | 
| 7683 |  |  |  |  |  |  | } | 
| 7684 |  |  |  |  |  |  | } | 
| 7685 |  |  |  |  |  |  |  | 
| 7686 | 40 | 50 |  |  |  | 544 | unless (defined $r) | 
| 7687 |  |  |  |  |  |  | { | 
| 7688 |  |  |  |  |  |  | # There was an error. | 
| 7689 | 0 |  |  |  |  | 0 | my $reason = $self->system_error_hook(); | 
| 7690 |  |  |  |  |  |  |  | 
| 7691 | 0 |  |  |  |  | 0 | $sock->close; | 
| 7692 | 0 |  |  |  |  | 0 | $file->close; | 
| 7693 | 0 |  |  |  |  | 0 | $self->reply (426, | 
| 7694 |  |  |  |  |  |  | "File store error: $reason", | 
| 7695 |  |  |  |  |  |  | "Data connection has been closed."); | 
| 7696 | 0 |  |  |  |  | 0 | return; | 
| 7697 |  |  |  |  |  |  | } | 
| 7698 |  |  |  |  |  |  | } | 
| 7699 |  |  |  |  |  |  | else			# ASCII type. | 
| 7700 |  |  |  |  |  |  | { | 
| 7701 |  |  |  |  |  |  | # XXX Do we need to support REST? | 
| 7702 |  |  |  |  |  |  |  | 
| 7703 |  |  |  |  |  |  | # Copy data. | 
| 7704 | 16 |  |  |  |  | 5693 | while (defined ($_ = $sock->getline)) | 
| 7705 |  |  |  |  |  |  | { | 
| 7706 | 54622 | 50 |  |  |  | 1614106 | $self->xfer (length $_) if $self->{_xferlog}; | 
| 7707 |  |  |  |  |  |  |  | 
| 7708 |  |  |  |  |  |  | # Remove any telnet-format line endings. | 
| 7709 | 54622 |  |  |  |  | 4106166 | s/[\n\r]*$//; | 
| 7710 |  |  |  |  |  |  |  | 
| 7711 |  |  |  |  |  |  | # Restart alarm clock timer. | 
| 7712 | 54622 |  |  |  |  | 176881 | alarm $self->{_idle_timeout}; | 
| 7713 |  |  |  |  |  |  |  | 
| 7714 | 54622 | 50 |  |  |  | 118291 | if ($transfer_hook = $self->transfer_hook ("w", $file, $sock, \$_)) | 
| 7715 |  |  |  |  |  |  | { | 
| 7716 | 0 |  |  |  |  | 0 | $sock->close; | 
| 7717 | 0 |  |  |  |  | 0 | $file->close; | 
| 7718 | 0 |  |  |  |  | 0 | $self->reply (426, | 
| 7719 |  |  |  |  |  |  | "File store error: $transfer_hook", | 
| 7720 |  |  |  |  |  |  | "Data connection has been closed."); | 
| 7721 | 0 |  |  |  |  | 0 | return; | 
| 7722 |  |  |  |  |  |  | } | 
| 7723 |  |  |  |  |  |  |  | 
| 7724 |  |  |  |  |  |  | # Write the line with native format line endings. | 
| 7725 | 54622 |  |  |  |  | 159334 | my $w = $file->print ("$_\n"); | 
| 7726 | 54622 | 50 |  |  |  | 1419521 | unless (defined $w) | 
| 7727 |  |  |  |  |  |  | { | 
| 7728 | 0 |  |  |  |  | 0 | my $reason = $self->system_error_hook(); | 
| 7729 |  |  |  |  |  |  | # There was an error. | 
| 7730 | 0 |  |  |  |  | 0 | $sock->close; | 
| 7731 | 0 |  |  |  |  | 0 | $file->close; | 
| 7732 | 0 |  |  |  |  | 0 | $self->reply (426, | 
| 7733 |  |  |  |  |  |  | "File store error: $reason", | 
| 7734 |  |  |  |  |  |  | "Data connection has been closed."); | 
| 7735 | 0 |  |  |  |  | 0 | return; | 
| 7736 |  |  |  |  |  |  | } | 
| 7737 |  |  |  |  |  |  | } | 
| 7738 |  |  |  |  |  |  | } | 
| 7739 |  |  |  |  |  |  |  | 
| 7740 | 56 | 50 | 33 |  |  | 761 | unless ($sock->close && $file->close) | 
| 7741 |  |  |  |  |  |  | { | 
| 7742 | 0 |  |  |  |  | 0 | my $reason = $self->system_error_hook(); | 
| 7743 | 0 |  |  |  |  | 0 | $self->reply (550, "File retrieval error: $reason"); | 
| 7744 | 0 |  |  |  |  | 0 | return; | 
| 7745 |  |  |  |  |  |  | } | 
| 7746 |  |  |  |  |  |  |  | 
| 7747 | 56 | 50 |  |  |  | 5681 | $self->xfer_complete if $self->{_xferlog}; | 
| 7748 | 56 |  |  |  |  | 232 | $self->reply (226, "File store complete. Data connection has been closed."); | 
| 7749 |  |  |  |  |  |  | } | 
| 7750 |  |  |  |  |  |  |  | 
| 7751 |  |  |  |  |  |  | =pod | 
| 7752 |  |  |  |  |  |  |  | 
| 7753 |  |  |  |  |  |  | =item $self->pre_configuration_hook (); | 
| 7754 |  |  |  |  |  |  |  | 
| 7755 |  |  |  |  |  |  | Hook: Called before command line arguments and configuration file | 
| 7756 |  |  |  |  |  |  | are read. | 
| 7757 |  |  |  |  |  |  |  | 
| 7758 |  |  |  |  |  |  | Status: optional. | 
| 7759 |  |  |  |  |  |  |  | 
| 7760 |  |  |  |  |  |  | Notes: You may append your own information to C<$self-E{version_string}> | 
| 7761 |  |  |  |  |  |  | from this hook. | 
| 7762 |  |  |  |  |  |  |  | 
| 7763 |  |  |  |  |  |  | =cut | 
| 7764 |  |  |  |  |  |  |  | 
| 7765 |  |  |  |  |  |  | sub pre_configuration_hook | 
| 7766 |  |  |  | 0 | 1 |  | { | 
| 7767 |  |  |  |  |  |  | } | 
| 7768 |  |  |  |  |  |  |  | 
| 7769 |  |  |  |  |  |  | =pod | 
| 7770 |  |  |  |  |  |  |  | 
| 7771 |  |  |  |  |  |  | =item $self->options_hook (\@args); | 
| 7772 |  |  |  |  |  |  |  | 
| 7773 |  |  |  |  |  |  | Hook: Called before command line arguments are parsed. | 
| 7774 |  |  |  |  |  |  |  | 
| 7775 |  |  |  |  |  |  | Status: optional. | 
| 7776 |  |  |  |  |  |  |  | 
| 7777 |  |  |  |  |  |  | Notes: You can use this hook to supply your own command line arguments. | 
| 7778 |  |  |  |  |  |  | If you parse any arguments, you should remove them from the @args | 
| 7779 |  |  |  |  |  |  | array. | 
| 7780 |  |  |  |  |  |  |  | 
| 7781 |  |  |  |  |  |  | =cut | 
| 7782 |  |  |  |  |  |  |  | 
| 7783 |  |  |  |  |  |  | sub options_hook | 
| 7784 |  |  |  | 41 | 1 |  | { | 
| 7785 |  |  |  |  |  |  | } | 
| 7786 |  |  |  |  |  |  |  | 
| 7787 |  |  |  |  |  |  | =pod | 
| 7788 |  |  |  |  |  |  |  | 
| 7789 |  |  |  |  |  |  | =item $self->post_configuration_hook (); | 
| 7790 |  |  |  |  |  |  |  | 
| 7791 |  |  |  |  |  |  | Hook: Called after all command line arguments and configuration file | 
| 7792 |  |  |  |  |  |  | have been read and parsed. | 
| 7793 |  |  |  |  |  |  |  | 
| 7794 |  |  |  |  |  |  | Status: optional. | 
| 7795 |  |  |  |  |  |  |  | 
| 7796 |  |  |  |  |  |  | =cut | 
| 7797 |  |  |  |  |  |  |  | 
| 7798 |  |  |  |  |  |  | sub post_configuration_hook | 
| 7799 |  |  |  | 41 | 1 |  | { | 
| 7800 |  |  |  |  |  |  | } | 
| 7801 |  |  |  |  |  |  |  | 
| 7802 |  |  |  |  |  |  | =pod | 
| 7803 |  |  |  |  |  |  |  | 
| 7804 |  |  |  |  |  |  | =item $self->post_bind_hook (); | 
| 7805 |  |  |  |  |  |  |  | 
| 7806 |  |  |  |  |  |  | Hook: Called only in daemon mode after the control port is bound | 
| 7807 |  |  |  |  |  |  | but before starting the accept infinite loop block. | 
| 7808 |  |  |  |  |  |  |  | 
| 7809 |  |  |  |  |  |  | Status: optional. | 
| 7810 |  |  |  |  |  |  |  | 
| 7811 |  |  |  |  |  |  | =cut | 
| 7812 |  |  |  |  |  |  |  | 
| 7813 |  |  |  |  |  |  | sub post_bind_hook | 
| 7814 |  |  |  | 0 | 1 |  | { | 
| 7815 |  |  |  |  |  |  | } | 
| 7816 |  |  |  |  |  |  |  | 
| 7817 |  |  |  |  |  |  | =pod | 
| 7818 |  |  |  |  |  |  |  | 
| 7819 |  |  |  |  |  |  | =item $self->pre_accept_hook (); | 
| 7820 |  |  |  |  |  |  |  | 
| 7821 |  |  |  |  |  |  | Hook: Called in daemon mode only just before C is called | 
| 7822 |  |  |  |  |  |  | in the parent FTP server process. | 
| 7823 |  |  |  |  |  |  |  | 
| 7824 |  |  |  |  |  |  | Status: optional. | 
| 7825 |  |  |  |  |  |  |  | 
| 7826 |  |  |  |  |  |  | =cut | 
| 7827 |  |  |  |  |  |  |  | 
| 7828 |  |  |  |  |  |  | sub pre_accept_hook | 
| 7829 |  |  |  | 0 | 1 |  | { | 
| 7830 |  |  |  |  |  |  | } | 
| 7831 |  |  |  |  |  |  |  | 
| 7832 |  |  |  |  |  |  | =pod | 
| 7833 |  |  |  |  |  |  |  | 
| 7834 |  |  |  |  |  |  | =item $self->post_accept_hook (); | 
| 7835 |  |  |  |  |  |  |  | 
| 7836 |  |  |  |  |  |  | Hook: Called both in daemon mode and in inetd mode just after the | 
| 7837 |  |  |  |  |  |  | connection has been accepted. This is called in the child process. | 
| 7838 |  |  |  |  |  |  |  | 
| 7839 |  |  |  |  |  |  | Status: optional. | 
| 7840 |  |  |  |  |  |  |  | 
| 7841 |  |  |  |  |  |  | =cut | 
| 7842 |  |  |  |  |  |  |  | 
| 7843 |  |  |  |  |  |  | sub post_accept_hook | 
| 7844 |  |  |  | 41 | 1 |  | { | 
| 7845 |  |  |  |  |  |  | } | 
| 7846 |  |  |  |  |  |  |  | 
| 7847 |  |  |  |  |  |  | =pod | 
| 7848 |  |  |  |  |  |  |  | 
| 7849 |  |  |  |  |  |  | =item $rv = $self->access_control_hook; | 
| 7850 |  |  |  |  |  |  |  | 
| 7851 |  |  |  |  |  |  | Hook: Called after C-ing the connection to perform access | 
| 7852 |  |  |  |  |  |  | control. Detailed request information is contained in the $self | 
| 7853 |  |  |  |  |  |  | object.  If the function returns -1 then the socket is immediately | 
| 7854 |  |  |  |  |  |  | closed and no FTP processing happens on it. If the function returns 0, | 
| 7855 |  |  |  |  |  |  | then normal access control is performed on the socket before FTP | 
| 7856 |  |  |  |  |  |  | processing starts. If the function returns 1, then normal access | 
| 7857 |  |  |  |  |  |  | control is I performed on the socket and FTP processing begins | 
| 7858 |  |  |  |  |  |  | immediately. | 
| 7859 |  |  |  |  |  |  |  | 
| 7860 |  |  |  |  |  |  | Status: optional. | 
| 7861 |  |  |  |  |  |  |  | 
| 7862 |  |  |  |  |  |  | =cut | 
| 7863 |  |  |  |  |  |  |  | 
| 7864 |  |  |  |  |  |  | sub access_control_hook | 
| 7865 |  |  |  |  |  |  | { | 
| 7866 | 41 |  |  | 41 | 1 | 166 | return 0; | 
| 7867 |  |  |  |  |  |  | } | 
| 7868 |  |  |  |  |  |  |  | 
| 7869 |  |  |  |  |  |  | =pod | 
| 7870 |  |  |  |  |  |  |  | 
| 7871 |  |  |  |  |  |  | =item $rv = $self->process_limits_hook; | 
| 7872 |  |  |  |  |  |  |  | 
| 7873 |  |  |  |  |  |  | Hook: Called after C-ing the connection to perform | 
| 7874 |  |  |  |  |  |  | per-process limits (eg. by using the setrlimit(2) system | 
| 7875 |  |  |  |  |  |  | call). Access control has already been performed and detailed | 
| 7876 |  |  |  |  |  |  | request information is contained in the C<$self> object. | 
| 7877 |  |  |  |  |  |  |  | 
| 7878 |  |  |  |  |  |  | If the function returns -1 then the socket is immediately closed and | 
| 7879 |  |  |  |  |  |  | no FTP processing happens on it. If the function returns 0, then | 
| 7880 |  |  |  |  |  |  | normal per-process limits are applied before any FTP processing | 
| 7881 |  |  |  |  |  |  | starts. If the function returns 1, then normal per-process limits are | 
| 7882 |  |  |  |  |  |  | I performed and FTP processing begins immediately. | 
| 7883 |  |  |  |  |  |  |  | 
| 7884 |  |  |  |  |  |  | Status: optional. | 
| 7885 |  |  |  |  |  |  |  | 
| 7886 |  |  |  |  |  |  | =cut | 
| 7887 |  |  |  |  |  |  |  | 
| 7888 |  |  |  |  |  |  | sub process_limits_hook | 
| 7889 |  |  |  |  |  |  | { | 
| 7890 | 41 |  |  | 41 | 1 | 741 | return 0; | 
| 7891 |  |  |  |  |  |  | } | 
| 7892 |  |  |  |  |  |  |  | 
| 7893 |  |  |  |  |  |  | =pod | 
| 7894 |  |  |  |  |  |  |  | 
| 7895 |  |  |  |  |  |  | =item $rv = $self->authentication_hook ($user, $pass, $user_is_anon) | 
| 7896 |  |  |  |  |  |  |  | 
| 7897 |  |  |  |  |  |  | Hook: Called to perform authentication. If the authentication | 
| 7898 |  |  |  |  |  |  | succeeds, this should return 0 (or any positive integer E= 0). | 
| 7899 |  |  |  |  |  |  | If the authentication fails, this should return -1. | 
| 7900 |  |  |  |  |  |  |  | 
| 7901 |  |  |  |  |  |  | Status: required. | 
| 7902 |  |  |  |  |  |  |  | 
| 7903 |  |  |  |  |  |  | =cut | 
| 7904 |  |  |  |  |  |  |  | 
| 7905 |  |  |  |  |  |  | sub authentication_hook | 
| 7906 |  |  |  |  |  |  | { | 
| 7907 | 0 |  |  | 0 | 1 | 0 | die "authentication_hook is required"; | 
| 7908 |  |  |  |  |  |  | } | 
| 7909 |  |  |  |  |  |  |  | 
| 7910 |  |  |  |  |  |  | =pod | 
| 7911 |  |  |  |  |  |  |  | 
| 7912 |  |  |  |  |  |  | =item $self->user_login_hook ($user, $user_is_anon) | 
| 7913 |  |  |  |  |  |  |  | 
| 7914 |  |  |  |  |  |  | Hook: Called just after user C<$user> has successfully logged in. A good | 
| 7915 |  |  |  |  |  |  | place to change uid and chroot if necessary. | 
| 7916 |  |  |  |  |  |  |  | 
| 7917 |  |  |  |  |  |  | Status: optional. | 
| 7918 |  |  |  |  |  |  |  | 
| 7919 |  |  |  |  |  |  | =cut | 
| 7920 |  |  |  |  |  |  |  | 
| 7921 |  |  |  |  |  |  | sub user_login_hook | 
| 7922 |  |  |  | 0 | 1 |  | { | 
| 7923 |  |  |  |  |  |  | } | 
| 7924 |  |  |  |  |  |  |  | 
| 7925 |  |  |  |  |  |  | =pod | 
| 7926 |  |  |  |  |  |  |  | 
| 7927 |  |  |  |  |  |  | =item $dirh = $self->root_directory_hook; | 
| 7928 |  |  |  |  |  |  |  | 
| 7929 |  |  |  |  |  |  | Hook: Return an instance of a subclass of Net::FTPServer::DirHandle | 
| 7930 |  |  |  |  |  |  | corresponding to the root directory. | 
| 7931 |  |  |  |  |  |  |  | 
| 7932 |  |  |  |  |  |  | Status: required. | 
| 7933 |  |  |  |  |  |  |  | 
| 7934 |  |  |  |  |  |  | =cut | 
| 7935 |  |  |  |  |  |  |  | 
| 7936 |  |  |  |  |  |  | sub root_directory_hook | 
| 7937 |  |  |  |  |  |  | { | 
| 7938 | 0 |  |  | 0 | 1 | 0 | die "root_directory_hook is required"; | 
| 7939 |  |  |  |  |  |  | } | 
| 7940 |  |  |  |  |  |  |  | 
| 7941 |  |  |  |  |  |  | =pod | 
| 7942 |  |  |  |  |  |  |  | 
| 7943 |  |  |  |  |  |  | =item $self->pre_command_hook; | 
| 7944 |  |  |  |  |  |  |  | 
| 7945 |  |  |  |  |  |  | Hook: This hook is called just before the server begins to wait for | 
| 7946 |  |  |  |  |  |  | the client to issue the next command over the control connection. | 
| 7947 |  |  |  |  |  |  |  | 
| 7948 |  |  |  |  |  |  | Status: optional. | 
| 7949 |  |  |  |  |  |  |  | 
| 7950 |  |  |  |  |  |  | =cut | 
| 7951 |  |  |  |  |  |  |  | 
| 7952 |  |  |  |  |  |  | sub pre_command_hook | 
| 7953 |  |  |  | 327 | 1 |  | { | 
| 7954 |  |  |  |  |  |  | } | 
| 7955 |  |  |  |  |  |  |  | 
| 7956 |  |  |  |  |  |  | =pod | 
| 7957 |  |  |  |  |  |  |  | 
| 7958 |  |  |  |  |  |  | =item $rv = $self->command_filter_hook ($cmdline); | 
| 7959 |  |  |  |  |  |  |  | 
| 7960 |  |  |  |  |  |  | Hook: This hook is called immediately after the client issues | 
| 7961 |  |  |  |  |  |  | command C<$cmdline>, but B any checking or processing | 
| 7962 |  |  |  |  |  |  | is performed on the command. If this function returns -1, then | 
| 7963 |  |  |  |  |  |  | the server immediately goes back to waiting for the next | 
| 7964 |  |  |  |  |  |  | command. If this function returns 0, then normal command filtering | 
| 7965 |  |  |  |  |  |  | is carried out and the command is processed. If this function | 
| 7966 |  |  |  |  |  |  | returns 1 then normal command filtering is B performed | 
| 7967 |  |  |  |  |  |  | and the command processing begins immediately. | 
| 7968 |  |  |  |  |  |  |  | 
| 7969 |  |  |  |  |  |  | Important Note: This hook must be careful B to overwrite | 
| 7970 |  |  |  |  |  |  | the global C<$_> variable. | 
| 7971 |  |  |  |  |  |  |  | 
| 7972 |  |  |  |  |  |  | Do not use this function to add your own commands. Instead | 
| 7973 |  |  |  |  |  |  | use the C<$self-E{command_table}> and C<$self-E{site_command_table}> | 
| 7974 |  |  |  |  |  |  | hashes. | 
| 7975 |  |  |  |  |  |  |  | 
| 7976 |  |  |  |  |  |  | Status: optional. | 
| 7977 |  |  |  |  |  |  |  | 
| 7978 |  |  |  |  |  |  | =cut | 
| 7979 |  |  |  |  |  |  |  | 
| 7980 |  |  |  |  |  |  | sub command_filter_hook | 
| 7981 |  |  |  |  |  |  | { | 
| 7982 | 311 |  |  | 311 | 1 | 613 | return 0; | 
| 7983 |  |  |  |  |  |  | } | 
| 7984 |  |  |  |  |  |  |  | 
| 7985 |  |  |  |  |  |  |  | 
| 7986 |  |  |  |  |  |  | =pod | 
| 7987 |  |  |  |  |  |  |  | 
| 7988 |  |  |  |  |  |  | =item $error = $self->transfer_hook ($mode, $file, $sock, \$buffer); | 
| 7989 |  |  |  |  |  |  |  | 
| 7990 |  |  |  |  |  |  | $mode     -  Open mode on the File object (Either reading or writing) | 
| 7991 |  |  |  |  |  |  | $file     -  File object as returned from DirHandle::open | 
| 7992 |  |  |  |  |  |  | $sock     -  Data IO::Socket object used for transfering | 
| 7993 |  |  |  |  |  |  | \$buffer  -  Reference to current buffer about to be written | 
| 7994 |  |  |  |  |  |  |  | 
| 7995 |  |  |  |  |  |  | The \$buffer is passed by reference to minimize the stack overhead | 
| 7996 |  |  |  |  |  |  | for efficiency purposes only.  It is B meant to be modified by | 
| 7997 |  |  |  |  |  |  | the transfer_hook subroutine.  (It can cause corruption if the | 
| 7998 |  |  |  |  |  |  | length of $buffer is modified.) | 
| 7999 |  |  |  |  |  |  |  | 
| 8000 |  |  |  |  |  |  | Hook: This hook is called after reading $buffer and before writing | 
| 8001 |  |  |  |  |  |  | $buffer to its destination.  If arg1 is "r", $buffer was read | 
| 8002 |  |  |  |  |  |  | from the File object and written to the Data socket.  If arg1 is | 
| 8003 |  |  |  |  |  |  | "w", $buffer will be written to the File object because it was | 
| 8004 |  |  |  |  |  |  | read from the Data Socket.  The return value is the error for not | 
| 8005 |  |  |  |  |  |  | being able to perform the write.  Return undef to avoid aborting | 
| 8006 |  |  |  |  |  |  | the transfer process. | 
| 8007 |  |  |  |  |  |  |  | 
| 8008 |  |  |  |  |  |  | Status: optional. | 
| 8009 |  |  |  |  |  |  |  | 
| 8010 |  |  |  |  |  |  | =cut | 
| 8011 |  |  |  |  |  |  |  | 
| 8012 |  |  |  |  |  |  | sub transfer_hook | 
| 8013 |  |  |  |  |  |  | { | 
| 8014 | 62962 |  |  | 62962 | 1 | 116146 | return undef; | 
| 8015 |  |  |  |  |  |  | } | 
| 8016 |  |  |  |  |  |  |  | 
| 8017 |  |  |  |  |  |  | =pod | 
| 8018 |  |  |  |  |  |  |  | 
| 8019 |  |  |  |  |  |  | =item $self->post_command_hook ($cmd, $rest) | 
| 8020 |  |  |  |  |  |  |  | 
| 8021 |  |  |  |  |  |  | Hook: This hook is called after all command processing has been | 
| 8022 |  |  |  |  |  |  | carried out on this command. C<$cmd> is the command, and | 
| 8023 |  |  |  |  |  |  | C<$rest> is the remainder of the command line. | 
| 8024 |  |  |  |  |  |  |  | 
| 8025 |  |  |  |  |  |  | Status: optional. | 
| 8026 |  |  |  |  |  |  |  | 
| 8027 |  |  |  |  |  |  | =cut | 
| 8028 |  |  |  |  |  |  |  | 
| 8029 |  |  |  |  |  |  | sub post_command_hook | 
| 8030 |  |  |  | 277 | 1 |  | { | 
| 8031 |  |  |  |  |  |  | } | 
| 8032 |  |  |  |  |  |  |  | 
| 8033 |  |  |  |  |  |  | =pod | 
| 8034 |  |  |  |  |  |  |  | 
| 8035 |  |  |  |  |  |  | =item $self->system_error_hook | 
| 8036 |  |  |  |  |  |  |  | 
| 8037 |  |  |  |  |  |  | Hook: This hook is used instead of $! when what looks like a system error | 
| 8038 |  |  |  |  |  |  | occurs during a virtual filesystem handle method.  It can be used by the | 
| 8039 |  |  |  |  |  |  | virtual filesystem to provide explanatory text for a virtual filesystem | 
| 8040 |  |  |  |  |  |  | failure which did not actually set the real $!. | 
| 8041 |  |  |  |  |  |  |  | 
| 8042 |  |  |  |  |  |  | Status: optional. | 
| 8043 |  |  |  |  |  |  |  | 
| 8044 |  |  |  |  |  |  | =cut | 
| 8045 |  |  |  |  |  |  |  | 
| 8046 |  |  |  |  |  |  | sub system_error_hook | 
| 8047 |  |  |  |  |  |  | { | 
| 8048 | 0 |  |  | 0 | 1 |  | return "$!"; | 
| 8049 |  |  |  |  |  |  | } | 
| 8050 |  |  |  |  |  |  |  | 
| 8051 |  |  |  |  |  |  | =pod | 
| 8052 |  |  |  |  |  |  |  | 
| 8053 |  |  |  |  |  |  | =item $self->quit_hook | 
| 8054 |  |  |  |  |  |  |  | 
| 8055 |  |  |  |  |  |  | Hook: This hook is called after the user has C or if the FTP | 
| 8056 |  |  |  |  |  |  | client cleanly drops the connection. Please note, however, that this | 
| 8057 |  |  |  |  |  |  | hook is I called whenever the FTP server exits, particularly in | 
| 8058 |  |  |  |  |  |  | cases such as: | 
| 8059 |  |  |  |  |  |  |  | 
| 8060 |  |  |  |  |  |  | * The FTP server, the Perl interpreter or the personality | 
| 8061 |  |  |  |  |  |  | crashes unexpectedly. | 
| 8062 |  |  |  |  |  |  | * The user fails to log in. | 
| 8063 |  |  |  |  |  |  | * The FTP server detects a fatal error, sends a "421" error code, | 
| 8064 |  |  |  |  |  |  | and abruptly exits. | 
| 8065 |  |  |  |  |  |  | * Idle timeouts. | 
| 8066 |  |  |  |  |  |  | * Access control violations. | 
| 8067 |  |  |  |  |  |  | * Manual server shutdowns. | 
| 8068 |  |  |  |  |  |  |  | 
| 8069 |  |  |  |  |  |  | Unfortunately it is not in general easily possible to catch these | 
| 8070 |  |  |  |  |  |  | cases and cleanly call a hook. If your personality needs to do cleanup | 
| 8071 |  |  |  |  |  |  | in all cases, then it is probably better to use an C block inside | 
| 8072 |  |  |  |  |  |  | your Server object (see C). Even using an C block | 
| 8073 |  |  |  |  |  |  | cannot catch cases where the Perl interpreter crashes. | 
| 8074 |  |  |  |  |  |  |  | 
| 8075 |  |  |  |  |  |  | Status: optional. | 
| 8076 |  |  |  |  |  |  |  | 
| 8077 |  |  |  |  |  |  | =cut | 
| 8078 |  |  |  |  |  |  |  | 
| 8079 |  |  |  |  |  |  | sub quit_hook | 
| 8080 |  |  |  | 40 | 1 |  | { | 
| 8081 |  |  |  |  |  |  | } | 
| 8082 |  |  |  |  |  |  |  | 
| 8083 |  |  |  |  |  |  | #---------------------------------------------------------------------- | 
| 8084 |  |  |  |  |  |  |  | 
| 8085 |  |  |  |  |  |  | # The Net::FTPServer::ZipMember class is used to implement the ZIP | 
| 8086 |  |  |  |  |  |  | # file generator (in archive mode). This class is carefully and | 
| 8087 |  |  |  |  |  |  | # cleverly designed so that it doesn't break if Archive::Zip is not | 
| 8088 |  |  |  |  |  |  | # present. This class is mostly based on Archive::Zip::NewFileMember. | 
| 8089 |  |  |  |  |  |  |  | 
| 8090 |  |  |  |  |  |  | package Net::FTPServer::ZipMember; | 
| 8091 |  |  |  |  |  |  |  | 
| 8092 | 75 |  |  | 75 |  | 680 | use strict; | 
|  | 75 |  |  |  |  | 196 |  | 
|  | 75 |  |  |  |  | 2228 |  | 
| 8093 |  |  |  |  |  |  |  | 
| 8094 | 75 |  |  | 75 |  | 397 | use vars qw(@ISA); | 
|  | 75 |  |  |  |  | 184 |  | 
|  | 75 |  |  |  |  | 3479 |  | 
| 8095 |  |  |  |  |  |  | @ISA = qw(Archive::Zip::Member); | 
| 8096 |  |  |  |  |  |  |  | 
| 8097 | 75 |  |  | 75 |  | 474 | use Net::FTPServer::FileHandle; | 
|  | 75 |  |  |  |  | 185 |  | 
|  | 75 |  |  |  |  | 28465 |  | 
| 8098 |  |  |  |  |  |  |  | 
| 8099 |  |  |  |  |  |  | # Verify this exists first by using ``exists $INC{"Archive/Zip.pm"}''. | 
| 8100 | 75 |  |  | 75 |  | 4208 | eval "use Archive::Zip"; | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 8101 |  |  |  |  |  |  |  | 
| 8102 |  |  |  |  |  |  | sub _newFromFileHandle | 
| 8103 |  |  |  |  |  |  | { | 
| 8104 | 0 |  |  | 0 |  |  | my $class = shift; | 
| 8105 | 0 |  |  |  |  |  | my $fileh = shift; | 
| 8106 |  |  |  |  |  |  |  | 
| 8107 | 0 | 0 |  |  |  |  | return undef unless exists $INC{"Archive/Zip.pm"}; | 
| 8108 |  |  |  |  |  |  |  | 
| 8109 | 0 |  |  |  |  |  | my $self = $class->new (@_); | 
| 8110 |  |  |  |  |  |  |  | 
| 8111 | 0 |  |  |  |  |  | $self->{fileh} = $fileh; | 
| 8112 |  |  |  |  |  |  |  | 
| 8113 | 0 |  |  |  |  |  | my $filename = $fileh->filename; | 
| 8114 | 0 |  |  |  |  |  | $self->fileName ($filename); | 
| 8115 | 0 |  |  |  |  |  | $self->{externalFileName} = $filename; | 
| 8116 |  |  |  |  |  |  |  | 
| 8117 | 0 |  |  |  |  |  | $self->{compressionMethod} = &Archive::Zip::COMPRESSION_STORED; | 
| 8118 |  |  |  |  |  |  |  | 
| 8119 | 0 |  |  |  |  |  | my ($mode, $perms, $nlink, $user, $group, $size, $time) = $fileh->status; | 
| 8120 | 0 |  |  |  |  |  | $self->{compressedSize} = $self->{uncompressedSize} = $size; | 
| 8121 | 0 | 0 |  |  |  |  | $self->desiredCompressionMethod | 
| 8122 |  |  |  |  |  |  | ($self->compressedSize > 0 | 
| 8123 |  |  |  |  |  |  | ? &Archive::Zip::COMPRESSION_DEFLATED | 
| 8124 |  |  |  |  |  |  | : &Archive::Zip::COMPRESSION_STORED); | 
| 8125 | 0 |  |  |  |  |  | $self->unixFileAttributes ($perms); | 
| 8126 | 0 | 0 |  |  |  |  | $self->setLastModFileDateTimeFromUnix ($time) if $time > 0; | 
| 8127 | 0 |  |  |  |  |  | $self->isTextFile (0); | 
| 8128 |  |  |  |  |  |  |  | 
| 8129 | 0 |  |  |  |  |  | $self; | 
| 8130 |  |  |  |  |  |  | } | 
| 8131 |  |  |  |  |  |  |  | 
| 8132 |  |  |  |  |  |  | sub externalFileName | 
| 8133 |  |  |  |  |  |  | { | 
| 8134 | 0 |  |  | 0 |  |  | shift->{externalFileName}; | 
| 8135 |  |  |  |  |  |  | } | 
| 8136 |  |  |  |  |  |  |  | 
| 8137 |  |  |  |  |  |  | sub fh | 
| 8138 |  |  |  |  |  |  | { | 
| 8139 | 0 |  |  | 0 |  |  | my $self = shift; | 
| 8140 |  |  |  |  |  |  |  | 
| 8141 | 0 | 0 |  |  |  |  | return $self->{fh} if $self->{fh}; | 
| 8142 |  |  |  |  |  |  |  | 
| 8143 | 0 | 0 |  |  |  |  | $self->{fh} = $self->{fileh}->open ("r") | 
| 8144 |  |  |  |  |  |  | or return &Archive::Zip::AZ_IO_ERROR; | 
| 8145 |  |  |  |  |  |  |  | 
| 8146 | 0 |  |  |  |  |  | $self->{fh}; | 
| 8147 |  |  |  |  |  |  | } | 
| 8148 |  |  |  |  |  |  |  | 
| 8149 |  |  |  |  |  |  | sub rewindData | 
| 8150 |  |  |  |  |  |  | { | 
| 8151 | 0 |  |  | 0 |  |  | my $self = shift; | 
| 8152 |  |  |  |  |  |  |  | 
| 8153 | 0 |  |  |  |  |  | my $status = $self->SUPER::rewindData (@_); | 
| 8154 | 0 | 0 |  |  |  |  | return $status if $status != &Archive::Zip::AZ_OK; | 
| 8155 |  |  |  |  |  |  |  | 
| 8156 | 0 | 0 |  |  |  |  | return &Archive::Zip::AZ_IO_ERROR unless $self->fh; | 
| 8157 |  |  |  |  |  |  |  | 
| 8158 |  |  |  |  |  |  | # Not all personalities can seek backwards in the stream. Close | 
| 8159 |  |  |  |  |  |  | # the file and reopen it instead. | 
| 8160 | 0 | 0 |  |  |  |  | $self->endRead == &Archive::Zip::AZ_OK | 
| 8161 |  |  |  |  |  |  | or return &Archive::Zip::AZ_IO_ERROR; | 
| 8162 | 0 |  |  |  |  |  | $self->fh; | 
| 8163 |  |  |  |  |  |  |  | 
| 8164 | 0 |  |  |  |  |  | return &Archive::Zip::AZ_OK; | 
| 8165 |  |  |  |  |  |  | } | 
| 8166 |  |  |  |  |  |  |  | 
| 8167 |  |  |  |  |  |  | sub _readRawChunk | 
| 8168 |  |  |  |  |  |  | { | 
| 8169 | 0 |  |  | 0 |  |  | my $self = shift; | 
| 8170 | 0 |  |  |  |  |  | my $dataref = shift; | 
| 8171 | 0 |  |  |  |  |  | my $chunksize = shift; | 
| 8172 |  |  |  |  |  |  |  | 
| 8173 | 0 | 0 |  |  |  |  | return (0, &Archive::Zip::AZ_OK) unless $chunksize; | 
| 8174 |  |  |  |  |  |  |  | 
| 8175 | 0 | 0 |  |  |  |  | my $bytesread = $self->fh->sysread ($$dataref, $chunksize) | 
| 8176 |  |  |  |  |  |  | or return (0, &Archive::Zip::AZ_IO_ERROR); | 
| 8177 |  |  |  |  |  |  |  | 
| 8178 | 0 |  |  |  |  |  | return ($bytesread, &Archive::Zip::AZ_OK); | 
| 8179 |  |  |  |  |  |  | } | 
| 8180 |  |  |  |  |  |  |  | 
| 8181 |  |  |  |  |  |  | sub endRead | 
| 8182 |  |  |  |  |  |  | { | 
| 8183 | 0 |  |  | 0 |  |  | my $self = shift; | 
| 8184 |  |  |  |  |  |  |  | 
| 8185 | 0 | 0 |  |  |  |  | if ($self->{fh}) | 
| 8186 |  |  |  |  |  |  | { | 
| 8187 |  |  |  |  |  |  | $self->{fh}->close | 
| 8188 | 0 | 0 |  |  |  |  | or return &Archive::Zip::AZ_IO_ERROR; | 
| 8189 | 0 |  |  |  |  |  | delete $self->{fh}; | 
| 8190 |  |  |  |  |  |  | } | 
| 8191 | 0 |  |  |  |  |  | return &Archive::Zip::AZ_OK; | 
| 8192 |  |  |  |  |  |  | } | 
| 8193 |  |  |  |  |  |  |  | 
| 8194 |  |  |  |  |  |  | 1 # So that the require or use succeeds. | 
| 8195 |  |  |  |  |  |  |  | 
| 8196 |  |  |  |  |  |  | __END__ |