From: Ted Zlatanov Date: Sat, 02 Feb 2013 11:57:29 GMT Subject: Re: [PATCH] git-send-email: add ~/.authinfo parsing Message-ID: <87k3qrx712.fsf@lifelogs.com> In-Reply-To: <20130131193844.GA14460@sigill.intra.peff.net> On Thu, 31 Jan 2013 14:38:45 -0500 Jeff King wrote: JK> On Thu, Jan 31, 2013 at 10:23:51AM -0500, Ted Zlatanov wrote: >> Jeff, is there a way for git-credential to currently support >> authinfo/netrc parsing? I assume that's the right way, instead of using >> Michal's proposal to parse internally? >> >> I'd like to add that, plus support for the 'string' and "string" >> formats, and authinfo.gpg decoding through GPG. I'd write it in Perl, >> if there's a choice. JK> Yes, you could write a credential helper that understands netrc and JK> friends; git talks to the helpers over a socket, so there is no problem JK> with writing it in Perl. See Documentation/technical/api-credentials.txt JK> for an overview, or the sample implementation in credential-store.c for a JK> simple example. I wrote a Perl credential helper for netrc parsing which is pretty robust, has built-in docs with -h, and doesn't depend on external modules. The netrc parser regex was stolen from Net::Netrc. It will by default use ~/.authinfo.gpg, ~/.netrc.gpg, ~/.authinfo, and ~/.netrc (whichever is found first) and this can be overridden with -f. If the file name ends with ".gpg", it will run "gpg --decrypt FILE" and use the output. So non-interactively, that could hang if GPG was waiting for input. Does Git handle that, or should I check for a TTY? Take a look at the proposed patch and let me know if it's usable, if you need a formal copyright assignment, etc. Thanks Ted commit 3d28bc2a610ebcc988eba5443d82d0ded92c24bc Author: Ted Zlatanov Date: Sat Feb 2 06:42:13 2013 -0500 Add contrib/credentials/netrc with GPG support diff --git a/contrib/credential/netrc/git-credential-netrc b/contrib/credential/netrc/git-credential-netrc new file mode 100755 index 0000000..92fc306 --- /dev/null +++ b/contrib/credential/netrc/git-credential-netrc @@ -0,0 +1,242 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Data::Dumper; + +use Getopt::Long; +use File::Basename; + +my $VERSION = "0.1"; + +my %options = ( + help => 0, + debug => 0, + + # identical token maps, e.g. host -> host, will be inserted later + tmap => { + port => 'protocol', + machine => 'host', + path => 'path', + login => 'username', + user => 'username', + password => 'password', + } + ); + +foreach my $v (values %{$options{tmap}}) +{ + $options{tmap}->{$v} = $v; +} + +foreach my $suffix ('.gpg', '') +{ + foreach my $base (qw/authinfo netrc/) + { + my $file = glob("~/.$base$suffix"); + next unless (defined $file && -f $file); + $options{file} = $file ; + } +} + +Getopt::Long::Configure("bundling"); + +# TODO: maybe allow the token map $options{tmap} to be configurable. +GetOptions(\%options, + "help|h", + "debug|d", + "file|f=s", + ); + +if ($options{help}) +{ + my $shortname = basename($0); + $shortname =~ s/git-credential-//; + + print <) +{ + next unless m/([a-z]+)=(.+)/; + + my ($token, $value) = ($1, $2); + die "Unknown search token $1" unless exists $q{$token}; + $q{$token} = $value; +} + +# build reverse token map +my %rmap; +foreach my $k (keys %{$options{tmap}}) +{ + push @{$rmap{$options{tmap}->{$k}}}, $k; +} + +# there are CPAN modules to do this better, but we want to avoid +# dependencies and generally, complex netrc-style files are rare + +if ($debug) +{ + foreach (sort keys %q) + { + printf STDERR "searching for %s = %s\n", + $_, $q{$_} || '(any value)'; + } +} + +LINE: foreach my $line (@data) +{ + + print STDERR "line [$line]\n" if $debug; + my @tok; + # gratefully stolen from Net::Netrc + while (length $line && + $line =~ s/^("((?:[^"]+|\\.)*)"|((?:[^\\\s]+|\\.)*))\s*//) + { + (my $tok = $+) =~ s/\\(.)/$1/g; + push(@tok, $tok); + } + + my %tokens; + while (@tok) + { + my ($k, $v) = (shift @tok, shift @tok); + next unless defined $v; + next unless exists $options{tmap}->{$k}; + $tokens{$options{tmap}->{$k}} = $v; + } + + foreach my $check (sort keys %q) + { + if (exists $tokens{$check} && defined $q{$check}) + { + print STDERR "comparing [$tokens{$check}] to [$q{$check}] in line [$line]\n" if $debug; + next LINE unless $tokens{$check} eq $q{$check}; + } + else + { + print STDERR "we could not find [$check] but it's OK\n" if $debug; + } + } + + print STDERR "line has passed all the search checks\n" if $debug; + foreach my $token (sort keys %rmap) + { + print STDERR "looking for useful token $token\n" if $debug; + next unless exists $tokens{$token}; # did we match? + + foreach my $rctoken (@{$rmap{$token}}) + { + next if defined $q{$rctoken}; # don't re-print given tokens + } + + print STDERR "FOUND: $token=$tokens{$token}\n" if $debug; + printf "%s=%s\n", $token, $tokens{$token}; + } + + last; +} + +sub load +{ + my $file = shift; + # this supports pipes too + my $io = new IO::File($file) or die "Could not open $file: $!\n"; + + return <$io>; # whole file +}