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