File Coverage

blib/lib/Symantec/PCAnywhere/Profile.pm
Criterion Covered Total %
statement 72 100 72.0
branch 9 30 30.0
condition 4 20 20.0
subroutine 12 18 66.6
pod 10 10 100.0
total 107 178 60.1


line stmt bran cond sub pod time code
1             package Symantec::PCAnywhere::Profile;
2              
3 4     4   42752 use strict;
  4         11  
  4         131  
4 4     4   22 use warnings;
  4         8  
  4         207  
5              
6             =head1 NAME
7              
8             Symantec::PCAnywhere::Profile - Base class for pcAnywhere utility functions
9              
10             =head1 VERSION
11              
12             Version 0.06
13              
14             =cut
15              
16             our $VERSION = '0.06';
17              
18             =head1 SYNOPSIS
19              
20             This class should not be instantiated or used by itself. Use one of its
21             subclasses instead.
22              
23             =head1 DESCRIPTION
24              
25             Provides methods common to pcAnywhere utility functions. See L. Below
26             is an overview of the general decoding mechaism.
27              
28             =head2 FILE OBSCURING ALGORITHM
29              
30             The general idea of the file obscuring algorithm is that each byte is XOR'd
31             with the previous byte plus an incrementing eight-bit counter. For reasons
32             unknown to us, there seems to be some kind of shift in the algorithm starting
33             at byte 448, so we split up our decoding into a "first part" and a "second
34             part).
35              
36             for each byte
37             do
38             char = thisbyte (XOR) prevbyte (XOR) counter++
39             done
40              
41             =head2 FIELD BREAKDOWN
42              
43             The interesting fields appear to be in fixed positions: this was very helpful.
44              
45             String fields seem to be terminated with a NUL byte, and we have observed that
46             changing a long value to a short one leaves the tail end of the long field
47             inside the file. In some cases we do not ever care about the "old" value, but
48             since passwords and login names are disabled by NULing out the first byte, the
49             bytes that remain might be interesting. See L for further
50             discussion.
51              
52             We believe that some fields are slightly overloaded - we have seen overlap -
53             and they mainly revolve around the GATEWAY fields. We don't know how
54             pcAnywhere gateways work well enough to really know what to make of it.
55              
56             =head2 FIELD DECODING
57              
58             We define all the fields of interest in a hash to allow us to do a bit more
59             than just decode: perhaps a bit of reporting or double-checking for overlaps
60             and the like.
61              
62             Each entry has a name, which is used as the key to the user-returned hash, plus
63             the zero-based offset into the setring, the length, and a "type". The type is
64             one of:
65              
66             0 = string, strip everything after first NUL byte
67             1 = string, strip trailing NUL bytes
68             2 = binary
69             3 = little-endian 16-bit word
70              
71             The reason we allow for type #1 is to avoid stripping NUL bytes from a few
72             fields, such as passwords. If you enter a login name or password, but then
73             disable the "auto-login", pcAnywhere simply NULs out the first byte: this is
74             still useful information.
75              
76             The original code on which this module is based was used for penetration
77             testing, and so Type 1 was useful for recovering partly-obscured credentials.
78             However, using Type 1 hampers the more likely use of this module, so the
79             Hostname, Domain_Logname, and Password fields have been changed from Type 1 to
80             Type 0.
81              
82             Type 3 currently exists only for decoding port numbers.
83              
84             =cut
85              
86 4     4   19 use Carp;
  4         10  
  4         7478  
87              
88             #
89             # These lists hold coderefs for en-/de-coding.
90             #
91             # Pass in (data). Modified on stack and returned.
92             #
93             my @DECODE_SUB = (
94             # Type 0 -- strip ALL after NUL
95             sub { $_[0] =~ s/\0.*$// },
96             # Type 1 -- strip trailing NUL only
97             sub { $_[0] =~ s/\0+$// },
98             # Type 2 -- binary byte
99             sub { $_[0] = ord($_[0]) },
100             # Type 3 -- little-endian 16-bit word
101             sub { $_[0] = unpack("v", $_[0]) }
102             );
103              
104             #
105             # Pass in (data, len). Modified on stack and returned.
106             #
107             my @ENCODE_SUB = (
108             # Type 0 -- pad with NUL
109             sub { $_[0] = substr($_[0] . ("\0" x ($_[1] - length $_[0])), 0, $_[1]) },
110             # Type 1 -- equivalent to Type 0 for encoding
111             sub { $_[0] = substr($_[0] . ("\0" x ($_[1] - length $_[0])), 0, $_[1]) },
112             # Type 2 -- binary byte
113             sub { $_[0] = chr($_[0]) },
114             # Type 3 -- little-endian 16-bit word
115             sub { $_[0] = pack("v", $_[0]) }
116             );
117              
118             =head1 METHODS
119              
120             =head2 PUBLIC
121              
122             =over 4
123              
124             =item new
125              
126             The "new" constructor takes any number of arguments and sets the appropriate
127             flags internally before returning a new object. The object is implemented as a
128             blessed hash; if more than one argument is passed in, the arguments are
129             considered as a list of key-value pairs which are inserted into the object
130             data. Both "regular" and dash-style arguments are supported.
131              
132             =cut
133              
134             sub new {
135 5     5 1 13 my $type = shift;
136 5         59 my %defaults = (
137             encode_sub => \@ENCODE_SUB,
138             decode_sub => \@DECODE_SUB,
139             );
140              
141             # Support dash- and regular-style arguments, stripping dashes
142 5 100       14 my %args = map { substr($_, /^-/ ? 1 : 0) => {@_}->{$_} } keys %{{@_}};
  12         108  
  5         30  
143 5         50 my $self = bless { %defaults, %args }, $type;
144 5         22 return $self;
145             }
146              
147             =item load_from_file
148              
149             $chf->load_from_file($filename);
150              
151             Loads a file for processing, optionally taking a filename.
152              
153             =cut
154              
155             sub load_from_file ($;$) {
156 0     0 1 0 my $self = shift;
157 0 0 0     0 $self->{filename} ||= shift or croak "No filename to read from";
158              
159 0         0 local $/ = undef;
160 0 0       0 open F, "<", $self->{filename}
161             or croak "Failed to open '$self->{filename}' for reading";
162 0         0 binmode F;
163 0         0 $self->{data} = ;
164 0         0 close F;
165             }
166              
167             =item set_attrs
168              
169             $chf->set_attrs(
170             PhoneNumber => 5551234,
171             AreaCode => 800,
172             IPAddress => '172.0.0.11',
173             ControlPort => '4763'
174             );
175              
176             Sets the attributes of the file; pass in any number of key-value pairs.
177              
178             =cut
179              
180             sub set_attrs ($%) {
181 2     2 1 22 my $self = shift;
182 2         7 my %attrs = @_;
183              
184 2   50     24 $self->{attrs} ||= { };
185 2         12 while (my ($attr, $value) = each %attrs) {
186 6 50       43 $self->{attrs}{$attr} = $value if $self->{fields}{$attr};
187             }
188             }
189              
190             =item set_attr
191              
192             $chf->set_attr($attr => $value);
193              
194             This convenience method sets the value for only one attribute. Note that
195             set_attrs() can be called with exactly the same arguments as this method.
196              
197             =cut
198              
199 0     0 1 0 sub set_attr ($$$) { shift->set_attrs(shift, shift) }
200              
201             =item get_attrs
202              
203             my @query = qw(PhoneNumber AreaCode IPAddress ControlPort);
204             my $attr = $chf->get_attrs(@query);
205             my $attrs = $chf->get_attrs(@query);
206              
207             Pass in a list of items whose attributes you wish to retrieve. Returns a
208             reference to a hash whose keys are the values you passed in and whose values
209             are the attributes retrieved.
210              
211             =cut
212              
213             sub get_attrs ($@) {
214 2     2 1 29 my $self = shift;
215 2         4 my %results;
216              
217             # Do the parsing if necessary
218 2   33     33 $self->{attrs} ||= $self->_parse_pca_file;
219 2         7 %results = map { $_ => $self->{attrs}{$_} } @_;
  6         24  
220 2         14 return \%results;
221             }
222              
223             =item get_attr
224              
225             my $value = $chf->get_attr($attr);
226              
227             This helper method gets the value for only one attribute and returns it as a
228             scalar.
229              
230             =cut
231              
232 0     0 1 0 sub get_attr ($$) { (values %{ shift->get_attrs(shift) })[0] }
  0         0  
233              
234             =item get_fields
235              
236             my @fields = $self->get_fields;
237              
238             Returns (in hash order) the names of fields that can be read from or written to
239             the file.
240              
241             =cut
242              
243 0     0 1 0 sub get_fields () { keys %{ shift->{fields} } }
  0         0  
244              
245             =item write_to_file
246              
247             Writes data to a file, optionally taking a filename (if none is supplied, the
248             filename object field is used)
249              
250             =cut
251              
252             sub write_to_file ($;$) {
253 0     0 1 0 my $self = shift;
254 0 0 0     0 $self->{filename} ||= shift or croak "No filename to write to";
255 0 0 0     0 $self->{data} or do { $self->encode } or croak "No data to write";
  0         0  
256              
257 0 0       0 open F, ">", $self->{filename}
258             or croak "Failed to open '$self->{filename}' for writing";
259 0         0 binmode F;
260 0         0 print F $self->{data};
261 0         0 close F;
262             }
263              
264             =item decode
265              
266             $chf->decode;
267             $chf->decode($chfdata);
268              
269             Decodes the currently-loaded data or new data passed in.
270              
271             =cut
272              
273             sub decode ($;$) {
274 2     2 1 3 my $self = shift;
275 2 50 33     15 $self->{data} ||= shift or do { $self->_load };
  0         0  
276 2 50       8 $self->{data} or croak "No data to decode";
277 2         15 $self->{decoded} = $self->_decode_pca_file;
278             }
279              
280             =item encode
281              
282             $chf->encode;
283            
284             Returns an encoded representation of the CHF file, constructed from the
285             attributes previously set by set_attrs or existing from a constructor or
286             load_from_file() call.
287              
288             =cut
289              
290             sub encode ($) {
291 3     3 1 25 my $self = shift;
292 3         18 $self->{decoded} = $self->_edit_pca_file;
293 3 50       12 $self->{decoded} or croak "No data to encode";
294 3         24 $self->{data} = $self->_encode_pca_file;
295             }
296              
297             =item _encode_pca_file
298              
299             =item _decode_pca_file
300              
301             Method declarations ("abstract" methods) to be implmented by subclasses
302              
303             =cut
304              
305             sub _encode_pca_file ($$);
306             sub _decode_pca_file ($$);
307              
308             =back
309              
310             =head2 PRIVATE
311              
312             =over 4
313              
314             =item _rawencode
315              
316             This is the low-level engine that handles the XOR encoding of the byte stream.
317             It knows nothing of pcAnywhere data, and it can be called on multiple sections
318             of the file independently.
319              
320             $roll - starting value of the rolling counter
321             $prev - the "previous byte" value upon entry to the loop
322             $str - the string we're to encode
323              
324             =cut
325              
326             sub _rawencode {
327 6     6   9 shift; # Get rid of my $self
328 6         10 my ($roll, $prev, $str) = @_;
329 6         8 my $encstr = ""; # encoded string
330              
331 6         3180 foreach ( split( m//, $str) ) {
332 17120         15834 $prev = ord($_) ^ $prev ^ ($roll++ & 0xFF);
333 17120         16405 $encstr .= chr($prev);
334             }
335              
336 6         1061 return $encstr;
337             }
338              
339             =item _rawdecode
340              
341             This is the low-level engine that handles the XOR decoding of the byte stream.
342             It knows nothing of pcAnywhere data, and it can be called on multiple sections
343             of the file independently.
344              
345             $roll - starting value of the rolling counter
346             $prev - the "previous byte" value upon entry to the loop
347             $str - the string we're to decode
348              
349             =cut
350              
351             sub _rawdecode {
352 4     4   6 shift; # Get rid of my $self
353 4         8 my ($roll, $prev, $str) = @_;
354 4         5 my $decstr = ""; # decoded string
355              
356 4         1464 foreach ( split( m//, $str) ) {
357 13812         11760 my $c = ord($_);
358              
359 13812         13872 $decstr .= chr( $c ^ $prev ^ ($roll++ & 0xFF) );
360 13812         16842 $prev = $c;
361             }
362              
363 4         937 return $decstr;
364             }
365              
366             =item _edit_pca_file
367              
368             Performs encoding operations (internal)
369              
370             =cut
371              
372             sub _edit_pca_file ($) {
373 3     3   5 my $self = shift;
374             # Make a copy of the template string
375 3         7 my $str = $self->{template};
376              
377 3         26 foreach my $key ( keys %{ $self->{attrs} } ) {
  3         13  
378 6         11 my $f = $self->{fields}{$key};
379             # This must be a known key to continue
380 6 50       14 unless ($f) {
381 0         0 carp "Tried to set unknown key -- continuing";
382 0         0 next;
383             }
384              
385 6         9 my ($off, $len, $type) = @$f;
386 6         13 my $val = $self->{attrs}{$key};
387              
388             # If there is a handler defined for this type, use it
389 6 50       60 $self->{encode_sub}[$type]->($val, $len)
390             if defined $self->{encode_sub}[$type];
391              
392 6         47 substr($str, $off, $len) = $val;
393             }
394              
395 3         31 return $str;
396             }
397              
398             =item _parse_pca_file
399              
400             Teases the binary format into a hash (internal)
401              
402             =cut
403              
404             sub _parse_pca_file ($) {
405 2     2   4 my $self = shift;
406 2   33     10 my $str = $self->{decoded} || do { $self->decode };
407 2         13 my $ref = { };
408              
409 2         8 foreach my $key ( keys %{ $self->{fields} } ) {
  2         22  
410 25         25 my ($off, $len, $type) = @{ $self->{fields}{$key} };
  25         52  
411 25         55 my $val = substr($str, $off, $len);
412              
413             # If there is a handler defined for this type, use it
414 25 50       91 $self->{decode_sub}[$type]->($val)
415             if defined $self->{decode_sub}[$type];
416              
417 25         64 $ref->{$key} = $val;
418             }
419              
420 2         16 return $ref;
421             }
422              
423             =item _load
424              
425             Does loading of filedata if necessary
426              
427             =cut
428              
429             sub _load ($) {
430 0     0     my $self = shift;
431 0 0         unless ($self->{data}) {
432 0 0         if ($self->{filename}) {
433 0           $self->load_from_file;
434             } else {
435 0           croak "No filename specified and no data loaded";
436             }
437             }
438             }
439              
440             =back
441              
442             =head1 TO DO
443              
444             Our understanding of the decoding process just looks incomplete: it's
445             complicated enough for no good reason that we really just suspect that we have
446             done it wrong. There are a couple of glitches even in the current decoding that
447             it requires a bit more thought.
448              
449             Implement better error handling.
450              
451             Explain the default values for certain special fields.
452              
453             Get rid of the silly prototype definitions on the method definitions.
454              
455             Create (more) tests!
456              
457             =head1 SEE ALSO
458              
459             See L for a useful subclass of this module.
460              
461             =head1 AUTHOR
462              
463             Darren Kulp, C<< >>, based on code from Stephen J. Friedl,
464             (http://unixwiz.net/)
465              
466             =head1 ACKNOWLEDGEMENTS
467              
468             This module is based on 'pcainfo' from Stephen J. Friedl. His work, which is in
469             the public domain, has been modified to add encoding capabilities to allow
470             creating CHFs (pcAnywhere connection profiles). Thanks, Stephen!
471              
472             The addition of encoding and an OO interface, as well as the packaging as a
473             CPAN module and the correcting of some typographical errors, semantic
474             redundancies, and spelling mistakes, was done by Darren Kulp.
475              
476             =head1 COPYRIGHT AND LICENSE
477              
478             This code is in the public domain. Contains code placed in the public domain
479             2002 by Stephen Friedl.
480              
481             "Symantec" and "pcAnywhere" are trademarks of Symantec Corp.
482              
483             =head1 BUGS
484              
485             Please report any bugs or feature requests to
486             C, or through the web interface at
487             L.
488             I will be notified, and then you'll automatically be notified of progress on
489             your bug as I make changes.
490              
491             =head1 SUPPORT
492              
493             You can find documentation for this module with the perldoc command.
494              
495             perldoc Symantec::PCAnywhere::Profile
496              
497             You can also look for information at:
498              
499             =over 4
500              
501             =item * AnnoCPAN: Annotated CPAN documentation
502              
503             L
504              
505             =item * CPAN Ratings
506              
507             L
508              
509             =item * RT: CPAN's request tracker
510              
511             L
512              
513             =item * Search CPAN
514              
515             L
516              
517             =back
518              
519             =cut
520              
521             1; # End of Symantec::PCAnywhere::Profile