File Coverage

blib/lib/Net/FTPServer/Full/Server.pm
Criterion Covered Total %
statement 23 111 20.7
branch 0 68 0.0
condition 0 8 0.0
subroutine 9 15 60.0
pod 3 3 100.0
total 35 205 17.0


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             =pod
22              
23             =head1 NAME
24              
25             Net::FTPServer::Full::Server - The full FTP server personality
26              
27             =head1 SYNOPSIS
28              
29             ftpd.sh [-d] [-v] [-p port] [-s] [-S] [-V] [-C conf_file]
30              
31             =head1 DESCRIPTION
32              
33             C is the full FTP server
34             personality. This personality implements a complete
35             FTP server with similar functionality to I.
36              
37             =head1 METHODS
38              
39             =cut
40              
41             package Net::FTPServer::Full::Server;
42              
43 1     1   2004 use strict;
  1         2  
  1         23  
44              
45             # Authen::PAM is an optional module.
46 1     1   41 BEGIN { eval "use Authen::PAM;"; }
  1     1   86  
  0         0  
  0         0  
47              
48 1     1   4 use vars qw($VERSION);
  1         2  
  1         56  
49             ( $VERSION ) = '$Revision: 1.1 $ ' =~ /\$Revision:\s+([^\s]+)/;
50              
51 1     1   6 use Net::FTPServer;
  1         1  
  1         17  
52 1     1   249 use Net::FTPServer::Full::FileHandle;
  1         3  
  1         24  
53 1     1   249 use Net::FTPServer::Full::DirHandle;
  1         2  
  1         30  
54              
55 1     1   6 use vars qw(@ISA);
  1         1  
  1         351  
56             @ISA = qw(Net::FTPServer);
57              
58             =pod
59              
60             =over 4
61              
62             =item $rv = $self->authentication_hook ($user, $pass, $user_is_anon)
63              
64             Perform login against C, the PAM database or the
65             C.
66              
67             =cut
68              
69             sub authentication_hook
70             {
71 0     0 1   my $self = shift;
72 0           my $user = shift;
73 0           my $pass = shift;
74 0           my $user_is_anon = shift;
75              
76             # Allow anonymous users. By this point we have already checked
77             # that allow anonymous is true in the configuration file.
78 0 0         return 0 if $user_is_anon;
79              
80 0 0         if ($self->config ("password file"))
    0          
81             {
82 0 0         return -1 if $self->_password_file ($user, $pass) < 0;
83             }
84             elsif (! $self->config ("pam authentication"))
85             {
86             # Verify user information against the password file.
87 0 0         my $hashed_pass = (getpwnam $user)[1] or return -1;
88              
89             # Check password.
90 0 0         return -1 if crypt ($pass, $hashed_pass) ne $hashed_pass;
91             }
92             else
93             {
94 0 0         return -1 if $self->_pam_check_password ($user, $pass) < 0;
95             }
96              
97             # Successful login.
98 0           return 0;
99             }
100              
101             sub _password_file
102             {
103 0     0     my $self = shift;
104 0           my $user = shift;
105 0           my $pass = shift;
106              
107 0           my $pw_file = $self->config ("password file");
108              
109             # Open the password file and parse it.
110 0 0         open PW_FILE, "<$pw_file"
111             or die "Administrator configured a password file, but ",
112             "the file is missing or cannot be opened. The error was:\n",
113             "$pw_file: $!";
114 0           while ()
115             {
116 0           s/[\n\r]+$//;
117 0 0 0       next if /^\s*\#/ || /^\s*$/;
118              
119             # Untaint lines from the password file, since we trust it
120             # unconditionally. Admin's fault if they make this file
121             # world writable or something :-)
122 0           /(.*)/; $_ = $1;
  0            
123              
124 0           my ($pw_username, $pw_crypted_pw, $unix_user, $root_directory)
125             = split /:/, $_;
126              
127 0 0 0       if ($user eq $pw_username &&
128             $pw_crypted_pw eq crypt ($pass, $pw_crypted_pw))
129             {
130 0           close PW_FILE;
131              
132             # Successful login. Remember the real Unix user, which
133             # we will substitute below.
134 0           $self->{full_unix_user} = $unix_user;
135 0 0         $self->{full_root_directory} = $root_directory
136             if defined $root_directory;
137              
138 0           return 0;
139             }
140             }
141 0           close PW_FILE;
142              
143             # Failed.
144 0           return -1;
145             }
146              
147             sub _pam_check_password
148             {
149 0     0     my $self = shift;
150 0           my $user = shift;
151 0           my $pass = shift;
152              
153             # Give a nice error message in the logs if PAM is not available.
154 0 0         unless (exists $INC{"Authen/PAM.pm"})
155             {
156 0           die
157             "Authen::PAM module is not available, yet PAM authentication ",
158             "was requested in the configuration file. You will not be able ",
159             "to log in to this server. Fetch and install Authen::PAM from ",
160             "CPAN."
161             }
162              
163             # As noted in the source to wu-ftpd, this is something
164             # of an abuse of the PAM protocol. However the FTP protocol
165             # gives us little choice in the matter.
166              
167             eval
168 0           {
169 1     1   6 no strict; # Otherwise Perl complains about barewords.
  1         1  
  1         492  
170              
171             my $pam_conv_func = sub
172             {
173 0     0     my @res;
174              
175 0           while (@_)
176             {
177 0           my $msg_type = shift;
178 0           my $msg = shift;
179              
180 0 0         if ($msg_type == PAM_PROMPT_ECHO_ON)
    0          
    0          
    0          
181             {
182 0           return ( PAM_CONV_ERR );
183             }
184             elsif ($msg_type == PAM_PROMPT_ECHO_OFF)
185             {
186 0           push @res, PAM_SUCCESS;
187 0           push @res, $pass;
188             }
189             elsif ($msg_type == PAM_TEXT_INFO)
190             {
191 0           push @res, PAM_SUCCESS;
192 0           push @res, "";
193             }
194             elsif ($msg_type == PAM_ERROR_MSG)
195             {
196 0           push @res, PAM_SUCCESS;
197 0           push @res, "";
198             }
199             else
200             {
201 0           return ( PAM_CONV_ERR );
202             }
203             }
204              
205 0           push @res, PAM_SUCCESS;
206 0           return @res;
207 0           };
208              
209 0   0       my $pam_appl = $self->config ("pam application name") || "ftp";
210 0           my $pamh = Authen::PAM->new ($pam_appl, $user, $pam_conv_func);
211              
212 0 0         ref ($pamh) || die "PAM error: pam_start: $pamh";
213              
214             $pamh->pam_set_item (PAM_RHOST, $self->{peeraddrstring})
215 0 0         == PAM_SUCCESS
216             or die "PAM error: pam_set_item";
217              
218 0 0         $pamh->pam_authenticate (0) == PAM_SUCCESS
219             or die "PAM error: pam_authenticate";
220              
221 0 0         $pamh->pam_acct_mgmt (0) == PAM_SUCCESS
222             or die "PAM error: pam_acct_mgmt";
223              
224 0 0         $pamh->pam_setcred (PAM_ESTABLISH_CRED) == PAM_SUCCESS
225             or die "PAM error: pam_setcred";
226             }; # eval
227              
228 0 0         if ($@)
229             {
230 0           $self->log ("info", "PAM authentication error: $@");
231 0           return -1;
232             }
233              
234 0           return 0;
235             }
236              
237             =pod
238              
239             =item $self->user_login_hook ($user, $user_is_anon)
240              
241             Hook: Called just after user C<$user> has successfully logged in.
242              
243             =cut
244              
245             sub user_login_hook
246             {
247 0     0 1   my $self = shift;
248 0           my $user = shift;
249 0           my $user_is_anon = shift;
250              
251 0           my ($login, $pass, $uid, $gid, $quota, $comment, $gecos, $homedir);
252              
253             # For non-anonymous users, just get the uid/gid.
254 0 0         if (! $user_is_anon)
255             {
256             # Saved real Unix user (from the "password file" option)?
257 0 0         $user = $self->{full_unix_user} if exists $self->{full_unix_user};
258              
259 0 0         ($login, $pass, $uid, $gid) = getpwnam $user
260             or die "no user $user in password file";
261              
262             # Chroot for this non-anonymous user? If using the "password file"
263             # option then we might have saved a root directory above.
264             my $root_directory =
265             exists $self->{full_root_directory} ?
266             $self->{full_root_directory} :
267 0 0         $self->config ("root directory");
268              
269 0 0         if (defined $root_directory)
270             {
271 0           $root_directory =~ s/%m/(getpwnam $user)[7]/ge;
  0            
272 0           $root_directory =~ s/%U/$user/ge;
  0            
273 0           $root_directory =~ s/%%/%/g;
274              
275 0 0         if ($< == 0)
276             {
277 0 0         chroot $root_directory
278             or die "cannot chroot: $root_directory: $!";
279             }
280             else
281             {
282 0 0         chroot $root_directory
283             or die "cannot chroot: $root_directory: $!"
284             . " (you need root privilege to use chroot feature)";
285             }
286             }
287             }
288             # For anonymous users, chroot to ftp directory.
289             else
290             {
291 0 0         ($login, $pass, $uid, $gid, $quota, $comment, $gecos, $homedir)
292             = getpwnam "ftp"
293             or die "no ftp user in password file";
294              
295 0 0         if ($< == 0)
296             {
297 0 0         chroot $homedir or die "cannot chroot: $homedir: $!";
298             }
299             else
300             {
301 0 0         chroot $homedir or die "cannot chroot: $homedir: $!"
302             . " (you need root privilege to use chroot feature)";
303             }
304             }
305              
306             # We don't allow users to relogin, so completely change to
307             # the user specified.
308 0           $self->_drop_privs ($uid, $gid, $login);
309             }
310              
311             =pod
312              
313             =item $dirh = $self->root_directory_hook;
314              
315             Hook: Return an instance of Net::FTPServer::FullDirHandle
316             corresponding to the root directory.
317              
318             =cut
319              
320             sub root_directory_hook
321             {
322 0     0 1   my $self = shift;
323              
324 0           return new Net::FTPServer::Full::DirHandle ($self);
325             }
326              
327             1 # So that the require or use succeeds.
328              
329             __END__