0.07
[Net-SCP.git] / SCP.pm
1 package Net::SCP;
2
3 use strict;
4 use vars qw($VERSION @ISA @EXPORT_OK $scp $DEBUG);
5 use Exporter;
6 use Carp;
7 use File::Basename;
8 use String::ShellQuote;
9 use IO::Handle;
10 use Net::SSH qw(sshopen3);
11 use IPC::Open3;
12
13 @ISA = qw(Exporter);
14 @EXPORT_OK = qw( scp iscp );
15 $VERSION = '0.07';
16
17 $scp = "scp";
18
19 $DEBUG = 0;
20
21 =head1 NAME
22
23 Net::SCP - Perl extension for secure copy protocol
24
25 =head1 SYNOPSIS
26
27   #procedural interface
28   use Net::SCP qw(scp iscp);
29   scp($source, $destination);
30   iscp($source, $destination); #shows command, asks for confirmation, and
31                                #allows user to type a password on tty
32
33   #OO interface
34   $scp = Net::SCP->new( "hostname", "username" );
35   #with named params
36   $scp = Net::SCP->new( { "host"=>$hostname, "user"=>$username } );
37   $scp->get("filename") or die $scp->{errstr};
38   $scp->put("filename") or die $scp->{errstr};
39   #tmtowtdi
40   $scp = new Net::SCP;
41   $scp->scp($source, $destination);
42
43   #Net::FTP-style
44   $scp = Net::SCP->new("hostname");
45   $scp->login("user");
46   $scp->cwd("/dir");
47   $scp->size("file");
48   $scp->get("file");
49   $scp->quit;
50
51 =head1 DESCRIPTION
52
53 Simple wrappers around ssh and scp commands.
54
55 =head1 SUBROUTINES
56
57 =over 4
58
59 =item scp SOURCE, DESTINATION
60
61 Can be called either as a subroutine or a method; however, the subroutine
62 interface is depriciated.
63
64 Calls scp in batch mode, with the B<-B> B<-p> B<-q> and B<-r> options.
65 Returns false upon error, with a text error message accessable in
66 $scp->{errstr}.
67
68 Returns false and sets the B<errstr> attribute if there is an error.
69
70 =cut
71
72 sub scp {
73   my $self = ref($_[0]) ? shift : {};
74   my($src, $dest, $interact) = @_;
75   my $flags = '-p';
76   $flags .= 'r' unless &_islocal($src) && ! -d $src;
77   my @cmd;
78   if ( ( defined($interact) && $interact )
79        || ( defined($self->{interact}) && $self->{interact} ) ) {
80     @cmd = ( $scp, $flags, $src, $dest );
81     print join(' ', @cmd), "\n";
82     unless ( &_yesno ) {
83       $self->{errstr} = "User declined";
84       return 0;
85     }
86   } else {
87     $flags .= 'qB';
88     @cmd = ( $scp, $flags, $src, $dest );
89   }
90   my($reader, $writer, $error ) =
91     ( new IO::Handle, new IO::Handle, new IO::Handle );
92   $writer->autoflush(1);#  $error->autoflush(1);
93   my $pid = open3($writer, $reader, $error, @cmd );
94   waitpid $pid, 0;
95   if ( $? >> 8 ) {
96     my $errstr = join('', <$error>);
97     #chomp(my $errstr = <$error>);
98     $self->{errstr} = $errstr;
99     0;
100   } else {
101     1;
102   }
103 }
104
105 =item iscp SOURCE, DESTINATION
106
107 Can be called either as a subroutine or a method; however, the subroutine
108 interface is depriciated.
109
110 Prints the scp command to be execute, waits for the user to confirm, and
111 (optionally) executes scp, with the B<-p> and B<-r> flags.
112
113 Returns false and sets the B<errstr> attribute if there is an error.
114
115 =cut
116
117 sub iscp {
118   if ( ref($_[0]) ) {
119     my $self = shift;
120     $self->{'interact'} = 1;
121     $self->scp(@_);
122   } else {
123     scp(@_, 1);
124   }
125 }
126
127 sub _yesno {
128   print "Proceed [y/N]:";
129   my $x = scalar(<STDIN>);
130   $x =~ /^y/i;
131 }
132
133 sub _islocal {
134   shift !~ /^[^:]+:/
135 }
136
137 =back
138
139 =head1 METHODS
140
141 =over 4
142
143 =item new HOSTNAME [ USER ] | HASHREF
144
145 This is the constructor for a new Net::SCP object.  You must specify a
146 hostname, and may optionally provide a user.  Alternatively, you may pass a
147 hashref of named params, with the following keys:
148
149     host - hostname
150     user - username
151     interactive - bool
152     cwd - current working directory on remote server
153
154 =cut
155
156 sub new {
157   my $proto = shift;
158   my $class = ref($proto) || $proto;
159   my $self;
160   if ( ref($_[0]) ) {
161     $self = shift;
162   } else {
163     $self = {
164               'host'        => shift,
165               'user'        => ( scalar(@_) ? shift : '' ),
166               'interactive' => 0,
167               'cwd'         => '',
168             };
169   }
170   bless($self, $class);
171 }
172
173 =item login [USER]
174
175 Compatibility method.  Optionally sets the user.
176
177 =cut
178
179 sub login {
180   my($self, $user) = @_;
181   $self->{'user'} = $user if $user;
182 }
183
184 =item cwd CWD
185
186 Sets the cwd (used for a subsequent get or put request without a full pathname).
187
188 =cut
189
190 sub cwd {
191   my($self, $cwd) = @_;
192   $self->{'cwd'} = $cwd || '/';
193 }
194
195 =item get REMOTE_FILE [, LOCAL_FILE]
196
197 Uses scp to transfer REMOTE_FILE from the remote host.  If a local filename is
198 omitted, uses the basename of the remote file.
199
200 =cut
201
202 sub get {
203   my($self, $remote, $local) = @_;
204   $remote = $self->{'cwd'}. "/$remote" if $self->{'cwd'} && $remote !~ /^\//;
205   $local ||= basename($remote);
206   my $source = $self->{'host'}. ":$remote";
207   $source = $self->{'user'}. '@'. $source if $self->{'user'};
208   $self->scp($source,$local);
209 }
210
211 =item mkdir DIRECTORY
212
213 Makes a directory on the remote server.  Returns false and sets the B<errstr>
214 attribute on errors.
215
216 (Implementation note: An ssh connection is established to the remote machine
217 and '/bin/mkdir B<-p>' is used to create the directory.)
218
219 =cut
220
221 sub mkdir {
222   my($self, $directory) = @_;
223   $directory = $self->{'cwd'}. "/$directory"
224     if $self->{'cwd'} && $directory !~ /^\//;
225   my $host = $self->{'host'};
226   $host = $self->{'user'}. '@'. $host if $self->{'user'};
227   my($reader, $writer, $error ) =
228     ( new IO::Handle, new IO::Handle, new IO::Handle );
229   $writer->autoflush(1);
230   my $pid = sshopen3( $host, $writer, $reader, $error,
231                       '/bin/mkdir', '-p ', shell_quote($directory) );
232   waitpid $pid, 0;
233   if ( $? >> 8 ) {
234     chomp(my $errstr = <$error>);
235     $self->{errstr} = $errstr || "mkdir exited with status ". $?>>8;
236     return 0;
237   }
238   1;
239 }
240
241 =item size FILE
242
243 Returns the size in bytes for the given file as stored on the remote server.
244 Returns 0 on error, and sets the B<errstr> attribute.  In the case of an actual
245 zero-length file on the remote server, the special value '0e0' is returned,
246 which evaluates to zero when used as a number, but is true.
247
248 (Implementation note: An ssh connection is established to the remote machine
249 and wc is used to determine the file size.)
250
251 =cut
252
253 sub size {
254   my($self, $file) = @_;
255   $file = $self->{'cwd'}. "/$file" if $self->{'cwd'} && $file !~ /^\//;
256   my $host = $self->{'host'};
257   $host = $self->{'user'}. '@'. $host if $self->{'user'};
258   my($reader, $writer, $error ) =
259     ( new IO::Handle, new IO::Handle, new IO::Handle );
260   $writer->autoflush(1);
261   #sshopen2($host, $reader, $writer, 'wc', '-c ', shell_quote($file) );
262   my $pid =
263     sshopen3($host, $writer, $reader, $error, 'wc', '-c ', shell_quote($file) );
264   waitpid $pid, 0;
265   if ( $? >> 8 ) {
266     chomp(my $errstr = <$error>);
267     $self->{errstr} = $errstr || "wc exited with status ". $?>>8;
268     0;
269   } else {
270     chomp( my $size = <$reader> || 0 );
271     if ( $size =~ /^\s*(\d+)/ ) {
272       $1 ? $1 : '0e0';
273     } else {
274       $self->{errstr} = "unparsable output from remote wc: $size";
275       0;
276     }
277   }
278 }
279
280 =item put LOCAL_FILE [, REMOTE_FILE]
281
282 Uses scp to trasnfer LOCAL_FILE to the remote host.  If a remote filename is
283 omitted, uses the basename of the local file.
284
285 =cut
286
287 sub put {
288   my($self, $local, $remote) = @_;
289   $remote ||= basename($local);
290   $remote = $self->{'cwd'}. "/$remote" if $self->{'cwd'} && $remote !~ /^\//;
291   my $dest = $self->{'host'}. ":$remote";
292   $dest = $self->{'user'}. '@'. $dest if $self->{'user'};
293   warn "scp $local $dest\n" if $DEBUG;
294   $self->scp($local, $dest);
295 }
296
297 =item binary
298
299 Compatibility method: does nothing; returns true.
300
301 =cut
302
303 sub binary { 1; }
304
305 =back
306
307 =head1 FREQUENTLY ASKED QUESTIONS
308
309 Q: How do you supply a password to connect with ssh within a perl script
310 using the Net::SSH module?
311
312 A: You don't.  Use RSA or DSA keys.  See the ssh-keygen(1) manpage.
313
314 Q: My script is "leaking" ssh processes.
315
316 A: See L<perlfaq8/"How do I avoid zombies on a Unix system">, L<IPC::Open2>,
317 L<IPC::Open3> and L<perlfunc/waitpid>.
318
319 =head1 AUTHORS
320
321 Ivan Kohler <ivan-netscp_pod@420.am>
322
323 Major updates Anthony Deaver <bishop@projectmagnus.org>
324
325 Thanks to Jon Gunnip <jon@soundbite.com> for fixing a bug with size().
326
327 Patch for the mkdir method by Anthony Awtrey <tony@awtrey.com>
328
329 =head1 COPYRIGHT
330
331 Copyright (c) 2000 Ivan Kohler.
332 Copyright (c) 2000 Silicon Interactive Software Design.
333 Copyright (c) 2000 Freeside Internet Services, LLC
334 All rights reserved.
335 This program is free software; you can redistribute it and/or modify it under
336 the same terms as Perl itself.
337
338 =head1 BUGS
339
340 Still has no-OO cruft.
341
342 In order to work around some problems with commercial SSH2, if the source file
343 is on the local system, and is not a directory, the B<-r> flag is omitted.
344
345 It's probably better just to use SSH1 or OpenSSH <http://www.openssh.com/>
346
347 The Net::FTP-style OO stuff is kinda lame.  And incomplete.
348
349 =head1 SEE ALSO
350
351 scp(1), ssh(1)
352
353 =cut
354
355 1;
356
357