20 # identical token maps, e.g. host -> host, will be inserted later
27 password => 'password',
31 # Map each credential protocol token to itself on the netrc side.
32 foreach (values %{$options{tmap}}) {
33 $options{tmap}->{$_} = $_;
36 # Now, $options{tmap} has a mapping from the netrc format to the Git credential
39 # Next, we build the reverse token map.
41 # When $rmap{foo} contains 'bar', that means that what the Git credential helper
42 # protocol calls 'bar' is found as 'foo' in the netrc/authinfo file. Keys in
43 # %rmap are what we expect to read from the netrc/authinfo file.
46 foreach my $k (keys %{$options{tmap}}) {
47 push @{$rmap{$options{tmap}->{$k}}}, $k;
50 Getopt::Long::Configure("bundling");
52 # TODO: maybe allow the token map $options{tmap} to be configurable.
63 my $shortname = basename($0);
64 $shortname =~ s/git-credential-//;
68 $0 [(-f <authfile>)...] [-g <program>] [-d] [-v] [-k] get
70 Version $VERSION by tzz\@lifelogs.com. License: BSD.
74 -f|--file <authfile>: specify netrc-style files. Files with the .gpg
75 extension will be decrypted by GPG before parsing.
76 Multiple -f arguments are OK. They are processed in
77 order, and the first matching entry found is returned
78 via the credential helper protocol (see below).
80 When no -f option is given, .authinfo.gpg, .netrc.gpg,
81 .authinfo, and .netrc files in your home directory are
84 -g|--gpg <program> : specify the program for GPG. By default, this is the
85 value of gpg.program in the git repository or global
88 -k|--insecure : ignore bad file ownership or permissions
90 -d|--debug : turn on debugging (developer info)
92 -v|--verbose : be more verbose (show files and information found)
94 To enable this credential helper:
96 git config credential.helper '$shortname -f AUTHFILE1 -f AUTHFILE2'
98 (Note that Git will prepend "git-credential-" to the helper name and look for it
101 ...and if you want lots of debugging info:
103 git config credential.helper '$shortname -f AUTHFILE -d'
105 ...or to see the files opened and data found:
107 git config credential.helper '$shortname -f AUTHFILE -v'
109 Only "get" mode is supported by this credential helper. It opens every
110 <authfile> and looks for the first entry that matches the requested search
114 The protocol that will be used (e.g., https). (protocol=X)
117 The remote hostname for a network credential. (host=X)
120 The path with which the credential will be used. (path=X)
122 'login|user|username':
123 The credential’s username, if we already have one. (username=X)
125 Thus, when we get this query on STDIN:
131 this credential helper will look for the first entry in every <authfile> that
134 machine github.com port https login tzz
138 machine github.com protocol https login tzz
140 OR... etc. acceptable tokens as listed above. Any unknown tokens are
143 Then, the helper will print out whatever tokens it got from the entry, including
144 "password" tokens, mapping back to Git's helper protocol; e.g. "port" is mapped
145 back to "protocol". Any redundant entry tokens (part of the original query) are
148 Again, note that only the first matching entry from all the <authfile>s,
149 processed in the sequence given on the command line, is used.
151 Netrc/authinfo tokens can be quoted as 'STRING' or "STRING".
153 No caching is performed by this credential helper.
160 my $mode = shift @ARGV;
162 # Credentials must get a parameter, so die if it's missing.
163 die "Syntax: $0 [(-f <authfile>)...] [-d] get" unless defined $mode;
165 # Only support 'get' mode; with any other unsupported ones we just exit.
166 exit 0 unless $mode eq 'get';
168 my $files = $options{file};
170 # if no files were given, use a predefined list.
171 # note that .gpg files come first
172 unless (scalar @$files) {
180 $files = $options{file} = [ map { glob $_ } @candidates ];
183 load_config(\%options);
185 my $query = read_credential_data_from_stdin();
188 foreach my $file (@$files) {
189 my $gpgmode = $file =~ m/\.gpg$/;
191 log_verbose("Unable to read $file; skipping it");
195 # the following check is copied from Net::Netrc, for non-GPG files
196 # OS/2 and Win32 do not handle stat in a way compatible with this check :-(
197 unless ($gpgmode || $options{insecure} ||
201 || $^O =~ /^cygwin/) {
202 my @stat = stat($file);
205 if ($stat[2] & 077) {
206 log_verbose("Insecure $file (mode=%04o); skipping it",
211 if ($stat[4] != $<) {
212 log_verbose("Not owner of $file; skipping it");
218 my @entries = load_netrc($file, $gpgmode);
220 unless (scalar @entries) {
222 log_verbose("Unable to open $file: $!");
224 log_verbose("No netrc entries found in $file");
230 my $entry = find_netrc_entry($query, @entries);
232 print_credential_data($entry, $query);
242 my $gpgmode = shift @_;
246 my @cmd = ($options{'gpg'}, qw(--decrypt), $file);
247 log_verbose("Using GPG to open $file: [@cmd]");
248 open $io, "-|", @cmd;
250 log_verbose("Opening $file...");
251 open $io, '<', $file;
254 # nothing to do if the open failed (we log the error later)
257 # Net::Netrc does this, but the functionality is merged with the file
258 # detection logic, so we have to extract just the part we need
259 my @netrc_entries = net_netrc_loader($io);
261 # these entries will use the credential helper protocol token names
264 foreach my $nentry (@netrc_entries) {
268 if (!defined $nentry->{machine}) {
271 if (defined $nentry->{port} && $nentry->{port} =~ m/^\d+$/) {
272 $num_port = $nentry->{port};
273 delete $nentry->{port};
276 # create the new entry for the credential helper protocol
277 $entry{$options{tmap}->{$_}} = $nentry->{$_} foreach keys %$nentry;
279 # for "host X port Y" where Y is an integer (captured by
280 # $num_port above), set the host to "X:Y"
281 if (defined $entry{host} && defined $num_port) {
282 $entry{host} = join(':', $entry{host}, $num_port);
285 push @entries, \%entry;
291 sub net_netrc_loader {
294 my ($mach, $macdef, $tok, @tok);
298 undef $macdef if /\A\n\Z/;
307 while (length && s/^("((?:[^"]+|\\.)*)"|((?:[^\\\s]+|\\.)*))\s*//) {
308 (my $tok = $+) =~ s/\\(.)/$1/g;
314 if ($tok[0] eq "default") {
316 $mach = { machine => undef };
322 if ($tok eq "machine") {
323 my $host = shift @tok;
324 $mach = { machine => $host };
325 push @entries, $mach;
326 } elsif (exists $options{tmap}->{$tok}) {
328 log_debug("Skipping token $tok because no machine was given");
332 my $value = shift @tok;
333 unless (defined $value) {
334 log_debug("Token $tok had no value, skipping it.");
338 # Following line added by rmerrell to remove '/' escape char in .netrc
339 $value =~ s/\/\\/\\/g;
340 $mach->{$tok} = $value;
341 } elsif ($tok eq "macdef") { # we ignore macros
342 next TOKEN unless $mach;
343 my $value = shift @tok;
352 sub read_credential_data_from_stdin {
353 # the query: start with every token with no value
354 my %q = map { $_ => undef } values(%{$options{tmap}});
357 next unless m/^([^=]+)=(.+)/;
359 my ($token, $value) = ($1, $2);
360 die "Unknown search token $token" unless exists $q{$token};
362 log_debug("We were given search token $token and value $value");
365 foreach (sort keys %q) {
366 log_debug("Searching for %s = %s", $_, $q{$_} || '(any value)');
372 # takes the search tokens and then a list of entries
373 # each entry is a hash reference
374 sub find_netrc_entry {
375 my $query = shift @_;
378 foreach my $entry (@_)
380 my $entry_text = join ', ', map { "$_=$entry->{$_}" } keys %$entry;
381 foreach my $check (sort keys %$query) {
382 if (!defined $entry->{$check}) {
383 log_debug("OK: entry has no $check token, so any value satisfies check $check");
384 } elsif (defined $query->{$check}) {
385 log_debug("compare %s [%s] to [%s] (entry: %s)",
390 unless ($query->{$check} eq $entry->{$check}) {
394 log_debug("OK: any value satisfies check $check");
405 sub print_credential_data {
406 my $entry = shift @_;
407 my $query = shift @_;
409 log_debug("entry has passed all the search checks");
411 foreach my $git_token (sort keys %$entry) {
412 log_debug("looking for useful token $git_token");
413 # don't print unknown (to the credential helper protocol) tokens
414 next TOKEN unless exists $query->{$git_token};
416 # don't print things asked in the query (the entry matches them)
417 next TOKEN if defined $query->{$git_token};
419 log_debug("FOUND: $git_token=$entry->{$git_token}");
420 printf "%s=%s\n", $git_token, $entry->{$git_token};
424 # load settings from git config
426 # set from command argument, gpg.program option, or default to gpg
427 $options->{'gpg'} //= Git->repository()->config('gpg.program')
429 log_verbose("using $options{'gpg'} for GPG operations");
432 return unless $options{verbose};
438 return unless $options{debug};