diff options
Diffstat (limited to 'contrib/credential')
-rw-r--r-- | contrib/credential/gnome-keyring/Makefile | 4 | ||||
-rw-r--r-- | contrib/credential/gnome-keyring/git-credential-gnome-keyring.c | 297 | ||||
-rw-r--r-- | contrib/credential/netrc/Makefile | 5 | ||||
-rwxr-xr-x | contrib/credential/netrc/git-credential-netrc | 423 | ||||
-rw-r--r-- | contrib/credential/netrc/test.netrc | 13 | ||||
-rwxr-xr-x | contrib/credential/netrc/test.pl | 106 | ||||
-rw-r--r-- | contrib/credential/osxkeychain/git-credential-osxkeychain.c | 12 |
7 files changed, 725 insertions, 135 deletions
diff --git a/contrib/credential/gnome-keyring/Makefile b/contrib/credential/gnome-keyring/Makefile index e6561d8db6..c3c7c98aa1 100644 --- a/contrib/credential/gnome-keyring/Makefile +++ b/contrib/credential/gnome-keyring/Makefile @@ -8,8 +8,8 @@ CFLAGS = -g -O2 -Wall -include ../../../config.mak.autogen -include ../../../config.mak -INCS:=$(shell pkg-config --cflags gnome-keyring-1) -LIBS:=$(shell pkg-config --libs gnome-keyring-1) +INCS:=$(shell pkg-config --cflags gnome-keyring-1 glib-2.0) +LIBS:=$(shell pkg-config --libs gnome-keyring-1 glib-2.0) SRCS:=$(MAIN).c OBJS:=$(SRCS:.c=.o) diff --git a/contrib/credential/gnome-keyring/git-credential-gnome-keyring.c b/contrib/credential/gnome-keyring/git-credential-gnome-keyring.c index f2cdefee60..635c96bc56 100644 --- a/contrib/credential/gnome-keyring/git-credential-gnome-keyring.c +++ b/contrib/credential/gnome-keyring/git-credential-gnome-keyring.c @@ -25,133 +25,150 @@ #include <stdio.h> #include <string.h> -#include <stdarg.h> #include <stdlib.h> -#include <errno.h> +#include <glib.h> #include <gnome-keyring.h> -/* - * This credential struct and API is simplified from git's credential.{h,c} - */ -struct credential -{ - char *protocol; - char *host; - unsigned short port; - char *path; - char *username; - char *password; -}; +#ifdef GNOME_KEYRING_DEFAULT -#define CREDENTIAL_INIT \ - { NULL,NULL,0,NULL,NULL,NULL } + /* Modern gnome-keyring */ -void credential_init(struct credential *c); -void credential_clear(struct credential *c); -int credential_read(struct credential *c); -void credential_write(const struct credential *c); +#include <gnome-keyring-memory.h> -typedef int (*credential_op_cb)(struct credential*); +#else -struct credential_operation -{ - char *name; - credential_op_cb op; -}; + /* + * Support ancient gnome-keyring, circ. RHEL 5.X. + * GNOME_KEYRING_DEFAULT seems to have been introduced with Gnome 2.22, + * and the other features roughly around Gnome 2.20, 6 months before. + * Ubuntu 8.04 used Gnome 2.22 (I think). Not sure any distro used 2.20. + * So the existence/non-existence of GNOME_KEYRING_DEFAULT seems like + * a decent thing to use as an indicator. + */ -#define CREDENTIAL_OP_END \ - { NULL,NULL } +#define GNOME_KEYRING_DEFAULT NULL /* - * Table with operation callbacks is defined in concrete - * credential helper implementation and contains entries - * like { "get", function_to_get_credential } terminated - * by CREDENTIAL_OP_END. + * ancient gnome-keyring returns DENIED when an entry is not found. + * Setting NO_MATCH to DENIED will prevent us from reporting DENIED + * errors during get and erase operations, but we will still report + * DENIED errors during a store. */ -struct credential_operation const credential_helper_ops[]; +#define GNOME_KEYRING_RESULT_NO_MATCH GNOME_KEYRING_RESULT_DENIED -/* ---------------- common helper functions ----------------- */ +#define gnome_keyring_memory_alloc g_malloc +#define gnome_keyring_memory_free gnome_keyring_free_password +#define gnome_keyring_memory_strdup g_strdup -static inline void free_password(char *password) +static const char* gnome_keyring_result_to_message(GnomeKeyringResult result) { - char *c = password; - if (!password) - return; - - while (*c) *c++ = '\0'; - free(password); + switch (result) { + case GNOME_KEYRING_RESULT_OK: + return "OK"; + case GNOME_KEYRING_RESULT_DENIED: + return "Denied"; + case GNOME_KEYRING_RESULT_NO_KEYRING_DAEMON: + return "No Keyring Daemon"; + case GNOME_KEYRING_RESULT_ALREADY_UNLOCKED: + return "Already UnLocked"; + case GNOME_KEYRING_RESULT_NO_SUCH_KEYRING: + return "No Such Keyring"; + case GNOME_KEYRING_RESULT_BAD_ARGUMENTS: + return "Bad Arguments"; + case GNOME_KEYRING_RESULT_IO_ERROR: + return "IO Error"; + case GNOME_KEYRING_RESULT_CANCELLED: + return "Cancelled"; + case GNOME_KEYRING_RESULT_ALREADY_EXISTS: + return "Already Exists"; + default: + return "Unknown Error"; + } } -static inline void warning(const char *fmt, ...) +/* + * Support really ancient gnome-keyring, circ. RHEL 4.X. + * Just a guess for the Glib version. Glib 2.8 was roughly Gnome 2.12 ? + * Which was released with gnome-keyring 0.4.3 ?? + */ +#if GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION < 8 + +static void gnome_keyring_done_cb(GnomeKeyringResult result, gpointer user_data) { - va_list ap; + gpointer *data = (gpointer*) user_data; + int *done = (int*) data[0]; + GnomeKeyringResult *r = (GnomeKeyringResult*) data[1]; - va_start(ap, fmt); - fprintf(stderr, "warning: "); - vfprintf(stderr, fmt, ap); - fprintf(stderr, "\n" ); - va_end(ap); + *r = result; + *done = 1; } -static inline void error(const char *fmt, ...) +static void wait_for_request_completion(int *done) { - va_list ap; - - va_start(ap, fmt); - fprintf(stderr, "error: "); - vfprintf(stderr, fmt, ap); - fprintf(stderr, "\n" ); - va_end(ap); + GMainContext *mc = g_main_context_default(); + while (!*done) + g_main_context_iteration(mc, TRUE); } -static inline void die(const char *fmt, ...) +static GnomeKeyringResult gnome_keyring_item_delete_sync(const char *keyring, guint32 id) { - va_list ap; + int done = 0; + GnomeKeyringResult result; + gpointer data[] = { &done, &result }; + + gnome_keyring_item_delete(keyring, id, gnome_keyring_done_cb, data, + NULL); + + wait_for_request_completion(&done); - va_start(ap,fmt); - error(fmt, ap); - va_end(ap); - exit(EXIT_FAILURE); + return result; } -static inline void die_errno(int err) +#endif +#endif + +/* + * This credential struct and API is simplified from git's credential.{h,c} + */ +struct credential { - error("%s", strerror(err)); - exit(EXIT_FAILURE); -} + char *protocol; + char *host; + unsigned short port; + char *path; + char *username; + char *password; +}; -static inline char *xstrdup(const char *str) +#define CREDENTIAL_INIT \ + { NULL,NULL,0,NULL,NULL,NULL } + +typedef int (*credential_op_cb)(struct credential*); + +struct credential_operation { - char *ret = strdup(str); - if (!ret) - die_errno(errno); + char *name; + credential_op_cb op; +}; - return ret; -} +#define CREDENTIAL_OP_END \ + { NULL,NULL } /* ----------------- GNOME Keyring functions ----------------- */ /* create a special keyring option string, if path is given */ static char* keyring_object(struct credential *c) { - char* object = NULL; - if (!c->path) - return object; - - object = (char*) malloc(strlen(c->host)+strlen(c->path)+8); - if(!object) - die_errno(errno); + return NULL; - if(c->port) - sprintf(object,"%s:%hd/%s",c->host,c->port,c->path); - else - sprintf(object,"%s/%s",c->host,c->path); + if (c->port) + return g_strdup_printf("%s:%hd/%s", c->host, c->port, c->path); - return object; + return g_strdup_printf("%s/%s", c->host, c->path); } -int keyring_get(struct credential *c) +static int keyring_get(struct credential *c) { char* object = NULL; GList *entries; @@ -173,7 +190,7 @@ int keyring_get(struct credential *c) c->port, &entries); - free(object); + g_free(object); if (result == GNOME_KEYRING_RESULT_NO_MATCH) return EXIT_SUCCESS; @@ -182,18 +199,18 @@ int keyring_get(struct credential *c) return EXIT_SUCCESS; if (result != GNOME_KEYRING_RESULT_OK) { - error("%s",gnome_keyring_result_to_message(result)); + g_critical("%s", gnome_keyring_result_to_message(result)); return EXIT_FAILURE; } /* pick the first one from the list */ password_data = (GnomeKeyringNetworkPasswordData *) entries->data; - free_password(c->password); - c->password = xstrdup(password_data->password); + gnome_keyring_memory_free(c->password); + c->password = gnome_keyring_memory_strdup(password_data->password); if (!c->username) - c->username = xstrdup(password_data->user); + c->username = g_strdup(password_data->user); gnome_keyring_network_password_list_free(entries); @@ -201,10 +218,11 @@ int keyring_get(struct credential *c) } -int keyring_store(struct credential *c) +static int keyring_store(struct credential *c) { guint32 item_id; char *object = NULL; + GnomeKeyringResult result; /* * Sanity check that what we are storing is actually sensible. @@ -219,7 +237,7 @@ int keyring_store(struct credential *c) object = keyring_object(c); - gnome_keyring_set_network_password_sync( + result = gnome_keyring_set_network_password_sync( GNOME_KEYRING_DEFAULT, c->username, NULL /* domain */, @@ -231,11 +249,18 @@ int keyring_store(struct credential *c) c->password, &item_id); - free(object); + g_free(object); + + if (result != GNOME_KEYRING_RESULT_OK && + result != GNOME_KEYRING_RESULT_CANCELLED) { + g_critical("%s", gnome_keyring_result_to_message(result)); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; } -int keyring_erase(struct credential *c) +static int keyring_erase(struct credential *c) { char *object = NULL; GList *entries; @@ -265,7 +290,7 @@ int keyring_erase(struct credential *c) c->port, &entries); - free(object); + g_free(object); if (result == GNOME_KEYRING_RESULT_NO_MATCH) return EXIT_SUCCESS; @@ -275,7 +300,7 @@ int keyring_erase(struct credential *c) if (result != GNOME_KEYRING_RESULT_OK) { - error("%s",gnome_keyring_result_to_message(result)); + g_critical("%s", gnome_keyring_result_to_message(result)); return EXIT_FAILURE; } @@ -289,7 +314,7 @@ int keyring_erase(struct credential *c) if (result != GNOME_KEYRING_RESULT_OK) { - error("%s",gnome_keyring_result_to_message(result)); + g_critical("%s", gnome_keyring_result_to_message(result)); return EXIT_FAILURE; } @@ -300,7 +325,7 @@ int keyring_erase(struct credential *c) * Table with helper operation callbacks, used by generic * credential helper main function. */ -struct credential_operation const credential_helper_ops[] = +static struct credential_operation const credential_helper_ops[] = { { "get", keyring_get }, { "store", keyring_store }, @@ -310,66 +335,69 @@ struct credential_operation const credential_helper_ops[] = /* ------------------ credential functions ------------------ */ -void credential_init(struct credential *c) +static void credential_init(struct credential *c) { memset(c, 0, sizeof(*c)); } -void credential_clear(struct credential *c) +static void credential_clear(struct credential *c) { - free(c->protocol); - free(c->host); - free(c->path); - free(c->username); - free_password(c->password); + g_free(c->protocol); + g_free(c->host); + g_free(c->path); + g_free(c->username); + gnome_keyring_memory_free(c->password); credential_init(c); } -int credential_read(struct credential *c) +static int credential_read(struct credential *c) { - char buf[1024]; - ssize_t line_len = 0; - char *key = buf; + char *buf; + size_t line_len; + char *key; char *value; - while (fgets(buf, sizeof(buf), stdin)) + key = buf = gnome_keyring_memory_alloc(1024); + + while (fgets(buf, 1024, stdin)) { line_len = strlen(buf); - if(buf[line_len-1]=='\n') + if (line_len && buf[line_len-1] == '\n') buf[--line_len]='\0'; - if(!line_len) + if (!line_len) break; value = strchr(buf,'='); - if(!value) { - warning("invalid credential line: %s", key); + if (!value) { + g_warning("invalid credential line: %s", key); + gnome_keyring_memory_free(buf); return -1; } *value++ = '\0'; if (!strcmp(key, "protocol")) { - free(c->protocol); - c->protocol = xstrdup(value); + g_free(c->protocol); + c->protocol = g_strdup(value); } else if (!strcmp(key, "host")) { - free(c->host); - c->host = xstrdup(value); + g_free(c->host); + c->host = g_strdup(value); value = strrchr(c->host,':'); if (value) { *value++ = '\0'; c->port = atoi(value); } } else if (!strcmp(key, "path")) { - free(c->path); - c->path = xstrdup(value); + g_free(c->path); + c->path = g_strdup(value); } else if (!strcmp(key, "username")) { - free(c->username); - c->username = xstrdup(value); + g_free(c->username); + c->username = g_strdup(value); } else if (!strcmp(key, "password")) { - free_password(c->password); - c->password = xstrdup(value); + gnome_keyring_memory_free(c->password); + c->password = gnome_keyring_memory_strdup(value); while (*value) *value++ = '\0'; } /* @@ -378,17 +406,20 @@ int credential_read(struct credential *c) * learn new lines, and the helpers are updated to match. */ } + + gnome_keyring_memory_free(buf); + return 0; } -void credential_write_item(FILE *fp, const char *key, const char *value) +static void credential_write_item(FILE *fp, const char *key, const char *value) { if (!value) return; fprintf(fp, "%s=%s\n", key, value); } -void credential_write(const struct credential *c) +static void credential_write(const struct credential *c) { /* only write username/password, if set */ credential_write_item(stdout, "username", c->username); @@ -402,9 +433,9 @@ static void usage(const char *name) basename = (basename) ? basename + 1 : name; fprintf(stderr, "usage: %s <", basename); - while(try_op->name) { + while (try_op->name) { fprintf(stderr,"%s",(try_op++)->name); - if(try_op->name) + if (try_op->name) fprintf(stderr,"%s","|"); } fprintf(stderr,"%s",">\n"); @@ -419,19 +450,21 @@ int main(int argc, char *argv[]) if (!argv[1]) { usage(argv[0]); - goto out; + exit(EXIT_FAILURE); } + g_set_application_name("Git Credential Helper"); + /* lookup operation callback */ - while(try_op->name && strcmp(argv[1], try_op->name)) + while (try_op->name && strcmp(argv[1], try_op->name)) try_op++; /* unsupported operation given -- ignore silently */ - if(!try_op->name || !try_op->op) + if (!try_op->name || !try_op->op) goto out; ret = credential_read(&cred); - if(ret) + if (ret) goto out; /* perform credential operation */ diff --git a/contrib/credential/netrc/Makefile b/contrib/credential/netrc/Makefile new file mode 100644 index 0000000000..51b76138a5 --- /dev/null +++ b/contrib/credential/netrc/Makefile @@ -0,0 +1,5 @@ +test: + ./test.pl + +testverbose: + ./test.pl -d -v diff --git a/contrib/credential/netrc/git-credential-netrc b/contrib/credential/netrc/git-credential-netrc new file mode 100755 index 0000000000..1571a7b269 --- /dev/null +++ b/contrib/credential/netrc/git-credential-netrc @@ -0,0 +1,423 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Getopt::Long; +use File::Basename; + +my $VERSION = "0.1"; + +my %options = ( + help => 0, + debug => 0, + verbose => 0, + insecure => 0, + file => [], + + # identical token maps, e.g. host -> host, will be inserted later + tmap => { + port => 'protocol', + machine => 'host', + path => 'path', + login => 'username', + user => 'username', + password => 'password', + } + ); + +# Map each credential protocol token to itself on the netrc side. +foreach (values %{$options{tmap}}) { + $options{tmap}->{$_} = $_; +} + +# Now, $options{tmap} has a mapping from the netrc format to the Git credential +# helper protocol. + +# Next, we build the reverse token map. + +# When $rmap{foo} contains 'bar', that means that what the Git credential helper +# protocol calls 'bar' is found as 'foo' in the netrc/authinfo file. Keys in +# %rmap are what we expect to read from the netrc/authinfo file. + +my %rmap; +foreach my $k (keys %{$options{tmap}}) { + push @{$rmap{$options{tmap}->{$k}}}, $k; +} + +Getopt::Long::Configure("bundling"); + +# TODO: maybe allow the token map $options{tmap} to be configurable. +GetOptions(\%options, + "help|h", + "debug|d", + "insecure|k", + "verbose|v", + "file|f=s@", + ); + +if ($options{help}) { + my $shortname = basename($0); + $shortname =~ s/git-credential-//; + + print <<EOHIPPUS; + +$0 [-f AUTHFILE1] [-f AUTHFILEN] [-d] [-v] [-k] get + +Version $VERSION by tzz\@lifelogs.com. License: BSD. + +Options: + + -f|--file AUTHFILE : specify netrc-style files. Files with the .gpg extension + will be decrypted by GPG before parsing. Multiple -f + arguments are OK. They are processed in order, and the + first matching entry found is returned via the credential + helper protocol (see below). + + When no -f option is given, .authinfo.gpg, .netrc.gpg, + .authinfo, and .netrc files in your home directory are used + in this order. + + -k|--insecure : ignore bad file ownership or permissions + + -d|--debug : turn on debugging (developer info) + + -v|--verbose : be more verbose (show files and information found) + +To enable this credential helper: + + git config credential.helper '$shortname -f AUTHFILE1 -f AUTHFILE2' + +(Note that Git will prepend "git-credential-" to the helper name and look for it +in the path.) + +...and if you want lots of debugging info: + + git config credential.helper '$shortname -f AUTHFILE -d' + +...or to see the files opened and data found: + + git config credential.helper '$shortname -f AUTHFILE -v' + +Only "get" mode is supported by this credential helper. It opens every AUTHFILE +and looks for the first entry that matches the requested search criteria: + + 'port|protocol': + The protocol that will be used (e.g., https). (protocol=X) + + 'machine|host': + The remote hostname for a network credential. (host=X) + + 'path': + The path with which the credential will be used. (path=X) + + 'login|user|username': + The credential’s username, if we already have one. (username=X) + +Thus, when we get this query on STDIN: + +host=github.com +protocol=https +username=tzz + +this credential helper will look for the first entry in every AUTHFILE that +matches + +machine github.com port https login tzz + +OR + +machine github.com protocol https login tzz + +OR... etc. acceptable tokens as listed above. Any unknown tokens are +simply ignored. + +Then, the helper will print out whatever tokens it got from the entry, including +"password" tokens, mapping back to Git's helper protocol; e.g. "port" is mapped +back to "protocol". Any redundant entry tokens (part of the original query) are +skipped. + +Again, note that only the first matching entry from all the AUTHFILEs, processed +in the sequence given on the command line, is used. + +Netrc/authinfo tokens can be quoted as 'STRING' or "STRING". + +No caching is performed by this credential helper. + +EOHIPPUS + + exit 0; +} + +my $mode = shift @ARGV; + +# Credentials must get a parameter, so die if it's missing. +die "Syntax: $0 [-f AUTHFILE1] [-f AUTHFILEN] [-d] get" unless defined $mode; + +# Only support 'get' mode; with any other unsupported ones we just exit. +exit 0 unless $mode eq 'get'; + +my $files = $options{file}; + +# if no files were given, use a predefined list. +# note that .gpg files come first +unless (scalar @$files) { + my @candidates = qw[ + ~/.authinfo.gpg + ~/.netrc.gpg + ~/.authinfo + ~/.netrc + ]; + + $files = $options{file} = [ map { glob $_ } @candidates ]; +} + +my $query = read_credential_data_from_stdin(); + +FILE: +foreach my $file (@$files) { + my $gpgmode = $file =~ m/\.gpg$/; + unless (-r $file) { + log_verbose("Unable to read $file; skipping it"); + next FILE; + } + + # the following check is copied from Net::Netrc, for non-GPG files + # OS/2 and Win32 do not handle stat in a way compatible with this check :-( + unless ($gpgmode || $options{insecure} || + $^O eq 'os2' + || $^O eq 'MSWin32' + || $^O eq 'MacOS' + || $^O =~ /^cygwin/) { + my @stat = stat($file); + + if (@stat) { + if ($stat[2] & 077) { + log_verbose("Insecure $file (mode=%04o); skipping it", + $stat[2] & 07777); + next FILE; + } + + if ($stat[4] != $<) { + log_verbose("Not owner of $file; skipping it"); + next FILE; + } + } + } + + my @entries = load_netrc($file, $gpgmode); + + unless (scalar @entries) { + if ($!) { + log_verbose("Unable to open $file: $!"); + } else { + log_verbose("No netrc entries found in $file"); + } + + next FILE; + } + + my $entry = find_netrc_entry($query, @entries); + if ($entry) { + print_credential_data($entry, $query); + # we're done! + last FILE; + } +} + +exit 0; + +sub load_netrc { + my $file = shift @_; + my $gpgmode = shift @_; + + my $io; + if ($gpgmode) { + my @cmd = (qw(gpg --decrypt), $file); + log_verbose("Using GPG to open $file: [@cmd]"); + open $io, "-|", @cmd; + } else { + log_verbose("Opening $file..."); + open $io, '<', $file; + } + + # nothing to do if the open failed (we log the error later) + return unless $io; + + # Net::Netrc does this, but the functionality is merged with the file + # detection logic, so we have to extract just the part we need + my @netrc_entries = net_netrc_loader($io); + + # these entries will use the credential helper protocol token names + my @entries; + + foreach my $nentry (@netrc_entries) { + my %entry; + my $num_port; + + if (!defined $nentry->{machine}) { + next; + } + if (defined $nentry->{port} && $nentry->{port} =~ m/^\d+$/) { + $num_port = $nentry->{port}; + delete $nentry->{port}; + } + + # create the new entry for the credential helper protocol + $entry{$options{tmap}->{$_}} = $nentry->{$_} foreach keys %$nentry; + + # for "host X port Y" where Y is an integer (captured by + # $num_port above), set the host to "X:Y" + if (defined $entry{host} && defined $num_port) { + $entry{host} = join(':', $entry{host}, $num_port); + } + + push @entries, \%entry; + } + + return @entries; +} + +sub net_netrc_loader { + my $fh = shift @_; + my @entries; + my ($mach, $macdef, $tok, @tok); + + LINE: + while (<$fh>) { + undef $macdef if /\A\n\Z/; + + if ($macdef) { + next LINE; + } + + s/^\s*//; + chomp; + + while (length && s/^("((?:[^"]+|\\.)*)"|((?:[^\\\s]+|\\.)*))\s*//) { + (my $tok = $+) =~ s/\\(.)/$1/g; + push(@tok, $tok); + } + + TOKEN: + while (@tok) { + if ($tok[0] eq "default") { + shift(@tok); + $mach = { machine => undef }; + next TOKEN; + } + + $tok = shift(@tok); + + if ($tok eq "machine") { + my $host = shift @tok; + $mach = { machine => $host }; + push @entries, $mach; + } elsif (exists $options{tmap}->{$tok}) { + unless ($mach) { + log_debug("Skipping token $tok because no machine was given"); + next TOKEN; + } + + my $value = shift @tok; + unless (defined $value) { + log_debug("Token $tok had no value, skipping it."); + next TOKEN; + } + + # Following line added by rmerrell to remove '/' escape char in .netrc + $value =~ s/\/\\/\\/g; + $mach->{$tok} = $value; + } elsif ($tok eq "macdef") { # we ignore macros + next TOKEN unless $mach; + my $value = shift @tok; + $macdef = 1; + } + } + } + + return @entries; +} + +sub read_credential_data_from_stdin { + # the query: start with every token with no value + my %q = map { $_ => undef } values(%{$options{tmap}}); + + while (<STDIN>) { + next unless m/^([^=]+)=(.+)/; + + my ($token, $value) = ($1, $2); + die "Unknown search token $token" unless exists $q{$token}; + $q{$token} = $value; + log_debug("We were given search token $token and value $value"); + } + + foreach (sort keys %q) { + log_debug("Searching for %s = %s", $_, $q{$_} || '(any value)'); + } + + return \%q; +} + +# takes the search tokens and then a list of entries +# each entry is a hash reference +sub find_netrc_entry { + my $query = shift @_; + + ENTRY: + foreach my $entry (@_) + { + my $entry_text = join ', ', map { "$_=$entry->{$_}" } keys %$entry; + foreach my $check (sort keys %$query) { + if (!defined $entry->{$check}) { + log_debug("OK: entry has no $check token, so any value satisfies check $check"); + } elsif (defined $query->{$check}) { + log_debug("compare %s [%s] to [%s] (entry: %s)", + $check, + $entry->{$check}, + $query->{$check}, + $entry_text); + unless ($query->{$check} eq $entry->{$check}) { + next ENTRY; + } + } else { + log_debug("OK: any value satisfies check $check"); + } + } + + return $entry; + } + + # nothing was found + return; +} + +sub print_credential_data { + my $entry = shift @_; + my $query = shift @_; + + log_debug("entry has passed all the search checks"); + TOKEN: + foreach my $git_token (sort keys %$entry) { + log_debug("looking for useful token $git_token"); + # don't print unknown (to the credential helper protocol) tokens + next TOKEN unless exists $query->{$git_token}; + + # don't print things asked in the query (the entry matches them) + next TOKEN if defined $query->{$git_token}; + + log_debug("FOUND: $git_token=$entry->{$git_token}"); + printf "%s=%s\n", $git_token, $entry->{$git_token}; + } +} +sub log_verbose { + return unless $options{verbose}; + printf STDERR @_; + printf STDERR "\n"; +} + +sub log_debug { + return unless $options{debug}; + printf STDERR @_; + printf STDERR "\n"; +} diff --git a/contrib/credential/netrc/test.netrc b/contrib/credential/netrc/test.netrc new file mode 100644 index 0000000000..ba119a937f --- /dev/null +++ b/contrib/credential/netrc/test.netrc @@ -0,0 +1,13 @@ +machine imap login tzz@lifelogs.com port imaps password letmeknow +machine imap login bob port imaps password bobwillknow + +# comment test + +machine imap2 login tzz port 1099 password tzzknow +machine imap2 login bob password bobwillknow + +# another command + +machine github.com + multilinetoken anothervalue + login carol password carolknows diff --git a/contrib/credential/netrc/test.pl b/contrib/credential/netrc/test.pl new file mode 100755 index 0000000000..169b6463c3 --- /dev/null +++ b/contrib/credential/netrc/test.pl @@ -0,0 +1,106 @@ +#!/usr/bin/perl + +use warnings; +use strict; +use Test; +use IPC::Open2; + +BEGIN { plan tests => 15 } + +my @global_credential_args = @ARGV; +my $netrc = './test.netrc'; +print "# Testing insecure file, nothing should be found\n"; +chmod 0644, $netrc; +my $cred = run_credential(['-f', $netrc, 'get'], + { host => 'github.com' }); + +ok(scalar keys %$cred, 0, "Got 0 keys from insecure file"); + +print "# Testing missing file, nothing should be found\n"; +chmod 0644, $netrc; +$cred = run_credential(['-f', '///nosuchfile///', 'get'], + { host => 'github.com' }); + +ok(scalar keys %$cred, 0, "Got 0 keys from missing file"); + +chmod 0600, $netrc; + +print "# Testing with invalid data\n"; +$cred = run_credential(['-f', $netrc, 'get'], + "bad data"); +ok(scalar keys %$cred, 4, "Got first found keys with bad data"); + +print "# Testing netrc file for a missing corovamilkbar entry\n"; +$cred = run_credential(['-f', $netrc, 'get'], + { host => 'corovamilkbar' }); + +ok(scalar keys %$cred, 0, "Got no corovamilkbar keys"); + +print "# Testing netrc file for a github.com entry\n"; +$cred = run_credential(['-f', $netrc, 'get'], + { host => 'github.com' }); + +ok(scalar keys %$cred, 2, "Got 2 Github keys"); + +ok($cred->{password}, 'carolknows', "Got correct Github password"); +ok($cred->{username}, 'carol', "Got correct Github username"); + +print "# Testing netrc file for a username-specific entry\n"; +$cred = run_credential(['-f', $netrc, 'get'], + { host => 'imap', username => 'bob' }); + +ok(scalar keys %$cred, 2, "Got 2 username-specific keys"); + +ok($cred->{password}, 'bobwillknow', "Got correct user-specific password"); +ok($cred->{protocol}, 'imaps', "Got correct user-specific protocol"); + +print "# Testing netrc file for a host:port-specific entry\n"; +$cred = run_credential(['-f', $netrc, 'get'], + { host => 'imap2:1099' }); + +ok(scalar keys %$cred, 2, "Got 2 host:port-specific keys"); + +ok($cred->{password}, 'tzzknow', "Got correct host:port-specific password"); +ok($cred->{username}, 'tzz', "Got correct host:port-specific username"); + +print "# Testing netrc file that 'host:port kills host' entry\n"; +$cred = run_credential(['-f', $netrc, 'get'], + { host => 'imap2' }); + +ok(scalar keys %$cred, 2, "Got 2 'host:port kills host' keys"); + +ok($cred->{password}, 'bobwillknow', "Got correct 'host:port kills host' password"); +ok($cred->{username}, 'bob', "Got correct 'host:port kills host' username"); + +sub run_credential +{ + my $args = shift @_; + my $data = shift @_; + my $pid = open2(my $chld_out, my $chld_in, + './git-credential-netrc', @global_credential_args, + @$args); + + die "Couldn't open pipe to netrc credential helper: $!" unless $pid; + + if (ref $data eq 'HASH') + { + print $chld_in "$_=$data->{$_}\n" foreach sort keys %$data; + } + else + { + print $chld_in "$data\n"; + } + + close $chld_in; + my %ret; + + while (<$chld_out>) + { + chomp; + next unless m/^([^=]+)=(.+)/; + + $ret{$1} = $2; + } + + return \%ret; +} diff --git a/contrib/credential/osxkeychain/git-credential-osxkeychain.c b/contrib/credential/osxkeychain/git-credential-osxkeychain.c index 3940202b36..bcd3f575a3 100644 --- a/contrib/credential/osxkeychain/git-credential-osxkeychain.c +++ b/contrib/credential/osxkeychain/git-credential-osxkeychain.c @@ -127,10 +127,20 @@ static void read_credential(void) *v++ = '\0'; if (!strcmp(buf, "protocol")) { - if (!strcmp(v, "https")) + if (!strcmp(v, "imap")) + protocol = kSecProtocolTypeIMAP; + else if (!strcmp(v, "imaps")) + protocol = kSecProtocolTypeIMAPS; + else if (!strcmp(v, "ftp")) + protocol = kSecProtocolTypeFTP; + else if (!strcmp(v, "ftps")) + protocol = kSecProtocolTypeFTPS; + else if (!strcmp(v, "https")) protocol = kSecProtocolTypeHTTPS; else if (!strcmp(v, "http")) protocol = kSecProtocolTypeHTTP; + else if (!strcmp(v, "smtp")) + protocol = kSecProtocolTypeSMTP; else /* we don't yet handle other protocols */ exit(0); } |