File Coverage

blib/lib/Config/OpenSSH/Authkey/Entry.pm
Criterion Covered Total %
statement 88 93 94.6
branch 35 42 83.3
condition 7 15 46.6
subroutine 21 21 100.0
pod 16 16 100.0
total 167 187 89.3


line stmt bran cond sub pod time code
1             # -*- Perl -*-
2             #
3             # Representation of individual OpenSSH authorized_keys entries, based on
4             # a study of the sshd(8) manual, along with the OpenSSH 5.2 sources.
5             # This module only weakly validates the data; in particular, no effort
6             # is made to confirm whether the key options are actual valid options
7             # for the version of OpenSSH in question.
8              
9             package Config::OpenSSH::Authkey::Entry;
10              
11 2     2   16534 use 5.006000;
  2         8  
  2         121  
12 2     2   11 use strict;
  2         3  
  2         67  
13 2     2   8 use warnings;
  2         6  
  2         59  
14              
15 2     2   1003 use Config::OpenSSH::Authkey::Entry::Options ();
  2         4  
  2         45  
16              
17 2     2   11 use Carp qw/croak/;
  2         3  
  2         2691  
18              
19             our $VERSION = '1.05';
20              
21             # This limit is set for various things under OpenSSH code. Used here to
22             # limit length of authorized_keys lines.
23             my $MAX_PUBKEY_BYTES = 8192;
24              
25             # Sanity check to ensure at least some data exists in the key field
26             my $MIN_KEY_LENGTH = 42;
27              
28             ######################################################################
29             #
30             # Data Parsing & Utility Methods - Internal
31              
32             my $_parse_entry = sub {
33             my $self = shift;
34             my $data = shift || q{};
35              
36             my ( $options, $key, $comment, $protocol, $keytype );
37              
38             chomp $data;
39              
40             if ( $data =~ m/^\s*$/ or $data =~ m/^\s*#/ ) {
41             return ( 0, 'no public key data' );
42             } elsif ( length $data >= $MAX_PUBKEY_BYTES ) {
43             return ( 0, 'exceeds size limit' );
44             }
45              
46             # OpenSSH supports leading whitespace before options or key. Strip
47             # this optional whitespace to simplify parsing.
48             $data =~ s/^[ \t]+//;
49              
50             ENTRY_LEXER: {
51             # Optional trailing comment (user@host, usually)
52             if ( defined $key and $data =~ m/ \G (.+) /cgx ) {
53             $comment = $1;
54              
55             last ENTRY_LEXER;
56             }
57              
58             # SSH2 RSA or DSA public key
59             if ( !defined $key
60             and $data =~
61             m/ \G ( ssh-(rsa|dss) [ \t]+? [A-Za-z0-9+\/]+ =* ) [ \t]* /cgx ) {
62              
63             $key = $1;
64             # follow the -t argument option to ssh-keygen(1)
65             $keytype = $2 eq 'rsa' ? 'rsa' : 'dsa';
66             $protocol = 2;
67              
68             redo ENTRY_LEXER;
69             }
70              
71             # SSH1 RSA public key
72             if ( !defined $key
73             and $data =~ m/ \G ( \d{3,5} [ \t]+? \d+ [ \t]+? \d+ ) [ \t]* /cgx ) {
74              
75             $key = $1;
76             $keytype = 'rsa1';
77             $protocol = 1;
78              
79             redo ENTRY_LEXER;
80             }
81              
82             # Optional leading options - may contain whitespace inside ""
83             if ( !defined $key and $data =~ m/ \G ([^ \t]+? [ \t]*) /cgx ) {
84             $options .= $1;
85              
86             redo ENTRY_LEXER;
87             }
88             }
89              
90             if ( !defined $key ) {
91             return ( 0, 'unable to parse public key' );
92              
93             } else {
94             $self->{_key} = $key;
95             $self->{_protocol} = $protocol;
96             $self->{_keytype} = $keytype;
97              
98             if ( defined $options ) {
99             $options =~ s/\s*$//;
100             $self->{_options} = $options;
101             }
102              
103             if ( defined $comment ) {
104             $comment =~ s/\s*$//;
105             $self->{_comment} = $comment;
106             }
107             }
108              
109             return ( 1, 'ok' );
110             };
111              
112             ######################################################################
113             #
114             # Class methods
115              
116             sub new {
117 24     24 1 4087 my $class = shift;
118 24         33 my $data = shift;
119              
120 24         55 my $self = { _dup_of => 0 };
121              
122 24 100       67 if ( defined $data ) {
123 18         39 my ( $is_parsed, $err_msg ) = $_parse_entry->( $self, $data );
124 18 100       47 if ( !$is_parsed ) {
125 3         511 croak $err_msg;
126             }
127             }
128              
129 21         75 bless $self, $class;
130 21         49 return $self;
131             }
132              
133             sub split_options {
134 1     1 1 389 my $class = shift;
135 1         8 Config::OpenSSH::Authkey::Entry::Options->split_options(@_);
136             }
137              
138             ######################################################################
139             #
140             # Instance methods
141              
142             sub parse {
143 3     3 1 6 my $self = shift;
144 3   33     9 my $data = shift || croak 'no data supplied to parse';
145              
146 3         6 my ( $is_parsed, $err_msg ) = $_parse_entry->( $self, $data );
147 3 50       8 if ( !$is_parsed ) {
148 0         0 croak $err_msg;
149             }
150              
151 3         8 return $self;
152             }
153              
154             sub as_string {
155 19     19 1 60 my $self = shift;
156 19         30 my $string = q{};
157              
158 19 100 66     175 if ( exists $self->{_parsed_options} ) {
    100          
159 1         5 $string .= $self->{_parsed_options}->as_string . q{ };
160              
161             } elsif ( exists $self->{_options} and length $self->{_options} > 0 ) {
162 9         22 $string .= $self->{_options} . q{ };
163             }
164              
165 19 50 33     106 if ( !defined $self->{_key} or length $self->{_key} < $MIN_KEY_LENGTH ) {
166 0         0 croak 'no key material present';
167             }
168 19         71 $string .= $self->{_key};
169              
170 19 100 66     70 if ( exists $self->{_comment} and length $self->{_comment} > 0 ) {
171 9         19 $string .= q{ } . $self->{_comment};
172             }
173              
174 19         112 return $string;
175             }
176              
177             sub key {
178 16     16 1 445 my $self = shift;
179 16         18 my $key = shift;
180 16 100       37 if ( defined $key ) {
181 3         10 my ( $is_parsed, $err_msg ) = $_parse_entry->( $self, $key );
182 3 50       8 if ( !$is_parsed ) {
183 0         0 croak $err_msg;
184             }
185             }
186 16 50 33     96 if ( !defined $self->{_key} or length $self->{_key} < $MIN_KEY_LENGTH ) {
187 0         0 croak 'no key material present';
188             }
189 16         70 return $self->{_key};
190             }
191              
192             sub protocol {
193 3 50   3 1 1138 shift->{_protocol} || 0;
194             }
195              
196             sub keytype {
197 3 50   3 1 28 shift->{_keytype} || '';
198             }
199              
200             sub comment {
201 18     18 1 50 my $self = shift;
202 18         21 my $comment = shift;
203 18 100       39 if ( defined $comment ) {
204 3         7 $self->{_comment} = $comment;
205             }
206 18 100       102 return defined $self->{_comment} ? $self->{_comment} : '';
207             }
208              
209             sub unset_comment {
210 3     3 1 6 my $self = shift;
211 3         9 delete $self->{_comment};
212 3         6 return 1;
213             }
214              
215             # The leading (optional!) options can be dealt with as a string
216             # (options, unset_options), or if parsed, as individual options
217             # (get_option, set_option, unset_option).
218              
219             sub options {
220 26     26 1 566 my $self = shift;
221 26         31 my $new_options = shift;
222              
223 26 100       55 if ( defined $new_options ) {
224 4         16 delete $self->{_parsed_options};
225 4         9 $self->{_options} = $new_options;
226             }
227              
228 26 100       73 my $options_str =
229             exists $self->{_parsed_options}
230             ? $self->{_parsed_options}->as_string
231             : $self->{_options};
232 26 100       122 return defined $options_str ? $options_str : '';
233             }
234              
235             sub unset_options {
236 3     3 1 7 my $self = shift;
237 3         5 delete $self->{_parsed_options};
238 3         6 delete $self->{_options};
239 3         4 return 1;
240             }
241              
242             sub get_option {
243 5     5 1 747 my $self = shift;
244              
245 5 100       16 if ( !exists $self->{_parsed_options} ) {
246 1         7 $self->{_parsed_options} =
247             Config::OpenSSH::Authkey::Entry::Options->new( $self->{_options} );
248             }
249              
250 5         15 $self->{_parsed_options}->get_option(@_);
251             }
252              
253             sub set_option {
254 4     4 1 5 my $self = shift;
255              
256 4 100       14 if ( !exists $self->{_parsed_options} ) {
257 1         6 $self->{_parsed_options} =
258             Config::OpenSSH::Authkey::Entry::Options->new( $self->{_options} );
259             }
260              
261 4         17 $self->{_parsed_options}->set_option(@_);
262             }
263              
264             sub unset_option {
265 3     3 1 7 my $self = shift;
266              
267 3 50       10 if ( !exists $self->{_parsed_options} ) {
268 0         0 $self->{_parsed_options} =
269             Config::OpenSSH::Authkey::Entry::Options->new( $self->{_options} );
270             }
271              
272 3         8 $self->{_parsed_options}->unset_option(@_);
273             }
274              
275             sub duplicate_of {
276 10     10 1 11 my $self = shift;
277 10         12 my $ref = shift;
278              
279 10 100       21 if ( defined $ref ) {
280 2         4 $self->{_dup_of} = $ref;
281             }
282              
283 10         41 return $self->{_dup_of};
284             }
285              
286             sub unset_duplicate {
287 1     1 1 2 my $self = shift;
288 1         3 $self->{_dup_of} = 0;
289 1         1 return 1;
290             }
291              
292             1;
293              
294             __END__