summaryrefslogtreecommitdiff
path: root/contrib/credential
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/credential')
-rw-r--r--contrib/credential/gnome-keyring/Makefile5
-rw-r--r--contrib/credential/gnome-keyring/git-credential-gnome-keyring.c345
-rw-r--r--contrib/credential/libsecret/Makefile25
-rw-r--r--contrib/credential/libsecret/git-credential-libsecret.c369
-rw-r--r--contrib/credential/netrc/.gitignore1
-rw-r--r--contrib/credential/netrc/Makefile30
-rwxr-xr-xcontrib/credential/netrc/git-credential-netrc.perl440
-rwxr-xr-xcontrib/credential/netrc/t-git-credential-netrc.sh32
-rwxr-xr-xcontrib/credential/netrc/test.command-option-gpg2
-rwxr-xr-xcontrib/credential/netrc/test.git-config-gpg2
-rw-r--r--contrib/credential/netrc/test.netrc13
-rw-r--r--contrib/credential/netrc/test.netrc.gpg0
-rwxr-xr-xcontrib/credential/netrc/test.pl139
-rw-r--r--contrib/credential/osxkeychain/git-credential-osxkeychain.c15
-rw-r--r--contrib/credential/wincred/Makefile16
-rw-r--r--contrib/credential/wincred/git-credential-wincred.c233
16 files changed, 1368 insertions, 299 deletions
diff --git a/contrib/credential/gnome-keyring/Makefile b/contrib/credential/gnome-keyring/Makefile
index e6561d8db6..22c19df94b 100644
--- a/contrib/credential/gnome-keyring/Makefile
+++ b/contrib/credential/gnome-keyring/Makefile
@@ -4,12 +4,13 @@ all:: $(MAIN)
CC = gcc
RM = rm -f
CFLAGS = -g -O2 -Wall
+PKG_CONFIG = pkg-config
-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 41f61c5db3..d389bfadce 100644
--- a/contrib/credential/gnome-keyring/git-credential-gnome-keyring.c
+++ b/contrib/credential/gnome-keyring/git-credential-gnome-keyring.c
@@ -13,8 +13,7 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
/*
@@ -25,135 +24,148 @@
#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 };
- va_start(ap,fmt);
- error(fmt, ap);
- va_end(ap);
- exit(EXIT_FAILURE);
-}
+ gnome_keyring_item_delete(keyring, id, gnome_keyring_done_cb, data,
+ NULL);
-static inline void die_errno(int err)
-{
- error("%s", strerror(err));
- exit(EXIT_FAILURE);
+ wait_for_request_completion(&done);
+
+ return result;
}
-static inline char *xstrdup(const char *str)
-{
- char *ret = strdup(str);
- if (!ret)
- die_errno(errno);
+#endif
+#endif
- return ret;
-}
+/*
+ * 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;
+};
+
+#define CREDENTIAL_INIT { NULL, NULL, 0, NULL, NULL, NULL }
+
+typedef int (*credential_op_cb)(struct credential *);
+
+struct credential_operation {
+ char *name;
+ credential_op_cb op;
+};
+
+#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)
+static char *keyring_object(struct credential *c)
{
- char* object = NULL;
-
if (!c->path)
- return object;
+ return NULL;
- object = (char*) malloc(strlen(c->host)+strlen(c->path)+8);
- if(!object)
- die_errno(errno);
+ if (c->port)
+ return g_strdup_printf("%s:%hd/%s", c->host, c->port, c->path);
- if(c->port)
- sprintf(object,"%s:%hd/%s",c->host,c->port,c->path);
- else
- sprintf(object,"%s/%s",c->host,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;
+ char *object = NULL;
GList *entries;
GnomeKeyringNetworkPasswordData *password_data;
GnomeKeyringResult result;
@@ -173,7 +185,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 +194,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;
+ 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 +213,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;
+ char *object = NULL;
+ GnomeKeyringResult result;
/*
* Sanity check that what we are storing is actually sensible.
@@ -219,7 +232,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,13 +244,20 @@ 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;
+ char *object = NULL;
GList *entries;
GnomeKeyringNetworkPasswordData *password_data;
GnomeKeyringResult result;
@@ -265,7 +285,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;
@@ -273,23 +293,21 @@ int keyring_erase(struct credential *c)
if (result == GNOME_KEYRING_RESULT_CANCELLED)
return EXIT_SUCCESS;
- if (result != GNOME_KEYRING_RESULT_OK)
- {
- error("%s",gnome_keyring_result_to_message(result));
+ if (result != GNOME_KEYRING_RESULT_OK) {
+ g_critical("%s", gnome_keyring_result_to_message(result));
return EXIT_FAILURE;
}
/* pick the first one from the list (delete all matches?) */
- password_data = (GnomeKeyringNetworkPasswordData *) entries->data;
+ password_data = (GnomeKeyringNetworkPasswordData *)entries->data;
result = gnome_keyring_item_delete_sync(
password_data->keyring, password_data->item_id);
gnome_keyring_network_password_list_free(entries);
- if (result != GNOME_KEYRING_RESULT_OK)
- {
- error("%s",gnome_keyring_result_to_message(result));
+ if (result != GNOME_KEYRING_RESULT_OK) {
+ g_critical("%s", gnome_keyring_result_to_message(result));
return EXIT_FAILURE;
}
@@ -300,9 +318,8 @@ 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[] =
-{
- { "get", keyring_get },
+static struct credential_operation const credential_helper_ops[] = {
+ { "get", keyring_get },
{ "store", keyring_store },
{ "erase", keyring_erase },
CREDENTIAL_OP_END
@@ -310,67 +327,70 @@ 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 *value;
+ 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')
- buf[--line_len]='\0';
+ 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);
+ value = strchr(buf, '=');
+ 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);
- value = strrchr(c->host,':');
+ 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);
- while (*value) *value++ = '\0';
+ gnome_keyring_memory_free(c->password);
+ c->password = gnome_keyring_memory_strdup(value);
+ while (*value)
+ *value++ = '\0';
}
/*
* Ignore other lines; we don't know what they mean, but
@@ -378,17 +398,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);
@@ -398,16 +421,16 @@ void credential_write(const struct credential *c)
static void usage(const char *name)
{
struct credential_operation const *try_op = credential_helper_ops;
- const char *basename = strrchr(name,'/');
+ const char *basename = strrchr(name, '/');
basename = (basename) ? basename + 1 : name;
- fprintf(stderr, "Usage: %s <", basename);
- while(try_op->name) {
- fprintf(stderr,"%s",(try_op++)->name);
- if(try_op->name)
- fprintf(stderr,"%s","|");
+ fprintf(stderr, "usage: %s <", basename);
+ while (try_op->name) {
+ fprintf(stderr, "%s", (try_op++)->name);
+ if (try_op->name)
+ fprintf(stderr, "%s", "|");
}
- fprintf(stderr,"%s",">\n");
+ fprintf(stderr, "%s", ">\n");
}
int main(int argc, char *argv[])
@@ -415,23 +438,25 @@ int main(int argc, char *argv[])
int ret = EXIT_SUCCESS;
struct credential_operation const *try_op = credential_helper_ops;
- struct credential cred = CREDENTIAL_INIT;
+ struct credential cred = CREDENTIAL_INIT;
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/libsecret/Makefile b/contrib/credential/libsecret/Makefile
new file mode 100644
index 0000000000..3e67552cc5
--- /dev/null
+++ b/contrib/credential/libsecret/Makefile
@@ -0,0 +1,25 @@
+MAIN:=git-credential-libsecret
+all:: $(MAIN)
+
+CC = gcc
+RM = rm -f
+CFLAGS = -g -O2 -Wall
+PKG_CONFIG = pkg-config
+
+-include ../../../config.mak.autogen
+-include ../../../config.mak
+
+INCS:=$(shell $(PKG_CONFIG) --cflags libsecret-1 glib-2.0)
+LIBS:=$(shell $(PKG_CONFIG) --libs libsecret-1 glib-2.0)
+
+SRCS:=$(MAIN).c
+OBJS:=$(SRCS:.c=.o)
+
+%.o: %.c
+ $(CC) $(CFLAGS) $(CPPFLAGS) $(INCS) -o $@ -c $<
+
+$(MAIN): $(OBJS)
+ $(CC) -o $@ $(LDFLAGS) $^ $(LIBS)
+
+clean:
+ @$(RM) $(MAIN) $(OBJS)
diff --git a/contrib/credential/libsecret/git-credential-libsecret.c b/contrib/credential/libsecret/git-credential-libsecret.c
new file mode 100644
index 0000000000..e6598b6383
--- /dev/null
+++ b/contrib/credential/libsecret/git-credential-libsecret.c
@@ -0,0 +1,369 @@
+/*
+ * Copyright (C) 2011 John Szakmeister <john@szakmeister.net>
+ * 2012 Philipp A. Hartmann <pah@qo.cx>
+ * 2016 Mantas Mikulėnas <grawity@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Credits:
+ * - GNOME Keyring API handling originally written by John Szakmeister
+ * - ported to credential helper API by Philipp A. Hartmann
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <glib.h>
+#include <libsecret/secret.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;
+};
+
+#define CREDENTIAL_INIT { NULL, NULL, 0, NULL, NULL, NULL }
+
+typedef int (*credential_op_cb)(struct credential *);
+
+struct credential_operation {
+ char *name;
+ credential_op_cb op;
+};
+
+#define CREDENTIAL_OP_END { NULL, NULL }
+
+/* ----------------- Secret Service functions ----------------- */
+
+static char *make_label(struct credential *c)
+{
+ if (c->port)
+ return g_strdup_printf("Git: %s://%s:%hu/%s",
+ c->protocol, c->host, c->port, c->path ? c->path : "");
+ else
+ return g_strdup_printf("Git: %s://%s/%s",
+ c->protocol, c->host, c->path ? c->path : "");
+}
+
+static GHashTable *make_attr_list(struct credential *c)
+{
+ GHashTable *al = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
+
+ if (c->username)
+ g_hash_table_insert(al, "user", g_strdup(c->username));
+ if (c->protocol)
+ g_hash_table_insert(al, "protocol", g_strdup(c->protocol));
+ if (c->host)
+ g_hash_table_insert(al, "server", g_strdup(c->host));
+ if (c->port)
+ g_hash_table_insert(al, "port", g_strdup_printf("%hu", c->port));
+ if (c->path)
+ g_hash_table_insert(al, "object", g_strdup(c->path));
+
+ return al;
+}
+
+static int keyring_get(struct credential *c)
+{
+ SecretService *service = NULL;
+ GHashTable *attributes = NULL;
+ GError *error = NULL;
+ GList *items = NULL;
+
+ if (!c->protocol || !(c->host || c->path))
+ return EXIT_FAILURE;
+
+ service = secret_service_get_sync(0, NULL, &error);
+ if (error != NULL) {
+ g_critical("could not connect to Secret Service: %s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
+ attributes = make_attr_list(c);
+ items = secret_service_search_sync(service,
+ SECRET_SCHEMA_COMPAT_NETWORK,
+ attributes,
+ SECRET_SEARCH_LOAD_SECRETS | SECRET_SEARCH_UNLOCK,
+ NULL,
+ &error);
+ g_hash_table_unref(attributes);
+ if (error != NULL) {
+ g_critical("lookup failed: %s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
+ if (items != NULL) {
+ SecretItem *item;
+ SecretValue *secret;
+ const char *s;
+
+ item = items->data;
+ secret = secret_item_get_secret(item);
+ attributes = secret_item_get_attributes(item);
+
+ s = g_hash_table_lookup(attributes, "user");
+ if (s) {
+ g_free(c->username);
+ c->username = g_strdup(s);
+ }
+
+ s = secret_value_get_text(secret);
+ if (s) {
+ g_free(c->password);
+ c->password = g_strdup(s);
+ }
+
+ g_hash_table_unref(attributes);
+ secret_value_unref(secret);
+ g_list_free_full(items, g_object_unref);
+ }
+
+ return EXIT_SUCCESS;
+}
+
+
+static int keyring_store(struct credential *c)
+{
+ char *label = NULL;
+ GHashTable *attributes = NULL;
+ GError *error = NULL;
+
+ /*
+ * Sanity check that what we are storing is actually sensible.
+ * In particular, we can't make a URL without a protocol field.
+ * Without either a host or pathname (depending on the scheme),
+ * we have no primary key. And without a username and password,
+ * we are not actually storing a credential.
+ */
+ if (!c->protocol || !(c->host || c->path) ||
+ !c->username || !c->password)
+ return EXIT_FAILURE;
+
+ label = make_label(c);
+ attributes = make_attr_list(c);
+ secret_password_storev_sync(SECRET_SCHEMA_COMPAT_NETWORK,
+ attributes,
+ NULL,
+ label,
+ c->password,
+ NULL,
+ &error);
+ g_free(label);
+ g_hash_table_unref(attributes);
+
+ if (error != NULL) {
+ g_critical("store failed: %s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}
+
+static int keyring_erase(struct credential *c)
+{
+ GHashTable *attributes = NULL;
+ GError *error = NULL;
+
+ /*
+ * Sanity check that we actually have something to match
+ * against. The input we get is a restrictive pattern,
+ * so technically a blank credential means "erase everything".
+ * But it is too easy to accidentally send this, since it is equivalent
+ * to empty input. So explicitly disallow it, and require that the
+ * pattern have some actual content to match.
+ */
+ if (!c->protocol && !c->host && !c->path && !c->username)
+ return EXIT_FAILURE;
+
+ attributes = make_attr_list(c);
+ secret_password_clearv_sync(SECRET_SCHEMA_COMPAT_NETWORK,
+ attributes,
+ NULL,
+ &error);
+ g_hash_table_unref(attributes);
+
+ if (error != NULL) {
+ g_critical("erase failed: %s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}
+
+/*
+ * Table with helper operation callbacks, used by generic
+ * credential helper main function.
+ */
+static struct credential_operation const credential_helper_ops[] = {
+ { "get", keyring_get },
+ { "store", keyring_store },
+ { "erase", keyring_erase },
+ CREDENTIAL_OP_END
+};
+
+/* ------------------ credential functions ------------------ */
+
+static void credential_init(struct credential *c)
+{
+ memset(c, 0, sizeof(*c));
+}
+
+static void credential_clear(struct credential *c)
+{
+ g_free(c->protocol);
+ g_free(c->host);
+ g_free(c->path);
+ g_free(c->username);
+ g_free(c->password);
+
+ credential_init(c);
+}
+
+static int credential_read(struct credential *c)
+{
+ char *buf;
+ size_t line_len;
+ char *key;
+ char *value;
+
+ key = buf = g_malloc(1024);
+
+ while (fgets(buf, 1024, stdin)) {
+ line_len = strlen(buf);
+
+ if (line_len && buf[line_len-1] == '\n')
+ buf[--line_len] = '\0';
+
+ if (!line_len)
+ break;
+
+ value = strchr(buf, '=');
+ if (!value) {
+ g_warning("invalid credential line: %s", key);
+ g_free(buf);
+ return -1;
+ }
+ *value++ = '\0';
+
+ if (!strcmp(key, "protocol")) {
+ g_free(c->protocol);
+ c->protocol = g_strdup(value);
+ } else if (!strcmp(key, "host")) {
+ 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")) {
+ g_free(c->path);
+ c->path = g_strdup(value);
+ } else if (!strcmp(key, "username")) {
+ g_free(c->username);
+ c->username = g_strdup(value);
+ } else if (!strcmp(key, "password")) {
+ g_free(c->password);
+ c->password = g_strdup(value);
+ while (*value)
+ *value++ = '\0';
+ }
+ /*
+ * Ignore other lines; we don't know what they mean, but
+ * this future-proofs us when later versions of git do
+ * learn new lines, and the helpers are updated to match.
+ */
+ }
+
+ g_free(buf);
+
+ return 0;
+}
+
+static void credential_write_item(FILE *fp, const char *key, const char *value)
+{
+ if (!value)
+ return;
+ fprintf(fp, "%s=%s\n", key, value);
+}
+
+static void credential_write(const struct credential *c)
+{
+ /* only write username/password, if set */
+ credential_write_item(stdout, "username", c->username);
+ credential_write_item(stdout, "password", c->password);
+}
+
+static void usage(const char *name)
+{
+ struct credential_operation const *try_op = credential_helper_ops;
+ const char *basename = strrchr(name, '/');
+
+ basename = (basename) ? basename + 1 : name;
+ fprintf(stderr, "usage: %s <", basename);
+ while (try_op->name) {
+ fprintf(stderr, "%s", (try_op++)->name);
+ if (try_op->name)
+ fprintf(stderr, "%s", "|");
+ }
+ fprintf(stderr, "%s", ">\n");
+}
+
+int main(int argc, char *argv[])
+{
+ int ret = EXIT_SUCCESS;
+
+ struct credential_operation const *try_op = credential_helper_ops;
+ struct credential cred = CREDENTIAL_INIT;
+
+ if (!argv[1]) {
+ usage(argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ g_set_application_name("Git Credential Helper");
+
+ /* lookup operation callback */
+ while (try_op->name && strcmp(argv[1], try_op->name))
+ try_op++;
+
+ /* unsupported operation given -- ignore silently */
+ if (!try_op->name || !try_op->op)
+ goto out;
+
+ ret = credential_read(&cred);
+ if (ret)
+ goto out;
+
+ /* perform credential operation */
+ ret = (*try_op->op)(&cred);
+
+ credential_write(&cred);
+
+out:
+ credential_clear(&cred);
+ return ret;
+}
diff --git a/contrib/credential/netrc/.gitignore b/contrib/credential/netrc/.gitignore
new file mode 100644
index 0000000000..d41cdde84b
--- /dev/null
+++ b/contrib/credential/netrc/.gitignore
@@ -0,0 +1 @@
+git-credential-netrc
diff --git a/contrib/credential/netrc/Makefile b/contrib/credential/netrc/Makefile
new file mode 100644
index 0000000000..c284fb8ac4
--- /dev/null
+++ b/contrib/credential/netrc/Makefile
@@ -0,0 +1,30 @@
+# The default target of this Makefile is...
+all::
+
+SCRIPT_PERL = git-credential-netrc.perl
+GIT_ROOT_DIR = ../../..
+HERE = contrib/credential/netrc
+
+SCRIPT_PERL_FULL = $(patsubst %,$(HERE)/%,$(SCRIPT_PERL))
+
+all:: build
+
+build:
+ $(MAKE) -C $(GIT_ROOT_DIR) SCRIPT_PERL="$(SCRIPT_PERL_FULL)" \
+ build-perl-script
+
+install: build
+ $(MAKE) -C $(GIT_ROOT_DIR) SCRIPT_PERL="$(SCRIPT_PERL_FULL)" \
+ install-perl-script
+
+clean:
+ $(MAKE) -C $(GIT_ROOT_DIR) SCRIPT_PERL="$(SCRIPT_PERL_FULL)" \
+ clean-perl-script
+
+test: build
+ ./t-git-credential-netrc.sh
+
+testverbose: build
+ ./t-git-credential-netrc.sh -d -v
+
+.PHONY: all build install clean test testverbose
diff --git a/contrib/credential/netrc/git-credential-netrc.perl b/contrib/credential/netrc/git-credential-netrc.perl
new file mode 100755
index 0000000000..bc57cc6588
--- /dev/null
+++ b/contrib/credential/netrc/git-credential-netrc.perl
@@ -0,0 +1,440 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Getopt::Long;
+use File::Basename;
+use Git;
+
+my $VERSION = "0.2";
+
+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@",
+ 'gpg|g:s',
+ );
+
+if ($options{help}) {
+ my $shortname = basename($0);
+ $shortname =~ s/git-credential-//;
+
+ print <<EOHIPPUS;
+
+$0 [(-f <authfile>)...] [-g <program>] [-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.
+
+ -g|--gpg <program> : specify the program for GPG. By default, this is the
+ value of gpg.program in the git repository or global
+ option or gpg.
+
+ -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 <authfile>s,
+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 <authfile>)...] [-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 ];
+}
+
+load_config(\%options);
+
+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 = ($options{'gpg'}, qw(--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 load_config {
+ # load settings from git config
+ my $options = shift;
+ # set from command argument, gpg.program option, or default to gpg
+ $options->{'gpg'} //= Git::config('gpg.program')
+ // 'gpg';
+ log_verbose("using $options{'gpg'} for GPG operations");
+}
+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/t-git-credential-netrc.sh b/contrib/credential/netrc/t-git-credential-netrc.sh
new file mode 100755
index 0000000000..07227d0228
--- /dev/null
+++ b/contrib/credential/netrc/t-git-credential-netrc.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+(
+ cd ../../../t
+ test_description='git-credential-netrc'
+ . ./test-lib.sh
+
+ if ! test_have_prereq PERL; then
+ skip_all='skipping perl interface tests, perl not available'
+ test_done
+ fi
+
+ perl -MTest::More -e 0 2>/dev/null || {
+ skip_all="Perl Test::More unavailable, skipping test"
+ test_done
+ }
+
+ # set up test repository
+
+ test_expect_success \
+ 'set up test repository' \
+ 'git config --add gpg.program test.git-config-gpg'
+
+ # The external test will outputs its own plan
+ test_external_has_tap=1
+
+ export PERL5LIB="$GITPERLLIB"
+ test_external \
+ 'git-credential-netrc' \
+ perl "$GIT_BUILD_DIR"/contrib/credential/netrc/test.pl
+
+ test_done
+)
diff --git a/contrib/credential/netrc/test.command-option-gpg b/contrib/credential/netrc/test.command-option-gpg
new file mode 100755
index 0000000000..d8f1285d41
--- /dev/null
+++ b/contrib/credential/netrc/test.command-option-gpg
@@ -0,0 +1,2 @@
+#!/bin/sh
+echo machine command-option-gpg login username password password
diff --git a/contrib/credential/netrc/test.git-config-gpg b/contrib/credential/netrc/test.git-config-gpg
new file mode 100755
index 0000000000..65cf594c20
--- /dev/null
+++ b/contrib/credential/netrc/test.git-config-gpg
@@ -0,0 +1,2 @@
+#!/bin/sh
+echo machine git-config-gpg login username password password
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.netrc.gpg b/contrib/credential/netrc/test.netrc.gpg
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/contrib/credential/netrc/test.netrc.gpg
diff --git a/contrib/credential/netrc/test.pl b/contrib/credential/netrc/test.pl
new file mode 100755
index 0000000000..c0fb3718b2
--- /dev/null
+++ b/contrib/credential/netrc/test.pl
@@ -0,0 +1,139 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+use Test::More qw(no_plan);
+use File::Basename;
+use File::Spec::Functions qw(:DEFAULT rel2abs);
+use IPC::Open2;
+
+BEGIN {
+ # t-git-credential-netrc.sh kicks off our testing, so we have to go
+ # from there.
+ Test::More->builder->current_test(1);
+}
+
+my @global_credential_args = @ARGV;
+my $scriptDir = dirname rel2abs $0;
+my ($netrc, $netrcGpg, $gcNetrc) = map { catfile $scriptDir, $_; }
+ qw(test.netrc
+ test.netrc.gpg
+ git-credential-netrc);
+local $ENV{PATH} = join ':'
+ , $scriptDir
+ , $ENV{PATH}
+ ? $ENV{PATH}
+ : ();
+
+diag "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");
+
+diag "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;
+
+diag "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");
+
+diag "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");
+
+diag "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");
+
+is($cred->{password}, 'carolknows', "Got correct Github password");
+is($cred->{username}, 'carol', "Got correct Github username");
+
+diag "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");
+
+is($cred->{password}, 'bobwillknow', "Got correct user-specific password");
+is($cred->{protocol}, 'imaps', "Got correct user-specific protocol");
+
+diag "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");
+
+is($cred->{password}, 'tzzknow', "Got correct host:port-specific password");
+is($cred->{username}, 'tzz', "Got correct host:port-specific username");
+
+diag "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");
+
+is($cred->{password}, 'bobwillknow', "Got correct 'host:port kills host' password");
+is($cred->{username}, 'bob', "Got correct 'host:port kills host' username");
+
+diag 'Testing netrc file decryption by git config gpg.program setting\n';
+$cred = run_credential( ['-f', $netrcGpg, 'get']
+ , { host => 'git-config-gpg' }
+ );
+
+ok(scalar keys %$cred == 2, 'Got keys decrypted by git config option');
+
+diag 'Testing netrc file decryption by gpg option\n';
+$cred = run_credential( ['-f', $netrcGpg, '-g', 'test.command-option-gpg', 'get']
+ , { host => 'command-option-gpg' }
+ );
+
+ok(scalar keys %$cred == 2, 'Got keys decrypted by command option');
+
+my $is_passing = eval { Test::More->is_passing };
+exit($is_passing ? 0 : 1) unless $@ =~ /Can't locate object method/;
+
+sub run_credential
+{
+ my $args = shift @_;
+ my $data = shift @_;
+ my $pid = open2(my $chld_out, my $chld_in,
+ $gcNetrc, @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 6beed123ab..0b44a9b7cc 100644
--- a/contrib/credential/osxkeychain/git-credential-osxkeychain.c
+++ b/contrib/credential/osxkeychain/git-credential-osxkeychain.c
@@ -10,6 +10,7 @@ static char *username;
static char *password;
static UInt16 port;
+__attribute__((format (printf, 1, 2)))
static void die(const char *err, ...)
{
char msg[4096];
@@ -127,10 +128,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);
}
@@ -154,7 +165,7 @@ static void read_credential(void)
int main(int argc, const char **argv)
{
const char *usage =
- "Usage: git credential-osxkeychain <get|store|erase>";
+ "usage: git credential-osxkeychain <get|store|erase>";
if (!argv[1])
die(usage);
diff --git a/contrib/credential/wincred/Makefile b/contrib/credential/wincred/Makefile
index bad45ca47a..6e992c0866 100644
--- a/contrib/credential/wincred/Makefile
+++ b/contrib/credential/wincred/Makefile
@@ -1,14 +1,22 @@
all: git-credential-wincred.exe
-CC = gcc
-RM = rm -f
-CFLAGS = -O2 -Wall
-
-include ../../../config.mak.autogen
-include ../../../config.mak
+CC ?= gcc
+RM ?= rm -f
+CFLAGS ?= -O2 -Wall
+
+prefix ?= /usr/local
+libexecdir ?= $(prefix)/libexec/git-core
+
+INSTALL ?= install
+
git-credential-wincred.exe : git-credential-wincred.c
$(LINK.c) $^ $(LOADLIBES) $(LDLIBS) -o $@
+install: git-credential-wincred.exe
+ $(INSTALL) -m 755 $^ $(libexecdir)
+
clean:
$(RM) git-credential-wincred.exe
diff --git a/contrib/credential/wincred/git-credential-wincred.c b/contrib/credential/wincred/git-credential-wincred.c
index cbaec5f24b..5091048f9c 100644
--- a/contrib/credential/wincred/git-credential-wincred.c
+++ b/contrib/credential/wincred/git-credential-wincred.c
@@ -9,6 +9,9 @@
/* common helpers */
+#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
+
+__attribute__((format (printf, 1, 2)))
static void die(const char *err, ...)
{
char msg[4096];
@@ -30,14 +33,6 @@ static void *xmalloc(size_t size)
return ret;
}
-static char *xstrdup(const char *str)
-{
- char *ret = strdup(str);
- if (!ret)
- die("Out of memory");
- return ret;
-}
-
/* MinGW doesn't have wincred.h, so we need to define stuff */
typedef struct _CREDENTIAL_ATTRIBUTEW {
@@ -67,95 +62,125 @@ typedef struct _CREDENTIALW {
#define CRED_MAX_ATTRIBUTES 64
typedef BOOL (WINAPI *CredWriteWT)(PCREDENTIALW, DWORD);
-typedef BOOL (WINAPI *CredUnPackAuthenticationBufferWT)(DWORD, PVOID, DWORD,
- LPWSTR, DWORD *, LPWSTR, DWORD *, LPWSTR, DWORD *);
typedef BOOL (WINAPI *CredEnumerateWT)(LPCWSTR, DWORD, DWORD *,
PCREDENTIALW **);
-typedef BOOL (WINAPI *CredPackAuthenticationBufferWT)(DWORD, LPWSTR, LPWSTR,
- PBYTE, DWORD *);
typedef VOID (WINAPI *CredFreeT)(PVOID);
typedef BOOL (WINAPI *CredDeleteWT)(LPCWSTR, DWORD, DWORD);
-static HMODULE advapi, credui;
+static HMODULE advapi;
static CredWriteWT CredWriteW;
-static CredUnPackAuthenticationBufferWT CredUnPackAuthenticationBufferW;
static CredEnumerateWT CredEnumerateW;
-static CredPackAuthenticationBufferWT CredPackAuthenticationBufferW;
static CredFreeT CredFree;
static CredDeleteWT CredDeleteW;
static void load_cred_funcs(void)
{
/* load DLLs */
- advapi = LoadLibrary("advapi32.dll");
- credui = LoadLibrary("credui.dll");
- if (!advapi || !credui)
- die("failed to load DLLs");
+ advapi = LoadLibraryExA("advapi32.dll", NULL,
+ LOAD_LIBRARY_SEARCH_SYSTEM32);
+ if (!advapi)
+ die("failed to load advapi32.dll");
/* get function pointers */
CredWriteW = (CredWriteWT)GetProcAddress(advapi, "CredWriteW");
- CredUnPackAuthenticationBufferW = (CredUnPackAuthenticationBufferWT)
- GetProcAddress(credui, "CredUnPackAuthenticationBufferW");
CredEnumerateW = (CredEnumerateWT)GetProcAddress(advapi,
"CredEnumerateW");
- CredPackAuthenticationBufferW = (CredPackAuthenticationBufferWT)
- GetProcAddress(credui, "CredPackAuthenticationBufferW");
CredFree = (CredFreeT)GetProcAddress(advapi, "CredFree");
CredDeleteW = (CredDeleteWT)GetProcAddress(advapi, "CredDeleteW");
- if (!CredWriteW || !CredUnPackAuthenticationBufferW ||
- !CredEnumerateW || !CredPackAuthenticationBufferW || !CredFree ||
- !CredDeleteW)
+ if (!CredWriteW || !CredEnumerateW || !CredFree || !CredDeleteW)
die("failed to load functions");
}
-static char target_buf[1024];
-static char *protocol, *host, *path, *username;
-static WCHAR *wusername, *password, *target;
+static WCHAR *wusername, *password, *protocol, *host, *path, target[1024];
-static void write_item(const char *what, WCHAR *wbuf)
+static void write_item(const char *what, LPCWSTR wbuf, int wlen)
{
char *buf;
- int len = WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, NULL, 0, NULL,
+
+ if (!wbuf || !wlen) {
+ printf("%s=\n", what);
+ return;
+ }
+
+ int len = WideCharToMultiByte(CP_UTF8, 0, wbuf, wlen, NULL, 0, NULL,
FALSE);
buf = xmalloc(len);
- if (!WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, buf, len, NULL, FALSE))
+ if (!WideCharToMultiByte(CP_UTF8, 0, wbuf, wlen, buf, len, NULL, FALSE))
die("WideCharToMultiByte failed!");
printf("%s=", what);
- fwrite(buf, 1, len - 1, stdout);
+ fwrite(buf, 1, len, stdout);
putchar('\n');
free(buf);
}
-static int match_attr(const CREDENTIALW *cred, const WCHAR *keyword,
- const char *want)
+/*
+ * Match an (optional) expected string and a delimiter in the target string,
+ * consuming the matched text by updating the target pointer.
+ */
+
+static LPCWSTR wcsstr_last(LPCWSTR str, LPCWSTR find)
+{
+ LPCWSTR res = NULL, pos;
+ for (pos = wcsstr(str, find); pos; pos = wcsstr(pos + 1, find))
+ res = pos;
+ return res;
+}
+
+static int match_part_with_last(LPCWSTR *ptarget, LPCWSTR want, LPCWSTR delim, int last)
{
- int i;
- if (!want)
- return 1;
+ LPCWSTR delim_pos, start = *ptarget;
+ int len;
+
+ /* find start of delimiter (or end-of-string if delim is empty) */
+ if (*delim)
+ delim_pos = last ? wcsstr_last(start, delim) : wcsstr(start, delim);
+ else
+ delim_pos = start + wcslen(start);
+
+ /*
+ * match text up to delimiter, or end of string (e.g. the '/' after
+ * host is optional if not followed by a path)
+ */
+ if (delim_pos)
+ len = delim_pos - start;
+ else
+ len = wcslen(start);
+
+ /* update ptarget if we either found a delimiter or need a match */
+ if (delim_pos || want)
+ *ptarget = delim_pos ? delim_pos + wcslen(delim) : start + len;
+
+ return !want || (!wcsncmp(want, start, len) && !want[len]);
+}
- for (i = 0; i < cred->AttributeCount; ++i)
- if (!wcscmp(cred->Attributes[i].Keyword, keyword))
- return !strcmp((const char *)cred->Attributes[i].Value,
- want);
+static int match_part(LPCWSTR *ptarget, LPCWSTR want, LPCWSTR delim)
+{
+ return match_part_with_last(ptarget, want, delim, 0);
+}
- return 0; /* not found */
+static int match_part_last(LPCWSTR *ptarget, LPCWSTR want, LPCWSTR delim)
+{
+ return match_part_with_last(ptarget, want, delim, 1);
}
static int match_cred(const CREDENTIALW *cred)
{
- return (!wusername || !wcscmp(wusername, cred->UserName)) &&
- match_attr(cred, L"git_protocol", protocol) &&
- match_attr(cred, L"git_host", host) &&
- match_attr(cred, L"git_path", path);
+ LPCWSTR target = cred->TargetName;
+ if (wusername && wcscmp(wusername, cred->UserName ? cred->UserName : L""))
+ return 0;
+
+ return match_part(&target, L"git", L":") &&
+ match_part(&target, protocol, L"://") &&
+ match_part_last(&target, wusername, L"@") &&
+ match_part(&target, host, L"/") &&
+ match_part(&target, path, L"");
}
static void get_credential(void)
{
- WCHAR *user_buf, *pass_buf;
- DWORD user_buf_size = 0, pass_buf_size = 0;
- CREDENTIALW **creds, *cred = NULL;
+ CREDENTIALW **creds;
DWORD num_creds;
int i;
@@ -165,90 +190,36 @@ static void get_credential(void)
/* search for the first credential that matches username */
for (i = 0; i < num_creds; ++i)
if (match_cred(creds[i])) {
- cred = creds[i];
+ write_item("username", creds[i]->UserName,
+ creds[i]->UserName ? wcslen(creds[i]->UserName) : 0);
+ write_item("password",
+ (LPCWSTR)creds[i]->CredentialBlob,
+ creds[i]->CredentialBlobSize / sizeof(WCHAR));
break;
}
- if (!cred)
- return;
-
- CredUnPackAuthenticationBufferW(0, cred->CredentialBlob,
- cred->CredentialBlobSize, NULL, &user_buf_size, NULL, NULL,
- NULL, &pass_buf_size);
-
- user_buf = xmalloc(user_buf_size * sizeof(WCHAR));
- pass_buf = xmalloc(pass_buf_size * sizeof(WCHAR));
-
- if (!CredUnPackAuthenticationBufferW(0, cred->CredentialBlob,
- cred->CredentialBlobSize, user_buf, &user_buf_size, NULL, NULL,
- pass_buf, &pass_buf_size))
- die("CredUnPackAuthenticationBuffer failed");
CredFree(creds);
-
- /* zero-terminate (sizes include zero-termination) */
- user_buf[user_buf_size - 1] = L'\0';
- pass_buf[pass_buf_size - 1] = L'\0';
-
- write_item("username", user_buf);
- write_item("password", pass_buf);
-
- free(user_buf);
- free(pass_buf);
-}
-
-static void write_attr(CREDENTIAL_ATTRIBUTEW *attr, const WCHAR *keyword,
- const char *value)
-{
- attr->Keyword = (LPWSTR)keyword;
- attr->Flags = 0;
- attr->ValueSize = strlen(value) + 1; /* store zero-termination */
- attr->Value = (LPBYTE)value;
}
static void store_credential(void)
{
CREDENTIALW cred;
- BYTE *auth_buf;
- DWORD auth_buf_size = 0;
- CREDENTIAL_ATTRIBUTEW attrs[CRED_MAX_ATTRIBUTES];
if (!wusername || !password)
return;
- /* query buffer size */
- CredPackAuthenticationBufferW(0, wusername, password,
- NULL, &auth_buf_size);
-
- auth_buf = xmalloc(auth_buf_size);
-
- if (!CredPackAuthenticationBufferW(0, wusername, password,
- auth_buf, &auth_buf_size))
- die("CredPackAuthenticationBuffer failed");
-
cred.Flags = 0;
cred.Type = CRED_TYPE_GENERIC;
cred.TargetName = target;
cred.Comment = L"saved by git-credential-wincred";
- cred.CredentialBlobSize = auth_buf_size;
- cred.CredentialBlob = auth_buf;
+ cred.CredentialBlobSize = (wcslen(password)) * sizeof(WCHAR);
+ cred.CredentialBlob = (LPVOID)password;
cred.Persist = CRED_PERSIST_LOCAL_MACHINE;
- cred.AttributeCount = 1;
- cred.Attributes = attrs;
+ cred.AttributeCount = 0;
+ cred.Attributes = NULL;
cred.TargetAlias = NULL;
cred.UserName = wusername;
- write_attr(attrs, L"git_protocol", protocol);
-
- if (host) {
- write_attr(attrs + cred.AttributeCount, L"git_host", host);
- cred.AttributeCount++;
- }
-
- if (path) {
- write_attr(attrs + cred.AttributeCount, L"git_path", path);
- cred.AttributeCount++;
- }
-
if (!CredWriteW(&cred, 0))
die("CredWrite failed");
}
@@ -284,10 +255,13 @@ static void read_credential(void)
while (fgets(buf, sizeof(buf), stdin)) {
char *v;
+ int len = strlen(buf);
+ /* strip trailing CR / LF */
+ while (len && strchr("\r\n", buf[len - 1]))
+ buf[--len] = 0;
- if (!strcmp(buf, "\n"))
+ if (!*buf)
break;
- buf[strlen(buf)-1] = '\0';
v = strchr(buf, '=');
if (!v)
@@ -295,13 +269,12 @@ static void read_credential(void)
*v++ = '\0';
if (!strcmp(buf, "protocol"))
- protocol = xstrdup(v);
+ protocol = utf8_to_utf16_dup(v);
else if (!strcmp(buf, "host"))
- host = xstrdup(v);
+ host = utf8_to_utf16_dup(v);
else if (!strcmp(buf, "path"))
- path = xstrdup(v);
+ path = utf8_to_utf16_dup(v);
else if (!strcmp(buf, "username")) {
- username = xstrdup(v);
wusername = utf8_to_utf16_dup(v);
} else if (!strcmp(buf, "password"))
password = utf8_to_utf16_dup(v);
@@ -313,7 +286,7 @@ static void read_credential(void)
int main(int argc, char *argv[])
{
const char *usage =
- "Usage: git credential-wincred <get|store|erase>\n";
+ "usage: git credential-wincred <get|store|erase>\n";
if (!argv[1])
die(usage);
@@ -330,22 +303,20 @@ int main(int argc, char *argv[])
return 0;
/* prepare 'target', the unique key for the credential */
- strncat(target_buf, "git:", sizeof(target_buf));
- strncat(target_buf, protocol, sizeof(target_buf));
- strncat(target_buf, "://", sizeof(target_buf));
- if (username) {
- strncat(target_buf, username, sizeof(target_buf));
- strncat(target_buf, "@", sizeof(target_buf));
+ wcscpy(target, L"git:");
+ wcsncat(target, protocol, ARRAY_SIZE(target));
+ wcsncat(target, L"://", ARRAY_SIZE(target));
+ if (wusername) {
+ wcsncat(target, wusername, ARRAY_SIZE(target));
+ wcsncat(target, L"@", ARRAY_SIZE(target));
}
if (host)
- strncat(target_buf, host, sizeof(target_buf));
+ wcsncat(target, host, ARRAY_SIZE(target));
if (path) {
- strncat(target_buf, "/", sizeof(target_buf));
- strncat(target_buf, path, sizeof(target_buf));
+ wcsncat(target, L"/", ARRAY_SIZE(target));
+ wcsncat(target, path, ARRAY_SIZE(target));
}
- target = utf8_to_utf16_dup(target_buf);
-
if (!strcmp(argv[1], "get"))
get_credential();
else if (!strcmp(argv[1], "store"))