Net-SSH-AuthorizedKeysFile-0.18/0000755000175000017500000000000013066345317016575 5ustar mschillimschilliNet-SSH-AuthorizedKeysFile-0.18/Makefile.PL0000644000175000017500000000221512332352213020533 0ustar mschillimschilli###################################################################### # Makefile.PL for Net::SSH::AuthorizedKeysFile # 2005, Mike Schilli ###################################################################### use ExtUtils::MakeMaker; my $meta_merge = { META_MERGE => { resources => { repository => 'http://github.com/mschilli/net-ssh-authorizedkeysfile-perl', }, } }; WriteMakefile( 'NAME' => 'Net::SSH::AuthorizedKeysFile', 'VERSION_FROM' => 'lib/Net/SSH/AuthorizedKeysFile.pm', # finds $VERSION 'PREREQ_PM' => { Test::More => 0, Text::ParseWords => 0, Log::Log4perl => 1, File::Temp => 0, File::Copy => 0, Digest::MD5 => 0, File::Spec => 0, }, # e.g., Module::Name => 1.1 $ExtUtils::MakeMaker::VERSION >= 6.50 ? (%$meta_merge) : (), 'EXE_FILES' => [ 'eg/authorized-keys-test' ], ($] >= 5.005 ? ## Add these new keywords supported since 5.005 (ABSTRACT_FROM => 'lib/Net/SSH/AuthorizedKeysFile.pm', AUTHOR => 'Mike Schilli ') : ()), ); Net-SSH-AuthorizedKeysFile-0.18/Changes0000644000175000017500000000512713066345111020065 0ustar mschillimschilli###################################################################### Revision history for Perl extension Net::SSH::AuthorizedKeysFile ###################################################################### 0.18 (03/27/2017) (ms) L. Alberto Giménez reported empty print lines in case of Net::SSH::AuthorizedKeysFile running with Log::Log4perl enabled. Fixed. 0.17 (12/22/2014) (ms) David Leon Gil added support for Ed25519 keys 0.16 (01/31/2012) (ms) Added support for ecdsa-* ssh-2 keys, requested by Christian Ruppert. 0.15 (02/16/2011) (ms) No longer using $& to eliminate both real and imaginary performance penalties. 0.14 (12/20/2010) (ms) Preserve order of options (ms) Bug fix: Binary options no longer expand as xxx="1" in as_string() 0.13 (2010/04/25) (ms) Moved ssh1 and ssh2 docs to base class. (ms) Added sanity check for files with extreme line lengths (ms) Added convenience methods path_locate() and ssh_dir() to locate a user's .ssh dir and authorized_keys file on disk. (ms) Added fingerprint method to parser base class, determining a key's functional uniqueness. 0.12 (2010/02/22) (ms) Added test cases with weird keys (ms) Deleting undefined/empty options to avoid warnings 0.11 (2009/11/30) (ms) Documentation fixes (ms) Added github repo link to Makefile.PL 0.10 2009/11/15 (ms) Complete rewrite of parsers and classes (ms) No longer supporting multi-line keys 0.06 2009/10/20 (ms) new() no longer reads the configuration file automatically, read() must be called separately. This breaks backward compatibility. Sorry, but it had to be fixed. (ms) Even odd cases of authorized_keys files are now handled correctly (but of course there could be even odder cases that caused the parser to bail :) 0.05 2009/08/07 (ms) Ignore empty lines (ms) Allow multi-lines for ssh2 pubkeys (ms) Fixes for more resilience towards mistyped authkey files 0.04 2009/08/07 (ms) Allow mixed v1/v2 keys in a single authorized_keys file. 0.03 2008/08/24 (ms) Rewrote entire parsing code to comply with ssh spec, thanks to Tobias Galitzien for the pointer. (ms) Ignore comment lines in authorized_keys files (ms) The 'email' field is now call 'comment' ('email' is still available for backwards compatibility, though). (ms) Comments can now contain blanks and commas without tripping the parser up. 0.02 2005/12/23 (ms) Fixed dependencies 0.01 2005/12/21 (ms) Where it all began. Net-SSH-AuthorizedKeysFile-0.18/eg/0000755000175000017500000000000013066345317017170 5ustar mschillimschilliNet-SSH-AuthorizedKeysFile-0.18/eg/authorized-keys-test0000755000175000017500000000356112332352213023213 0ustar mschillimschilli#!/usr/local/bin/perl use strict; use warnings; use Net::SSH::AuthorizedKeysFile; use Log::Log4perl qw(:easy); use Getopt::Std; getopts("sv", \my %opts); my $level = $INFO; if($opts{v}) { $level = $DEBUG; } Log::Log4perl->easy_init({ level => $level, layout => "%F{1}-%L: %m%n" }); my($file) = @ARGV; die "usage: $0 file" unless defined $file; my $ak = Net::SSH::AuthorizedKeysFile->new( file => $file, strict => ($opts{s} ? 1 : 0), ); my $rc = $ak->read(); if($rc) { print "$file ok\n"; } else { print "$file *not* ok\n"; } my @keys = $ak->keys(); print "Found ", scalar @keys, " keys\n"; for my $key (@keys) { print " * ", $key->type(), "\n"; } __END__ =head1 NAME authorized-keys-test - Validate a authorized_keys file =head1 SYNOPSIS authorized-keys-test ~/.ssh/authorized_keys =head1 DESCRIPTION authorized-keys-test reads in the keys of an ssh client authorized_keys file and reports any errors. =head1 OPTIONS =over 4 =item -v Verbose mode. Turns on DEBUG instead of INFO. =item -s Strict mode. If on, the test insists on properly formatted authorized_keys files and isn't nearly as lenient as the sshd daemon's parser. =back =head1 EXAMPLES $ authorized-keys-test ~/.ssh/authorized_keys /home/joe/.ssh/authorized_keys ok Found 4 keys * ssh-2 * ssh-2 * ssh-2 * ssh-2 # strict mode: $ authorized-keys-test -s ~/.ssh/authorized_keys AuthorizedKeysFile.pm-69: Key [eme alsdkfj] failed sanity check -- ignored AuthorizedKeysFile.pm-71: Strict mode on: Abort /home/joe/.ssh/authorized_keys *not* ok Found 4 keys * ssh-2 * ssh-2 * ssh-2 * ssh-2 =head1 LEGALESE Copyright 2009 by Mike Schilli, all rights reserved. This program is free software, you can redistribute it and/or modify it under the same terms as Perl itself. =head1 AUTHOR 2009, Mike Schilli Net-SSH-AuthorizedKeysFile-0.18/MANIFEST.SKIP0000644000175000017500000000013312446310706020463 0ustar mschillimschilliblib ^Makefile$ ^Makefile.old$ CVS .cvsignore MANIFEST.bak MYMETA.json MYMETA.yml adm .git Net-SSH-AuthorizedKeysFile-0.18/META.yml0000664000175000017500000000133413066345317020051 0ustar mschillimschilli--- abstract: "Read and modify ssh's authorized_keys files" author: - 'Mike Schilli ' build_requires: ExtUtils::MakeMaker: '0' configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 1 generated_by: 'ExtUtils::MakeMaker version 7.24, CPAN::Meta::Converter version 2.143240' license: unknown meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: '1.4' name: Net-SSH-AuthorizedKeysFile no_index: directory: - t - inc requires: Digest::MD5: '0' File::Copy: '0' File::Spec: '0' File::Temp: '0' Log::Log4perl: '1' Test::More: '0' Text::ParseWords: '0' resources: repository: http://github.com/mschilli/net-ssh-authorizedkeysfile-perl version: '0.18' Net-SSH-AuthorizedKeysFile-0.18/t/0000755000175000017500000000000013066345317017040 5ustar mschillimschilliNet-SSH-AuthorizedKeysFile-0.18/t/001Basic.t0000644000175000017500000000344312332352213020457 0ustar mschillimschilli###################################################################### # Test suite for Net::SSH::AuthorizedKeysFile # by Mike Schilli ###################################################################### use warnings; use strict; use File::Temp qw(tempfile); use File::Copy; use Log::Log4perl qw(:easy); # Log::Log4perl->easy_init($DEBUG); use Test::More tests => 12; BEGIN { use_ok('Net::SSH::AuthorizedKeysFile') }; my $tdir = "t"; $tdir = "../t" unless -d $tdir; my $cdir = "$tdir/canned"; use Net::SSH::AuthorizedKeysFile; my $ak = Net::SSH::AuthorizedKeysFile->new(file => "$cdir/ak.txt"); $ak->read(); my @keys = $ak->keys(); is($keys[0]->keylen(), 1024, "keylen"); is($keys[1]->keylen(), 1024, "keylen"); is($keys[1]->exponent(), 35, "exponent"); is($keys[0]->email(), 'quack@schmack.com', "email"); is($keys[1]->email(), 'quack2@schmack.com', "email"); like($keys[0]->key(), qr/^1\d+$/, "key"); like($keys[1]->key(), qr/^2\d+$/, "key"); my($fh, $filename) = tempfile(); # Modify a authkey file copy "$cdir/ak.txt", $filename; my $ak2 = Net::SSH::AuthorizedKeysFile->new(file => $filename); $ak2->read(); @keys = $ak2->keys(); $keys[0]->keylen(1025); $keys[1]->option("From", 'hugo@hugo.com'); $ak2->save(); # Read in modifications my $ak3 = Net::SSH::AuthorizedKeysFile->new(file => $filename); $ak3->read(); @keys = $ak3->keys(); is($keys[0]->keylen(), 1025, "modified keylen"); is($keys[1]->option("From"), 'hugo@hugo.com', "modified from="); # Remove option $keys[1]->option_delete("From"); $ak3->save(); my $ak4 = Net::SSH::AuthorizedKeysFile->new(file => $filename); $ak4->read(); @keys = $ak4->keys(); is($keys[1]->option("From"), undef, "Removed from"); #print $ak4->as_string(); is($keys[1]->fingerprint(), "39db833dbf737ea9b95e378bd43a4008", "fingerprint"); Net-SSH-AuthorizedKeysFile-0.18/t/011order.t0000644000175000017500000000270112332352213020546 0ustar mschillimschilli# # Test cases for ssh2 keys # use Net::SSH::AuthorizedKey; use Net::SSH::AuthorizedKey::SSH2; use Test::More; use Log::Log4perl qw(:easy); # Log::Log4perl->easy_init($DEBUG); plan tests => 3; my $t2key = 'ssh-rsa AAAAB3NzaCKK7696k6U= bar@foo.ms.com'; # specific my $pk = Net::SSH::AuthorizedKey::SSH2->parse($t2key); $pk->option( "no-port-forwarding", 1 ); $pk->option( "no-agent-forwarding", 1 ); $pk->option( "no-x11-forwarding", 1 ); $pk->option( "no-pty", 1 ); $pk->option( "no-user-rc", 1 ); $pk->option( "command", "blah blah" ); $pk->option( "environment", "moo" ); $pk->option( "from", "here,there" ); $pk->option( "permitopen", "oink" ); $pk->option( "tunnel", "yes, please" ); like $pk->as_string(), qr/no-port-forwarding,no-agent-forwarding,no-x11-forwarding,no-pty,no-user-rc,command="blah blah",environment="moo",from="here,there",permitopen="oink",tunnel="yes, please" ssh-rsa/, "options in order"; $pk->option_delete( "command"); $pk->option_delete( "no-pty"); like $pk->as_string(), qr/no-port-forwarding,no-agent-forwarding,no-x11-forwarding,no-user-rc,environment="moo",from="here,there",permitopen="oink",tunnel="yes, please" ssh-rsa/, "options in order after delete"; my $t3key = 'from="a,b",no-pty,environment="moo",no-agent-forwarding ssh-rsa AAAAB3NzaCKK7696k6U= bar@foo.ms.com'; # specific $pk = Net::SSH::AuthorizedKey::SSH2->parse($t3key); like $pk->as_string(), qr/from="a,b",no-pty,environment="moo",no-agent-forwarding ssh-rsa/; Net-SSH-AuthorizedKeysFile-0.18/t/canned/0000755000175000017500000000000013066345317020270 5ustar mschillimschilliNet-SSH-AuthorizedKeysFile-0.18/t/canned/ak-ed25519.txt0000644000175000017500000000006612446175263022424 0ustar mschillimschillissh-ed25519 AAAAAlkj2lkjalsdfkjlaskdfj234 foo@bar.com Net-SSH-AuthorizedKeysFile-0.18/t/canned/ak-ssh1-weirdo.txt0000644000175000017500000000041212332352213023550 0ustar mschillimschillifrom="bing.bang.boom",no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,command="bingo server" 1024 35 37234908273429384792837492837498273948729384792837498237948273948729387492837492837498273948723948729384792387492834792837498 bozo@quack.schmack.com Net-SSH-AuthorizedKeysFile-0.18/t/canned/ak-mixed.txt0000644000175000017500000000033712332352213022517 0ustar mschillimschilli # here's two v2 keys ssh-dss AAAAAlkj2lkjalsdfkjlaskdfj234 foo@bar.com ssh-rsa AAAAAlkj2lkjalsdfkjlaskdfj234 bar@foo.com # here comes a v1 key 1024 35 1242342352342342342342222222225235463563463456345 quack@schmack.com Net-SSH-AuthorizedKeysFile-0.18/t/canned/ak-ssh2.txt0000644000175000017500000000014412332352213022264 0ustar mschillimschillissh-dss AAAAAlkj2lkjalsdfkjlaskdfj234 foo@bar.com ssh-dss AAAAAlkj2lkjalsdfkjlaskdfj234 bar@foo.com Net-SSH-AuthorizedKeysFile-0.18/t/canned/ak-manpage.txt0000644000175000017500000000065612332352213023025 0ustar mschillimschilli# from the sshd manpage (AUTHORIZED_KEYS FILE FORMAT section) ssh-rsa AAAAB3Nza...LiPk== user@example.net from="*.sales.example.net,!pc.sales.example.net" ssh-rsa AAAAB2...19Q== john@example.net command="dump /home",no-pty,no-port-forwarding ssh-dss AAAAC3...51R== example.net permitopen="192.0.2.1:80",permitopen="192.0.2.2:25" ssh-dss AAAAB5...21S== tunnel="0",command="sh /etc/netstart tun0" ssh-rsa AAAA...== jane@example.net Net-SSH-AuthorizedKeysFile-0.18/t/canned/pk-ssh2.txt0000644000175000017500000000054712332352213022312 0ustar mschillimschilli---- BEGIN SSH2 PUBLIC KEY ---- Comment: "rsa-key-20090703" AAAAB3XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXX== ---- END SSH2 PUBLIC KEY ---- Net-SSH-AuthorizedKeysFile-0.18/t/canned/ak-ecdsa.txt0000644000175000017500000000017612332352213022471 0ustar mschillimschilliecdsa-sha2-nistp521 AAAAAlkj2lkjalsdfkjlaskdfj234 foo@bar.com ecdsa-sha512-nistp521 AAAAAlkj2lkjalsdfkjlaskdfj234 bar@foo.com Net-SSH-AuthorizedKeysFile-0.18/t/canned/ak-broken.txt0000644000175000017500000000001612332352213022663 0ustar mschillimschilliene mene meck Net-SSH-AuthorizedKeysFile-0.18/t/canned/ak-comments.txt0000644000175000017500000000044412332352213023235 0ustar mschillimschilli# comments with white space ssh-rsa AAAAB3Nza...LiPk== Quack Schmack quack@schmack.com command="dump /home",no-pty,no-port-forwarding ssh-dss AAAAC3...51R== Quack Schmack quack@schmack.com command="dump /home",no-pty,no-port-forwarding ssh-dss AAAAC3...51R== Quack Schmack, quack@schmack.com Net-SSH-AuthorizedKeysFile-0.18/t/canned/pk-empty.txt0000644000175000017500000000004012332352213022555 0ustar mschillimschilli---- BEGIN SSH2 PUBLIC KEY ---- Net-SSH-AuthorizedKeysFile-0.18/t/canned/ak.txt0000644000175000017500000000023312332352213021406 0ustar mschillimschilli1024 35 1242342352342342342342222222225235463563463456345 quack@schmack.com 1024 35 2242342352342342342342222222225235463563463456345 quack2@schmack.com Net-SSH-AuthorizedKeysFile-0.18/t/009weird-keys.t0000644000175000017500000000226512332352213021532 0ustar mschillimschilli# # Test cases for ssh2 keys # use Net::SSH::AuthorizedKey; use Net::SSH::AuthorizedKey::SSH2; use Test::More; use Log::Log4perl qw(:easy); use strict; use warnings; # Log::Log4perl->easy_init($DEBUG); my $offset = tell DATA; my @data = ; plan tests => scalar @data; seek DATA, $offset, 0; while() { my($key, $comment) = split / ## /, $_; chomp $comment; my $ssh = Net::SSH::AuthorizedKey->parse($key); ok !defined $ssh, "$comment"; } __DATA__ from="*.onk.com" from="*.onk.com" 1024 37 133009991 abc@foo.com ## spaces between options AAAAB3NzaC1yc2EU= worp@corp.com ## ssh-2 key without enc algo AAAAB3NzaC1yc2EU= ## ssh-2 key without enc algo from="*.onk.com",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,1024 35 1409076329 worp@corp.com ## no space but comma before ssh1 key len from ="*.onk.com" 1024 35 1743547142167 abc@foo.bar.baz.com ## space before options's "=" 63548219 abc@bar.baz.com ## Missing ssh1 keylen sh-rsa AAAAB3Nz ## Misspelled (sh-rsa) ssh2 algo ssh-dsa AAAAB3Nz ## Misspelled (ssh-dsa) ssh2 algo from="abc.com" no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,command="ls" 1024 35 12923 abc@def.com ## space in options Net-SSH-AuthorizedKeysFile-0.18/t/008ssh-2.t0000644000175000017500000000146012332352213020376 0ustar mschillimschilli# # Test cases for ssh2 keys # use Net::SSH::AuthorizedKey; use Net::SSH::AuthorizedKey::SSH2; use Test::More; use Log::Log4perl qw(:easy); # Log::Log4perl->easy_init($DEBUG); plan tests => 10; my $t2key = 'ssh-rsa AAAAB3NzaCKK7696k6U= bar@foo.ms.com'; # specific my $pk = Net::SSH::AuthorizedKey::SSH2->parse($t2key); is($pk->encryption(), "ssh-rsa", "encryption"); is($pk->key(), "AAAAB3NzaCKK7696k6U=", "key"); is($pk->email(), 'bar@foo.ms.com', "email"); is($pk->type(), "ssh-2", "type"); ok($pk->sanity_check(), "sanity check"); # generic $pk = Net::SSH::AuthorizedKey->parse($t2key); is($pk->encryption(), "ssh-rsa", "encryption"); is($pk->key(), "AAAAB3NzaCKK7696k6U=", "key"); is($pk->email(), 'bar@foo.ms.com', "email"); is($pk->type(), "ssh-2", "type"); ok($pk->sanity_check(), "sanity check"); Net-SSH-AuthorizedKeysFile-0.18/t/007ssh-1.t0000644000175000017500000000143312332352213020374 0ustar mschillimschilli# # Test cases for ssh1 keys # use Net::SSH::AuthorizedKey; use Net::SSH::AuthorizedKey::SSH1; use Test::More; use Log::Log4perl qw(:easy); # Log::Log4perl->easy_init($DEBUG); plan tests => 12; my $t1key = " 1042 17 123123123"; # direct my $pk = Net::SSH::AuthorizedKey::SSH1->parse($t1key); is($pk->keylen(), "1042", "keylen"); is($pk->key(), "123123123", "key"); is($pk->exponent(), "17", "exponent"); is($pk->email(), "", "email"); is($pk->type(), "ssh-1", "type"); ok($pk->sanity_check(), "sanity check"); # generic $pk = Net::SSH::AuthorizedKey->parse($t1key); is($pk->keylen(), "1042", "keylen"); is($pk->key(), "123123123", "key"); is($pk->exponent(), "17", "exponent"); is($pk->email(), "", "email"); is($pk->type(), "ssh-1", "type"); ok($pk->sanity_check(), "sanity check"); Net-SSH-AuthorizedKeysFile-0.18/t/010sanity.t0000644000175000017500000000160012332352213020736 0ustar mschillimschilli# # Test cases for pathetic abnormities # use Test::More; use Net::SSH::AuthorizedKeysFile; use File::Temp qw(tempfile); my $tdir = "t"; $tdir = "../t" unless -d $tdir; my $cdir = "$tdir/canned"; plan tests => 3; my $keyfile = Net::SSH::AuthorizedKeysFile->new( file => "$cdir/ak-comments.txt", ); is $keyfile->sanity_check(), 1, "sanity of regular file succeeds"; my($fh, $tmpfile) = tempfile( UNLINK => 1 ); my $string = ("a" x ($keyfile->{ridiculous_line_len} + 1)); print $fh "$string\n"; close $fh; is $keyfile->sanity_check($tmpfile), undef, "check sanity of insane file"; ($fh, $tmpfile) = tempfile( UNLINK => 1 ); $string = ("a" x ($keyfile->{ridiculous_line_len} / 2) . "\n" . "a" x ($keyfile->{ridiculous_line_len} / 2) ); print $fh "$string\n"; close $fh; is $keyfile->sanity_check($tmpfile), 1, "sanity of regular file succeeds"; Net-SSH-AuthorizedKeysFile-0.18/t/002Ssh-2.t0000644000175000017500000000405712446175263020353 0ustar mschillimschilli###################################################################### # Test suite for Net::SSH::AuthorizedKeysFile (ssh-2) # by Mike Schilli ###################################################################### use warnings; use strict; use File::Temp qw(tempfile); use Log::Log4perl qw(:easy); use File::Copy; # Log::Log4perl->easy_init($DEBUG); use Test::More tests => 16; BEGIN { use_ok('Net::SSH::AuthorizedKeysFile') }; my $tdir = "t"; $tdir = "../t" unless -d $tdir; my $cdir = "$tdir/canned"; use Net::SSH::AuthorizedKeysFile; my $ak = Net::SSH::AuthorizedKeysFile->new(file => "$cdir/ak-ssh2.txt"); $ak->read(); my @keys = $ak->keys(); is($keys[0]->type(), "ssh-2", "type"); is($keys[1]->type(), "ssh-2", "type"); is($keys[0]->key(), "AAAAAlkj2lkjalsdfkjlaskdfj234", "key"); is($keys[1]->key(), "AAAAAlkj2lkjalsdfkjlaskdfj234", "key"); is($keys[0]->email(), 'foo@bar.com', "key"); is($keys[1]->email(), 'bar@foo.com', "key"); # modify a ssh-2 key my($fh, $filename) = tempfile(); copy "$cdir/ak-ssh2.txt", $filename; $ak = Net::SSH::AuthorizedKeysFile->new(file => "$cdir/ak-ssh2.txt"); $ak->read(); $ak = Net::SSH::AuthorizedKeysFile->new(file => $filename); $ak->read(); @keys = $ak->keys(); $keys[0]->key("123"); is($keys[0]->key(), "123", "modified key"); $ak->save(); $ak = Net::SSH::AuthorizedKeysFile->new(file => $filename); $ak->read(); is($keys[0]->key(), "123", "modified key"); is($keys[1]->key(), "AAAAAlkj2lkjalsdfkjlaskdfj234", "unmodified key"); # ECDSA support $ak = Net::SSH::AuthorizedKeysFile->new(file => "$cdir/ak-ecdsa.txt"); $ak->read(); @keys = $ak->keys(); is($keys[0]->type(), "ssh-2", "type"); # ecdsa-sha2-nistp521 is($keys[1]->type(), "ssh-2", "type"); is($keys[0]->key(), "AAAAAlkj2lkjalsdfkjlaskdfj234", "key"); is($keys[1]->key(), "AAAAAlkj2lkjalsdfkjlaskdfj234", "key"); # Ed25519 support $ak = Net::SSH::AuthorizedKeysFile->new(file => "$cdir/ak-ed25519.txt"); $ak->read(); @keys = $ak->keys(); is($keys[0]->type(), "ssh-2", "type"); # ed25519 is($keys[0]->key(), "AAAAAlkj2lkjalsdfkjlaskdfj234", "key"); Net-SSH-AuthorizedKeysFile-0.18/t/012l4p.t0000644000175000017500000000166213066344633020154 0ustar mschillimschilli###################################################################### # Test suite for Net::SSH::AuthorizedKeysFile # by Mike Schilli ###################################################################### use warnings; use strict; use File::Temp qw(tempfile); use FindBin qw( $Bin ); use Test::More; use Log::Log4perl qw(:easy); use Net::SSH::AuthorizedKeysFile; Log::Log4perl->easy_init({level => $ERROR, file => "stdout"}); my $cdir = "$Bin/canned"; my $ak = Net::SSH::AuthorizedKeysFile->new( file => "$cdir/ak.txt", ); my($tmp_fh, $tmp_file) = tempfile( UNLINK => 1 ); use vars qw($OLDOUT); open(OLDOUT, ">&STDOUT"); open(STDOUT, ">$tmp_file") || die "Can't redirect stdout $tmp_file $!"; select(STDOUT); $| = 1; # make unbuffered $ak->read(); close(STDOUT); open(STDOUT, ">&OLDOUT"); open FILE, "<$tmp_file" or die; my $data = join "", ; close FILE; is $data, "", "nothing printed"; done_testing; Net-SSH-AuthorizedKeysFile-0.18/t/005Empty.t0000644000175000017500000000144512332352213020540 0ustar mschillimschilli###################################################################### # Test suite for Net::SSH::AuthorizedKeysFile # by Mike Schilli ###################################################################### use warnings; use strict; use Log::Log4perl qw(:easy); #Log::Log4perl->easy_init($DEBUG); use Test::More tests => 3; BEGIN { use_ok('Net::SSH::AuthorizedKeysFile') }; my $tdir = "t"; $tdir = "../t" unless -d $tdir; my $cdir = "$tdir/canned"; use Net::SSH::AuthorizedKeysFile; my $ak = Net::SSH::AuthorizedKeysFile->new(file => "$cdir/pk-ssh2.txt"); $ak->read(); my @keys = $ak->keys(); is((scalar @keys), 0, "no keys found"); $ak = Net::SSH::AuthorizedKeysFile->new(file => "$cdir/pk-empty.txt"); $ak->read(); @keys = $ak->keys(); is((scalar @keys), 0, "no keys found"); Net-SSH-AuthorizedKeysFile-0.18/t/006Broken.t0000644000175000017500000000345512332352213020666 0ustar mschillimschilli###################################################################### # Test suite for Net::SSH::AuthorizedKeysFile # by Mike Schilli ###################################################################### use warnings; use strict; use File::Temp qw(tempfile); use Log::Log4perl qw(:easy); #Log::Log4perl->easy_init($DEBUG); use Test::More tests => 10; BEGIN { use_ok('Net::SSH::AuthorizedKeysFile') }; my $tdir = "t"; $tdir = "../t" unless -d $tdir; my $cdir = "$tdir/canned"; use Net::SSH::AuthorizedKeysFile; my $ak = Net::SSH::AuthorizedKeysFile->new( file => "$cdir/ak-broken.txt", ); my $rc = $ak->read(); is($rc, 1, "read ok on broken authorized_keys (no strict)"); $ak = Net::SSH::AuthorizedKeysFile->new( file => "$cdir/ak-broken.txt", abort_on_error => 1, ); $rc = $ak->read(); is($rc, undef, "read fail on broken authorized_keys (strict)"); is($ak->error(), "Line 1: [ene mene meck] rejected by all parsers", "error message"); $ak = Net::SSH::AuthorizedKeysFile->new(file => "$cdir/ak.txt"); $rc = $ak->read(); is($rc, 1, "read ok on ok authorized_keys"); $ak = Net::SSH::AuthorizedKeysFile->new(file => "$cdir/ak-broken.txt", strict => 1, abort_on_error => 1); $rc = $ak->read(); is($rc, undef, "read fail on broken authorized_keys"); is($ak->error(), "Line 1: [ene mene meck] rejected by all parsers", "error message"); $ak = Net::SSH::AuthorizedKey->parse( 'from="bing.bang.boom",no-pty,,, 1024 35 372'); my $options = $ak->options(); is($options->{from}, "bing.bang.boom", "options with trailing commas"); is($options->{"no-pty"}, 1, "options with trailing commas"); is(join("-", sort keys %$options), "from-no-pty", "options with trailing commas"); Net-SSH-AuthorizedKeysFile-0.18/t/004Mixed.t0000644000175000017500000000243312332352213020505 0ustar mschillimschilli###################################################################### # Test suite for Net::SSH::AuthorizedKeysFile # by Mike Schilli ###################################################################### use warnings; use strict; #use Log::Log4perl qw(:easy); #Log::Log4perl->easy_init($DEBUG); use Test::More tests => 7; BEGIN { use_ok('Net::SSH::AuthorizedKeysFile') }; my $tdir = "t"; $tdir = "../t" unless -d $tdir; my $cdir = "$tdir/canned"; use Net::SSH::AuthorizedKeysFile; my $ak = Net::SSH::AuthorizedKeysFile->new(file => "$cdir/ak-mixed.txt"); $ak->read(); my @keys = $ak->keys(); is($keys[0]->email(), 'foo@bar.com', "email1"); is($keys[1]->email(), 'bar@foo.com', "email2"); is($keys[2]->email(), 'quack@schmack.com', "email3"); my $org_data = slurp("$cdir/ak-mixed.txt"); $org_data =~ s/^\s*#.*\n//mg; is($ak->as_string(), $org_data, "write-back"); $ak = Net::SSH::AuthorizedKeysFile->new(file => "$cdir/ak-ssh1-weirdo.txt"); $ak->read(); @keys = $ak->keys(); is(scalar @keys, 1, "1 key found"); is($keys[0]->email(), 'bozo@quack.schmack.com', "email4"); ########################################### sub slurp { ########################################### open FILE, "$_[0]" or die $!; my $data = join "", ; close FILE; return $data; } Net-SSH-AuthorizedKeysFile-0.18/t/003Format.t0000644000175000017500000000515712332352213020674 0ustar mschillimschilli###################################################################### # Test suite for Net::SSH::AuthorizedKeysFile # by Mike Schilli ###################################################################### use warnings; use strict; use File::Temp qw(tempfile); use File::Copy; #use Log::Log4perl qw(:easy); #Log::Log4perl->easy_init($DEBUG); use Test::More tests => 17; BEGIN { use_ok('Net::SSH::AuthorizedKeysFile') }; my $tdir = "t"; $tdir = "../t" unless -d $tdir; my $cdir = "$tdir/canned"; use Net::SSH::AuthorizedKeysFile; my $ak = Net::SSH::AuthorizedKeysFile->new(file => "$cdir/ak-manpage.txt"); $ak->read(); my @keys = $ak->keys(); is($keys[0]->email(), 'user@example.net', "email"); is($keys[1]->option('from'), '*.sales.example.net,!pc.sales.example.net', "from with comma"); is($keys[1]->option('From'), '*.sales.example.net,!pc.sales.example.net', "from case insensitive"); is($keys[1]->email(), 'john@example.net', "comment"); #command="dump /home",no-pty,no-port-forwarding ssh-dss AAAAC3...51R== example.net is($keys[2]->option('command'), 'dump /home', "options including blank"); is($keys[2]->option('no-pty'), 1, "no-pty option set"); is($keys[2]->option('no-port-forwarding'), 1, "no-pty option set"); is($keys[2]->encryption(), "ssh-dss", "encryption"); is($keys[2]->email(), 'example.net', "email"); #permitopen="192.0.2.1:80",permitopen="192.0.2.2:25" ssh-dss AAAAB5...21S== is($keys[3]->option('permitopen')->[0], "192.0.2.1:80", "option array"); is($keys[3]->option('permitopen')->[1], "192.0.2.2:25", "option array"); #tunnel="0",command="sh /etc/netstart tun0" ssh-rsa AAAA...== jane@example.net is($keys[4]->option('command'), "sh /etc/netstart tun0", "command with blanks"); is($keys[4]->email(), 'jane@example.net', "comment"); # Modifications my($fh, $filename) = tempfile(); # Modify a authkey file copy "$cdir/ak-manpage.txt", $filename; my $ak2 = Net::SSH::AuthorizedKeysFile->new(file => $filename); $ak2->read(); @keys = $ak2->keys(); # Write option containing blank $keys[4]->option('command', "waah waah waah"); $ak2->save(); # Read in modifications my $ak3 = Net::SSH::AuthorizedKeysFile->new(file => $filename); $ak3->read(); @keys = $ak3->keys(); is($keys[4]->option("command"), 'waah waah waah', "read back option with blanks"); # Comments with blanks $ak = Net::SSH::AuthorizedKeysFile->new(file => "$cdir/ak-comments.txt"); $ak->read(); @keys = $ak->keys(); # Write option containing blank is($keys[1]->comment(), 'Quack Schmack quack@schmack.com', "comments with blanks"); is($keys[2]->comment(), 'Quack Schmack, quack@schmack.com', "comments with commas"); Net-SSH-AuthorizedKeysFile-0.18/lib/0000755000175000017500000000000013066345317017343 5ustar mschillimschilliNet-SSH-AuthorizedKeysFile-0.18/lib/Net/0000755000175000017500000000000013066345317020071 5ustar mschillimschilliNet-SSH-AuthorizedKeysFile-0.18/lib/Net/SSH/0000755000175000017500000000000013066345317020526 5ustar mschillimschilliNet-SSH-AuthorizedKeysFile-0.18/lib/Net/SSH/AuthorizedKey/0000755000175000017500000000000013066345317023315 5ustar mschillimschilliNet-SSH-AuthorizedKeysFile-0.18/lib/Net/SSH/AuthorizedKey/SSH2.pm0000644000175000017500000000710712446175263024401 0ustar mschillimschilli########################################### package Net::SSH::AuthorizedKey::SSH2; ########################################### use strict; use warnings; use Net::SSH::AuthorizedKey::Base; use base qw(Net::SSH::AuthorizedKey::Base); use Log::Log4perl qw(:easy); # No additional options, only global ones our %VALID_OPTIONS = (); our $KEYTYPE_REGEX = qr/rsa|dsa|ssh-rsa|ssh-dss|ssh-ed25519|ecdsa-\S+/; our @REQUIRED_FIELDS = qw( encryption ); __PACKAGE__->make_accessor( $_ ) for (@REQUIRED_FIELDS); ########################################### sub new { ########################################### my($class, %options) = @_; return $class->SUPER::new( %options, type => "ssh-2" ); } ########################################### sub as_string { ########################################### my($self) = @_; my $string = $self->options_as_string(); $string .= " " if length $string; $string .= "$self->{encryption} $self->{key}"; $string .= " $self->{email}" if length $self->{email}; return $string; } ########################################### sub parse_multi_line { ########################################### my($self, $string) = @_; my @fields = (); while($string =~ s/^(.*):\s+(.*)//gm) { my($field, $value) = ($1, $2); # remove quotes $value =~ s/^"(.*)"$/$1/; push @fields, $field, $value; my $lcfield = lc $field; if( $self->accessor_exists( $lcfield ) ) { $self->$lcfield( $value ); } else { WARN "Ignoring unknown field '$field'"; } } # Rest is the key, split across several lines $string =~ s/\n//g; $self->key( $string ); $self->type( "ssh-2" ); # Comment: "rsa-key-20090703" if($self->comment() =~ /\b(.*?)-key/) { $self->encryption( "ssh-" . $1 ); } elsif( ! $self->{strict} ) { WARN "Unknown encryption [", $self->comment(), "] fixed to ssh-rsa"; $self->encryption( "ssh-rsa" ); } } ########################################### sub key_read { ############################################ my($class, $line) = @_; if($line !~ s/^($KEYTYPE_REGEX)\s*//) { DEBUG "No SSH2 keytype found"; return undef; } my $encryption = $1; DEBUG "Parsed encryption $encryption"; if($line !~ s/^(\S+)\s*//) { DEBUG "No SSH2 key found"; return undef; } my $key = $1; DEBUG "Parsed key $key"; my $email = $line; my $obj = __PACKAGE__->new(); $obj->encryption( $encryption ); $obj->key( $key ); $obj->email( $email ); $obj->comment( $email ); return $obj; } ########################################### sub sanity_check { ########################################### my($self) = @_; for my $field (@REQUIRED_FIELDS) { if(! length $self->$field()) { WARN "ssh-2 sanity check failed '$field' requirement"; return undef; } } return 1; } ########################################### sub option_type { ########################################### my($self, $option) = @_; if(exists $VALID_OPTIONS{ $option }) { return $VALID_OPTIONS{ $option }; } return undef; } 1; __END__ =head1 NAME Net::SSH::AuthorizedKey::SSH2 - Net::SSH::AuthorizedKey subclass for ssh-2 =head1 DESCRIPTION See Net::SSH::AuthorizedKey. =head1 LEGALESE Copyright 2005 by Mike Schilli, all rights reserved. This program is free software, you can redistribute it and/or modify it under the same terms as Perl itself. =head1 AUTHOR 2005, Mike Schilli Net-SSH-AuthorizedKeysFile-0.18/lib/Net/SSH/AuthorizedKey/SSH1.pm0000644000175000017500000000531612332352213024362 0ustar mschillimschilli########################################### package Net::SSH::AuthorizedKey::SSH1; ########################################### use strict; use warnings; use Net::SSH::AuthorizedKey::Base; use base qw(Net::SSH::AuthorizedKey::Base); use Log::Log4perl qw(:easy); our @REQUIRED_FIELDS = qw( keylen exponent ); __PACKAGE__->make_accessor( $_ ) for @REQUIRED_FIELDS; # No additional options, only global ones our %VALID_OPTIONS = (); ########################################### sub new { ########################################### my($class, %options) = @_; return $class->SUPER::new( %options, type => "ssh-1" ); } ########################################### sub key_read { ############################################ my($class, $line) = @_; if($line !~ s/^(\d+)\s*//) { DEBUG "Cannot find ssh-1 keylen"; return undef; } my $keylen = $1; DEBUG "Parsed keylen: $keylen"; if($line !~ s/^(\d+)\s*//) { DEBUG "Cannot find ssh-1 exponent"; return undef; } my $exponent = $1; DEBUG "Parsed exponent: $exponent"; if($line !~ s/^(\d+)\s*//) { DEBUG "Cannot find ssh-1 key"; return undef; } my $key = $1; DEBUG "Parsed key: $key"; my $obj = __PACKAGE__->new(); $obj->keylen( $keylen ); $obj->key( $key ); $obj->exponent( $exponent ); $obj->email( $line ); $obj->comment( $line ); return $obj; } ########################################### sub as_string { ########################################### my($self) = @_; my $string = $self->options_as_string(); $string .= " " if length $string; $string .= "$self->{keylen} $self->{exponent} $self->{key}"; $string .= " $self->{email}" if length $self->{email}; return $string; } ########################################### sub sanity_check { ########################################### my($self) = @_; for my $field (@REQUIRED_FIELDS) { if(! length $self->$field()) { WARN "ssh-1 sanity check failed '$field' requirement"; return undef; } } return 1; } ########################################### sub option_type { ########################################### my($self, $option) = @_; if(exists $VALID_OPTIONS{ $option }) { return $VALID_OPTIONS{ $option }; } return undef; } 1; __END__ =head1 NAME Net::SSH::AuthorizedKey::SSH1 - Net::SSH::AuthorizedKey subclass for ssh-1 =head1 DESCRIPTION See Net::SSH::AuthorizedKey. =head1 LEGALESE Copyright 2005 by Mike Schilli, all rights reserved. This program is free software, you can redistribute it and/or modify it under the same terms as Perl itself. =head1 AUTHOR 2005, Mike Schilli Net-SSH-AuthorizedKeysFile-0.18/lib/Net/SSH/AuthorizedKey/Base.pm0000644000175000017500000002077712332352213024526 0ustar mschillimschilli########################################### package Net::SSH::AuthorizedKey::Base; ########################################### use strict; use warnings; use Log::Log4perl qw(:easy); use Text::ParseWords; use Digest::MD5 qw(md5_hex); # Accessors common for both ssh1 and ssh2 keys our @accessors = qw(key type error email comment); __PACKAGE__->make_accessor( $_ ) for @accessors; # Some functions must be implemented in the subclass do { no strict qw(refs); *{__PACKAGE__ . "::$_"} = sub { die "Whoa! '$_' in the virtual base class has to be ", " implemented by a real subclass."; }; } for qw(option_type as_string); # Options accepted by all keys our %VALID_OPTIONS = ( "no-port-forwarding" => 1, "no-agent-forwarding" => 1, "no-x11-forwarding" => 1, "no-pty" => 1, "no-user-rc" => 1, command => "s", environment => "s", from => "s", permitopen => "s", tunnel => "s", ); ########################################### sub new { ########################################### my($class, %options) = @_; my $self = { error => "(no error)", option_order => [], %options, }; bless $self, $class; return $self; } ########################################### sub option_type_global { ########################################### my($self, $key) = @_; if(exists $VALID_OPTIONS{ $key }) { return $VALID_OPTIONS{ $key }; } # Maybe the subclass knows about it return $self->option_type($key); } ########################################### sub options { ########################################### my($self) = @_; return { map { $_ => $self->option( $_ ) } keys %{ $self->{ options } } }; } ########################################### sub option { ########################################### my($self, $key, $value, $append) = @_; $key = lc $key; my $option_type = $self->option_type_global($key); if(! defined $option_type) { LOGWARN "Illegal option '$key'"; return undef; } if(defined $value) { if( $append ) { if( $self->{options}->{$key} and ref($self->{options}->{$key}) ne "ARRAY" ) { $self->{options}->{$key} = [ $self->{options}->{$key} ]; } } else { $self->option_delete( $key ); } if($option_type eq "s") { if( $self->{options}->{$key} and ref($self->{options}->{$key}) eq "ARRAY" ) { DEBUG "Adding option $key to $value"; push @{ $self->{options}->{$key} }, $value; } else { DEBUG "Setting option $key to $value"; $self->{options}->{$key} = $value; } } else { $self->{options}->{$key} = undef; } push @{ $self->{option_order} }, $key; } if( "$option_type" eq "1" ) { return exists $self->{options}->{$key}; } return $self->{options}->{$key}; } ########################################### sub option_delete { ########################################### my($self, $key) = @_; $key = lc $key; @{ $self->{option_order} } = grep { $_ ne $key } @{ $self->{option_order} }; delete $self->{options}->{$key}; } ########################################### sub options_as_string { ########################################### my($self) = @_; my $string = ""; my @parts = (); for my $option ( @{ $self->{option_order} } ) { if(defined $self->{options}->{$option}) { if(ref($self->{options}->{$option}) eq "ARRAY") { for (@{ $self->{options}->{$option} }) { push @parts, option_quote($option, $_); } } else { push @parts, option_quote($option, $self->{options}->{$option}); } } else { push @parts, $option; } } return join(',', @parts); } ########################################### sub option_quote { ########################################### my($option, $text) = @_; $text =~ s/([\\"])/\\$1/g; return "$option=\"" . $text . "\""; } ########################################### sub parse { ########################################### my($class, $string) = @_; DEBUG "Parsing line '$string'"; # Clean up leading whitespace $string =~ s/^\s+//; $string =~ s/^#.*//; if(! length $string) { DEBUG "Nothing to parse"; return; } if(my $key = $class->key_read( $string ) ) { # We found a key without options $key->{options} = {}; DEBUG "Found ", $key->type(), " key: ", $key->as_string(); return $key; } # No key found. Probably there are options in front of the key. # By the way: the openssh-5.x parser doesn't allow escaped # backslashes (\\), so we don't either. my $rc = ( (my $key_string = $string) =~ s/^((?: (?:"(?:\\"|.)*?)"| \S )+ ) //x ); my $options_string = ($rc ? $1 : ""); $key_string =~ s/^\s+//; DEBUG "Trying line with options stripped: [$key_string]"; if(my $key = $class->key_read( $key_string ) ) { # We found a key with options $key->{options} = {}; $key->options_parse( $options_string ); DEBUG "Found ", $key->type(), " key: ", $key->as_string(); return $key; } DEBUG "$class cannot parse line: $string"; return undef; } ########################################### sub options_parse { ########################################### my($self, $string) = @_; DEBUG "Parsing options: [$string]"; my @options = parse_line(qr/\s*,\s*/, 0, $string); # delete empty/undefined fields @options = grep { defined $_ and length $_ } @options; DEBUG "Parsed options: ", join(' ', map { "[$_]" } @options); for my $option (@options) { my($key, $value) = split /=/, $option, 2; $value = 1 unless defined $value; $value =~ s/^"(.*)"$/$1/; # remove quotes $self->option($key, $value, 1); } } ########################################### sub fingerprint { ########################################### my($self) = @_; my $data = $self->options(); my $string = join '', map { $_ => $data->{$_} } sort keys %$data; $string .= $self->key(); return md5_hex($string); } ################################################## # Poor man's Class::Struct ################################################## sub make_accessor { ################################################## my($package, $name) = @_; no strict qw(refs); my $code = <{$name} = \$value; } if(exists \$self->{$name}) { return (\$self->{$name}); } else { return ""; } } EOT if(! defined *{"$package\::$name"}) { eval $code or die "$@"; } } 1; __END__ =head1 NAME Net::SSH::AuthorizedKey::Base - Virtual Base Class for ssh keys =head1 SYNOPSIS # Documentation to understand methods shared # by all parsers. Not for direct use. =head1 DESCRIPTION This is the key parser base class, offering methods common to all parsers. Don't use it directly, but read the documentation below to see what functionality all parsers offer. =over 4 =item error() If a parser fails for any reason, it will leave a textual description of the error that threw it off. This methods retrieves the error text. =item options() =item key() The actual content of the key, either a big number in case of ssh-1 or a base64-encoded string for ssh-2. =item type() Type of a key. (Somewhat redundant, as you could also check what subclass a key is of). Either set to C<"ssh-1"> or C<"ssh-2">. =item email() Identical with comment(). =item comment() Identical with email(). This is the text that follows in the authorized_keys file after the key content. Mostly used for emails and host names. =back =head1 LEGALESE Copyright 2005-2009 by Mike Schilli, all rights reserved. This program is free software, you can redistribute it and/or modify it under the same terms as Perl itself. =head1 AUTHOR 2005, Mike Schilli Net-SSH-AuthorizedKeysFile-0.18/lib/Net/SSH/AuthorizedKey.pm0000644000175000017500000001232612332352213023643 0ustar mschillimschilli########################################### package Net::SSH::AuthorizedKey; ########################################### use strict; use warnings; use Log::Log4perl qw(:easy); use Net::SSH::AuthorizedKey::SSH1; use Net::SSH::AuthorizedKey::SSH2; ########################################### sub parse { ########################################### my($class, $string) = @_; my @subclasses = qw( Net::SSH::AuthorizedKey::SSH1 Net::SSH::AuthorizedKey::SSH2 ); for my $subclass ( @subclasses ) { DEBUG "Parsing with $subclass: $string"; my $pk = $subclass->parse( $string ); if($pk) { DEBUG "Successfully parsed $subclass key"; return $pk; } } return undef; } 1; __END__ =head1 NAME Net::SSH::AuthorizedKey - Virtual Base Class for SSH Public Keys =head1 SYNOPSIS use Net::SSH::AuthorizedKey; # Either parse a string (without leading whitespace or comments): my $key = Net::SSH::AuthorizedKey->parse( $line ); if(defined $key) { # ssh-1 or ssh-2 print "Key parsed, type is ", $key->type(), "\n"; } else { die "Cannot parse key '$line'"; } # ... or create an object yourself: my $pubkey = Net::SSH::AuthorizedKey->new( options => { from => 'foo@bar.com', "no-agent-forwarding" => 1 }, key => "123....890", keylen => 1024, exponent => 35, type => "ssh-1", ); =head1 DESCRIPTION Net::SSH::AuthorizedKey is a virtual base class for ssh public keys. Real implementations of it are Net::SSH::AuthorizedKey::SSH1 and Net::SSH::AuthorizedKey::SSH2. The only way to using it directly is by calling its parse() method, and passing it an authorized_keys string (aka a line from an authorized_keys file). If it recognizes either a ssh-1 or a ssh-2 type key, it will return a Net::SSH::AuthorizedKey::SSH1 or a Net::SSH::AuthorizedKey::SSH2 object, both of which support the accessor methods defined in the FIELDS section below. The as_string() method will cobble the (perhaps modified) fields together and return them as a string suitable as a line for an authorized_keys file. =head2 METHODS =over 4 =item C Reads in a single text line containing a ssh-1 or ssh-2 key. Returns a Net::SSH::AuthorizedKey::SSH1 or a Net::SSH::AuthorizedKey::SSH2 object, or C in case of an error. =item C Returns a MD5 hex hash of the parsed key. The hash is unique for functionally identical keys. Fields not contributing to the key's functional uniqueness are ignored. =item C Returns the last parsing error encountered as a text string. =item C Return the object as a string suitable as a autorized_keys line. =back =head2 FIELDS All of the following fields are available via accessors: =over 4 =item C Type of ssh key, usually C<"ssh-1"> or C<"ssh-2">. =item C Public key, either a long number (ssh-1) or a line of alphanumeric characters (ssh-2). =item C Length of the key in bit (e.g. 1024). =item C Two-digit number in front of the key in ssh-1 authorized_keys lines. =item C Returns a reference to a hash with options key/value pairs, listed in front of the key. =back =head2 IMPLEMENTATION REFERENCE The key parsers implemented in this distribution are implemented similarily as the authorized_keys file parser in the openssh source distribution. Openssh contains the authorized_keys parser in its auth2_pubkey.c file. The user_key_allowed2() function opens the file and reads it line by line, ignoring leading whitespace, empty and comment lines. After that, if a line doesn't contain a plain key, the parser skips ahead until the first whitespace (zooming through quoted areas "..." and interpreting '\"' as an escaped quote), then skips this whitespace and tries to read a key one more time. Regarding options, the Perl parser isn't as elaborate with semantic peculiarities as openssh's auth_parse_options(), but this might be added in the future. =head1 NOTES FOR SUBCLASS DEVELOPERS If you're just using Net::SSH::AuthorizedKey to parse keys, the following section doesn't concern you. It's only relevant if you add new subclasses to this package, on top of what's already provided. Net::SSH::AuthorizedKey is a (semi-)virtual base class that implements options handling for its SSH1 and SSH2 subclasses. SSH key lines can contain options that carry values (like command="ls") and binary options that are either set or unset (like "no_agent_forwarding"). To distinguish the two, and to provide a set of allowed option names, the subclass has to implement the method option_type(), which takes an option name, and returns =over 4 =item * undef if the option is not supported =item * "s" if the option is a "string" option that carries a value =item * 1 if the option is a binary option =back The subclasses Net::SSH::AuthorizedKey::SSH1 and Net::SSH::AuthorizedKey::SSH2 are doing this already. =head1 LEGALESE Copyright 2005-2009 by Mike Schilli, all rights reserved. This program is free software, you can redistribute it and/or modify it under the same terms as Perl itself. =head1 AUTHOR 2005, Mike Schilli Net-SSH-AuthorizedKeysFile-0.18/lib/Net/SSH/AuthorizedKeysFile.pm0000644000175000017500000002464213066345130024637 0ustar mschillimschilli########################################### package Net::SSH::AuthorizedKeysFile; ########################################### use strict; use warnings; use Log::Log4perl qw(:easy); use Text::ParseWords; use Net::SSH::AuthorizedKey; use Net::SSH::AuthorizedKey::SSH1; use Net::SSH::AuthorizedKey::SSH2; our $VERSION = "0.18"; ########################################### sub new { ########################################### my($class, @options) = @_; my $self = { default_file => "$ENV{HOME}/.ssh/authorized_keys", strict => 0, abort_on_error => 0, append => 0, ridiculous_line_len => 100_000, @options, }; bless $self, $class; # We allow keys to be set in the constructor my $keys = $self->{keys} if exists $self->{keys}; $self->reset(); $self->{keys} = $keys if defined $keys; return $self; } ########################################### sub sanity_check { ########################################### my($self, $file) = @_; $self->{file} = $file if defined $file; $self->{file} = $self->{default_file} if !defined $self->{file}; my $result = undef; my $fh; if(! open $fh, "<$self->{file}") { ERROR "Cannot open file $self->{file}"; return undef; } while( defined(my $rc = sysread($fh, my $chunk, $self->{ridiculous_line_len}))) { if($rc < $self->{ridiculous_line_len}) { $result = 1; last; } if(index( $chunk, "\n" ) >= 0) { # contains a newline, looks good next; } # we've got a line that's between ridiculous_line_len and # 2*ridiculous_line_len characters long. Pull the plug. $self->error("File $self->{file} contains insanely long lines " . "(> $self->{ridiculous_line_len} chars"); last; } DONE: close $fh; if(!$result) { ERROR "Sanity check of file $self->{file} failed"; } return $result; } ########################################### sub keys { ########################################### my($self) = @_; return @{$self->{keys}}; } ########################################### sub reset { ########################################### my($self) = @_; $self->{keys} = []; $self->{content} = ""; $self->{error} = undef; } ########################################### sub content { ########################################### my($self, $new_content) = @_; if( defined $new_content ) { $self->reset(); $self->{content} = $new_content; } return $self->{content}; } ########################################### sub line_parse { ########################################### my($self, $line, $line_number) = @_; chomp $line; DEBUG "Parsing line [$line]"; $self->error( "" ); my $pk = Net::SSH::AuthorizedKey->parse( $line ); if( !$pk ) { my $msg = "[$line] rejected by all parsers"; WARN $msg; $self->error($msg); return undef; } if(! $self->{strict} or $pk->sanity_check()) { return $pk; } WARN "Key [$line] failed sanity check"; if($self->{strict}) { $self->error( $pk->error() ); return undef; } # Key is corrupted, but ok in non-strict mode return $pk; } ########################################### sub parse { ########################################### my($self) = @_; $self->{keys} = []; $self->{error} = ""; my $line_number = 0; for my $line (split /\n/, $self->{content}) { $line_number++; $line =~ s/^\s+//; # Remove leading blanks $line =~ s/\s+$//; # Remove trailing blanks next if $line =~ /^$/; # Ignore empty lines next if $line =~ /^#/; # Ignore comment lines my $key = $self->line_parse($line, $line_number); if( defined $key ) { push @{$self->{keys}}, $key; } else { if($self->{abort_on_error}) { $self->error("Line $line_number: " . $self->error()); return undef; } } } return 1; } ########################################### sub read { ########################################### my($self, $file) = @_; $self->reset(); $self->{file} = $file if defined $file; $self->{file} = $self->{default_file} if !defined $self->{file}; $self->{content} = ""; DEBUG "Reading in $self->{file}"; open FILE, "<$self->{file}" or LOGDIE "Cannot open $self->{file}"; while() { $self->{content} .= $_; } close FILE; return $self->parse(); } ########################################### sub as_string { ########################################### my($self) = @_; my $string = ""; for my $key ( @{ $self->{keys} } ) { $string .= $key->as_string . "\n"; } return $string; } ########################################### sub save { ########################################### my($self, $file) = @_; if(!defined $file) { $file = $self->{file}; } if(! open FILE, ">$file") { $self->error("Cannot open $file ($!)"); WARN $self->error(); return undef; } print FILE $self->as_string(); close FILE; } ########################################### sub append { ########################################### my($self, $key) = @_; $self->{append} = 1; } ########################################### sub error { ########################################### my($self, $text) = @_; if(defined $text) { $self->{error} = $text; if(length $text) { ERROR "$text"; } } return $self->{error}; } ########################################### sub ssh_dir { ########################################### my($self, $user) = @_; if(!defined $user) { my $uid = $>; $user = getpwuid($uid); if(!defined $user) { ERROR "getpwuid of $uid failed ($!)"; return undef; } } my @pwent = getpwnam($user); if(! defined $pwent[0]) { ERROR "getpwnam of $user failed ($!)"; return undef; } my $home = $pwent[7]; return File::Spec->catfile($home, ".ssh"); } ########################################### sub path_locate { ########################################### my($self, $user) = @_; my $ssh_dir = $self->ssh_dir($user); return undef if !defined $ssh_dir; return File::Spec->catfile($ssh_dir, "authorized_keys"); } 1; __END__ =head1 NAME Net::SSH::AuthorizedKeysFile - Read and modify ssh's authorized_keys files =head1 SYNOPSIS use Net::SSH::AuthorizedKeysFile; # Reads $HOME/.ssh/authorized_keys by default my $akf = Net::SSH::AuthorizedKeysFile->new(); $akf->read("authorized_keys"); # Iterate over entries for my $key ($akf->keys()) { print $key->as_string(), "\n"; } # Modify entries: for my $key ($akf->keys()) { $key->option("from", 'quack@quack.com'); $key->keylen(1025); } # Save changes back to $HOME/.ssh/authorized_keys $akf->save() or die "Cannot save"; =head1 DESCRIPTION Net::SSH::AuthorizedKeysFile reads and modifies C files. C files contain public keys and meta information to be used by C on the remote host to let users in without having to type their password. =head1 METHODS =over 4 =item C Creates a new Net::SSH::AuthorizedKeysFile object and reads in the authorized_keys file. The filename defaults to C<$HOME/.ssh/authorized_keys> unless overridden with Net::SSH::AuthorizedKeysFile->new( file => "/path/other_authkeys_file" ); Normally, the C method described below will just silently ignore faulty lines and only gobble up keys that either one of the two parsers accepts. If you want it to be stricter, set Net::SSH::AuthorizedKeysFile->new( file => "authkeys_file", abort_on_error => 1 ); and read will immediately abort after the first faulty line. Also, the key parsers are fairly lenient in default mode. Adding strict => 1 adds sanity checks before a key is accepted. =item C Reads in the file defined by new(). By default, strict mode is off and read() will silently ignore faulty lines. If it's on (see new() above), read() will immediately abort after the first faulty line. A textual description of the last error will be available via error(). =item C Contains the original file content, read by C earlier. Can be used to set arbitrary content: $keysfile->content( "some\nrandom\nlines\n" ); and have C operate on a string instead of an actual file this way. =item C Returns a list of Net::SSH::AuthorizedKey objects. Methods are described in L. =item C String representation of all keys, ultimately the content that gets written out when calling the C method. Note that comments from the original file are lost. =item C Write changes back to the authorized_keys file using the as_string() method described above. Note that comments from the original file are lost. Optionally takes a file name parameter, so calling C<$akf-Esave("foo.txt")> will save the data in the file "foo.txt" instead of the file the data was read from originally. Returns 1 if successful, and undef on error. In case of an error, error() contains a textual error description. =item C Run a sanity check on the currently selected authorized_keys file. If it contains insanely long lines, then parsing with read() (and potential crashes because of out-of-memory errors) should be avoided. =item C Locate the .ssh dir of a given user. If no user name is given, ssh_dir will look up the .ssh dir of the effective user. Typically returns something like "/home/gonzo/.ssh". =item C Locate the authorized_keys file of a given user. Typically returns something like "/home/gonzo/.ssh/authorized_keys". See C for how the containing directory is located with and without a given user name. =item C Description of last error that occurred. =back =head1 LEGALESE Copyright 2005-2009 by Mike Schilli, all rights reserved. This program is free software, you can redistribute it and/or modify it under the same terms as Perl itself. =head1 AUTHOR 2005, Mike Schilli Net-SSH-AuthorizedKeysFile-0.18/README0000644000175000017500000001061513066345317017460 0ustar mschillimschilli###################################################################### Net::SSH::AuthorizedKeysFile 0.18 ###################################################################### NAME Net::SSH::AuthorizedKeysFile - Read and modify ssh's authorized_keys files SYNOPSIS use Net::SSH::AuthorizedKeysFile; # Reads $HOME/.ssh/authorized_keys by default my $akf = Net::SSH::AuthorizedKeysFile->new(); $akf->read("authorized_keys"); # Iterate over entries for my $key ($akf->keys()) { print $key->as_string(), "\n"; } # Modify entries: for my $key ($akf->keys()) { $key->option("from", 'quack@quack.com'); $key->keylen(1025); } # Save changes back to $HOME/.ssh/authorized_keys $akf->save() or die "Cannot save"; DESCRIPTION Net::SSH::AuthorizedKeysFile reads and modifies "authorized_keys" files. "authorized_keys" files contain public keys and meta information to be used by "ssh" on the remote host to let users in without having to type their password. METHODS "new" Creates a new Net::SSH::AuthorizedKeysFile object and reads in the authorized_keys file. The filename defaults to "$HOME/.ssh/authorized_keys" unless overridden with Net::SSH::AuthorizedKeysFile->new( file => "/path/other_authkeys_file" ); Normally, the "read" method described below will just silently ignore faulty lines and only gobble up keys that either one of the two parsers accepts. If you want it to be stricter, set Net::SSH::AuthorizedKeysFile->new( file => "authkeys_file", abort_on_error => 1 ); and read will immediately abort after the first faulty line. Also, the key parsers are fairly lenient in default mode. Adding strict => 1 adds sanity checks before a key is accepted. "read" Reads in the file defined by new(). By default, strict mode is off and read() will silently ignore faulty lines. If it's on (see new() above), read() will immediately abort after the first faulty line. A textual description of the last error will be available via error(). "content" Contains the original file content, read by "read()" earlier. Can be used to set arbitrary content: $keysfile->content( "some\nrandom\nlines\n" ); and have "parse()" operate on a string instead of an actual file this way. "keys" Returns a list of Net::SSH::AuthorizedKey objects. Methods are described in Net::SSH::AuthorizedKey. "as_string" String representation of all keys, ultimately the content that gets written out when calling the "save()" method. Note that comments from the original file are lost. "save" Write changes back to the authorized_keys file using the as_string() method described above. Note that comments from the original file are lost. Optionally takes a file name parameter, so calling "$akf->save("foo.txt")" will save the data in the file "foo.txt" instead of the file the data was read from originally. Returns 1 if successful, and undef on error. In case of an error, error() contains a textual error description. "sanity_check" Run a sanity check on the currently selected authorized_keys file. If it contains insanely long lines, then parsing with read() (and potential crashes because of out-of-memory errors) should be avoided. "ssh_dir( [$user] )" Locate the .ssh dir of a given user. If no user name is given, ssh_dir will look up the .ssh dir of the effective user. Typically returns something like "/home/gonzo/.ssh". "path_locate( [$user] )" Locate the authorized_keys file of a given user. Typically returns something like "/home/gonzo/.ssh/authorized_keys". See "ssh_dir()" for how the containing directory is located with and without a given user name. "error" Description of last error that occurred. LEGALESE Copyright 2005-2009 by Mike Schilli, all rights reserved. This program is free software, you can redistribute it and/or modify it under the same terms as Perl itself. AUTHOR 2005, Mike Schilli Net-SSH-AuthorizedKeysFile-0.18/META.json0000664000175000017500000000233213066345317020220 0ustar mschillimschilli{ "abstract" : "Read and modify ssh's authorized_keys files", "author" : [ "Mike Schilli " ], "dynamic_config" : 1, "generated_by" : "ExtUtils::MakeMaker version 7.24, CPAN::Meta::Converter version 2.143240", "license" : [ "unknown" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : "2" }, "name" : "Net-SSH-AuthorizedKeysFile", "no_index" : { "directory" : [ "t", "inc" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "runtime" : { "requires" : { "Digest::MD5" : "0", "File::Copy" : "0", "File::Spec" : "0", "File::Temp" : "0", "Log::Log4perl" : "1", "Test::More" : "0", "Text::ParseWords" : "0" } } }, "release_status" : "stable", "resources" : { "repository" : { "url" : "http://github.com/mschilli/net-ssh-authorizedkeysfile-perl" } }, "version" : "0.18" } Net-SSH-AuthorizedKeysFile-0.18/MANIFEST0000644000175000017500000000150613066345317017730 0ustar mschillimschilliChanges eg/authorized-keys-test lib/Net/SSH/AuthorizedKey.pm lib/Net/SSH/AuthorizedKey/Base.pm lib/Net/SSH/AuthorizedKey/SSH1.pm lib/Net/SSH/AuthorizedKey/SSH2.pm lib/Net/SSH/AuthorizedKeysFile.pm Makefile.PL MANIFEST This list of files MANIFEST.SKIP README t/001Basic.t t/002Ssh-2.t t/003Format.t t/004Mixed.t t/005Empty.t t/006Broken.t t/007ssh-1.t t/008ssh-2.t t/009weird-keys.t t/010sanity.t t/011order.t t/012l4p.t t/canned/ak-broken.txt t/canned/ak-comments.txt t/canned/ak-ecdsa.txt t/canned/ak-ed25519.txt t/canned/ak-manpage.txt t/canned/ak-mixed.txt t/canned/ak-ssh1-weirdo.txt t/canned/ak-ssh2.txt t/canned/ak.txt t/canned/pk-empty.txt t/canned/pk-ssh2.txt META.yml Module YAML meta-data (added by MakeMaker) META.json Module JSON meta-data (added by MakeMaker)