File Coverage

blib/lib/Net/SFTP/SftpServer.pm
Criterion Covered Total %
statement 343 1326 25.8
branch 7 392 1.7
condition 2 181 1.1
subroutine 110 232 47.4
pod 0 47 0.0
total 462 2178 21.2


line stmt bran cond sub pod time code
1             #/*
2             # * Based on sftp-server.c
3             # * Copyright (c) 2000-2004 Markus Friedl. All rights reserved.
4             # *
5             # * Ported to Perl and extended by Simon Day
6             # * Copyright (c) 2009 Pirum Systems Ltd. All rights reserved.
7             # *
8             # * Permission to use, copy, modify, and distribute this software for any
9             # * purpose with or without fee is hereby granted, provided that the above
10             # * copyright notice and this permission notice appear in all copies.
11             # *
12             # * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13             # * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14             # * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
15             # * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16             # * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17             # * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
18             # * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19             # */
20             #
21             package Net::SFTP::SftpServer;
22             require Exporter;
23             @ISA = qw(Exporter);
24              
25             @EXPORT_OK = qw(
26             ALL
27             NET_SFTP_SYMLINKS
28             NET_SFTP_RENAME_DIR
29             SSH2_FXP_INIT
30             SSH2_FXP_OPEN
31             SSH2_FXP_CLOSE
32             SSH2_FXP_READ
33             SSH2_FXP_WRITE
34             SSH2_FXP_LSTAT
35             SSH2_FXP_STAT_VERSION_0
36             SSH2_FXP_FSTAT
37             SSH2_FXP_SETSTAT
38             SSH2_FXP_FSETSTAT
39             SSH2_FXP_OPENDIR
40             SSH2_FXP_READDIR
41             SSH2_FXP_REMOVE
42             SSH2_FXP_MKDIR
43             SSH2_FXP_RMDIR
44             SSH2_FXP_REALPATH
45             SSH2_FXP_STAT
46             SSH2_FXP_RENAME
47             SSH2_FXP_READLINK
48             SSH2_FXP_SYMLINK
49             logError
50             logWarning
51             logGeneral
52             logDetail
53             );
54              
55              
56             %EXPORT_TAGS = ( ACTIONS => [ qw(
57             ALL
58             NET_SFTP_SYMLINKS
59             NET_SFTP_RENAME_DIR
60             SSH2_FXP_OPEN
61             SSH2_FXP_CLOSE
62             SSH2_FXP_READ
63             SSH2_FXP_WRITE
64             SSH2_FXP_LSTAT
65             SSH2_FXP_STAT_VERSION_0
66             SSH2_FXP_FSTAT
67             SSH2_FXP_SETSTAT
68             SSH2_FXP_FSETSTAT
69             SSH2_FXP_OPENDIR
70             SSH2_FXP_READDIR
71             SSH2_FXP_REMOVE
72             SSH2_FXP_MKDIR
73             SSH2_FXP_RMDIR
74             SSH2_FXP_STAT
75             SSH2_FXP_RENAME
76             SSH2_FXP_READLINK
77             SSH2_FXP_SYMLINK
78             ) ],
79             LOG => [qw(
80             logError
81             logWarning
82             logGeneral
83             logDetail
84             )]);
85              
86 1     1   24689 use strict;
  1         1  
  1         38  
87 1     1   5 use warnings;
  1         1  
  1         30  
88              
89 1     1   1041 use version; our $VERSION = qv('1.1.0');
  1         2217  
  1         6  
90              
91 1     1   1109 use Stat::lsMode;
  1         1003  
  1         73  
92 1     1   6 use Fcntl qw( O_RDWR O_CREAT O_TRUNC O_EXCL O_RDONLY O_WRONLY SEEK_SET );
  1         2  
  1         79  
93 1     1   949 use POSIX qw(strftime);
  1         8896  
  1         8  
94 1     1   2461 use Sys::Syslog;
  1         16570  
  1         129  
95              
96             $SIG{__DIE__} = sub { ## still dies upon return
97             syslog 'warning', join(" : ", @_);
98             };
99              
100 1     1   877 use Errno qw(:POSIX);
  1         1284  
  1         544  
101              
102 1     1   6 use constant TIMEOUT => 300;
  1         2  
  1         80  
103 1     1   5 use constant MAX_PACKET_SIZE => 1024 * 1024;
  1         2  
  1         52  
104 1     1   4 use constant MAX_OPEN_HANDLES => 512;
  1         2  
  1         45  
105              
106             #/* version */
107 1     1   6 use constant SSH2_FILEXFER_VERSION => 3;
  1         2  
  1         47  
108              
109             #/* client to server */
110 1     1   6 use constant SSH2_FXP_INIT => 1;
  1         2  
  1         52  
111 1     1   13 use constant SSH2_FXP_OPEN => 3;
  1         2  
  1         42  
112 1     1   5 use constant SSH2_FXP_CLOSE => 4;
  1         2  
  1         44  
113 1     1   4 use constant SSH2_FXP_READ => 5;
  1         2  
  1         45  
114 1     1   4 use constant SSH2_FXP_WRITE => 6;
  1         2  
  1         37  
115 1     1   5 use constant SSH2_FXP_LSTAT => 7;
  1         2  
  1         51  
116 1     1   5 use constant SSH2_FXP_STAT_VERSION_0 => 7;
  1         2  
  1         41  
117 1     1   5 use constant SSH2_FXP_FSTAT => 8;
  1         2  
  1         52  
118 1     1   5 use constant SSH2_FXP_SETSTAT => 9;
  1         2  
  1         46  
119 1     1   5 use constant SSH2_FXP_FSETSTAT => 10;
  1         1  
  1         48  
120 1     1   6 use constant SSH2_FXP_OPENDIR => 11;
  1         1  
  1         54  
121 1     1   5 use constant SSH2_FXP_READDIR => 12;
  1         2  
  1         47  
122 1     1   6 use constant SSH2_FXP_REMOVE => 13;
  1         2  
  1         40  
123 1     1   4 use constant SSH2_FXP_MKDIR => 14;
  1         2  
  1         48  
124 1     1   5 use constant SSH2_FXP_RMDIR => 15;
  1         2  
  1         38  
125 1     1   4 use constant SSH2_FXP_REALPATH => 16;
  1         2  
  1         49  
126 1     1   5 use constant SSH2_FXP_STAT => 17;
  1         1  
  1         41  
127 1     1   6 use constant SSH2_FXP_RENAME => 18;
  1         2  
  1         48  
128 1     1   5 use constant SSH2_FXP_READLINK => 19;
  1         2  
  1         55  
129 1     1   6 use constant SSH2_FXP_SYMLINK => 20;
  1         8  
  1         46  
130              
131             # SFTP allow/deny actions
132              
133 1     1   5 use constant ALL => 1000;
  1         2  
  1         46  
134 1     1   6 use constant NET_SFTP_RENAME_DIR => 1001;
  1         2  
  1         48  
135 1     1   6 use constant NET_SFTP_SYMLINKS => 1002;
  1         17  
  1         42  
136              
137             #/* server to client */
138 1     1   5 use constant SSH2_FXP_VERSION => 2;
  1         3  
  1         41  
139 1     1   5 use constant SSH2_FXP_STATUS => 101;
  1         1  
  1         46  
140 1     1   5 use constant SSH2_FXP_HANDLE => 102;
  1         1  
  1         40  
141 1     1   13 use constant SSH2_FXP_DATA => 103;
  1         2  
  1         54  
142 1     1   5 use constant SSH2_FXP_NAME => 104;
  1         1  
  1         54  
143 1     1   6 use constant SSH2_FXP_ATTRS => 105;
  1         2  
  1         43  
144              
145 1     1   6 use constant SSH2_FXP_EXTENDED => 200;
  1         1  
  1         48  
146 1     1   5 use constant SSH2_FXP_EXTENDED_REPLY => 201;
  1         2  
  1         50  
147              
148             #/* attributes */
149 1     1   4 use constant SSH2_FILEXFER_ATTR_SIZE => 0x00000001;
  1         2  
  1         34  
150 1     1   5 use constant SSH2_FILEXFER_ATTR_UIDGID => 0x00000002;
  1         2  
  1         39  
151 1     1   5 use constant SSH2_FILEXFER_ATTR_PERMISSIONS => 0x00000004;
  1         1  
  1         47  
152 1     1   5 use constant SSH2_FILEXFER_ATTR_ACMODTIME => 0x00000008;
  1         2  
  1         38  
153 1     1   4 use constant SSH2_FILEXFER_ATTR_EXTENDED => 0x80000000;
  1         1  
  1         42  
154              
155             #/* portable open modes */
156 1     1   5 use constant SSH2_FXF_READ => 0x00000001;
  1         2  
  1         39  
157 1     1   4 use constant SSH2_FXF_WRITE => 0x00000002;
  1         2  
  1         36  
158 1     1   4 use constant SSH2_FXF_APPEND => 0x00000004;
  1         8  
  1         29  
159 1     1   3 use constant SSH2_FXF_CREAT => 0x00000008;
  1         6  
  1         28  
160 1     1   3 use constant SSH2_FXF_TRUNC => 0x00000010;
  1         1  
  1         31  
161 1     1   4 use constant SSH2_FXF_EXCL => 0x00000020;
  1         2  
  1         42  
162              
163             #/* status messages */
164 1     1   4 use constant SSH2_FX_OK => 0;
  1         1  
  1         28  
165 1     1   3 use constant SSH2_FX_EOF => 1;
  1         1  
  1         36  
166 1     1   3 use constant SSH2_FX_NO_SUCH_FILE => 2;
  1         1  
  1         27  
167 1     1   4 use constant SSH2_FX_PERMISSION_DENIED => 3;
  1         2  
  1         35  
168 1     1   4 use constant SSH2_FX_FAILURE => 4;
  1         2  
  1         28  
169 1     1   3 use constant SSH2_FX_BAD_MESSAGE => 5;
  1         1  
  1         34  
170 1     1   3 use constant SSH2_FX_NO_CONNECTION => 6;
  1         2  
  1         30  
171 1     1   9 use constant SSH2_FX_CONNECTION_LOST => 7;
  1         1  
  1         31  
172 1     1   5 use constant SSH2_FX_OP_UNSUPPORTED => 8;
  1         1  
  1         32  
173 1     1   3 use constant SSH2_FX_MAX => 8;#8 is the highest that is available
  1         2  
  1         292  
174              
175 1         263 use constant MESSAGE_HANDLER => {
176             SSH2_FXP_INIT() => 'processInit',
177             SSH2_FXP_OPEN() => 'processOpen',
178             SSH2_FXP_CLOSE() => 'processClose',
179             SSH2_FXP_READ() => 'processRead',
180             SSH2_FXP_WRITE() => 'processWrite',
181             SSH2_FXP_LSTAT() => 'processLstat',
182             SSH2_FXP_FSTAT() => 'processFstat',
183             SSH2_FXP_SETSTAT() => 'processSetstat',
184             SSH2_FXP_FSETSTAT() => 'processFsetstat',
185             SSH2_FXP_OPENDIR() => 'processOpendir',
186             SSH2_FXP_READDIR() => 'processReaddir',
187             SSH2_FXP_REMOVE() => 'processRemove',
188             SSH2_FXP_MKDIR() => 'processMkdir',
189             SSH2_FXP_RMDIR() => 'processRmdir',
190             SSH2_FXP_REALPATH() => 'processRealpath',
191             SSH2_FXP_STAT() => 'processStat',
192             SSH2_FXP_RENAME() => 'processRename',
193             SSH2_FXP_READLINK() => 'processReadlink',
194             SSH2_FXP_SYMLINK() => 'processSymlink',
195             SSH2_FXP_EXTENDED() => 'processExtended',
196 1     1   4 };
  1         2  
197              
198 1         199 use constant MESSAGE_TYPES => {
199             SSH2_FXP_INIT() => 'SSH2_FXP_INIT',
200             SSH2_FXP_OPEN() => 'SSH2_FXP_OPEN',
201             SSH2_FXP_CLOSE() => 'SSH2_FXP_CLOSE',
202             SSH2_FXP_READ() => 'SSH2_FXP_READ',
203             SSH2_FXP_WRITE() => 'SSH2_FXP_WRITE',
204             SSH2_FXP_LSTAT() => 'SSH2_FXP_LSTAT',
205             SSH2_FXP_FSTAT() => 'SSH2_FXP_FSTAT',
206             SSH2_FXP_SETSTAT() => 'SSH2_FXP_SETSTAT',
207             SSH2_FXP_FSETSTAT() => 'SSH2_FXP_FSETSTAT',
208             SSH2_FXP_OPENDIR() => 'SSH2_FXP_OPENDIR',
209             SSH2_FXP_READDIR() => 'SSH2_FXP_READDIR',
210             SSH2_FXP_REMOVE() => 'SSH2_FXP_REMOVE',
211             SSH2_FXP_MKDIR() => 'SSH2_FXP_MKDIR',
212             SSH2_FXP_RMDIR() => 'SSH2_FXP_RMDIR',
213             SSH2_FXP_REALPATH() => 'SSH2_FXP_REALPATH',
214             SSH2_FXP_STAT() => 'SSH2_FXP_STAT',
215             SSH2_FXP_RENAME() => 'SSH2_FXP_RENAME',
216             SSH2_FXP_READLINK() => 'SSH2_FXP_READLINK',
217             SSH2_FXP_SYMLINK() => 'SSH2_FXP_SYMLINK',
218             SSH2_FXP_EXTENDED() => 'SSH2_FXP_EXTENDED',
219             ALL() => 'ALL',
220             NET_SFTP_SYMLINKS() => 'NET_SFTP_SYMLINKS',
221             NET_SFTP_RENAME_DIR() => 'NET_SFTP_RENAME_DIR',
222 1     1   5 };
  1         2  
223              
224 1         58 use constant ACTIONS => [
225             ALL,
226             NET_SFTP_SYMLINKS,
227             NET_SFTP_RENAME_DIR,
228             SSH2_FXP_OPEN,
229             SSH2_FXP_CLOSE,
230             SSH2_FXP_READ,
231             SSH2_FXP_WRITE,
232             SSH2_FXP_LSTAT,
233             SSH2_FXP_STAT_VERSION_0,
234             SSH2_FXP_FSTAT,
235             SSH2_FXP_SETSTAT,
236             SSH2_FXP_FSETSTAT,
237             SSH2_FXP_OPENDIR,
238             SSH2_FXP_READDIR,
239             SSH2_FXP_REMOVE,
240             SSH2_FXP_MKDIR,
241             SSH2_FXP_RMDIR,
242             SSH2_FXP_STAT,
243             SSH2_FXP_RENAME,
244             SSH2_FXP_READLINK,
245             SSH2_FXP_SYMLINK,
246 1     1   4 ];
  1         1  
247              
248 1         7609 use constant STATUS_MESSAGE => [
249             "Success", #/* SSH2_FX_OK */
250             "End of file", #/* SSH2_FX_EOF */
251             "No such file", #/* SSH2_FX_NO_SUCH_FILE */
252             "Permission denied", #/* SSH2_FX_PERMISSION_DENIED */
253             "Failure", #/* SSH2_FX_FAILURE */
254             "Bad message", #/* SSH2_FX_BAD_MESSAGE */
255             "No connection", #/* SSH2_FX_NO_CONNECTION */
256             "Connection lost", #/* SSH2_FX_CONNECTION_LOST */
257             "Operation unsupported", #/* SSH2_FX_OP_UNSUPPORTED */
258             "Unknown error" #/* Others */
259 1     1   4 ];
  1         1  
260              
261             my $USER = getpwuid($>);
262             my $ESCALATE_DEBUG = 0;
263             # --------------------------------------------------------------------
264             # Do evilness with symbol tables to ge
265             sub import{
266 1     1   12 my $self = shift;
267 1         3 my $opt = {};
268 1 50       6 if (ref $_[0] eq 'HASH'){
269 0         0 $opt = shift;
270             }
271 1   50     9 $opt->{log} ||= 'daemon';
272 1         4 initLog($opt->{log});
273              
274 1         2261 __PACKAGE__->export_to_level(1, $self, @_ ); # Call Exporter.
275             }
276             #-------------------------------------------------------------------------------
277             sub logItem {
278 3     3 0 8 my ($level, $prefix, @msg) = @_;
279 3         24 syslog $level, "[$USER]: $prefix" . join(" : ", @msg);
280             }
281             #-------------------------------------------------------------------------------
282             sub logDetail {
283 1 50   1 0 8 logItem( $ESCALATE_DEBUG ? 'info' : 'debug', '', @_);
284             }
285             #-------------------------------------------------------------------------------
286             sub logGeneral {
287 1     1 0 5 logItem('info', '', @_);
288             }
289             #-------------------------------------------------------------------------------
290             sub logWarning {
291 1     1 0 5 logItem('warning', 'WARNING: ', @_);
292             }
293             #-------------------------------------------------------------------------------
294             sub logError {
295 0     0 0 0 logItem('err', 'ERROR: ', @_);
296             }
297             #-------------------------------------------------------------------------------
298             sub initLog {
299 1     1 0 2 my $syslog = shift;
300 1         8 openlog( 'sftp', 'pid', $syslog);
301 1         68 my ($remote_ip, $remote_port, $local_ip, $local_port) = split(' ', $ENV{SSH_CONNECTION});
302 1         22 logGeneral "Client connected from $remote_ip:$remote_port";
303 1         433 logDetail "Client connected to $local_ip:$local_port";
304             }
305             #-------------------------------------------------------------------------------
306             sub getLogMsg {
307 0     0 0 0 my $self = shift;
308 0         0 my %arg = @_;
309              
310 0         0 my $req = $self->{_payload}->getPayloadContent();
311              
312 0         0 my $process = MESSAGE_TYPES->{$req->{message_type}};
313              
314 0 0       0 if ($req->{handle}){
315 0         0 $req->{name} = $self->{_payload}->getFilename() ;
316             }
317              
318 0         0 my $msg = '';
319 0 0 0     0 if (defined $arg{response} and $arg{response}->getType() == SSH2_FXP_STATUS ){
320 0         0 $msg = 'response: ' . STATUS_MESSAGE->[$arg{response}->getStatus()] . ' ';
321             }
322              
323 0         0 $msg .= "process: $process";
324              
325 0 0       0 if ($req->{id}){
326 0         0 $msg .= " id: $req->{id}";
327             }
328              
329 0 0       0 if ($req->{name}){
330 0         0 $msg .= " filename: $req->{name}";
331             }
332              
333 0         0 for my $field( qw( source_name target_name off len pflags ) ){
334 0 0       0 if (defined $req->{$field}){
335 0         0 $msg .= " $field: $req->{$field}";
336             }
337             }
338              
339 0 0       0 if ($req->{attr}){
340 0         0 for my $key (keys %{$req->{attr}}){
  0         0  
341 0         0 $msg .= " attr-$key: $req->{attr}{$key}";
342             }
343             }
344              
345 0         0 return $msg;
346             }
347             #-------------------------------------------------------------------------------
348             sub logAction {
349 0     0 0 0 my $self = shift;
350              
351 0         0 my $req = $self->{_payload}->getPayloadContent();
352              
353 0         0 my $msg = $self->getLogMsg();
354              
355 0 0       0 if ( $self->{log_action_supress}{ $req->{message_type} } ){
356 0         0 logDetail $msg;
357             }
358             else {
359 0         0 logGeneral $msg;
360             }
361             }
362             #-------------------------------------------------------------------------------
363             sub logStatus {
364 0     0 0 0 my $self = shift;
365 0         0 my $response = shift;
366 0         0 my $msg = $self->getLogMsg(response => $response);
367 0         0 my $req = $self->{_payload}->getPayloadContent();
368              
369 0 0 0     0 if ( $response->getType() == SSH2_FXP_STATUS
    0 0        
      0        
370             and ( $self->{log_all_status} or ( $response->getStatus() != SSH2_FX_OK and $response->getStatus() != SSH2_FX_EOF ) )){
371 0         0 logGeneral $msg;
372             }
373             elsif ( $response->getType() == SSH2_FXP_DATA or $req->{message_type} == SSH2_FXP_WRITE ){
374             # Do nothing - otherwise we spam the syslog with every read/write packet
375             }
376             else {
377 0         0 logDetail $msg;
378             }
379              
380             }
381             #-------------------------------------------------------------------------------
382             sub new {
383 1     1 0 330 my $class = shift;
384 1         2 my $self = {};
385 1         3 bless $self, $class;
386 1         9 Stat::lsMode->novice(0); #disable warnings from this module
387              
388 1         12 $self->{client_version} = 3; # Just in case we have a bad client that doesn't init the connection properly, treat it as latest version
389              
390 1         3 my %arg = @_;
391 1 50       4 if (defined $arg{debug} ){ $ESCALATE_DEBUG = $arg{debug} };
  0         0  
392              
393 1   50     7 $self->{home} = $arg{home} || '/home';
394 1         3 $self->{home} =~ s!/$!!; # strip trailing /
395 1 50       4 if (defined $arg{file_perms}){ $self->{file_perms} = $arg{file_perms} };
  0         0  
396 1 50       3 if (defined $arg{dir_perms} ){ $self->{dir_perms} = $arg{dir_perms} };
  0         0  
397              
398 1         4 $self->{home_dir} = "$self->{home}/$USER";
399 1         9 $self->{FS} = Net::SFTP::SftpServer::FS->new();
400 1         5 $self->{FS}->setChrootDir( $self->{home_dir} );
401 1 50       20 unless ( -d $self->{home_dir} ){
402 1         16 logWarning "No sftp folder $self->{home_dir} found for $USER";
403 1         217 exit 1;
404             }
405 0 0       0 unless ( -o $self->{home_dir} ){
406 0         0 logWarning "No $self->{home_dir} is not owned by $USER";
407 0         0 exit 1;
408             }
409              
410 0 0       0 if (defined $arg{on_file_sent}){
411 0         0 $self->{on_file_sent} = $arg{on_file_sent};
412             }
413 0 0       0 if (defined $arg{on_file_received}){
414 0         0 $self->{on_file_received} = $arg{on_file_received};
415             }
416 0 0       0 if (defined $arg{move_on_sent}){
417 0         0 $self->{move_on_sent} = $arg{move_on_sent};
418             }
419 0 0       0 if (defined $arg{move_on_received}){
420 0         0 $self->{move_on_received} = $arg{move_on_received};
421             }
422              
423 0 0 0     0 $self->{use_tmp_upload} = (defined $arg{use_tmp_upload} and $arg{use_tmp_upload}) ? 1 : 0;
424              
425 0 0       0 $self->{max_file_size} = (defined $arg{max_file_size}) ? $arg{max_file_size} : 0;
426              
427 0 0 0     0 $self->{valid_filename_char} = (defined $arg{valid_filename_char} and ref $arg{valid_filename_char} eq 'ARRAY') ? quotemeta join ('', @{$arg{valid_filename_char}}) : '';
  0         0  
428              
429              
430 0 0 0     0 if ( (defined $arg{deny} and $arg{deny} == ALL) or
      0        
      0        
      0        
431             (defined $arg{allow} and $arg{allow} != ALL and not defined $arg{deny})
432             ){
433 0         0 $self->{deny} = { map { $_ => 1 } @{ACTIONS()} };
  0         0  
  0         0  
434             }
435              
436 0 0       0 $arg{deny} = (not defined $arg{deny}) ? [] :
    0          
437             (ref $arg{deny} eq 'ARRAY') ? $arg{deny} : [ $arg{deny} ];
438 0 0       0 $arg{allow} = (not defined $arg{allow}) ? [] :
    0          
439             (ref $arg{allow} eq 'ARRAY') ? $arg{allow} : [ $arg{allow} ];
440              
441 0         0 for my $deny (@{$arg{deny}}){
  0         0  
442 0         0 $self->{deny}{$deny} = 1;
443             }
444 0         0 for my $allow (@{$arg{allow}}){
  0         0  
445 0         0 $self->{deny}{$allow} = 0;
446             }
447              
448             # These have not been implemented yet
449 0         0 $self->{deny}{SSH2_FXP_SETSTAT()} = 1;
450 0         0 $self->{deny}{SSH2_FXP_FSETSTAT()} = 1;
451 0         0 $self->{deny}{SSH2_FXP_SYMLINK()} = 1;
452 0         0 $self->{deny}{SSH2_FXP_READLINK()} = 1;
453              
454 0         0 $self->{no_symlinks} = $self->{deny}{NET_SFTP_SYMLINKS()};
455 0 0       0 if ($self->{no_symlinks}){
456             # if denying symlinks then must deny these
457 0         0 $self->{deny}{SSH2_FXP_SYMLINK()} = 1;
458 0         0 $self->{deny}{SSH2_FXP_READLINK()} = 1;
459             }
460              
461 0 0       0 $arg{fake_ok} = (not defined $arg{fake_ok}) ? [] :
    0          
462             (ref $arg{fake_ok} eq 'ARRAY') ? $arg{fake_ok} : [ $arg{fake_ok} ];
463 0         0 $self->{fake_ok} = { map {$_ => 1} @{$arg{fake_ok}} };
  0         0  
  0         0  
464              
465 0         0 $self->{handles} = {};
466 0         0 $self->{handle_count} = 0;
467 0         0 $self->{open_handle_count} = 0;
468              
469             # Logging levels
470              
471 0         0 $self->{log_action} = { map { $_ => 1 } @{ $arg{log_action} } };
  0         0  
  0         0  
472 0         0 $self->{log_action_supress} = { map { $_ => 1 }
  0         0  
473 0         0 grep { not defined $self->{log_action}{$_} }
474 0         0 @{ $arg{log_action_supress} },
475             ( SSH2_FXP_READ,
476             SSH2_FXP_READDIR,
477             SSH2_FXP_WRITE,
478             SSH2_FXP_CLOSE,
479             SSH2_FXP_OPENDIR,
480             SSH2_FXP_STAT,
481             SSH2_FXP_FSTAT,
482             SSH2_FXP_LSTAT,
483             SSH2_FXP_REALPATH,
484             ) };
485              
486 0 0       0 $self->{log_all_status} = defined $arg{log_all_status} ? $arg{log_all_status} : 0;
487              
488 0         0 return $self;
489             }
490             #-------------------------------------------------------------------------------
491             sub run {
492 0     0 0 0 my $self = shift;
493 0         0 while (1) {
494             #/* copy stdin to iqueue */
495             # Read 4 byte length of message
496             # read length = payload
497 0         0 my $packet_length = unpack("N", $self->readData(4));
498 0 0       0 if ($packet_length > MAX_PACKET_SIZE){
499 0         0 logError "Packet length of $packet_length received - exiting";
500 0         0 exit 1;
501             }
502              
503 0         0 my $req;
504 0         0 eval {
505 0     0   0 local $SIG{ALRM} = sub { die "alarm\n" }; # NB: \n required
  0         0  
506 0         0 alarm TIMEOUT;
507 0         0 $req = $self->readData( $packet_length );
508 0         0 alarm 0;
509             };
510 0 0       0 if ($@) {
511 0         0 logError "Connection timed out trying to read $packet_length bytes";
512 0         0 exit 1;
513             }
514              
515 0         0 my $payload = Net::SFTP::SftpServer::Buffer->new( data => $req );
516 0         0 $self->{_payload} = $payload; # Keep a copy on self for debug output
517             #/* process requests from client */
518             # note - all send data will be called from the handler for this message type
519 0         0 $self->process($payload);
520             }
521             }
522             #-------------------------------------------------------------------------------
523             sub readData {
524 0     0 0 0 my $self = shift;
525 0         0 my $len = shift;
526 0         0 my $req = '';
527             #logDetail "Going to read $len bytes";
528 0         0 while (length $req < $len){
529 0         0 my $buf;
530 0         0 my $read_len = sysread( STDIN, $buf, $len - length $req );
531 0 0       0 if ($read_len == 0) {
    0          
532 0         0 logGeneral("Client disconnected");
533 0         0 $self->closeHandlesOnExit();
534 0         0 exit 0;
535             }
536             elsif ($read_len < 0) {
537 0         0 logWarning("read error");
538 0         0 $self->closeHandlesOnExit();
539 0         0 exit 1;
540             }
541             else {
542 0         0 $req .= $buf;
543             }
544             }
545 0         0 return $req;
546             }
547             #-------------------------------------------------------------------------------
548             sub closeHandlesOnExit {
549 0     0 0 0 my $self = shift;
550 0         0 for my $fd (values %{$self->{handles}}){
  0         0  
551 0         0 $fd->close();
552 0         0 logWarning "Handle for " . $fd->getFilename() . " still open on client exit";
553             }
554             }
555             #-------------------------------------------------------------------------------
556             sub sendMessage {
557 0     0 0 0 my $self = shift;
558 0         0 my $msg = shift;
559             #/* copy stdin to iqueue */
560             # calc 4 byte length of message
561             # put on front of message
562             # send to STDOUT
563 0         0 my $l = length $msg;
564             #logDetail "Going to send $l bytes";
565 0         0 my $len = pack('N', $l);
566 0         0 my $write_len = syswrite( STDOUT, $len . $msg );
567 0 0       0 if ($write_len < 0){
568 0         0 logWarning "Write Error $!";
569 0         0 $self->closeHandlesOnExit();
570 0         0 exit 1;
571             }
572             }
573             #-------------------------------------------------------------------------------
574             sub getHandle {
575 0     0 0 0 my $self = shift;
576 0         0 my $payload = shift;
577 0   0     0 my $type = shift || '';
578 0         0 my $req = $payload->getPayloadContent();
579 0         0 my $handle_no = $req->{handle};
580 0 0 0     0 if (defined $self->{handles}{$handle_no} and ($type eq '' or $type eq $self->{handles}{$handle_no}->getType())){
      0        
581 0         0 my $handle = $self->{handles}{$handle_no};
582 0         0 $payload->setFilename( $handle->getFilename() );
583 0         0 $payload->setFileType( $handle->getType() );
584 0         0 return $handle;
585             }
586 0         0 return;
587             }
588             #-------------------------------------------------------------------------------
589             sub deleteHandle {
590 0     0 0 0 my $self = shift;
591 0         0 my $handle_no = shift;
592              
593 0 0       0 if (defined $self->{handles}{$handle_no}){
594 0         0 $self->{open_handle_count}--;
595 0         0 delete $self->{handles}{$handle_no};
596             }
597             }
598             #-------------------------------------------------------------------------------
599             sub addHandle {
600 0     0 0 0 my $self = shift;
601 0         0 my $new_handle = shift;
602 0         0 $self->{handle_count}++;
603 0         0 $self->{open_handle_count}++;
604 0 0       0 if ($self->{open_handle_count} > MAX_OPEN_HANDLES){
605 0         0 logWarning "Exceeding max handle count";
606 0         0 return;
607             }
608 0         0 $self->{handles}{$self->{handle_count}} = $new_handle;
609 0         0 return $self->{handle_count};
610             }
611             #-------------------------------------------------------------------------------
612             sub process {
613 0     0 0 0 my $self = shift;
614 0         0 my $payload = shift;
615              
616 0         0 my $req = $payload->getPayloadContent(
617             message_type => 'char',
618             );
619              
620 0         0 my $response = Net::SFTP::SftpServer::Response->new();
621              
622 0 0       0 if ($req->{message_type} != SSH2_FXP_INIT){
623             # Init does not have an id - it has a client version - handled in processInit
624 0         0 $req = $payload->getPayloadContent(
625             id => 'int',
626             );
627 0         0 $response->setId( $req->{id} )
628             }
629              
630 0         0 logDetail "Got message_type " . MESSAGE_TYPES->{$req->{message_type}};
631              
632 0 0       0 if (defined MESSAGE_HANDLER->{$req->{message_type}}){
633 0         0 my $method = MESSAGE_HANDLER->{$req->{message_type}};
634 0         0 $self->$method($payload, $response);
635             }
636             else {
637 0         0 logWarning("Unknown message $req->{message_type}");
638 0         0 $response->setStatus( SSH2_FX_BAD_MESSAGE );
639             }
640 0 0       0 logWarning "Data left in buffer" unless $payload->done(); # check buffer is empty or warn
641              
642 0         0 $self->sendResponse( $response );
643             }
644             #-------------------------------------------------------------------------------
645             sub sendResponse {
646 0     0 0 0 my $self = shift;
647 0         0 my $response = shift;
648              
649 0         0 $self->logStatus( $response );
650              
651 0         0 my $msg;
652 0         0 my $type = $response->getType();
653              
654 0 0       0 if ($type == SSH2_FXP_STATUS){
    0          
    0          
    0          
    0          
    0          
655 0         0 my $status = $response->getStatus();
656 0   0     0 $msg = pack('CNN', SSH2_FXP_STATUS, $response->getId() || 0, $status);
657 0 0       0 if ($self->{client_version} >= 3){
658 0         0 $msg .= pack('N', length STATUS_MESSAGE->[$status]) . STATUS_MESSAGE->[$status] . pack('N', 0);
659             }
660             }
661             elsif ($type == SSH2_FXP_HANDLE){
662 0         0 my $handle = $response->getHandle();
663 0         0 $msg = pack('CNN', SSH2_FXP_HANDLE, $response->getId(), length $handle) . $handle;
664             }
665             elsif ($type == SSH2_FXP_DATA){
666 0         0 $msg = pack('CNN', SSH2_FXP_DATA, $response->getId(), $response->getDataLength() ) . $response->getData();
667             }
668             elsif ($type == SSH2_FXP_VERSION){
669 0         0 $msg = pack('CN', SSH2_FXP_VERSION, $response->getVersion());
670             }
671             elsif ($type == SSH2_FXP_ATTRS){
672 0         0 $msg = pack('CN', SSH2_FXP_ATTRS, $response->getId() ) . $self->encodeAttrib( $response->getAttrs() );
673             }
674             elsif ($type == SSH2_FXP_NAME){
675 0         0 my $files = $response->getNames();
676 0         0 $msg = pack('CNN', SSH2_FXP_NAME, $response->getId(), scalar @$files );
677 0         0 for my $file (@$files) {
678 0         0 $msg .= pack('N', length $file->{name}) . $file->{name};
679 0         0 $msg .= pack('N', length $file->{long_name}) . $file->{long_name};
680 0         0 $msg .= $self->encodeAttrib($file->{attrib});
681             }
682             }
683             else {
684 0         0 logError "Unhandled response type: $type";
685             # Make sure we send something back
686 0   0     0 $msg = pack('CNN', SSH2_FXP_STATUS, $response->getId() || 0, SSH2_FX_BAD_MESSAGE );
687 0 0       0 if ($self->{client_version} >= 3){
688 0         0 $msg .= pack('N', length STATUS_MESSAGE->[SSH2_FX_BAD_MESSAGE]) . STATUS_MESSAGE->[SSH2_FX_BAD_MESSAGE] . pack('N', 0);
689             }
690             }
691 0         0 $self->sendMessage( $msg );
692             }
693             #-------------------------------------------------------------------------------
694             sub processInit {
695 0     0 0 0 my $self = shift;
696 0         0 my $payload = shift;
697 0         0 my $response = shift;
698              
699 0         0 my $req = $payload->getPayloadContent( client_version => 'int' );
700 0         0 $self->{client_version} = $req->{client_version};
701 0         0 logGeneral sprintf("Connection accepted, client version: %d", $self->{client_version});
702              
703 0         0 $response->setInitVersion( SSH2_FILEXFER_VERSION );
704             }
705             #-------------------------------------------------------------------------------
706             sub processOpen {
707 0     0 0 0 my $self = shift;
708 0         0 my $payload = shift;
709 0         0 my $response = shift;
710              
711 0         0 my $req = $payload->getPayloadContent(
712             name => 'string',
713             pflags => 'int', #/* portable flags */
714             attr => 'attrib',
715             );
716              
717 0         0 my $flags = $self->flagsFromPortable($req->{pflags});
718 0 0       0 my $perm = defined $self->{file_perms} ? $self->{file_perms} :
    0          
719             ($req->{attr}{flags} & SSH2_FILEXFER_ATTR_PERMISSIONS) ? $req->{attr}{perm} : 0666;
720              
721 0         0 $self->logAction();
722              
723 0 0       0 return if $self->denyOperation(SSH2_FXP_OPEN, $response);
724              
725 0         0 my $filename = $self->makeSafeFileName($req->{name});
726              
727 0 0 0     0 if ((not defined $filename) or ($self->{no_symlinks} and $self->{FS}->IsSymlink( $filename ))){
      0        
728 0         0 $response->setStatus( SSH2_FX_NO_SUCH_FILE );
729 0         0 return;
730             }
731             # is this an upload
732             # We use a tmp file if:
733             # We have specified use tmp upload
734             # And we have asked to create the file
735             # And we are opening for writing
736             # And we have either said to truncate the file on opening, or the file does not exist or is empty
737 0 0 0     0 my $use_temp = ($self->{use_tmp_upload} and
738             $req->{pflags} & SSH2_FXF_CREAT and
739             $req->{pflags} & SSH2_FXF_WRITE and
740             ( $req->{pflags} & SSH2_FXF_TRUNC or $self->{FS}->ZeroSize( $filename ) ) ) ? 1 : 0;
741              
742 0         0 my $fd = Net::SFTP::SftpServer::File->new( $filename, $flags, $req->{perm}, $use_temp);
743 0 0       0 if (not defined $fd) {
744 0         0 $response->setStatus( $self->errnoToPortable($! + 0) );
745             } else {
746 0         0 my $handle = $self->addHandle($fd);
747 0 0       0 if (defined $handle){
748 0         0 $response->setHandle( $handle );
749 0         0 logDetail "Opened handle $handle for file $filename";
750             }
751             else {
752 0         0 $response->setStatus( SSH2_FX_FAILURE );
753             }
754             }
755             }
756             #-------------------------------------------------------------------------------
757             sub processClose {
758 0     0 0 0 my $self = shift;
759 0         0 my $payload = shift;
760 0         0 my $response = shift;
761              
762 0         0 my $req = $payload->getPayloadContent(
763             handle => 'string',
764             );
765              
766 0         0 $self->logAction();
767              
768 0         0 my $ret = -1;
769 0         0 my $status;
770 0         0 my $fd = $self->getHandle($payload);
771 0 0       0 if (defined $fd){
772 0         0 $ret = $fd->close();
773 0 0       0 $response->setStatus( $ret ? SSH2_FX_OK : $self->errnoToPortable($fd->err()) );
774 0 0       0 if( $fd->getType() eq 'file'){
775             #log file transmission stats
776 0         0 logGeneral $fd->getStats();
777 0 0 0     0 if (defined $self->{move_on_sent} and $fd->wasSent()){
    0 0        
778 0         0 $fd->moveToProcessed( %{$self->{move_on_sent}} );
  0         0  
779             }
780             elsif (defined $self->{move_on_received} and $fd->wasReceived()){
781 0         0 $fd->moveToProcessed( %{$self->{move_on_received}} );
  0         0  
782             }
783 0 0 0     0 if (defined $self->{on_file_sent} and $fd->wasSent()){
    0 0        
784 0         0 $fd->setCallback();
785 0         0 eval { $self->{on_file_sent}($fd) };
  0         0  
786 0 0       0 if ($@){
787 0         0 logError "on_file_sent Handler died with $@";
788             }
789             }
790             elsif (defined $self->{on_file_received} and $fd->wasReceived()){
791 0         0 $fd->setCallback();
792 0         0 eval { $self->{on_file_received}($fd) };
  0         0  
793 0 0       0 if ($@){
794 0         0 logError "on_file_received Handler died with $@";
795             }
796             }
797             }
798             }
799             else {
800 0         0 $response->setStatus( SSH2_FX_NO_SUCH_FILE );
801             }
802              
803 0         0 $self->deleteHandle($req->{handle});
804             }
805             #-------------------------------------------------------------------------------
806             sub processRead {
807 0     0 0 0 my $self = shift;
808 0         0 my $payload = shift;
809 0         0 my $response = shift;
810              
811 0         0 my $req = $payload->getPayloadContent(
812             handle => 'string',
813             off => 'int64',
814             len => 'int',
815             );
816              
817 0         0 $self->logAction();
818              
819 0 0       0 return if $self->denyOperation(SSH2_FXP_READ, $response);
820              
821 0         0 my $fd = $self->getHandle($payload, 'file');
822 0 0       0 if (defined $fd) {
823 0 0       0 if ($fd->sysseek($req->{off}, SEEK_SET) < 0) {
824 0         0 my $errno = $!+0;
825 0         0 logWarning "processRead: seek failed $!";
826 0         0 $response->setStatus( $self->errnoToPortable($errno) );
827             } else {
828 0         0 my $buf;
829 0         0 my $ret = $fd->sysread( $buf, $req->{len} );
830 0 0       0 if ($ret < 0) {
    0          
831 0         0 $response->setStatus( $self->errnoToPortable($!+0) );
832             }
833             elsif ($ret == 0) {
834 0         0 $response->setStatus( SSH2_FX_EOF );
835             } else {
836 0         0 $response->setData( $ret, $buf );
837 0 0       0 $fd->readBytes( $ret ) if $fd->getReadBytes() eq $req->{off}; #Only log sequential reads
838             }
839             }
840             }
841             else {
842 0         0 $response->setStatus( SSH2_FX_FAILURE );
843             }
844             }
845             #-------------------------------------------------------------------------------
846             sub processWrite {
847 0     0 0 0 my $self = shift;
848 0         0 my $payload = shift;
849 0         0 my $response = shift;
850              
851 0         0 my $req = $payload->getPayloadContent(
852             handle => 'string',
853             off => 'int64',
854             data => 'string',
855             );
856              
857 0         0 $self->logAction();
858              
859 0 0       0 return if $self->denyOperation(SSH2_FXP_WRITE, $response);
860              
861              
862 0         0 my $fd = $self->getHandle($payload, 'file');
863 0 0       0 if (defined $fd) {
864 0 0 0     0 if ($self->{max_file_size} and $req->{off} + length $req->{data} > $self->{max_file_size}){
    0 0        
865 0         0 logError "Attempt to write greater than Max file size, offset: $req->{off}, data length:" . length $req->{data} . " on file ". $fd->getFilename();
866 0         0 $response->setStatus( SSH2_FX_PERMISSION_DENIED );
867 0         0 return;
868             }
869             elsif ($self->{max_file_size} and $req->{off} + length $req->{data} > 0.75 * $self->{max_file_size}){
870 0         0 logWarning "Writing greater than 75% of Max file size, offset: $req->{off}, data length:" . length $req->{data} . " on file ". $fd->getFilename();
871             }
872 0 0       0 if ($fd->sysseek($req->{off}, SEEK_SET) < 0) {
873 0         0 my $errno = $!+0;
874 0         0 logWarning "processRead: seek failed $!";
875 0         0 $response->setStatus( $self->errnoToPortable($errno) );
876             } else {
877 0         0 my $len = length $req->{data};
878 0         0 my $ret = $fd->syswrite($req->{data}, $len);
879 0 0       0 if ($ret < 0) {
    0          
880 0         0 logWarning "process_write: write failed";
881 0         0 $response->setStatus( $self->errnoToPrtable($!+0) );
882             }
883             elsif ($ret == $len) {
884 0 0       0 $fd->wroteBytes( $ret ) if $fd->getWrittenBytes() eq $req->{off}; #Only log sequential writes;
885 0         0 $response->setStatus( SSH2_FX_OK );
886             } else {
887 0         0 logGeneral("nothing at all written");
888             }
889             }
890             }
891             }
892             #-------------------------------------------------------------------------------
893             sub processDoStat{
894 0     0 0 0 my $self = shift;
895 0         0 my $mode = shift;
896 0         0 my $payload = shift;
897 0         0 my $response = shift;
898              
899              
900 0         0 my $req = $payload->getPayloadContent(
901             name => 'string',
902             );
903              
904 0         0 my $filename = $self->makeSafeFileName($req->{name});
905              
906 0         0 $self->logAction();
907 0 0       0 return if $self->denyOperation(($mode ? SSH2_FXP_LSTAT : SSH2_FXP_STAT), $response);
    0          
908              
909 0 0 0     0 if ((not defined $filename) or ($self->{no_symlinks} and $self->{FS}->IsSymlink( $filename ))){
      0        
910 0         0 $response->setStatus( SSH2_FX_NO_SUCH_FILE );
911 0         0 return;
912             }
913 0 0       0 my @st = $mode ? $self->{FS}->LStat($filename) : $self->{FS}->Stat($filename);
914 0 0       0 if (scalar @st == 0) {
915 0         0 $response->setStatus( $self->errnoToPortable($!+0) );
916             }
917             else {
918 0         0 $response->setAttrs( $self->statToAttrib(@st) );
919             }
920             }
921             #-------------------------------------------------------------------------------
922             sub processStat {
923 0     0 0 0 my $self = shift;
924 0         0 my $payload = shift;
925 0         0 my $response = shift;
926 0         0 $self->processDoStat(0, $payload, $response);
927             }
928             #-------------------------------------------------------------------------------
929             sub processLstat {
930 0     0 0 0 my $self = shift;
931 0         0 my $payload = shift;
932 0         0 my $response = shift;
933 0         0 $self->processDoStat(1, $payload, $response);
934             }
935             #-------------------------------------------------------------------------------
936             sub processFstat {
937 0     0 0 0 my $self = shift;
938 0         0 my $payload = shift;
939 0         0 my $response = shift;
940              
941 0         0 my $status = SSH2_FX_FAILURE;
942              
943 0         0 my $req = $payload->getPayloadContent(
944             handle => 'string',
945             );
946              
947 0         0 $self->logAction();
948              
949 0 0       0 return if $self->denyOperation(SSH2_FXP_FSTAT, $response);
950              
951 0         0 my $fd = $self->getHandle($payload);
952 0 0       0 if (defined $fd) {
953 0         0 my @st = stat($fd);
954 0 0       0 if (scalar @st == 0) {
955 0         0 $response->setStatus( $self->errnoToPortable($!+0) );
956             } else {
957 0         0 $response->setAttrs( $self->statToAttrib(@st) );
958             }
959             }
960             else {
961 0         0 $response->setStatus( SSH2_FX_FAILURE );
962             }
963             }
964             #-------------------------------------------------------------------------------
965             sub processSetstat {
966 0     0 0 0 my $self = shift;
967 0         0 my $payload = shift;
968 0         0 my $response = shift;
969              
970             #We choose not to allow any setting of stats
971              
972 0         0 my $req = $payload->getPayloadContent(
973             name => 'string',
974             attr => 'attrib',
975             );
976              
977 0         0 $self->logAction();
978              
979 0         0 my $filename = $self->makeSafeFileName($req->{name});
980              
981 0 0 0     0 if ((not defined $filename) or ($self->{no_symlinks} and $self->{FS}->IsSymlink( $filename ))){
      0        
982 0         0 $response->setStatus( SSH2_FX_NO_SUCH_FILE );
983 0         0 return;
984             }
985              
986 0 0       0 return if $self->denyOperation(SSH2_FXP_SETSTAT, $response);
987              
988 0         0 logError "processSetstat not implemented";
989             }
990             #-------------------------------------------------------------------------------
991             sub processFsetstat {
992 0     0 0 0 my $self = shift;
993 0         0 my $payload = shift;
994 0         0 my $response = shift;
995              
996             #We choose not to allow any setting of stats
997              
998 0         0 my $req = $payload->getPayloadContent(
999             handle => 'string',
1000             attr => 'attrib',
1001             );
1002              
1003 0         0 $self->logAction();
1004              
1005 0 0       0 return if $self->denyOperation(SSH2_FXP_FSETSTAT, $response);
1006              
1007 0         0 logError "processFsetstat not implemented";
1008             }
1009             #-------------------------------------------------------------------------------
1010             sub processOpendir {
1011 0     0 0 0 my $self = shift;
1012 0         0 my $payload = shift;
1013 0         0 my $response = shift;
1014              
1015 0         0 my $req = $payload->getPayloadContent(
1016             name => 'string',
1017             );
1018              
1019 0         0 $self->logAction();
1020              
1021 0         0 my $pathname = $self->makeSafeFileName($req->{name});
1022              
1023 0 0       0 return if $self->denyOperation(SSH2_FXP_OPENDIR, $response);
1024              
1025 0 0 0     0 if ((not defined $pathname) or ($self->{no_symlinks} and $self->{FS}->IsSymlink( $pathname ))){
      0        
1026 0         0 $response->setStatus( SSH2_FX_NO_SUCH_FILE );
1027 0         0 return;
1028             }
1029              
1030 0         0 my $dirp = Net::SFTP::SftpServer::Dir->new($pathname);
1031 0 0       0 if (!defined $dirp) {
1032 0         0 $response->setStatus( $self->errnoToPortable($!+0) );
1033             } else {
1034 0         0 my $handle = $self->addHandle($dirp);
1035 0 0       0 if (defined $handle){
1036 0         0 $response->setHandle( $handle );
1037             }
1038             else {
1039 0         0 $response->setStatus( SSH2_FX_FAILURE );
1040             }
1041             }
1042             }
1043             #-------------------------------------------------------------------------------
1044             sub processReaddir {
1045 0     0 0 0 my $self = shift;
1046 0         0 my $payload = shift;
1047 0         0 my $response = shift;
1048              
1049 0         0 my $req = $payload->getPayloadContent(
1050             handle => 'string',
1051             );
1052              
1053 0         0 $self->logAction();
1054              
1055 0 0       0 return if $self->denyOperation(SSH2_FXP_READDIR, $response);
1056              
1057 0         0 my $dirp = $self->getHandle($payload, 'dir');
1058 0 0       0 if (not defined $dirp) {
1059 0         0 $response->setStatus( SSH2_FX_FAILURE );
1060             }
1061             else {
1062 0         0 my $fullpath = $dirp->getPath();
1063 0         0 my $stats = [];
1064 0         0 my $count = 0;
1065 0         0 while (my $dp = $dirp->readdir()) {
1066 0         0 my $pathname = $fullpath . $dp;
1067 0 0 0     0 next if ( $self->{no_symlinks} and $self->{FS}->IsSymlink( $pathname ) ); # we only inform the user about files and directories
1068 0         0 my @st = $self->{FS}->LStat($pathname);
1069 0 0       0 next unless scalar @st;
1070 0         0 my $file = {};
1071 0         0 $file->{attrib} = $self->statToAttrib(@st);
1072 0         0 $file->{name} = $dp;
1073 0         0 $file->{long_name} = $self->lsFile($dp, \@st);
1074 0         0 $count++;
1075 0         0 push @{$stats}, $file;
  0         0  
1076             #/* send up to 100 entries in one message */
1077             #/* XXX check packet size instead */
1078 0 0       0 last if $count == 100;
1079             }
1080 0 0       0 if ($count > 0) {
1081 0         0 $response->setNames($stats);
1082             }
1083             else {
1084 0         0 $response->setStatus( SSH2_FX_EOF );
1085             }
1086             }
1087             }
1088             #-------------------------------------------------------------------------------
1089             sub processRemove {
1090 0     0 0 0 my $self = shift;
1091 0         0 my $payload = shift;
1092 0         0 my $response = shift;
1093              
1094 0         0 my $req = $payload->getPayloadContent(
1095             name => 'string',
1096             );
1097              
1098 0         0 $self->logAction();
1099              
1100 0         0 my $filename = $self->makeSafeFileName($req->{name});
1101              
1102 0         0 logDetail sprintf("processRemove: remove id %u name %s", $req->{id}, $req->{name});
1103              
1104 0 0       0 return if $self->denyOperation(SSH2_FXP_REMOVE, $response);
1105              
1106 0 0 0     0 if ((not defined $filename) or ($self->{no_symlinks} and $self->{FS}->IsSymlink( $filename ))){
      0        
1107 0         0 $response->setStatus( SSH2_FX_NO_SUCH_FILE );
1108 0         0 return;
1109             }
1110              
1111 0         0 my $ret = $self->{FS}->Unlink($filename);
1112 0 0       0 my $status = $ret ? SSH2_FX_OK : $self->errnoToPortable($!+0);
1113 0 0       0 if ( $status == SSH2_FX_OK ){
1114 0         0 logGeneral "Removed $filename";
1115             }
1116 0         0 $response->setStatus( $status );
1117             }
1118             #-------------------------------------------------------------------------------
1119             sub processMkdir {
1120 0     0 0 0 my $self = shift;
1121 0         0 my $payload = shift;
1122 0         0 my $response = shift;
1123              
1124              
1125 0         0 my $req = $payload->getPayloadContent(
1126             name => 'string',
1127             attr => 'attrib',
1128             );
1129              
1130 0         0 my $filename = $self->makeSafeFileName($req->{name});
1131              
1132 0 0       0 my $mode = defined $self->{dir_perms} ? $self->{dir_perms} :
    0          
1133             ($req->{attr}{flags} & SSH2_FILEXFER_ATTR_PERMISSIONS) ? $req->{attr}{perm} & 0777 : 0777;
1134              
1135 0         0 $self->logAction();
1136              
1137 0 0       0 return if $self->denyOperation(SSH2_FXP_MKDIR, $response);
1138              
1139 0 0       0 if (not defined $filename){
1140 0         0 $response->setStatus( SSH2_FX_NO_SUCH_FILE );
1141 0         0 return;
1142             }
1143              
1144 0         0 my $ret = $self->{FS}->Mkdir($filename, $mode);
1145 0 0       0 $response->setStatus( $ret ? SSH2_FX_OK : $self->errnoToPortable($!+0) );
1146             }
1147             #-------------------------------------------------------------------------------
1148             sub processRmdir {
1149 0     0 0 0 my $self = shift;
1150 0         0 my $payload = shift;
1151 0         0 my $response = shift;
1152              
1153 0         0 my $req = $payload->getPayloadContent(
1154             name => 'string',
1155             );
1156              
1157 0         0 my $filename = $self->makeSafeFileName($req->{name});
1158              
1159 0         0 $self->logAction();
1160              
1161 0 0       0 return if $self->denyOperation(SSH2_FXP_RMDIR, $response);
1162              
1163 0 0       0 if (not defined $filename){
1164 0         0 $response->setStatus( SSH2_FX_NO_SUCH_FILE );
1165 0         0 return;
1166             }
1167              
1168 0         0 my $ret = $self->{FS}->Rmdir($filename);
1169 0 0       0 $response->setStatus( $ret ? SSH2_FX_OK : $self->errnoToPortable($!+0) );
1170             }
1171             #-------------------------------------------------------------------------------
1172             sub processRealpath {
1173 0     0 0 0 my $self = shift;
1174 0         0 my $payload = shift;
1175 0         0 my $response = shift;
1176              
1177 0         0 my $req = $payload->getPayloadContent(
1178             name => 'string',
1179             );
1180              
1181 0         0 $self->logAction();
1182              
1183 0         0 my $path = $self->makeSafeFileName($req->{name});
1184              
1185 0         0 logDetail sprintf("processRealpath: realpath id %u path %s", $req->{id}, $req->{name});
1186              
1187 0         0 my $file = { name => $path, long_name => $path, attrib => { flags => 0 } };
1188              
1189 0         0 $response->setNames( $file );
1190             }
1191             #-------------------------------------------------------------------------------
1192             sub processRename {
1193 0     0 0 0 my $self = shift;
1194 0         0 my $payload = shift;
1195 0         0 my $response = shift;
1196              
1197 0         0 my $req = $payload->getPayloadContent(
1198             source_name => 'string',
1199             target_name => 'string',
1200             );
1201              
1202 0         0 my $oldpath = $self->makeSafeFileName($req->{source_name});
1203 0         0 my $newpath = $self->makeSafeFileName($req->{target_name});
1204              
1205 0         0 $self->logAction();
1206              
1207 0 0       0 return if $self->denyOperation(SSH2_FXP_RENAME, $response);
1208              
1209 0 0 0     0 if ((not defined $oldpath or not defined $newpath) or ($self->{no_symlinks} and $self->{FS}->IsSymlink( $oldpath ) )){
      0        
      0        
1210 0         0 $response->setStatus( SSH2_FX_NO_SUCH_FILE );
1211 0         0 return;
1212             }
1213              
1214 0 0 0     0 return if $self->{FS}->IsDir( $oldpath ) and $self->denyOperation(NET_SFTP_RENAME_DIR, $response);
1215              
1216 0 0       0 if ( $self->{FS}->IsFile( $oldpath )) {
    0          
1217             #/* Race-free rename of regular files */
1218 0 0       0 if (! $self->{FS}->Link( $oldpath, $newpath)) {#FIXME test all codepaths
    0          
1219             # link method failed - try just a rename
1220 0 0       0 if (! $self->{FS}->Rename($oldpath, $newpath)){
1221 0         0 $response->setStatus($self->errnoToPortable($!+0));
1222             }
1223             else {
1224 0         0 $response->setStatus(SSH2_FX_OK);
1225             }
1226             }
1227             elsif (! $self->{FS}->Unlink($oldpath)) {
1228 0         0 $response->setStatus( $self->errnoToPortable($!+0) );
1229             #/* clean spare link */
1230 0         0 $self->{FS}->Unlink($newpath);
1231             }
1232             else {
1233 0         0 $response->setStatus(SSH2_FX_OK);
1234             }
1235             }
1236             elsif ( $self->{FS}->IsDir( $oldpath ) ) {
1237 0 0       0 if (! $self->{FS}->Rename($oldpath, $newpath)){
1238 0         0 $response->setStatus($self->errnoToPortable($!+0));
1239             }
1240             else {
1241 0         0 $response->setStatus(SSH2_FX_OK);
1242             }
1243             }
1244             else {
1245             # File does not exist or is a symlink - deny all knowlege
1246 0         0 $response->setStatus(SSH2_FX_NO_SUCH_FILE);
1247             }
1248             }
1249             #-------------------------------------------------------------------------------
1250             sub processReadlink {
1251 0     0 0 0 my $self = shift;
1252 0         0 my $payload = shift;
1253 0         0 my $response = shift;
1254              
1255 0         0 my $req = $payload->getPayloadContent(
1256             name => 'string',
1257             );
1258              
1259 0         0 $self->logAction();
1260              
1261 0         0 $response->setStatus(SSH2_FX_NO_SUCH_FILE); # all symlinks hidden
1262             }
1263             #-------------------------------------------------------------------------------
1264             sub processSymlink {
1265 0     0 0 0 my $self = shift;
1266 0         0 my $payload = shift;
1267 0         0 my $response = shift;
1268              
1269 0         0 my $req = $payload->getPayloadContent(
1270             source_name => 'string',
1271             target_name => 'string',
1272             );
1273              
1274 0         0 my $oldpath = $self->makeSafeFileName($req->{source_name});
1275 0         0 my $newpath = $self->makeSafeFileName($req->{target_name});
1276              
1277 0         0 $self->logAction();
1278              
1279 0 0       0 return if $self->denyOperation(SSH2_FXP_SYMLINK, $response);
1280              
1281 0         0 logError "processSymlink not implemented";
1282             }
1283             #-------------------------------------------------------------------------------
1284             sub processExtended {
1285 0     0 0 0 my $self = shift;
1286 0         0 my $payload = shift;
1287 0         0 my $response = shift;
1288              
1289 0         0 my $req = $payload->getPayloadContent(
1290             request => 'string',
1291             );
1292              
1293 0         0 $self->logAction();
1294              
1295 0         0 $response->setStatus( SSH2_FX_OP_UNSUPPORTED ); #/* MUST */
1296             }
1297             #-------------------------------------------------------------------------------
1298             sub denyOperation {
1299 0     0 0 0 my $self = shift;
1300 0         0 my ($op, $response) = @_;
1301 0 0 0     0 if (defined $self->{deny}{$op} and $self->{deny}{$op}){
1302 0         0 logWarning "Denying request operation: " . MESSAGE_TYPES->{$op} . ", id: " . $response->getId();
1303 0 0 0     0 if (defined $self->{fake_ok}{$op} and $self->{fake_ok}{$op}){
1304 0         0 $response->setStatus( SSH2_FX_OK );
1305             }
1306             else {
1307 0         0 $response->setStatus( SSH2_FX_PERMISSION_DENIED );
1308             }
1309 0         0 return 1;
1310             }
1311 0         0 return;
1312             }
1313             #-------------------------------------------------------------------------------
1314             sub lsFile {
1315 0     0 0 0 my $self = shift;
1316 0         0 my $name = shift;
1317 0         0 my $st = shift;
1318 0         0 my @ltime = localtime($st->[9]);
1319 0         0 my $mode = format_mode($st->[2]);
1320              
1321 0         0 my $user = getpwuid($st->[4]);
1322 0         0 my $group = getgrgid($st->[5]);
1323 0         0 my $sz;
1324 0 0       0 if (scalar @ltime) {
1325 0 0       0 if (time() - $st->[9] < (365*24*60*60)/2){
1326 0         0 $sz = strftime "%b %e %H:%M", @ltime;
1327             }
1328             else {
1329 0         0 $sz = strftime "%b %e %Y", @ltime;
1330             }
1331             }
1332              
1333 0 0       0 my $ulen = length $user > 8 ? length $user : 8;
1334 0 0       0 my $glen = length $group > 8 ? length $group : 8;
1335 0         0 return sprintf("%s %3u %-*s %-*s %8llu %s %s", $mode, $st->[3], $ulen, $user, $glen, $group, $st->[7], $sz, $name);
1336             }
1337             #-------------------------------------------------------------------------------
1338             sub makeSafeFileName {
1339 0     0 0 0 my $self = shift;
1340             # We force all file names to be treated as from / which we treat as the users home directory
1341 0         0 my $name = shift;
1342              
1343 0         0 $name = "/$name";
1344 0         0 while ($name =~ s!/\./!/!g) {}
1345 0         0 $name =~ s!//+!/!g;
1346              
1347 0         0 my @path = split('/', $name);
1348 0         0 my @newpath;
1349 0         0 for my $d (@path){
1350 0 0       0 if ($d eq '..'){
    0          
1351 0         0 pop @newpath;
1352             }
1353             elsif ($d ne '.') {
1354 0 0       0 if ($self->{valid_filename_char}){
1355 0 0       0 if ($d !~ /^[$self->{valid_filename_char}]*$/){
1356 0         0 logError "Invalid characters in $name";
1357 0         0 return;
1358             }
1359             }
1360 0         0 push @newpath, $d;
1361             }
1362 0 0       0 if ($self->{no_symlinks}){
1363 0 0       0 if ( $self->{FS}->IsSymlink( join('/', @newpath) ) ){
1364 0         0 return; # no symlinks
1365             }
1366             }
1367             }
1368              
1369 0   0     0 $name = join('/', @newpath) || '/';
1370 0         0 $name =~ s!/\.$!/!;
1371 0         0 return $name;
1372             }
1373             #-------------------------------------------------------------------------------
1374             sub encodeAttrib {
1375 0     0 0 0 my $self = shift;
1376 0         0 my $attr = shift;
1377 0   0     0 $attr->{flags} ||= 0;
1378 0         0 my $msg = pack('N', $attr->{flags});
1379 0 0       0 if ($attr->{flags} & SSH2_FILEXFER_ATTR_SIZE){
1380 0         0 my $h = int($attr->{size} / (1 << 32));
1381 0         0 my $l = $attr->{size} % (1 << 32);
1382 0         0 $msg .= pack('NN', $h, $l );
1383             }
1384 0 0       0 if ($attr->{flags} & SSH2_FILEXFER_ATTR_UIDGID) {
1385 0         0 $msg .= pack('N', $attr->{uid});
1386 0         0 $msg .= pack('N', $attr->{gid});
1387             }
1388 0 0       0 if ($attr->{flags} & SSH2_FILEXFER_ATTR_PERMISSIONS){
1389 0         0 $msg .= pack('N', $attr->{perm});
1390             }
1391 0 0       0 if ($attr->{flags} & SSH2_FILEXFER_ATTR_ACMODTIME) {
1392 0         0 $msg .= pack('N', $attr->{atime});
1393 0         0 $msg .= pack('N', $attr->{mtime});
1394             }
1395 0         0 return $msg;
1396             }
1397             #-------------------------------------------------------------------------------
1398             sub statToAttrib {
1399 0     0 0 0 my $self = shift;
1400 0         0 my @stats = @_;
1401             #/* Convert from struct stat to filexfer attribs */
1402 0         0 my $attr = {};
1403 0         0 $attr->{flags} = 0;
1404 0         0 $attr->{flags} |= SSH2_FILEXFER_ATTR_SIZE;
1405 0         0 $attr->{size} = $stats[7];
1406 0         0 $attr->{flags} |= SSH2_FILEXFER_ATTR_UIDGID;
1407 0         0 $attr->{uid} = $stats[4];
1408 0         0 $attr->{gid} = $stats[5];
1409 0         0 $attr->{flags} |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1410 0         0 $attr->{perm} = $stats[2];
1411 0         0 $attr->{flags} |= SSH2_FILEXFER_ATTR_ACMODTIME;
1412 0         0 $attr->{atime} = $stats[8];
1413 0         0 $attr->{mtime} = $stats[9];
1414              
1415 0         0 return $attr;
1416             }
1417             #-------------------------------------------------------------------------------
1418             sub flagsFromPortable{
1419 0     0 0 0 my $self = shift;
1420 0         0 my $pflags = shift;
1421 0         0 my $flags = 0;
1422              
1423 0 0 0     0 if (($pflags & SSH2_FXF_READ) &&
    0          
    0          
1424             ($pflags & SSH2_FXF_WRITE)) {
1425 0         0 $flags = O_RDWR;
1426             }
1427             elsif ($pflags & SSH2_FXF_READ) {
1428 0         0 $flags = O_RDONLY;
1429             }
1430             elsif ($pflags & SSH2_FXF_WRITE) {
1431 0         0 $flags = O_WRONLY;
1432             }
1433 0 0       0 if ($pflags & SSH2_FXF_CREAT){
1434 0         0 $flags |= O_CREAT;
1435             }
1436 0 0       0 if ($pflags & SSH2_FXF_TRUNC){
1437 0         0 $flags |= O_TRUNC;
1438             }
1439 0 0       0 if ($pflags & SSH2_FXF_EXCL){
1440 0         0 $flags |= O_EXCL;
1441             }
1442 0         0 return $flags;
1443             }
1444             #-------------------------------------------------------------------------------
1445             sub errnoToPortable {
1446 0     0 0 0 my $self = shift;
1447 0         0 my $errno = shift;
1448              
1449 0 0 0     0 if ($errno == 0){
    0 0        
    0 0        
    0 0        
      0        
      0        
1450 0         0 logWarning "Good error code received by errnoToPortable";
1451 0         0 return SSH2_FX_OK;
1452             }
1453             elsif ( $errno == ENOENT or
1454             $errno == ENOTDIR or
1455             $errno == EBADF or
1456             $errno == ELOOP ){
1457 0         0 return SSH2_FX_NO_SUCH_FILE;
1458             }
1459             elsif ( $errno == EPERM or
1460             $errno == EACCES or
1461             $errno == EFAULT ){
1462 0         0 return SSH2_FX_PERMISSION_DENIED;
1463             }
1464             elsif ( $errno == ENAMETOOLONG or
1465             $errno == EINVAL){
1466 0         0 return SSH2_FX_BAD_MESSAGE;
1467             }
1468             else {
1469 0         0 return SSH2_FX_FAILURE;
1470             }
1471             }
1472             #-------------------------------------------------------------------------------
1473             #-------------------------------------------------------------------------------
1474             #-------------------------------------------------------------------------------
1475             #-------------------------------------------------------------------------------
1476             #-------------------------------------------------------------------------------
1477             package Net::SFTP::SftpServer::Buffer;
1478 1     1   8 use strict;
  1         2  
  1         37  
1479 1     1   4 use warnings;
  1         2  
  1         34  
1480              
1481             #/* attributes */
1482 1     1   5 use constant SSH2_FILEXFER_ATTR_SIZE => 0x00000001;
  1         1  
  1         64  
1483 1     1   18 use constant SSH2_FILEXFER_ATTR_UIDGID => 0x00000002;
  1         2  
  1         37  
1484 1     1   5 use constant SSH2_FILEXFER_ATTR_PERMISSIONS => 0x00000004;
  1         1  
  1         30  
1485 1     1   9 use constant SSH2_FILEXFER_ATTR_ACMODTIME => 0x00000008;
  1         1  
  1         35  
1486 1     1   4 use constant SSH2_FILEXFER_ATTR_EXTENDED => 0x80000000;
  1         1  
  1         1630  
1487              
1488             1;
1489             #-------------------------------------------------------------------------------
1490             sub new {
1491 0     0   0 my $class = shift;
1492 0         0 my $self = {};
1493 0         0 bless $self, $class;
1494 0         0 my %arg = @_;
1495 0         0 $self->{data} = $arg{data};
1496 0         0 return $self;
1497             }
1498             #-------------------------------------------------------------------------------
1499             sub asString {
1500 0     0   0 my $self = shift;
1501              
1502 0         0 my @strings;
1503 0         0 push @strings, length $self->{data} . " bytes left to decode";
1504 0         0 push @strings, "Decoded: ";
1505 0         0 for my $key ( sort keys %{$self->{_decoded_data}} ){
  0         0  
1506 0 0 0     0 if ($key eq 'data' and $self->{_decoded_data}{data} !~ /^[\s\w]*$/){
1507 0         0 push @strings, "$key\t\t=>";
1508             }
1509             else {
1510 0         0 push @strings, "$key\t\t=>$self->{_decoded_data}{$key}";
1511             }
1512             }
1513              
1514 0         0 return join("\n", @strings)
1515             }
1516             # ------------------------------------------------------------------------------
1517             sub getPayloadContent {
1518 0     0   0 my $self = shift;
1519              
1520 0   0     0 while ( my $name = shift and my $type = shift ){
1521 0 0       0 if ($type eq 'int'){
    0          
    0          
    0          
    0          
1522 0         0 $self->{_decoded_data}{$name} = $self->getInt();
1523             }
1524             elsif ($type eq 'int64'){
1525 0         0 $self->{_decoded_data}{$name} = $self->getInt64();
1526             }
1527             elsif ($type eq 'char'){
1528 0         0 $self->{_decoded_data}{$name} = $self->getChar();
1529             }
1530             elsif ($type eq 'string'){
1531 0         0 $self->{_decoded_data}{$name} = $self->getString();
1532             }
1533             elsif ($type eq 'attrib'){
1534 0         0 $self->{_decoded_data}{$name} = $self->getAttrib();
1535             }
1536             }
1537              
1538 0         0 return $self->{_decoded_data};
1539             }
1540             # ------------------------------------------------------------------------------
1541             sub getInt {
1542 0     0   0 my $self = shift;
1543 0         0 my $i = substr($self->{data}, 0, 4);
1544 0         0 $self->{data} = substr($self->{data}, 4);
1545 0         0 return unpack("N", $i);
1546             }
1547             # ------------------------------------------------------------------------------
1548             sub getInt64 {
1549 0     0   0 my $self = shift;
1550 0         0 my $i = substr($self->{data}, 0, 8);
1551 0         0 $self->{data} = substr($self->{data}, 8);
1552 0         0 my ($h, $l) = unpack("NN", $i);
1553 0         0 return ($h << 32) + $l;
1554             }
1555             # ------------------------------------------------------------------------------
1556             sub getChar {
1557 0     0   0 my $self = shift;
1558 0         0 my $c = substr($self->{data}, 0, 1);
1559 0         0 $self->{data} = substr($self->{data}, 1);
1560 0         0 return unpack("C", $c);
1561             }
1562             # ------------------------------------------------------------------------------
1563             sub getString {
1564 0     0   0 my $self = shift;
1565 0         0 my $len = $self->getInt();
1566 0         0 my $str = substr($self->{data}, 0, $len);
1567 0         0 $self->{data} = substr($self->{data}, $len);
1568 0         0 return $str;
1569             }
1570             #-------------------------------------------------------------------------------
1571             sub getAttrib {
1572 0     0   0 my $self = shift;
1573             #/* Decode attributes in buffer */
1574              
1575 0         0 my $attr = {};
1576              
1577 0         0 $attr->{flags} = $self->getInt();
1578 0 0       0 if ($attr->{flags} & SSH2_FILEXFER_ATTR_SIZE){
1579 0         0 $attr->{size} = $self->getInt64();
1580             }
1581 0 0       0 if ($attr->{flags} & SSH2_FILEXFER_ATTR_UIDGID) {
1582 0         0 $attr->{uid} = $self->getInt();
1583 0         0 $attr->{gid} = $self->getInt();
1584             }
1585 0 0       0 if ($attr->{flags} & SSH2_FILEXFER_ATTR_PERMISSIONS){
1586 0         0 $attr->{perm} = $self->getInt();
1587             }
1588 0 0       0 if ($attr->{flags} & SSH2_FILEXFER_ATTR_ACMODTIME) {
1589 0         0 $attr->{atime} = $self->getInt();
1590 0         0 $attr->{mtime} = $self->getInt();
1591             }
1592              
1593             #/* vendor-specific extensions */
1594 0 0       0 if ($attr->{flags} & SSH2_FILEXFER_ATTR_EXTENDED) {
1595 0         0 my $count = $self->getInt();
1596 0         0 for (my $i = 0; $i < $count; $i++) {
1597 0         0 my $type = $self->getString();
1598 0         0 my $req = $self->getString();
1599 0         0 logDetail("Got file attribute \"%s\"", $type);
1600             }
1601             }
1602 0         0 return $attr;
1603             }
1604             # ------------------------------------------------------------------------------
1605             sub done {
1606 0     0   0 my $self = shift;
1607 0 0       0 return 1 if length $self->{data} eq 0;
1608 0         0 return;
1609             }
1610             #-------------------------------------------------------------------------------
1611             sub setFileType {
1612 0     0   0 my $self = shift;
1613 0         0 $self->{file_type} = shift;
1614             }
1615             #-------------------------------------------------------------------------------
1616             sub getFileType {
1617 0     0   0 my $self = shift;
1618 0         0 return $self->{file_type};
1619             }
1620             #-------------------------------------------------------------------------------
1621             sub setFilename {
1622 0     0   0 my $self = shift;
1623 0         0 $self->{filename} = shift;
1624             }
1625             #-------------------------------------------------------------------------------
1626             sub getFilename {
1627 0     0   0 my $self = shift;
1628 0         0 return $self->{filename};
1629             }
1630             1;
1631             #-------------------------------------------------------------------------------
1632             #-------------------------------------------------------------------------------
1633             #-------------------------------------------------------------------------------
1634             #-------------------------------------------------------------------------------
1635             #-------------------------------------------------------------------------------
1636             #-------------------------------------------------------------------------------
1637             package Net::SFTP::SftpServer::Response;
1638 1     1   6 use strict;
  1         2  
  1         22  
1639 1     1   8 use warnings;
  1         7  
  1         23  
1640              
1641             #/* server to client */
1642 1     1   40 use constant SSH2_FXP_VERSION => 2;
  1         2  
  1         37  
1643 1     1   4 use constant SSH2_FXP_STATUS => 101;
  1         1  
  1         36  
1644 1     1   4 use constant SSH2_FXP_HANDLE => 102;
  1         1  
  1         31  
1645 1     1   3 use constant SSH2_FXP_DATA => 103;
  1         2  
  1         41  
1646 1     1   3 use constant SSH2_FXP_NAME => 104;
  1         1  
  1         29  
1647 1     1   4 use constant SSH2_FXP_ATTRS => 105;
  1         2  
  1         1423  
1648              
1649             1;
1650             #-------------------------------------------------------------------------------
1651             sub new {
1652 0     0   0 my $class = shift;
1653 0         0 my $self = {};
1654 0         0 bless $self, $class;
1655 0         0 return $self;
1656             }
1657             #-------------------------------------------------------------------------------
1658             sub asString {
1659 0     0   0 my $self = shift;
1660              
1661 0         0 my @strings;
1662 0         0 for my $key ( sort keys %$self ){
1663 0 0 0     0 if ($key eq 'data' and $self->{data} !~ /^[\s\w]*$/){
1664 0         0 push @strings, "$key\t\t=>";
1665             }
1666             else {
1667 0         0 push @strings, "$key\t\t=>$self->{$key}";
1668             }
1669             }
1670              
1671 0         0 return join("\n", @strings)
1672             }
1673             #-------------------------------------------------------------------------------
1674             sub setId {
1675 0     0   0 my $self = shift;
1676 0         0 $self->{id} = shift;
1677             }
1678             #-------------------------------------------------------------------------------
1679             sub getId {
1680 0     0   0 my $self = shift;
1681 0         0 return $self->{id};
1682             }
1683             #-------------------------------------------------------------------------------
1684             sub getType {
1685 0     0   0 my $self = shift;
1686 0         0 return $self->{type};
1687             }
1688             #-------------------------------------------------------------------------------
1689             sub setStatus {
1690 0     0   0 my $self = shift;
1691 0         0 $self->{status} = shift;
1692 0         0 $self->{type} = SSH2_FXP_STATUS;
1693             }
1694             #-------------------------------------------------------------------------------
1695             sub getStatus {
1696 0     0   0 my $self = shift;
1697 0         0 return $self->{status};
1698             }
1699             #-------------------------------------------------------------------------------
1700             sub setData {
1701 0     0   0 my $self = shift;
1702 0         0 $self->{data_length} = shift;
1703 0         0 $self->{data} = shift;
1704 0         0 $self->{type} = SSH2_FXP_DATA;
1705             }
1706             #-------------------------------------------------------------------------------
1707             sub getData {
1708 0     0   0 my $self = shift;
1709 0         0 return $self->{data};
1710             }
1711             #-------------------------------------------------------------------------------
1712             sub getDataLength {
1713 0     0   0 my $self = shift;
1714 0         0 return $self->{data_length};
1715             }
1716             #-------------------------------------------------------------------------------
1717             sub setHandle {
1718 0     0   0 my $self = shift;
1719 0         0 $self->{handle} = shift;
1720 0         0 $self->{type} = SSH2_FXP_HANDLE;
1721             }
1722             #-------------------------------------------------------------------------------
1723             sub getHandle {
1724 0     0   0 my $self = shift;
1725 0         0 return $self->{handle};
1726             }
1727             #-------------------------------------------------------------------------------
1728             sub setNames {
1729 0     0   0 my $self = shift;
1730 0         0 $self->{names} = shift;
1731 0 0       0 $self->{names} = [ $self->{names} ] unless ref $self->{names} eq 'ARRAY';
1732 0         0 $self->{type} = SSH2_FXP_NAME;
1733             }
1734             #-------------------------------------------------------------------------------
1735             sub getNames {
1736 0     0   0 my $self = shift;
1737 0         0 return $self->{names};
1738             }
1739             #-------------------------------------------------------------------------------
1740             sub setInitVersion {
1741 0     0   0 my $self = shift;
1742 0         0 $self->{version} = shift;
1743 0         0 $self->{type} = SSH2_FXP_VERSION;
1744             }
1745             #-------------------------------------------------------------------------------
1746             sub getVersion {
1747 0     0   0 my $self = shift;
1748 0         0 return $self->{version};
1749             }
1750             #-------------------------------------------------------------------------------
1751             sub setAttrs {
1752 0     0   0 my $self = shift;
1753 0         0 $self->{attr} = shift;
1754 0         0 $self->{type} = SSH2_FXP_ATTRS;
1755             }
1756             #-------------------------------------------------------------------------------
1757             sub getAttrs {
1758 0     0   0 my $self = shift;
1759 0         0 return $self->{attr};
1760             }
1761             1;
1762             #-------------------------------------------------------------------------------
1763             #-------------------------------------------------------------------------------
1764             #-------------------------------------------------------------------------------
1765             #-------------------------------------------------------------------------------
1766             #-------------------------------------------------------------------------------
1767             #-------------------------------------------------------------------------------
1768             package Net::SFTP::SftpServer::FS;
1769              
1770 1     1   5 no strict;
  1         6  
  1         27  
1771              
1772 1     1   4 use Exporter qw( import );
  1         7  
  1         43  
1773              
1774             @EXPORT = qw(
1775             setChrootDir
1776             );
1777              
1778 1     1   3 use strict;
  1         2  
  1         18  
1779 1     1   4 use warnings;
  1         2  
  1         566  
1780              
1781             {
1782             my %callback_of;
1783              
1784             my $chroot_dir = '';
1785              
1786             #-------------------------------------------------------------------------------
1787             sub new {
1788 1     1   1 my $class = shift;
1789 1         2 my $self = bless \do{my $anon}, $class;
  1         3  
1790 1 50       4 return unless $self->initialise( @_ ); # Dont keep the object unless we initialise sucessfully
1791              
1792 1         2 my $ident = scalar $self;
1793              
1794 1         6 $callback_of{$ident} = 0;
1795              
1796 1         3 return $self;
1797             }
1798             #-------------------------------------------------------------------------------
1799             sub initialise {
1800 1     1   4 return 1;
1801             }
1802             #-------------------------------------------------------------------------------
1803             sub setChrootDir {
1804 1     1   5 my $self = shift;
1805 1         2 $chroot_dir = shift;
1806             }
1807             #-------------------------------------------------------------------------------
1808             sub IsSymlink {
1809 0     0   0 my $self = shift;
1810 0         0 return -l $chroot_dir . shift;
1811             }
1812             #-------------------------------------------------------------------------------
1813             sub Exists {
1814 0     0   0 my $self = shift;
1815 0         0 return -e $chroot_dir . shift;
1816             }
1817             #-------------------------------------------------------------------------------
1818             sub IsFile {
1819 0     0   0 my $self = shift;
1820 0         0 return -f $chroot_dir . shift;
1821             }
1822             #-------------------------------------------------------------------------------
1823             sub IsDir {
1824 0     0   0 my $self = shift;
1825 0         0 return -d $chroot_dir . shift;
1826             }
1827             #-------------------------------------------------------------------------------
1828             sub ZeroSize {
1829 0     0   0 my $self = shift;
1830 0         0 return -z $chroot_dir . shift;
1831             }
1832             #-------------------------------------------------------------------------------
1833             sub Link {
1834 0     0   0 my $self = shift;
1835 0         0 return link( $chroot_dir . shift, $chroot_dir . shift);
1836             }
1837             #-------------------------------------------------------------------------------
1838             sub LStat {
1839 0     0   0 my $self = shift;
1840 0         0 return lstat $chroot_dir . shift;
1841             }
1842             #-------------------------------------------------------------------------------
1843             sub Stat {
1844 0     0   0 my $self = shift;
1845 0         0 return stat $chroot_dir . shift;
1846             }
1847             #-------------------------------------------------------------------------------
1848             sub Size {
1849 0     0   0 my $self = shift;
1850 0         0 return -s $chroot_dir . shift;
1851             }
1852             #-------------------------------------------------------------------------------
1853             sub Unlink {
1854 0     0   0 my $self = shift;
1855 0         0 return unlink $chroot_dir . shift;
1856             }
1857             #-------------------------------------------------------------------------------
1858             sub Mkdir {
1859 0     0   0 my $self = shift;
1860 0         0 return mkdir( $chroot_dir . shift, shift);
1861             }
1862             #-------------------------------------------------------------------------------
1863             sub Rmdir {
1864 0     0   0 my $self = shift;
1865 0         0 return rmdir $chroot_dir . shift;
1866             }
1867             #-------------------------------------------------------------------------------
1868             sub Rename {
1869 0     0   0 my $self = shift;
1870 0         0 my ($old, $new) = @_;
1871 0         0 return rename( $chroot_dir . $old, $chroot_dir . $new);
1872             }
1873             #-------------------------------------------------------------------------------
1874             sub chrootDir {
1875 0     0   0 my $self = shift;
1876 0         0 return $chroot_dir;
1877             }
1878             #-------------------------------------------------------------------------------
1879             sub setCallback {
1880 0     0   0 my $self = shift;
1881 0         0 my $ident = scalar($self);
1882 0         0 $callback_of{$ident} = 1;
1883             }
1884             #-------------------------------------------------------------------------------
1885             sub callback {
1886 0     0   0 my $self = shift;
1887 0         0 my $ident = scalar($self);
1888 0         0 return $callback_of{$ident};
1889             }
1890             #-------------------------------------------------------------------------------
1891             sub DESTROY {
1892 1     1   2 my $self = shift;
1893 1         2 my $ident = scalar($self);
1894 1         109 delete $callback_of{$ident};
1895             }
1896             }
1897             1;
1898             #-------------------------------------------------------------------------------
1899             #-------------------------------------------------------------------------------
1900             #-------------------------------------------------------------------------------
1901             #-------------------------------------------------------------------------------
1902             #-------------------------------------------------------------------------------
1903             package Net::SFTP::SftpServer::FileChrootBroken;
1904              
1905 1     1   4 use strict;
  1         1  
  1         20  
1906 1     1   4 use warnings;
  1         1  
  1         109  
1907              
1908             our $AUTOLOAD;
1909              
1910             sub AUTOLOAD {
1911 0     0     my $self = shift;
1912              
1913 0           my $method = $AUTOLOAD;
1914 0           $method =~ m/.+::(.+)(?!::)/;
1915 0 0         $method = $1 if $1;
1916              
1917 0           Net::SFTP::SftpServer::logError "$method is not supported after chroot is broken";
1918              
1919 0           return;
1920             }
1921             #-------------------------------------------------------------------------------
1922             #-------------------------------------------------------------------------------
1923             #-------------------------------------------------------------------------------
1924             #-------------------------------------------------------------------------------
1925             #-------------------------------------------------------------------------------
1926             package Net::SFTP::SftpServer::File;
1927 1     1   4 use strict;
  1         1  
  1         29  
1928 1     1   4 use warnings;
  1         1  
  1         18  
1929              
1930 1     1   855 use IO::File;
  1         9749  
  1         139  
1931 1     1   8 use File::Basename;
  1         2  
  1         62  
1932 1     1   4 use Fcntl qw( O_RDWR O_CREAT O_TRUNC O_EXCL O_RDONLY O_WRONLY SEEK_SET );
  1         9  
  1         96  
1933              
1934 1     1   6 use base qw( Net::SFTP::SftpServer::FS );
  1         3  
  1         2558  
1935              
1936             {
1937             my $TMP_EXT = ".SftpXFR.$$";
1938              
1939             my %fh_of;
1940             my %filename_of;
1941             my %mode_of;
1942             my %perm_of;
1943             my %write_of;
1944             my %read_of;
1945             my %opentime_of;
1946             my %use_temp_of;
1947             my %err_of;
1948             my %state_of;
1949              
1950             #-------------------------------------------------------------------------------
1951             sub initialise {
1952 0     0     my $self = shift;
1953              
1954 0           my ($filename, $mode, $perm, $use_tmp) = @_;
1955              
1956 0   0       $use_tmp ||= 0;
1957 0           my $realfile = $filename;
1958 0 0         if ($use_tmp){
1959 0           $filename .= $TMP_EXT;
1960             }
1961              
1962 0           my $fd = IO::File->new($self->chrootDir . $filename, $mode, $perm);
1963              
1964 0 0         return unless defined $fd;
1965              
1966 0           my $ident = scalar($self);
1967 0           $filename_of{$ident} = $realfile;
1968 0           $fh_of{$ident} = $fd;
1969 0           $mode_of{$ident} = $mode;
1970 0           $perm_of{$ident} = $perm;
1971 0           $write_of{$ident} = 0;
1972 0           $read_of{$ident} = 0;
1973 0           $opentime_of{$ident} = time();
1974 0           $use_temp_of{$ident} = $use_tmp;
1975 0           $state_of{$ident} = 'open';
1976              
1977 0           return 1;
1978             }
1979             #-------------------------------------------------------------------------------
1980             sub err {
1981 0     0     my $self = shift;
1982 0           my $ident = scalar($self);
1983              
1984 0           return $err_of{$ident};
1985             }
1986             #-------------------------------------------------------------------------------
1987             sub close {
1988 0     0     my $self = shift;
1989 0           my $ident = scalar($self);
1990 0           my $ret = $fh_of{$ident}->close();
1991 0 0         unless ($ret){
1992 0           $err_of{$ident} = $!+0;
1993             }
1994              
1995 0 0         if ($use_temp_of{$ident}){
1996 0           $self->Rename( $filename_of{$ident} . $TMP_EXT, $filename_of{$ident} );
1997 0           $use_temp_of{$ident} = 0;
1998             }
1999              
2000 0           $state_of{$ident} = 'closed';
2001 0           return $ret;
2002             }
2003             #-------------------------------------------------------------------------------
2004             sub getFilename {
2005 0     0     my $self = shift;
2006 0           my $ident = scalar($self);
2007 0           return $filename_of{$ident};
2008             }
2009             #-------------------------------------------------------------------------------
2010             sub getMode {
2011 0     0     my $self = shift;
2012 0           my $ident = scalar($self);
2013 0           return $mode_of{$ident};
2014             }
2015             #-------------------------------------------------------------------------------
2016             sub getPerm {
2017 0     0     my $self = shift;
2018 0           my $ident = scalar($self);
2019 0           return $perm_of{$ident};
2020             }
2021             #-------------------------------------------------------------------------------
2022             sub wroteBytes {
2023 0     0     my $self = shift;
2024 0           my $ident = scalar($self);
2025 0           my $size = shift;
2026 0           $write_of{$ident} += $size;
2027             }
2028             #-------------------------------------------------------------------------------
2029             sub readBytes {
2030 0     0     my $self = shift;
2031 0           my $ident = scalar($self);
2032 0           my $size = shift;
2033 0           $read_of{$ident} += $size;
2034             }
2035             #-------------------------------------------------------------------------------
2036             sub getWrittenBytes {
2037 0     0     my $self = shift;
2038 0           my $ident = scalar($self);
2039 0           return $write_of{$ident};
2040             }
2041             #-------------------------------------------------------------------------------
2042             sub getReadBytes {
2043 0     0     my $self = shift;
2044 0           my $ident = scalar($self);
2045 0           $read_of{$ident};
2046             }
2047             #-------------------------------------------------------------------------------
2048             sub getStats {
2049 0     0     my $self = shift;
2050 0           my $ident = scalar($self);
2051 0           my $stats = "Filename: $filename_of{$ident} ";
2052 0   0       my $dtime = (time() - $opentime_of{$ident}) || 1;
2053 0 0 0       if ($write_of{$ident} and $read_of{$ident}){
    0          
    0          
2054             ## reads and writes
2055 0           my $speed = int(($write_of{$ident} + $read_of{$ident}) / (1024 * $dtime));
2056 0           $stats .= "Received: $write_of{$ident} bytes Sent: $read_of{$ident} in $dtime seconds Speed: $speed K/s";
2057             }
2058             elsif ($write_of{$ident}){
2059             # File received
2060 0           my $speed = int($write_of{$ident} / (1024 * $dtime));
2061 0           $stats .= "Received: $write_of{$ident} bytes in $dtime seconds Speed: $speed K/s";
2062             }
2063             elsif ($read_of{$ident}){
2064             # File Sent
2065 0           my $speed = int($read_of{$ident} / (1024 * $dtime));
2066 0           $stats .= "Sent: $read_of{$ident} bytes in $dtime seconds Speed: $speed K/s";
2067             }
2068             else {
2069 0           $stats .= "No data sent or received";
2070             }
2071 0           return $stats;
2072             }
2073             #-------------------------------------------------------------------------------
2074             sub wasReceived {
2075 0     0     my $self = shift;
2076 0           my $ident = scalar($self);
2077 0 0 0       if ($write_of{$ident} and ! $read_of{$ident} and $self->Size( $filename_of{$ident} ) eq $write_of{$ident}){
      0        
2078 0           return 1;
2079             }
2080 0           return;
2081             }
2082             #-------------------------------------------------------------------------------
2083             sub wasSent {
2084 0     0     my $self = shift;
2085 0           my $ident = scalar($self);
2086 0 0 0       if ($read_of{$ident} and ! $write_of{$ident} and $self->Size( $filename_of{$ident} ) eq $read_of{$ident}){
      0        
2087 0           return 1;
2088             }
2089 0           return;
2090             }
2091             #-------------------------------------------------------------------------------
2092             sub getType {
2093 0     0     my $self = shift;
2094 0           return 'file';
2095             }
2096             #-------------------------------------------------------------------------------
2097             sub sysread {
2098 0     0     my $self = shift;
2099 0           my $ident = scalar($self);
2100 0           return $fh_of{$ident}->sysread( @_ );
2101             }
2102             #-------------------------------------------------------------------------------
2103             sub syswrite {
2104 0     0     my $self = shift;
2105 0           my $ident = scalar($self);
2106 0           return $fh_of{$ident}->syswrite( @_ );
2107             }
2108             #-------------------------------------------------------------------------------
2109             sub sysseek {
2110 0     0     my $self = shift;
2111 0           my $ident = scalar($self);
2112 0           return $fh_of{$ident}->sysseek( @_ );
2113             }
2114             #-------------------------------------------------------------------------------
2115             sub read {
2116 0     0     my $self = shift;
2117 0           my $ident = scalar($self);
2118 0 0         unless ( $self->callback ){
2119 0           Net::SFTP::SftpServer::logError "read method called outside from callback";
2120 0           return;
2121             }
2122              
2123 0 0         if ($state_of{$ident} ne 'open'){
2124 0           $fh_of{$ident}->open( $self->chrootDir . $filename_of{$ident}, '<' );
2125 0           $state_of{$ident} = 'open';
2126             }
2127 0           return $fh_of{$ident}->read( @_ );
2128             }
2129             #-------------------------------------------------------------------------------
2130             sub open {
2131 0     0     my $self = shift;
2132 0           my $ident = scalar($self);
2133 0 0         unless ( $self->callback ){
2134 0           Net::SFTP::SftpServer::logError "open method called outside from callback";
2135 0           return;
2136             }
2137              
2138 0 0         if ($state_of{$ident} ne 'open'){
2139 0           my $ret = $fh_of{$ident}->open( $self->chrootDir . $filename_of{$ident}, @_ );
2140 0           $state_of{$ident} = 'open';
2141 0           return $ret;
2142             }
2143             }
2144             #-------------------------------------------------------------------------------
2145             sub moveToProcessed {
2146 0     0     my $self = shift;
2147 0           my %arg = @_;
2148              
2149 0           my $ident = scalar $self;
2150              
2151 0 0         if ($arg{BREAKCHROOT}){
2152 0           return $self->moveToProcessedBREAKCHROOT( @_ );
2153             }
2154              
2155 0   0       $arg{dst} ||= 'processed';
2156 0   0       $arg{dir_perms} ||= 0770;
2157              
2158 0 0         unless ($self->Exists($filename_of{$ident})){
2159 0           Net::SFTP::SftpServer::logWarning "moveToProcessed: File $filename_of{$ident} does not exist";
2160 0           return;
2161             }
2162              
2163              
2164 0 0         if ($filename_of{$ident} =~ m!/$arg{dst}/!){
2165             # file is already in a processed directory
2166 0           return;
2167             }
2168              
2169 0 0         if ($arg{filename_condition}){
2170 0 0         return unless ($filename_of{$ident} =~ m/$arg{filename_condition}/ );
2171             }
2172              
2173 0           my $dir = dirname($filename_of{$ident});
2174 0 0         if (! $self->Exists( "$dir/processed" )){
    0          
2175 0 0         unless ($self->Mkdir( "$dir/processed", $arg{dir_perms} )){
2176 0           Net::SFTP::SftpServer::logWarning "moveToProcessed: failed to mkdir $dir/processed";
2177 0           return;
2178             }
2179             }
2180             elsif (! $self->IsDir( "$dir/processed") ){
2181 0           Net::SFTP::SftpServer::logWarning "moveToProcessed: $dir/processed exists but is not a directory";
2182 0           return;
2183             }
2184              
2185 0           my $name = fileparse($filename_of{$ident});
2186 0 0         if ( $self->Exists( "$dir/processed/$name" ) ){
2187 0           Net::SFTP::SftpServer::logWarning "moveToProcessed: cannot move $filename_of{$ident} - $dir/processed/$name already exists";
2188 0           return;
2189             }
2190              
2191 0 0         unless ($self->Rename( $filename_of{$ident}, "$dir/processed/$name" )){
2192 0           Net::SFTP::SftpServer::logWarning "moveToProcessed: failed to rename $filename_of{$ident} to $dir/processed/$name";
2193 0           return;
2194             }
2195              
2196 0           $filename_of{$ident} = "$dir/processed/$name";
2197              
2198 0           Net::SFTP::SftpServer::logGeneral "moveToProcessed: moved $filename_of{$ident} to $dir/processed/$name";
2199             }
2200             #-------------------------------------------------------------------------------
2201             sub moveToProcessedBREAKCHROOT {
2202 0     0     my $self = shift;
2203 0           my %arg = @_;
2204              
2205 0           my $ident = scalar $self;
2206              
2207 0 0 0       unless ( -d $arg{dst} and -w $arg{dst} ){
2208 0           Net::SFTP::SftpServer::logWarning "Cannot write to target directory $arg{dst}";
2209 0           return;
2210             }
2211              
2212 0 0         unless ($self->Exists($filename_of{$ident})){
2213 0           Net::SFTP::SftpServer::logWarning "moveToProcessed: File $filename_of{$ident} does not exist";
2214 0           return;
2215             }
2216              
2217 0 0         if ($arg{filename_condition}){
2218 0 0         return unless ($filename_of{$ident} =~ m/$arg{filename_condition}/ );
2219             }
2220              
2221 0           my $name = fileparse($filename_of{$ident});
2222              
2223 0           bless $self, 'Net::SFTP::SftpServer::FileChrootBroken';
2224              
2225 0           $self->renameBREADCHROOT( $arg{dst} . "/$name" );
2226              
2227 0           Net::SFTP::SftpServer::logGeneral "moveToProcessed: moved $filename_of{$ident} to $arg{dst}/$name";
2228             }
2229             #-------------------------------------------------------------------------------
2230             sub getFullFilenameBREAKCHROOT {
2231 0     0     my $self = shift;
2232 0           my $ident = scalar $self;
2233              
2234 0           my $chroot_dir = $self->chrootDir;
2235              
2236 0           bless $self, 'Net::SFTP::SftpServer::FileChrootBroken';
2237              
2238 0           return $chroot_dir . $filename_of{$ident}
2239             }
2240             #-------------------------------------------------------------------------------
2241             sub renameBREAKCHROOT {
2242 0     0     my $self = shift;
2243 0           my $ident = scalar $self;
2244              
2245 0           my $newname = shift;
2246              
2247 0           my $chroot_dir = $self->chrootDir;
2248              
2249 0           bless $self, 'Net::SFTP::SftpServer::FileChrootBroken';
2250              
2251 0           return rename $chroot_dir . $filename_of{$ident}, $newname;
2252             }
2253             #-------------------------------------------------------------------------------
2254             sub DESTROY {
2255 0     0     my $self = shift;
2256 0           my $ident = scalar($self);
2257              
2258 0 0 0       $fh_of{$ident}->close() if defined $fh_of{$ident} and $fh_of{$ident}->opened;
2259 0           delete $fh_of{$ident};
2260 0           delete $filename_of{$ident};
2261 0           delete $mode_of{$ident};
2262 0           delete $perm_of{$ident};
2263 0           delete $write_of{$ident};
2264 0           delete $read_of{$ident};
2265 0           delete $opentime_of{$ident};
2266 0           delete $use_temp_of{$ident};
2267 0           delete $err_of{$ident};
2268 0           delete $state_of{$ident};
2269              
2270 0           $self->SUPER::DESTROY()
2271             }
2272             }
2273             1;
2274             #-----------------------------------------------------------------------------
2275             #-----------------------------------------------------------------------------
2276             #------------------------------------------------------------------------------
2277             #------------------------------------------------------------------------------
2278             #------------------------------------------------------------------------------
2279             #------------------------------------------------------------------------------
2280             package Net::SFTP::SftpServer::Dir;
2281 1     1   5 use strict;
  1         2  
  1         36  
2282 1     1   5 use warnings;
  1         1  
  1         31  
2283              
2284 1     1   729 use IO::Dir;
  1         11471  
  1         53  
2285              
2286 1     1   8 use base qw( Net::SFTP::SftpServer::FS );
  1         2  
  1         850  
2287              
2288             {
2289             my %fd_of;
2290             my %path_of;
2291             my %dir_err_of;
2292             #-------------------------------------------------------------------------------
2293             sub initialise {
2294 0     0     my $self = shift;
2295              
2296 0           my ($path) = @_;
2297              
2298 0           my $fd = IO::Dir->new($self->chrootDir() . $path);
2299              
2300 0 0         return unless defined $fd;
2301              
2302 0           $path .= '/';
2303 0           $path =~ s!//$!/!; # make sure we have a trailing /
2304 0           my $ident = scalar($self);
2305 0           $path_of{$ident} = $path;
2306 0           $fd_of{$ident} = $fd;
2307              
2308 0           return 1;
2309             }
2310             #-------------------------------------------------------------------------------
2311             sub err {
2312 0     0     my $self = shift;
2313 0           my $ident = scalar($self);
2314              
2315 0           return $dir_err_of{$ident};
2316             }
2317             #-------------------------------------------------------------------------------
2318             sub close {
2319 0     0     my $self = shift;
2320 0           my $ident = scalar($self);
2321              
2322 0           my $ret = $fd_of{$ident}->close();
2323 0 0         unless ($ret){
2324 0           $dir_err_of{$ident} = $!+0;
2325             }
2326              
2327 0           return $ret;
2328             }
2329             #-------------------------------------------------------------------------------
2330             sub getFilename {
2331 0     0     my $self = shift;
2332 0           my $ident = scalar($self);
2333 0           return "$path_of{$ident}";
2334             }
2335             #-------------------------------------------------------------------------------
2336             sub getPath {
2337 0     0     my $self = shift;
2338 0           my $ident = scalar($self);
2339 0           return $path_of{$ident};
2340             }
2341             #-------------------------------------------------------------------------------
2342             sub getType {
2343 0     0     my $self = shift;
2344 0           return 'dir';
2345             }
2346             #-------------------------------------------------------------------------------
2347             sub readdir {
2348 0     0     my $self = shift;
2349 0           my $ident = scalar $self;
2350 0           return $fd_of{$ident}->read();
2351             }
2352             #-------------------------------------------------------------------------------
2353             sub DESTROY {
2354 0     0     my $self = shift;
2355 0           my $ident = scalar($self);
2356              
2357 0           delete $fd_of{$ident};
2358 0           delete $path_of{$ident};
2359 0           delete $dir_err_of{$ident};
2360              
2361 0           $self->SUPER::DESTROY()
2362             }
2363             }
2364             1;
2365             #-------------------------------------------------------------------------------
2366             __END__