fixed up several broken URLs (minor but annoying)
[gitolite.git] / src / commands / sshkeys-lint
blob3f07b13de624c6b60c14ec0caac4a7e920e4ff12
1 #!/usr/bin/perl
2 use strict;
3 use warnings;
5 # complete rewrite of the sshkeys-lint program. Usage has changed, see
6 # usage() function or run without arguments.
7 use lib $ENV{GL_LIBDIR};
8 use Gitolite::Common;
10 use Getopt::Long;
11 my $admin = 0;
12 my $quiet = 0;
13 my $help = 0;
14 GetOptions( 'admin|a=s' => \$admin, 'quiet|q' => \$quiet, 'help|h' => \$help );
16 use Data::Dumper;
17 $Data::Dumper::Deepcopy = 1;
18 $|++;
20 my $in_gl_section = 0;
21 my $warnings = 0;
22 my $KEYTYPE_REGEX = qr/\b(?:ssh-(?:rsa|dss|ed25519)|ecdsa-sha2-nistp(?:256|384|521))\b/;
24 sub msg {
25 my $warning = shift;
26 return if $quiet and not $warning;
27 $warnings++ if $warning;
28 print "sshkeys-lint: " . ( $warning ? "WARNING: " : "" ) . $_ for @_;
31 usage() if $help;
33 our @pubkeyfiles = @ARGV; @ARGV = ();
34 my $kd = "$ENV{HOME}/.gitolite/keydir";
35 if ( not @pubkeyfiles ) {
36 chomp( @pubkeyfiles = `find $kd -type f -name "*.pub" | sort` );
39 if ( -t STDIN ) {
40 @ARGV = ("$ENV{HOME}/.ssh/authorized_keys");
43 # ------------------------------------------------------------------------
45 my @authkeys;
46 my %seen_fprints;
47 my %pkf_by_fp;
48 msg 0, "==== checking authkeys file:\n";
49 fill_authkeys(); # uses up STDIN
51 if ($admin) {
52 my $fp = fprint("$admin.pub");
53 my $fpu = ( $fp && $seen_fprints{$fp}{user} || 'no access' );
54 # dbg("fpu = $fpu, admin=$admin");
55 #<<<
56 die "\t\t*** FATAL ***\n" .
57 "$admin.pub maps to $fpu, not $admin.\n" .
58 "You will not be able to access gitolite with this key.\n" .
59 "Look for the 'ssh troubleshooting' link in http://gitolite.com/gitolite/ssh.html.\n"
60 if $fpu ne "user $admin";
61 #>>>
64 msg 0, "==== checking pubkeys:\n" if @pubkeyfiles;
65 for my $pkf (@pubkeyfiles) {
66 # get the short name for the pubkey file
67 ( my $pkfsn = $pkf ) =~ s(^$kd/)();
69 my $fp = fprint($pkf);
70 next unless $fp;
71 msg 1, "$pkfsn appears to be a COPY of $pkf_by_fp{$fp}\n" if $pkf_by_fp{$fp};
72 $pkf_by_fp{$fp} ||= $pkfsn;
73 my $fpu = ( $seen_fprints{$fp}{user} || 'no access' );
74 msg 0, "$pkfsn maps to $fpu\n";
77 if ($warnings) {
78 print "\n$warnings warnings found\n";
81 exit $warnings;
83 # ------------------------------------------------------------------------
84 sub fill_authkeys {
85 while (<>) {
86 my $seq = $.;
87 next if ak_comment($_); # also sets/clears $in_gl_section global
88 my $fp = fprint($_);
89 my $user = user($_);
91 check( $seq, $fp, $user );
93 $authkeys[$seq]{fprint} = $fp;
94 $authkeys[$seq]{ustatus} = $user;
98 sub check {
99 my ( $seq, $fp, $user ) = @_;
101 msg 1, "line $seq, $user key found *outside* gitolite section!\n"
102 if $user =~ /^user / and not $in_gl_section;
104 msg 1, "line $seq, $user key found *inside* gitolite section!\n"
105 if $user !~ /^user / and $in_gl_section;
107 if ( $seen_fprints{$fp} ) {
108 #<<<
109 msg 1, "authkeys line $seq ($user) will be ignored by sshd; " .
110 "same key found on line " .
111 $seen_fprints{$fp}{seq} . " (" .
112 $seen_fprints{$fp}{user} . ")\n";
113 return;
114 #>>>
117 $seen_fprints{$fp}{seq} = $seq;
118 $seen_fprints{$fp}{user} = $user;
121 sub user {
122 my $user = '';
123 $user ||= "user $1" if /^command=.*gitolite-shell (.*?)"/;
124 $user ||= "unknown command" if /^command/;
125 $user ||= "shell access" if /$KEYTYPE_REGEX/;
127 return $user;
130 sub ak_comment {
131 local $_ = shift;
132 $in_gl_section = 1 if /^# gitolite start/;
133 $in_gl_section = 0 if /^# gitolite end/;
134 die "gitosis? what's that?\n" if /^#.*gitosis/;
135 return /^\s*(#|$)/;
138 sub fprint {
139 local $_ = shift;
140 my ($fp, $output);
141 if ( /$KEYTYPE_REGEX/ ) {
142 # an actual key was passed. ssh-keygen CAN correctly handle options on
143 # the front of the key, so don't bother to strip them at all.
144 ($fp, $output) = ssh_fingerprint_line($_);
145 } else {
146 # a filename was passed
147 ($fp, $output) = ssh_fingerprint_file($_);
148 # include the line of input as well, as it won't always be included by the ssh-keygen command
149 warn "Bad line: $_\n" unless $fp;
151 # sshkeys-lint should only be run by a trusted admin, so we can give the output here.
152 warn "$output\n" unless $fp;
153 return $fp;
156 # ------------------------------------------------------------------------
157 =for usage
159 Usage: gitolite sshkeys-lint [-q] [optional list of pubkey filenames]
160 (optionally, STDIN can be a pipe or redirected from a file; see below)
162 Look for potential problems in ssh keys.
164 sshkeys-lint expects:
165 - the contents of an authorized_keys file via STDIN, otherwise it uses
166 \$HOME/.ssh/authorized_keys
167 - one or more pubkey filenames as arguments, otherwise it uses all the keys
168 found (recursively) in \$HOME/.gitolite/keydir
170 The '-q' option will print only warnings instead of all mappings.
172 Note that this runs ssh-keygen -l for each line in the authkeys file and each
173 pubkey in the argument list, so be wary of running it on something huge. This
174 is meant for troubleshooting.
176 =cut