File Coverage

blib/lib/Config/OpenSSH/Authkey/Entry.pm
Criterion Covered Total %
statement 85 90 94.4
branch 35 42 83.3
condition 7 15 46.6
subroutine 20 20 100.0
pod 16 16 100.0
total 163 183 89.0


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