File Coverage

blib/lib/XAS/Lib/SSH/Client.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package XAS::Lib::SSH::Client;
2              
3             our $VERSION = '0.01';
4              
5 1     1   756 use IO::Select;
  1         1  
  1         39  
6 1     1   5 use Errno 'EAGAIN';
  1         1  
  1         37  
7 1     1   198 use Net::SSH2 ':all';
  0            
  0            
8              
9             use XAS::Class
10             debug => 0,
11             version => $VERSION,
12             base => 'XAS::Base',
13             mixin => 'XAS::Lib::Mixins::Bufops',
14             accessors => 'ssh chan sock select exit_code exit_signal',
15             mutators => 'attempts',
16             utils => ':validation dotid trim',
17             import => 'class',
18             vars => {
19             PARAMS => {
20             -port => { optional => 1, default => 22 },
21             -timeout => { optional => 1, default => 0.2 },
22             -username => { optional => 1, default => undef},
23             -host => { optional => 1, default => 'localhost' },
24             -eol => { optional => 1, default => "\015\012" },
25             -password => { optional => 1, default => undef, depends => [ 'username' ] },
26             -priv_key => { optional => 1, default => undef, depends => [ 'pub_key', 'username' ] },
27             -pub_key => { optional => 1, default => undef, depends => [ 'priv_key', 'username' ] },
28             },
29             ERRNO => 0,
30             ERRSTR => '',
31             }
32             ;
33              
34             #use Data::Dumper;
35              
36             # ----------------------------------------------------------------------
37             # Public Methods
38             # ----------------------------------------------------------------------
39              
40             sub connect {
41             my $self = shift;
42              
43             my ($errno, $name, $errstr);
44              
45             $self->class->var('ERRNO', 0);
46             $self->class->var('ERRSTR', '');
47              
48             if ($self->ssh->connect($self->host, $self->port)) {
49              
50             if ($self->pub_key) {
51              
52             unless ($self->ssh->auth_publickey($self->username,
53             $self->pub_key, $self->priv_key)) {
54              
55             ($errno, $name, $errstr) = $self->ssh->error();
56              
57             $self->class->var('ERRNO', $errno);
58             $self->class->var('ERRSTR', $errstr);
59              
60             $self->throw_msg(
61             dotid($self->class) . '.autherr',
62             'ssh_client_autherr',
63             $name, $errstr
64             );
65              
66             }
67              
68             } else {
69              
70             unless ($self->ssh->auth_password($self->username, $self->password)) {
71              
72             ($errno, $name, $errstr) = $self->ssh->error();
73              
74             $self->class->var('ERRNO', $errno);
75             $self->class->var('ERRSTR', $errstr);
76              
77             $self->throw_msg(
78             dotid($self->class) . '.autherr',
79             'ssh_client_autherr',
80             $name, $errstr
81             );
82              
83             }
84              
85             }
86              
87             $self->{'sock'} = $self->ssh->sock();
88             $self->{'chan'} = $self->ssh->channel();
89             $self->{'select'} = IO::Select->new($self->sock);
90              
91             $self->setup();
92              
93             } else {
94              
95             ($errno, $name, $errstr) = $self->ssh->error();
96              
97             $self->class->var('ERRNO', $errno);
98             $self->class->var('ERRSTR', $errstr);
99              
100             $self->throw_msg(
101             dotid($self->class) . '.conerr',
102             'ssh_client_conerr',
103             $name, $errstr
104             );
105              
106             }
107              
108             }
109              
110             sub setup {
111             my $self = shift;
112              
113             }
114              
115             sub pending {
116             my $self = shift;
117              
118             return length($self->{'buffer'});
119              
120             }
121              
122             sub disconnect {
123             my $self = shift;
124              
125             if (my $chan = $self->chan) {
126              
127             $chan->send_eof();
128             $chan->close();
129              
130             }
131              
132             if (my $ssh = $self->ssh) {
133              
134             $ssh->disconnect();
135              
136             }
137              
138             }
139              
140             sub get {
141             my $self = shift;
142             my ($length) = validate_params(\@_, [
143             { optional => 1, default => 512 }
144             ]);
145              
146             my $output = '';
147              
148             # extract $length from buffer. if the buffer size is > $length then
149             # try to refill buffer. If there is nothing to read, then return
150             # the remainder of the buffer.
151             #
152             # Patterned after some libssh2 examples and C network programming
153             # "best practices".
154              
155             if ($self->pending > $length) {
156              
157             $output = $self->buf_slurp(\$self->{'buffer'}, $length);
158              
159             } else {
160              
161             $self->_fill_buffer();
162              
163             my $l = ($self->pending > $length) ? $length : $self->pending;
164             $output = $self->buf_slurp(\$self->{'buffer'}, $l);
165              
166             }
167              
168             return $output;
169              
170             }
171              
172             sub gets {
173             my $self = shift;
174              
175             my $buffer;
176             my $output = '';
177              
178             while (my $buf = $self->get()) {
179              
180             $buffer .= $buf;
181              
182             if ($output = $self->buf_get_line(\$buffer, $self->eol)) {
183              
184             $self->{'buffer'} = $buffer . $self->{'buffer'};
185             last;
186              
187             }
188              
189             }
190              
191             return trim($output);
192              
193             }
194              
195             sub put {
196             my $self = shift;
197             my ($buffer) = validate_params(\@_, [1]);
198              
199             my $counter = 0;
200             my $working = 1;
201             my $written = 0;
202             my $bufsize = length($buffer);
203              
204             $self->class->var('ERRNO', 0);
205             $self->class->var('ERRSTR', '');
206              
207             # Setup non-blocking writes. Keep writting until nothing is left.
208             # Returns the number of bytes written, if any.
209             #
210             # Patterned after some libssh2 examples and C network programming
211             # "best practices".
212              
213             $self->chan->blocking(0);
214              
215             do {
216              
217             if (my $bytes = $self->chan->write($buffer)) {
218              
219             $written += $bytes;
220             $buffer = substr($buffer, $bytes);
221             $working = 0 if ($written >= $bufsize);
222              
223             } else {
224              
225             my ($errno, $name, $errstr) = $self->ssh->error();
226             if ($errno == LIBSSH2_ERROR_EAGAIN) {
227              
228             $counter++;
229              
230             $working = 0 if ($counter > $self->attempts);
231             $self->_waitsocket() if ($counter <= $self->attempts);
232              
233             } else {
234              
235             $self->chan->blocking(1);
236              
237             $self->class->var('ERRNO', $errno);
238             $self->class->var('ERRSTR', $errstr);
239              
240             $self->throw_msg(
241             dotid($self->class) . '.protoerr',
242             'ssh_client_protoerr',
243             $name, $errstr
244             );
245              
246             }
247              
248             }
249              
250             } while ($working);
251              
252             $self->chan->blocking(1);
253              
254             return $written;
255              
256             }
257              
258             sub puts {
259             my $self = shift;
260             my ($buffer) = validate_params(\@_, [1]);
261              
262             my $output = sprintf("%s%s", trim($buffer), $self->eol);
263             my $written = $self->put($output);
264              
265             return $written;
266              
267             }
268              
269             sub errno {
270             my $class = shift;
271             my ($value) = validate_params(\@_, [
272             { optional => 1, default => undef }
273             ]);
274              
275             class->var('ERRNO', $value) if (defined($value));
276              
277             return class->var('ERRNO');
278              
279             }
280              
281             sub errstr {
282             my $class = shift;
283             my ($value) = validate_params(\@_, [
284             { optional => 1, default => undef }
285             ]);
286              
287             class->var('ERRSTR', $value) if (defined($value));
288              
289             return class->var('ERRSTR');
290              
291             }
292              
293             sub DESTROY {
294             my $self = shift;
295              
296             $self->disconnect();
297              
298             }
299              
300             # ----------------------------------------------------------------------
301             # Private Methods
302             # ----------------------------------------------------------------------
303              
304             sub init {
305             my $class = shift;
306              
307             my $self = $class->SUPER::init(@_);
308              
309             $self->{'ssh'} = Net::SSH2->new();
310             $self->{'buffer'} = '';
311              
312             $self->attempts(5); # number of EAGAIN attempts
313              
314             return $self;
315              
316             }
317              
318             sub _fill_buffer {
319             my $self = shift;
320              
321             my $read = 0;
322             my $counter = 0;
323             my $working = 1;
324              
325             # Setup non-blocking read. Keep reading until nothing is left.
326             # i.e. the reads timeout.
327              
328             $self->chan->blocking(0);
329              
330             while ($working) {
331              
332             my $buf;
333              
334             if (my $bytes = $self->chan->read($buf, 512)) {
335              
336             $self->{'buffer'} .= $buf;
337             $read += $bytes;
338              
339             } else {
340              
341             my $syserr = $! + 0;
342             my ($errno, $name, $errstr) = $self->ssh->error();
343              
344             if (($errno == LIBSSH2_ERROR_EAGAIN) || ($syserr == EAGAIN)) {
345              
346             $counter++;
347            
348             $working = 0 if ($counter > $self->attempts);
349             $self->_waitsocket() if ($counter <= $self->attempts);
350              
351             } else {
352              
353             $self->chan->blocking(1);
354              
355             $self->class->var('ERRNO', $errno);
356             $self->class->var('ERRSTR', $errstr);
357              
358             $self->throw_msg(
359             dotid($self->class) . '.protoerr',
360             'ssh_client_protoerr',
361             $name, $errstr
362             );
363              
364             }
365              
366             }
367              
368             }
369              
370             $self->chan->blocking(1);
371              
372             return $read;
373              
374             }
375            
376             sub _waitsocket {
377             my $self = shift;
378              
379             my $to = $self->timeout;
380             my $dir = $self->ssh->block_directions();
381              
382             # If $dir is 1, then input is blocking.
383             # If $dir is 2, then output is blocking.
384             #
385             # Patterned after some libssh2 examples.
386              
387             if ($dir == 1) {
388              
389             $self->select->can_read($to);
390              
391             } else {
392              
393             $self->select->can_write($to);
394              
395             }
396              
397             return $! + 0;
398              
399             }
400              
401             1;
402              
403             __END__