{"thread":{"id":"31334","subject":"[PATCH] contrib: GnomeKeyring support + generic helper implementation","startedAt":"2012-08-23T16:57:44Z","lastAt":"2012-09-01T12:42:34Z","messageCount":16,"participants":["Philipp A. Hartmann","Junio C Hamano","Jeff King","Erik Faye-Lund"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"197718","messageId":"1345741068-11004-1-git-send-email-pah@qo.cx","threadId":"31334","inReplyTo":null,"subject":"[PATCH] contrib: GnomeKeyring support + generic helper implementation","fromName":"Philipp A. Hartmann","fromEmail":"pah@qo.cx","sentAt":"2012-08-23T16:57:44Z","receivedAt":"2012-08-23T16:57:44Z","isPatch":true,"sender":{"key":"pah@qo.cx","avatar":"https://avatars.githubusercontent.com/u/177333?v=4"},"body":"All,\n\nthe following patch series proposes enhancements to the credential helper\nimplementations in the contrib section.  The detailed development history\ncan be found at GitHub [1].\n\n  The first patch adds a GnomeKeyring credential backend.  The GnomeKeyring\nspecific parts are based on the work by John Szakmeister, who wrote the\nhelper originally for the initial, unpublished version of the credential\nhelper protocol.\n\n  The second patch adds a generic implementation of a credential helper\nbased on a simplified credential API and common helper functions.  Helpers\nbased on this implementation do not need to worry about the credential\nprotocol and only need to implement callback functions for the different\ncredential operations.\n\n  The third and fourth patches port the existing helpers to this generic\nimplementation.\n\nLooking forward to your thoughts.\n\nGreetings from Oldenburg,\n  Philipp\n\n[1] https://github.com/pah/git-credential-helper\n"},{"id":"197722","messageId":"1345741068-11004-2-git-send-email-pah@qo.cx","threadId":"31334","inReplyTo":"1345741068-11004-1-git-send-email-pah@qo.cx","subject":"[PATCH 1/4] contrib: add credential helper for GnomeKeyring","fromName":"Philipp A. Hartmann","fromEmail":"pah@qo.cx","sentAt":"2012-08-23T16:57:45Z","receivedAt":"2012-08-23T16:57:45Z","isPatch":true,"sender":{"key":"pah@qo.cx","avatar":"https://avatars.githubusercontent.com/u/177333?v=4"},"body":"From: \"Philipp A. Hartmann\" <pah@qo.cx>\n\nWith this installed in your $PATH, you can store\ngit-over-http passwords in your keyring by doing:\n\ngit config credential.helper gnome-keyring\n\nThe code is based in large part on the work of John Szakmeister\nwho wrote the helper originally for the initial, unpublished\nversion of the credential helper protocol.\n\nThis version will pass t0303 if you do:\n\n  GIT_TEST_CREDENTIAL_HELPER=gnome-keyring \\\n  ./t0303-credential-external.sh\n\nSigned-off-by: Philipp A. Hartmann <pah@qo.cx>\n---\n contrib/credential/gnome-keyring/.gitignore        |    1 +\n contrib/credential/gnome-keyring/Makefile          |   24 ++\n .../gnome-keyring/git-credential-gnome-keyring.c   |  445 ++++++++++++++++++++\n 3 files changed, 470 insertions(+)\n create mode 100644 contrib/credential/gnome-keyring/.gitignore\n create mode 100644 contrib/credential/gnome-keyring/Makefile\n create mode 100644 contrib/credential/gnome-keyring/git-credential-gnome-keyring.c\n\ndiff --git a/contrib/credential/gnome-keyring/.gitignore b/contrib/credential/gnome-keyring/.gitignore\nnew file mode 100644\nindex 0000000..88d8fcd\n--- /dev/null\n+++ b/contrib/credential/gnome-keyring/.gitignore\n@@ -0,0 +1 @@\n+git-credential-gnome-keyring\ndiff --git a/contrib/credential/gnome-keyring/Makefile b/contrib/credential/gnome-keyring/Makefile\nnew file mode 100644\nindex 0000000..e6561d8\n--- /dev/null\n+++ b/contrib/credential/gnome-keyring/Makefile\n@@ -0,0 +1,24 @@\n+MAIN:=git-credential-gnome-keyring\n+all:: $(MAIN)\n+\n+CC = gcc\n+RM = rm -f\n+CFLAGS = -g -O2 -Wall\n+\n+-include ../../../config.mak.autogen\n+-include ../../../config.mak\n+\n+INCS:=$(shell pkg-config --cflags gnome-keyring-1)\n+LIBS:=$(shell pkg-config --libs gnome-keyring-1)\n+\n+SRCS:=$(MAIN).c\n+OBJS:=$(SRCS:.c=.o)\n+\n+%.o: %.c\n+\t$(CC) $(CFLAGS) $(CPPFLAGS) $(INCS) -o $@ -c $<\n+\n+$(MAIN): $(OBJS)\n+\t$(CC) -o $@ $(LDFLAGS) $^ $(LIBS)\n+\n+clean:\n+\t@$(RM) $(MAIN) $(OBJS)\ndiff --git a/contrib/credential/gnome-keyring/git-credential-gnome-keyring.c b/contrib/credential/gnome-keyring/git-credential-gnome-keyring.c\nnew file mode 100644\nindex 0000000..41f61c5\n--- /dev/null\n+++ b/contrib/credential/gnome-keyring/git-credential-gnome-keyring.c\n@@ -0,0 +1,445 @@\n+/*\n+ * Copyright (C) 2011 John Szakmeister <john@szakmeister.net>\n+ *               2012 Philipp A. Hartmann <pah@qo.cx>\n+ *\n+ *  This program is free software; you can redistribute it and/or modify\n+ *  it under the terms of the GNU General Public License as published by\n+ *  the Free Software Foundation; either version 2 of the License, or\n+ *  (at your option) any later version.\n+ *\n+ *  This program is distributed in the hope that it will be useful,\n+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n+ *  GNU General Public License for more details.\n+ *\n+ *  You should have received a copy of the GNU General Public License\n+ *  along with this program; if not, write to the Free Software\n+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\n+ */\n+\n+/*\n+ * Credits:\n+ * - GNOME Keyring API handling originally written by John Szakmeister\n+ * - ported to credential helper API by Philipp A. Hartmann\n+ */\n+\n+#include <stdio.h>\n+#include <string.h>\n+#include <stdarg.h>\n+#include <stdlib.h>\n+#include <errno.h>\n+#include <gnome-keyring.h>\n+\n+/*\n+ * This credential struct and API is simplified from git's credential.{h,c}\n+ */\n+struct credential\n+{\n+\tchar          *protocol;\n+\tchar          *host;\n+\tunsigned short port;\n+\tchar          *path;\n+\tchar          *username;\n+\tchar          *password;\n+};\n+\n+#define CREDENTIAL_INIT \\\n+  { NULL,NULL,0,NULL,NULL,NULL }\n+\n+void credential_init(struct credential *c);\n+void credential_clear(struct credential *c);\n+int  credential_read(struct credential *c);\n+void credential_write(const struct credential *c);\n+\n+typedef int (*credential_op_cb)(struct credential*);\n+\n+struct credential_operation\n+{\n+\tchar             *name;\n+\tcredential_op_cb op;\n+};\n+\n+#define CREDENTIAL_OP_END \\\n+  { NULL,NULL }\n+\n+/*\n+ * Table with operation callbacks is defined in concrete\n+ * credential helper implementation and contains entries\n+ * like { \"get\", function_to_get_credential } terminated\n+ * by CREDENTIAL_OP_END.\n+ */\n+struct credential_operation const credential_helper_ops[];\n+\n+/* ---------------- common helper functions ----------------- */\n+\n+static inline void free_password(char *password)\n+{\n+\tchar *c = password;\n+\tif (!password)\n+\t\treturn;\n+\n+\twhile (*c) *c++ = '\\0';\n+\tfree(password);\n+}\n+\n+static inline void warning(const char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\tfprintf(stderr, \"warning: \");\n+\tvfprintf(stderr, fmt, ap);\n+\tfprintf(stderr, \"\\n\" );\n+\tva_end(ap);\n+}\n+\n+static inline void error(const char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\tfprintf(stderr, \"error: \");\n+\tvfprintf(stderr, fmt, ap);\n+\tfprintf(stderr, \"\\n\" );\n+\tva_end(ap);\n+}\n+\n+static inline void die(const char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap,fmt);\n+\terror(fmt, ap);\n+\tva_end(ap);\n+\texit(EXIT_FAILURE);\n+}\n+\n+static inline void die_errno(int err)\n+{\n+\terror(\"%s\", strerror(err));\n+\texit(EXIT_FAILURE);\n+}\n+\n+static inline char *xstrdup(const char *str)\n+{\n+\tchar *ret = strdup(str);\n+\tif (!ret)\n+\t\tdie_errno(errno);\n+\n+\treturn ret;\n+}\n+\n+/* ----------------- GNOME Keyring functions ----------------- */\n+\n+/* create a special keyring option string, if path is given */\n+static char* keyring_object(struct credential *c)\n+{\n+\tchar* object = NULL;\n+\n+\tif (!c->path)\n+\t\treturn object;\n+\n+\tobject = (char*) malloc(strlen(c->host)+strlen(c->path)+8);\n+\tif(!object)\n+\t\tdie_errno(errno);\n+\n+\tif(c->port)\n+\t\tsprintf(object,\"%s:%hd/%s\",c->host,c->port,c->path);\n+\telse\n+\t\tsprintf(object,\"%s/%s\",c->host,c->path);\n+\n+\treturn object;\n+}\n+\n+int keyring_get(struct credential *c)\n+{\n+\tchar* object = NULL;\n+\tGList *entries;\n+\tGnomeKeyringNetworkPasswordData *password_data;\n+\tGnomeKeyringResult result;\n+\n+\tif (!c->protocol || !(c->host || c->path))\n+\t\treturn EXIT_FAILURE;\n+\n+\tobject = keyring_object(c);\n+\n+\tresult = gnome_keyring_find_network_password_sync(\n+\t\t\t\tc->username,\n+\t\t\t\tNULL /* domain */,\n+\t\t\t\tc->host,\n+\t\t\t\tobject,\n+\t\t\t\tc->protocol,\n+\t\t\t\tNULL /* authtype */,\n+\t\t\t\tc->port,\n+\t\t\t\t&entries);\n+\n+\tfree(object);\n+\n+\tif (result == GNOME_KEYRING_RESULT_NO_MATCH)\n+\t\treturn EXIT_SUCCESS;\n+\n+\tif (result == GNOME_KEYRING_RESULT_CANCELLED)\n+\t\treturn EXIT_SUCCESS;\n+\n+\tif (result != GNOME_KEYRING_RESULT_OK) {\n+\t\terror(\"%s\",gnome_keyring_result_to_message(result));\n+\t\treturn EXIT_FAILURE;\n+\t}\n+\n+\t/* pick the first one from the list */\n+\tpassword_data = (GnomeKeyringNetworkPasswordData *) entries->data;\n+\n+\tfree_password(c->password);\n+\tc->password = xstrdup(password_data->password);\n+\n+\tif (!c->username)\n+\t\tc->username = xstrdup(password_data->user);\n+\n+\tgnome_keyring_network_password_list_free(entries);\n+\n+\treturn EXIT_SUCCESS;\n+}\n+\n+\n+int keyring_store(struct credential *c)\n+{\n+\tguint32 item_id;\n+\tchar  *object = NULL;\n+\n+\t/*\n+\t * Sanity check that what we are storing is actually sensible.\n+\t * In particular, we can't make a URL without a protocol field.\n+\t * Without either a host or pathname (depending on the scheme),\n+\t * we have no primary key. And without a username and password,\n+\t * we are not actually storing a credential.\n+\t */\n+\tif (!c->protocol || !(c->host || c->path) ||\n+\t    !c->username || !c->password)\n+\t\treturn EXIT_FAILURE;\n+\n+\tobject = keyring_object(c);\n+\n+\tgnome_keyring_set_network_password_sync(\n+\t\t\t\tGNOME_KEYRING_DEFAULT,\n+\t\t\t\tc->username,\n+\t\t\t\tNULL /* domain */,\n+\t\t\t\tc->host,\n+\t\t\t\tobject,\n+\t\t\t\tc->protocol,\n+\t\t\t\tNULL /* authtype */,\n+\t\t\t\tc->port,\n+\t\t\t\tc->password,\n+\t\t\t\t&item_id);\n+\n+\tfree(object);\n+\treturn EXIT_SUCCESS;\n+}\n+\n+int keyring_erase(struct credential *c)\n+{\n+\tchar  *object = NULL;\n+\tGList *entries;\n+\tGnomeKeyringNetworkPasswordData *password_data;\n+\tGnomeKeyringResult result;\n+\n+\t/*\n+\t * Sanity check that we actually have something to match\n+\t * against. The input we get is a restrictive pattern,\n+\t * so technically a blank credential means \"erase everything\".\n+\t * But it is too easy to accidentally send this, since it is equivalent\n+\t * to empty input. So explicitly disallow it, and require that the\n+\t * pattern have some actual content to match.\n+\t */\n+\tif (!c->protocol && !c->host && !c->path && !c->username)\n+\t\treturn EXIT_FAILURE;\n+\n+\tobject = keyring_object(c);\n+\n+\tresult = gnome_keyring_find_network_password_sync(\n+\t\t\t\tc->username,\n+\t\t\t\tNULL /* domain */,\n+\t\t\t\tc->host,\n+\t\t\t\tobject,\n+\t\t\t\tc->protocol,\n+\t\t\t\tNULL /* authtype */,\n+\t\t\t\tc->port,\n+\t\t\t\t&entries);\n+\n+\tfree(object);\n+\n+\tif (result == GNOME_KEYRING_RESULT_NO_MATCH)\n+\t\treturn EXIT_SUCCESS;\n+\n+\tif (result == GNOME_KEYRING_RESULT_CANCELLED)\n+\t\treturn EXIT_SUCCESS;\n+\n+\tif (result != GNOME_KEYRING_RESULT_OK)\n+\t{\n+\t\terror(\"%s\",gnome_keyring_result_to_message(result));\n+\t\treturn EXIT_FAILURE;\n+\t}\n+\n+\t/* pick the first one from the list (delete all matches?) */\n+\tpassword_data = (GnomeKeyringNetworkPasswordData *) entries->data;\n+\n+\tresult = gnome_keyring_item_delete_sync(\n+\t\tpassword_data->keyring, password_data->item_id);\n+\n+\tgnome_keyring_network_password_list_free(entries);\n+\n+\tif (result != GNOME_KEYRING_RESULT_OK)\n+\t{\n+\t\terror(\"%s\",gnome_keyring_result_to_message(result));\n+\t\treturn EXIT_FAILURE;\n+\t}\n+\n+\treturn EXIT_SUCCESS;\n+}\n+\n+/*\n+ * Table with helper operation callbacks, used by generic\n+ * credential helper main function.\n+ */\n+struct credential_operation const credential_helper_ops[] =\n+{\n+\t{ \"get\",   keyring_get   },\n+\t{ \"store\", keyring_store },\n+\t{ \"erase\", keyring_erase },\n+\tCREDENTIAL_OP_END\n+};\n+\n+/* ------------------ credential functions ------------------ */\n+\n+void credential_init(struct credential *c)\n+{\n+\tmemset(c, 0, sizeof(*c));\n+}\n+\n+void credential_clear(struct credential *c)\n+{\n+\tfree(c->protocol);\n+\tfree(c->host);\n+\tfree(c->path);\n+\tfree(c->username);\n+\tfree_password(c->password);\n+\n+\tcredential_init(c);\n+}\n+\n+int credential_read(struct credential *c)\n+{\n+\tchar    buf[1024];\n+\tssize_t line_len = 0;\n+\tchar   *key      = buf;\n+\tchar   *value;\n+\n+\twhile (fgets(buf, sizeof(buf), stdin))\n+\t{\n+\t\tline_len = strlen(buf);\n+\n+\t\tif(buf[line_len-1]=='\\n')\n+\t\t\tbuf[--line_len]='\\0';\n+\n+\t\tif(!line_len)\n+\t\t\tbreak;\n+\n+\t\tvalue = strchr(buf,'=');\n+\t\tif(!value) {\n+\t\t\twarning(\"invalid credential line: %s\", key);\n+\t\t\treturn -1;\n+\t\t}\n+\t\t*value++ = '\\0';\n+\n+\t\tif (!strcmp(key, \"protocol\")) {\n+\t\t\tfree(c->protocol);\n+\t\t\tc->protocol = xstrdup(value);\n+\t\t} else if (!strcmp(key, \"host\")) {\n+\t\t\tfree(c->host);\n+\t\t\tc->host = xstrdup(value);\n+\t\t\tvalue = strrchr(c->host,':');\n+\t\t\tif (value) {\n+\t\t\t\t*value++ = '\\0';\n+\t\t\t\tc->port = atoi(value);\n+\t\t\t}\n+\t\t} else if (!strcmp(key, \"path\")) {\n+\t\t\tfree(c->path);\n+\t\t\tc->path = xstrdup(value);\n+\t\t} else if (!strcmp(key, \"username\")) {\n+\t\t\tfree(c->username);\n+\t\t\tc->username = xstrdup(value);\n+\t\t} else if (!strcmp(key, \"password\")) {\n+\t\t\tfree_password(c->password);\n+\t\t\tc->password = xstrdup(value);\n+\t\t\twhile (*value) *value++ = '\\0';\n+\t\t}\n+\t\t/*\n+\t\t * Ignore other lines; we don't know what they mean, but\n+\t\t * this future-proofs us when later versions of git do\n+\t\t * learn new lines, and the helpers are updated to match.\n+\t\t */\n+\t}\n+\treturn 0;\n+}\n+\n+void credential_write_item(FILE *fp, const char *key, const char *value)\n+{\n+\tif (!value)\n+\t\treturn;\n+\tfprintf(fp, \"%s=%s\\n\", key, value);\n+}\n+\n+void credential_write(const struct credential *c)\n+{\n+\t/* only write username/password, if set */\n+\tcredential_write_item(stdout, \"username\", c->username);\n+\tcredential_write_item(stdout, \"password\", c->password);\n+}\n+\n+static void usage(const char *name)\n+{\n+\tstruct credential_operation const *try_op = credential_helper_ops;\n+\tconst char *basename = strrchr(name,'/');\n+\n+\tbasename = (basename) ? basename + 1 : name;\n+\tfprintf(stderr, \"Usage: %s <\", basename);\n+\twhile(try_op->name) {\n+\t\tfprintf(stderr,\"%s\",(try_op++)->name);\n+\t\tif(try_op->name)\n+\t\t\tfprintf(stderr,\"%s\",\"|\");\n+\t}\n+\tfprintf(stderr,\"%s\",\">\\n\");\n+}\n+\n+int main(int argc, char *argv[])\n+{\n+\tint ret = EXIT_SUCCESS;\n+\n+\tstruct credential_operation const *try_op = credential_helper_ops;\n+\tstruct credential                  cred   = CREDENTIAL_INIT;\n+\n+\tif (!argv[1]) {\n+\t\tusage(argv[0]);\n+\t\tgoto out;\n+\t}\n+\n+\t/* lookup operation callback */\n+\twhile(try_op->name && strcmp(argv[1], try_op->name))\n+\t\ttry_op++;\n+\n+\t/* unsupported operation given -- ignore silently */\n+\tif(!try_op->name || !try_op->op)\n+\t\tgoto out;\n+\n+\tret = credential_read(&cred);\n+\tif(ret)\n+\t\tgoto out;\n+\n+\t/* perform credential operation */\n+\tret = (*try_op->op)(&cred);\n+\n+\tcredential_write(&cred);\n+\n+out:\n+\tcredential_clear(&cred);\n+\treturn ret;\n+}\n-- \n1.7.10.4\n"},{"id":"197721","messageId":"1345741068-11004-3-git-send-email-pah@qo.cx","threadId":"31334","inReplyTo":"1345741068-11004-1-git-send-email-pah@qo.cx","subject":"[PATCH 2/4] contrib: add generic credential helper","fromName":"Philipp A. Hartmann","fromEmail":"pah@qo.cx","sentAt":"2012-08-23T16:57:46Z","receivedAt":"2012-08-23T16:57:46Z","isPatch":true,"sender":{"key":"pah@qo.cx","avatar":"https://avatars.githubusercontent.com/u/177333?v=4"},"body":"From: \"Philipp A. Hartmann\" <pah@qo.cx>\n\nThis adds a generic implementation for credential helpers.\n\nIt provides a header file credential_helper.h containing\na simplified credential API and common helper functions.\nThe implementation in credential_helper.c already provides\na main() function and chooses the credential operation\nfrom a function table:\n\n  struct credential_operation const credential_helper_ops[] =\n  {\n      { \"get\",   my_backend_get   },\n      { \"store\", my_backend_store },\n      { \"erase\", my_backend_erase },\n      CREDENTIAL_OP_END\n  };\n\nWith this, a specific credential helper backend usually only\nneeds to implement these operation callbacks.\n\nSigned-off-by: Philipp A. Hartmann <pah@qo.cx>\n---\n contrib/credential/helper/credential_helper.c |  149 +++++++++++++++++++++++++\n contrib/credential/helper/credential_helper.h |  116 +++++++++++++++++++\n 2 files changed, 265 insertions(+)\n create mode 100644 contrib/credential/helper/credential_helper.c\n create mode 100644 contrib/credential/helper/credential_helper.h\n\ndiff --git a/contrib/credential/helper/credential_helper.c b/contrib/credential/helper/credential_helper.c\nnew file mode 100644\nindex 0000000..e99c2ec\n--- /dev/null\n+++ b/contrib/credential/helper/credential_helper.c\n@@ -0,0 +1,149 @@\n+/*\n+ * Copyright (C) 2012 Philipp A. Hartmann <pah@qo.cx>\n+ *\n+ * This file is licensed under the GPL v2, or a later version\n+ * at the discretion of Linus.\n+ *\n+ * This credential struct and API is simplified from git's\n+ * credential.{h,c} to be used within credential helper\n+ * implementations.\n+ */\n+\n+#include <credential_helper.h>\n+\n+void credential_init(struct credential *c)\n+{\n+\tmemset(c, 0, sizeof(*c));\n+}\n+\n+void credential_clear(struct credential *c)\n+{\n+\tfree(c->protocol);\n+\tfree(c->host);\n+\tfree(c->path);\n+\tfree(c->username);\n+\tfree_password(c->password);\n+\n+\tcredential_init(c);\n+}\n+\n+int credential_read(struct credential *c)\n+{\n+\tchar    buf[1024];\n+\tssize_t line_len = 0;\n+\tchar   *key      = buf;\n+\tchar   *value;\n+\n+\twhile (fgets(buf, sizeof(buf), stdin))\n+\t{\n+\t\tline_len = strlen(buf);\n+\n+\t\tif(buf[line_len-1]=='\\n')\n+\t\t\tbuf[--line_len]='\\0';\n+\n+\t\tif(!line_len)\n+\t\t\tbreak;\n+\n+\t\tvalue = strchr(buf,'=');\n+\t\tif(!value) {\n+\t\t\twarning(\"invalid credential line: %s\", key);\n+\t\t\treturn -1;\n+\t\t}\n+\t\t*value++ = '\\0';\n+\n+\t\tif (!strcmp(key, \"protocol\")) {\n+\t\t\tfree(c->protocol);\n+\t\t\tc->protocol = xstrdup(value);\n+\t\t} else if (!strcmp(key, \"host\")) {\n+\t\t\tfree(c->host);\n+\t\t\tc->host = xstrdup(value);\n+\t\t\tvalue = strrchr(c->host,':');\n+\t\t\tif (value) {\n+\t\t\t\t*value++ = '\\0';\n+\t\t\t\tc->port = atoi(value);\n+\t\t\t}\n+\t\t} else if (!strcmp(key, \"path\")) {\n+\t\t\tfree(c->path);\n+\t\t\tc->path = xstrdup(value);\n+\t\t} else if (!strcmp(key, \"username\")) {\n+\t\t\tfree(c->username);\n+\t\t\tc->username = xstrdup(value);\n+\t\t} else if (!strcmp(key, \"password\")) {\n+\t\t\tfree_password(c->password);\n+\t\t\tc->password = xstrdup(value);\n+\t\t\twhile (*value) *value++ = '\\0';\n+\t\t}\n+\t\t/*\n+\t\t * Ignore other lines; we don't know what they mean, but\n+\t\t * this future-proofs us when later versions of git do\n+\t\t * learn new lines, and the helpers are updated to match.\n+\t\t */\n+\t}\n+\treturn 0;\n+}\n+\n+void credential_write_item(FILE *fp, const char *key, const char *value)\n+{\n+\tif (!value)\n+\t\treturn;\n+\tfprintf(fp, \"%s=%s\\n\", key, value);\n+}\n+\n+void credential_write(const struct credential *c)\n+{\n+\t/* only write username/password, if set */\n+\tcredential_write_item(stdout, \"username\", c->username);\n+\tcredential_write_item(stdout, \"password\", c->password);\n+}\n+\n+static void usage(const char *name)\n+{\n+\tstruct credential_operation const *try_op = credential_helper_ops;\n+\tconst char *basename = strrchr(name,'/');\n+\n+\tbasename = (basename) ? basename + 1 : name;\n+\tfprintf(stderr, \"Usage: %s <\", basename);\n+\twhile(try_op->name) {\n+\t\tfprintf(stderr,\"%s\",(try_op++)->name);\n+\t\tif(try_op->name)\n+\t\t\tfprintf(stderr,\"%s\",\"|\");\n+\t}\n+\tfprintf(stderr,\"%s\",\">\\n\");\n+}\n+\n+/*\n+ * generic main function for credential helpers\n+ */\n+int main(int argc, char *argv[])\n+{\n+\tint ret = EXIT_SUCCESS;\n+\n+\tstruct credential_operation const *try_op = credential_helper_ops;\n+\tstruct credential                  cred   = CREDENTIAL_INIT;\n+\n+\tif (!argv[1]) {\n+\t\tusage(argv[0]);\n+\t\tgoto out;\n+\t}\n+\n+\t/* lookup operation callback */\n+\twhile(try_op->name && strcmp(argv[1], try_op->name))\n+\t\ttry_op++;\n+\n+\t/* unsupported operation given -- ignore silently */\n+\tif(!try_op->name || !try_op->op)\n+\t\tgoto out;\n+\n+\tret = credential_read(&cred);\n+\tif(ret)\n+\t\tgoto out;\n+\n+\t/* perform credential operation */\n+\tret = (*try_op->op)(&cred);\n+\n+\tcredential_write(&cred);\n+\n+out:\n+\tcredential_clear(&cred);\n+\treturn ret;\n+}\ndiff --git a/contrib/credential/helper/credential_helper.h b/contrib/credential/helper/credential_helper.h\nnew file mode 100644\nindex 0000000..8266078\n--- /dev/null\n+++ b/contrib/credential/helper/credential_helper.h\n@@ -0,0 +1,116 @@\n+/*\n+ * Copyright (C) 2012 Philipp A. Hartmann <pah@qo.cx>\n+ *\n+ * This file is licensed under the GPL v2, or a later version\n+ * at the discretion of Linus.\n+ *\n+ * This credential struct and API is simplified from git's\n+ * credential.{h,c} to be used within credential helper\n+ * implementations.\n+ */\n+#ifndef CREDENTIAL_HELPER_H_INCLUDED_\n+#define CREDENTIAL_HELPER_H_INCLUDED_\n+\n+#include <stdio.h>\n+#include <string.h>\n+#include <stdarg.h>\n+#include <stdlib.h>\n+#include <errno.h>\n+\n+struct credential\n+{\n+\tchar          *protocol;\n+\tchar          *host;\n+\tunsigned short port;\n+\tchar          *path;\n+\tchar          *username;\n+\tchar          *password;\n+};\n+\n+#define CREDENTIAL_INIT \\\n+  { NULL,NULL,0,NULL,NULL,NULL }\n+\n+void credential_init(struct credential *c);\n+void credential_clear(struct credential *c);\n+int  credential_read(struct credential *c);\n+void credential_write(const struct credential *c);\n+\n+typedef int (*credential_op_cb)(struct credential*);\n+\n+struct credential_operation\n+{\n+\tchar             *name;\n+\tcredential_op_cb op;\n+};\n+\n+#define CREDENTIAL_OP_END \\\n+  { NULL,NULL }\n+\n+/*\n+ * Table with operation callbacks is defined in concrete\n+ * credential helper implementation and contains entries\n+ * like { \"get\", function_to_get_credential } terminated\n+ * by CREDENTIAL_OP_END.\n+ */\n+extern struct credential_operation const credential_helper_ops[];\n+\n+/* ---------------- common helper functions ---------------- */\n+\n+static inline void free_password(char *password)\n+{\n+\tchar *c = password;\n+\tif (!password)\n+\t\treturn;\n+\n+\twhile (*c) *c++ = '\\0';\n+\tfree(password);\n+}\n+\n+static inline void warning(const char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\tfprintf(stderr, \"warning: \");\n+\tvfprintf(stderr, fmt, ap);\n+\tfprintf(stderr, \"\\n\" );\n+\tva_end(ap);\n+}\n+\n+static inline void error(const char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\tfprintf(stderr, \"error: \");\n+\tvfprintf(stderr, fmt, ap);\n+\tfprintf(stderr, \"\\n\" );\n+\tva_end(ap);\n+}\n+\n+static inline void die(const char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap,fmt);\n+\terror(fmt, ap);\n+\tva_end(ap);\n+\texit(EXIT_FAILURE);\n+}\n+\n+static inline void die_errno(int err)\n+{\n+\terror(\"%s\", strerror(err));\n+\texit(EXIT_FAILURE);\n+}\n+\n+static inline char *xstrdup(const char *str)\n+{\n+\tchar *ret = strdup(str);\n+\tif (!ret)\n+\t\tdie_errno(errno);\n+\n+\treturn ret;\n+}\n+\n+#endif /* CREDENTIAL_HELPER_H_INCLUDED_ */\n-- \n1.7.10.4\n"},{"id":"197719","messageId":"1345741068-11004-4-git-send-email-pah@qo.cx","threadId":"31334","inReplyTo":"1345741068-11004-1-git-send-email-pah@qo.cx","subject":"[PATCH 3/4] gnome-keyring: port to generic helper implementation","fromName":"Philipp A. Hartmann","fromEmail":"pah@qo.cx","sentAt":"2012-08-23T16:57:47Z","receivedAt":"2012-08-23T16:57:47Z","isPatch":true,"sender":{"key":"pah@qo.cx","avatar":"https://avatars.githubusercontent.com/u/177333?v=4"},"body":"From: \"Philipp A. Hartmann\" <pah@qo.cx>\n\nUse generic credential helper implementation in the\nGnomeKeyring credential helper.\n\nThe GnomeKeyring helper has been using the generic implementation\ninternally already and therefore only drops the duplicate code.\n\nSigned-off-by: Philipp A. Hartmann <pah@qo.cx>\n---\n contrib/credential/gnome-keyring/Makefile          |    6 +-\n .../gnome-keyring/git-credential-gnome-keyring.c   |  243 +-------------------\n 2 files changed, 6 insertions(+), 243 deletions(-)\n\ndiff --git a/contrib/credential/gnome-keyring/Makefile b/contrib/credential/gnome-keyring/Makefile\nindex e6561d8..7f3ec11 100644\n--- a/contrib/credential/gnome-keyring/Makefile\n+++ b/contrib/credential/gnome-keyring/Makefile\n@@ -11,11 +11,15 @@ CFLAGS = -g -O2 -Wall\n INCS:=$(shell pkg-config --cflags gnome-keyring-1)\n LIBS:=$(shell pkg-config --libs gnome-keyring-1)\n \n+HELPER:=../helper\n+VPATH +=$(HELPER)\n+\n SRCS:=$(MAIN).c\n+SRCS+=credential_helper.c\n OBJS:=$(SRCS:.c=.o)\n \n %.o: %.c\n-\t$(CC) $(CFLAGS) $(CPPFLAGS) $(INCS) -o $@ -c $<\n+\t$(CC) $(CFLAGS) $(CPPFLAGS) -I$(HELPER) $(INCS) -o $@ -c $<\n \n $(MAIN): $(OBJS)\n \t$(CC) -o $@ $(LDFLAGS) $^ $(LIBS)\ndiff --git a/contrib/credential/gnome-keyring/git-credential-gnome-keyring.c b/contrib/credential/gnome-keyring/git-credential-gnome-keyring.c\nindex 41f61c5..00244aa 100644\n--- a/contrib/credential/gnome-keyring/git-credential-gnome-keyring.c\n+++ b/contrib/credential/gnome-keyring/git-credential-gnome-keyring.c\n@@ -23,114 +23,9 @@\n  * - ported to credential helper API by Philipp A. Hartmann\n  */\n \n-#include <stdio.h>\n-#include <string.h>\n-#include <stdarg.h>\n-#include <stdlib.h>\n-#include <errno.h>\n+#include <credential_helper.h>\n #include <gnome-keyring.h>\n \n-/*\n- * This credential struct and API is simplified from git's credential.{h,c}\n- */\n-struct credential\n-{\n-\tchar          *protocol;\n-\tchar          *host;\n-\tunsigned short port;\n-\tchar          *path;\n-\tchar          *username;\n-\tchar          *password;\n-};\n-\n-#define CREDENTIAL_INIT \\\n-  { NULL,NULL,0,NULL,NULL,NULL }\n-\n-void credential_init(struct credential *c);\n-void credential_clear(struct credential *c);\n-int  credential_read(struct credential *c);\n-void credential_write(const struct credential *c);\n-\n-typedef int (*credential_op_cb)(struct credential*);\n-\n-struct credential_operation\n-{\n-\tchar             *name;\n-\tcredential_op_cb op;\n-};\n-\n-#define CREDENTIAL_OP_END \\\n-  { NULL,NULL }\n-\n-/*\n- * Table with operation callbacks is defined in concrete\n- * credential helper implementation and contains entries\n- * like { \"get\", function_to_get_credential } terminated\n- * by CREDENTIAL_OP_END.\n- */\n-struct credential_operation const credential_helper_ops[];\n-\n-/* ---------------- common helper functions ----------------- */\n-\n-static inline void free_password(char *password)\n-{\n-\tchar *c = password;\n-\tif (!password)\n-\t\treturn;\n-\n-\twhile (*c) *c++ = '\\0';\n-\tfree(password);\n-}\n-\n-static inline void warning(const char *fmt, ...)\n-{\n-\tva_list ap;\n-\n-\tva_start(ap, fmt);\n-\tfprintf(stderr, \"warning: \");\n-\tvfprintf(stderr, fmt, ap);\n-\tfprintf(stderr, \"\\n\" );\n-\tva_end(ap);\n-}\n-\n-static inline void error(const char *fmt, ...)\n-{\n-\tva_list ap;\n-\n-\tva_start(ap, fmt);\n-\tfprintf(stderr, \"error: \");\n-\tvfprintf(stderr, fmt, ap);\n-\tfprintf(stderr, \"\\n\" );\n-\tva_end(ap);\n-}\n-\n-static inline void die(const char *fmt, ...)\n-{\n-\tva_list ap;\n-\n-\tva_start(ap,fmt);\n-\terror(fmt, ap);\n-\tva_end(ap);\n-\texit(EXIT_FAILURE);\n-}\n-\n-static inline void die_errno(int err)\n-{\n-\terror(\"%s\", strerror(err));\n-\texit(EXIT_FAILURE);\n-}\n-\n-static inline char *xstrdup(const char *str)\n-{\n-\tchar *ret = strdup(str);\n-\tif (!ret)\n-\t\tdie_errno(errno);\n-\n-\treturn ret;\n-}\n-\n-/* ----------------- GNOME Keyring functions ----------------- */\n-\n /* create a special keyring option string, if path is given */\n static char* keyring_object(struct credential *c)\n {\n@@ -307,139 +202,3 @@ struct credential_operation const credential_helper_ops[] =\n \t{ \"erase\", keyring_erase },\n \tCREDENTIAL_OP_END\n };\n-\n-/* ------------------ credential functions ------------------ */\n-\n-void credential_init(struct credential *c)\n-{\n-\tmemset(c, 0, sizeof(*c));\n-}\n-\n-void credential_clear(struct credential *c)\n-{\n-\tfree(c->protocol);\n-\tfree(c->host);\n-\tfree(c->path);\n-\tfree(c->username);\n-\tfree_password(c->password);\n-\n-\tcredential_init(c);\n-}\n-\n-int credential_read(struct credential *c)\n-{\n-\tchar    buf[1024];\n-\tssize_t line_len = 0;\n-\tchar   *key      = buf;\n-\tchar   *value;\n-\n-\twhile (fgets(buf, sizeof(buf), stdin))\n-\t{\n-\t\tline_len = strlen(buf);\n-\n-\t\tif(buf[line_len-1]=='\\n')\n-\t\t\tbuf[--line_len]='\\0';\n-\n-\t\tif(!line_len)\n-\t\t\tbreak;\n-\n-\t\tvalue = strchr(buf,'=');\n-\t\tif(!value) {\n-\t\t\twarning(\"invalid credential line: %s\", key);\n-\t\t\treturn -1;\n-\t\t}\n-\t\t*value++ = '\\0';\n-\n-\t\tif (!strcmp(key, \"protocol\")) {\n-\t\t\tfree(c->protocol);\n-\t\t\tc->protocol = xstrdup(value);\n-\t\t} else if (!strcmp(key, \"host\")) {\n-\t\t\tfree(c->host);\n-\t\t\tc->host = xstrdup(value);\n-\t\t\tvalue = strrchr(c->host,':');\n-\t\t\tif (value) {\n-\t\t\t\t*value++ = '\\0';\n-\t\t\t\tc->port = atoi(value);\n-\t\t\t}\n-\t\t} else if (!strcmp(key, \"path\")) {\n-\t\t\tfree(c->path);\n-\t\t\tc->path = xstrdup(value);\n-\t\t} else if (!strcmp(key, \"username\")) {\n-\t\t\tfree(c->username);\n-\t\t\tc->username = xstrdup(value);\n-\t\t} else if (!strcmp(key, \"password\")) {\n-\t\t\tfree_password(c->password);\n-\t\t\tc->password = xstrdup(value);\n-\t\t\twhile (*value) *value++ = '\\0';\n-\t\t}\n-\t\t/*\n-\t\t * Ignore other lines; we don't know what they mean, but\n-\t\t * this future-proofs us when later versions of git do\n-\t\t * learn new lines, and the helpers are updated to match.\n-\t\t */\n-\t}\n-\treturn 0;\n-}\n-\n-void credential_write_item(FILE *fp, const char *key, const char *value)\n-{\n-\tif (!value)\n-\t\treturn;\n-\tfprintf(fp, \"%s=%s\\n\", key, value);\n-}\n-\n-void credential_write(const struct credential *c)\n-{\n-\t/* only write username/password, if set */\n-\tcredential_write_item(stdout, \"username\", c->username);\n-\tcredential_write_item(stdout, \"password\", c->password);\n-}\n-\n-static void usage(const char *name)\n-{\n-\tstruct credential_operation const *try_op = credential_helper_ops;\n-\tconst char *basename = strrchr(name,'/');\n-\n-\tbasename = (basename) ? basename + 1 : name;\n-\tfprintf(stderr, \"Usage: %s <\", basename);\n-\twhile(try_op->name) {\n-\t\tfprintf(stderr,\"%s\",(try_op++)->name);\n-\t\tif(try_op->name)\n-\t\t\tfprintf(stderr,\"%s\",\"|\");\n-\t}\n-\tfprintf(stderr,\"%s\",\">\\n\");\n-}\n-\n-int main(int argc, char *argv[])\n-{\n-\tint ret = EXIT_SUCCESS;\n-\n-\tstruct credential_operation const *try_op = credential_helper_ops;\n-\tstruct credential                  cred   = CREDENTIAL_INIT;\n-\n-\tif (!argv[1]) {\n-\t\tusage(argv[0]);\n-\t\tgoto out;\n-\t}\n-\n-\t/* lookup operation callback */\n-\twhile(try_op->name && strcmp(argv[1], try_op->name))\n-\t\ttry_op++;\n-\n-\t/* unsupported operation given -- ignore silently */\n-\tif(!try_op->name || !try_op->op)\n-\t\tgoto out;\n-\n-\tret = credential_read(&cred);\n-\tif(ret)\n-\t\tgoto out;\n-\n-\t/* perform credential operation */\n-\tret = (*try_op->op)(&cred);\n-\n-\tcredential_write(&cred);\n-\n-out:\n-\tcredential_clear(&cred);\n-\treturn ret;\n-}\n-- \n1.7.10.4\n"},{"id":"197720","messageId":"1345741068-11004-5-git-send-email-pah@qo.cx","threadId":"31334","inReplyTo":"1345741068-11004-1-git-send-email-pah@qo.cx","subject":"[PATCH 4/4] osxkeychain: port to generic credential helper implementation","fromName":"Philipp A. Hartmann","fromEmail":"pah@qo.cx","sentAt":"2012-08-23T16:57:48Z","receivedAt":"2012-08-23T16:57:48Z","isPatch":true,"sender":{"key":"pah@qo.cx","avatar":"https://avatars.githubusercontent.com/u/177333?v=4"},"body":"From: \"Philipp A. Hartmann\" <pah@qo.cx>\n\nThis reduces code duplication in the osxkeychain helper by\nbasing the implementation on the generic helper implementation.\n\nAlongside, the return codes of the helper are tightened to be\nmore consistent in corner cases and the memory containing\ncleartext passwords is explicitly cleared when possible.\n\nSigned-off-by: Philipp A. Hartmann <pah@qo.cx>\n---\n contrib/credential/helper/credential_helper.h      |    9 +\n contrib/credential/osxkeychain/Makefile            |   22 ++-\n .../osxkeychain/git-credential-osxkeychain.c       |  183 ++++++++------------\n 3 files changed, 96 insertions(+), 118 deletions(-)\n\ndiff --git a/contrib/credential/helper/credential_helper.h b/contrib/credential/helper/credential_helper.h\nindex 8266078..76b6e50 100644\n--- a/contrib/credential/helper/credential_helper.h\n+++ b/contrib/credential/helper/credential_helper.h\n@@ -113,4 +113,13 @@ static inline char *xstrdup(const char *str)\n \treturn ret;\n }\n \n+static inline char *xstrndup(const char *str, size_t len)\n+{\n+\tchar *ret = strndup(str,len);\n+\tif (!ret)\n+\t\tdie_errno(errno);\n+\n+\treturn ret;\n+}\n+\n #endif /* CREDENTIAL_HELPER_H_INCLUDED_ */\ndiff --git a/contrib/credential/osxkeychain/Makefile b/contrib/credential/osxkeychain/Makefile\nindex 4b3a08a..64ee7c5 100644\n--- a/contrib/credential/osxkeychain/Makefile\n+++ b/contrib/credential/osxkeychain/Makefile\n@@ -1,4 +1,5 @@\n-all:: git-credential-osxkeychain\n+MAIN:=git-credential-osxkeychain\n+all:: $(MAIN)\n \n CC = gcc\n RM = rm -f\n@@ -7,11 +8,20 @@ CFLAGS = -g -O2 -Wall\n -include ../../../config.mak.autogen\n -include ../../../config.mak\n \n-git-credential-osxkeychain: git-credential-osxkeychain.o\n-\t$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) -Wl,-framework -Wl,Security\n+INCS:=\n+LIBS:=-Wl,-framework -Wl,Security\n+HELPER:=../helper\n+VPATH +=$(HELPER)\n \n-git-credential-osxkeychain.o: git-credential-osxkeychain.c\n-\t$(CC) -c $(CFLAGS) $<\n+SRCS:=$(MAIN).c\n+SRCS+=credential_helper.c\n+OBJS:=$(SRCS:.c=.o)\n+\n+%.o: %.c\n+\t$(CC) $(CFLAGS) $(CPPFLAGS) -I$(HELPER) $(INCS) -o $@ -c $<\n+\n+$(MAIN): $(OBJS)\n+\t$(CC) -o $@ $(LDFLAGS) $^ $(LIBS)\n \n clean:\n-\t$(RM) git-credential-osxkeychain git-credential-osxkeychain.o\n+\t@$(RM) $(MAIN) $(OBJS)\ndiff --git a/contrib/credential/osxkeychain/git-credential-osxkeychain.c b/contrib/credential/osxkeychain/git-credential-osxkeychain.c\nindex 6beed12..60bd973 100644\n--- a/contrib/credential/osxkeychain/git-credential-osxkeychain.c\n+++ b/contrib/credential/osxkeychain/git-credential-osxkeychain.c\n@@ -1,53 +1,40 @@\n+\n+#include <credential_helper.h>\n+\n #include <stdio.h>\n #include <string.h>\n #include <stdlib.h>\n #include <Security/Security.h>\n \n static SecProtocolType protocol;\n-static char *host;\n-static char *path;\n-static char *username;\n-static char *password;\n-static UInt16 port;\n-\n-static void die(const char *err, ...)\n-{\n-\tchar msg[4096];\n-\tva_list params;\n-\tva_start(params, err);\n-\tvsnprintf(msg, sizeof(msg), err, params);\n-\tfprintf(stderr, \"%s\\n\", msg);\n-\tva_end(params);\n-\texit(1);\n-}\n-\n-static void *xstrdup(const char *s1)\n-{\n-\tvoid *ret = strdup(s1);\n-\tif (!ret)\n-\t\tdie(\"Out of memory\");\n-\treturn ret;\n-}\n \n #define KEYCHAIN_ITEM(x) (x ? strlen(x) : 0), x\n-#define KEYCHAIN_ARGS \\\n+#define KEYCHAIN_ARGS(c) \\\n \tNULL, /* default keychain */ \\\n-\tKEYCHAIN_ITEM(host), \\\n+\tKEYCHAIN_ITEM(c->host), \\\n \t0, NULL, /* account domain */ \\\n-\tKEYCHAIN_ITEM(username), \\\n-\tKEYCHAIN_ITEM(path), \\\n-\tport, \\\n+\tKEYCHAIN_ITEM(c->username), \\\n+\tKEYCHAIN_ITEM(c->path), \\\n+\t(UInt16) c->port, \\\n \tprotocol, \\\n \tkSecAuthenticationTypeDefault\n \n-static void write_item(const char *what, const char *buf, int len)\n+static int prepare_internet_password(struct credential *c)\n {\n-\tprintf(\"%s=\", what);\n-\tfwrite(buf, 1, len, stdout);\n-\tputchar('\\n');\n+\tif (!c->protocol)\n+\t\treturn -1;\n+\telse if (!strcmp(c->protocol, \"https\"))\n+\t\tprotocol = kSecProtocolTypeHTTPS;\n+\telse if (!strcmp(c->protocol, \"http\"))\n+\t\tprotocol = kSecProtocolTypeHTTP;\n+\telse /* we don't yet handle other protocols */\n+\t\treturn -1;\n+\n+\treturn 0;\n }\n \n-static void find_username_in_item(SecKeychainItemRef item)\n+static void\n+find_username_in_item(SecKeychainItemRef item, struct credential *c)\n {\n \tSecKeychainAttributeList list;\n \tSecKeychainAttribute attr;\n@@ -59,27 +46,37 @@ static void find_username_in_item(SecKeychainItemRef item)\n \tif (SecKeychainItemCopyContent(item, NULL, &list, NULL, NULL))\n \t\treturn;\n \n-\twrite_item(\"username\", attr.data, attr.length);\n+\tfree(c->username);\n+\tc->username = xstrndup(attr.data, attr.length);\n+\n \tSecKeychainItemFreeContent(&list, NULL);\n }\n \n-static void find_internet_password(void)\n+static int find_internet_password(struct credential *c)\n {\n \tvoid *buf;\n \tUInt32 len;\n \tSecKeychainItemRef item;\n \n-\tif (SecKeychainFindInternetPassword(KEYCHAIN_ARGS, &len, &buf, &item))\n-\t\treturn;\n+\t/* Silently ignore unsupported protocols */\n+\tif (prepare_internet_password(c))\n+\t\treturn EXIT_SUCCESS;\n \n-\twrite_item(\"password\", buf, len);\n-\tif (!username)\n-\t\tfind_username_in_item(item);\n+\tif (SecKeychainFindInternetPassword(KEYCHAIN_ARGS(c), &len, &buf, &item))\n+\t\treturn EXIT_SUCCESS;\n+\n+\tfree_password(c->password);\n+\tc->password = xstrndup(buf, len);\n+\tmemset(buf,len,'\\0');\n+\n+\tif (!c->username)\n+\t\tfind_username_in_item(item, c);\n \n \tSecKeychainItemFreeContent(NULL, buf);\n+\treturn EXIT_SUCCESS;\n }\n \n-static void delete_internet_password(void)\n+static int delete_internet_password(struct credential *c)\n {\n \tSecKeychainItemRef item;\n \n@@ -88,86 +85,48 @@ static void delete_internet_password(void)\n \t * will give us; if you want to do something more fancy, use the\n \t * Keychain manager.\n \t */\n-\tif (!protocol || !host)\n-\t\treturn;\n+\tif (!c->protocol || !c->host)\n+\t\treturn EXIT_FAILURE;\n \n-\tif (SecKeychainFindInternetPassword(KEYCHAIN_ARGS, 0, NULL, &item))\n-\t\treturn;\n+\t/* Silently ignore unsupported protocols */\n+\tif (prepare_internet_password(c))\n+\t\treturn EXIT_SUCCESS;\n+\n+\tif (SecKeychainFindInternetPassword(KEYCHAIN_ARGS(c), 0, NULL, &item))\n+\t\treturn EXIT_SUCCESS;\n \n-\tSecKeychainItemDelete(item);\n+\tif (!SecKeychainItemDelete(item))\n+\t\treturn EXIT_SUCCESS;\n+\n+\treturn EXIT_FAILURE;\n }\n \n-static void add_internet_password(void)\n+static int add_internet_password(struct credential *c)\n {\n \t/* Only store complete credentials */\n-\tif (!protocol || !host || !username || !password)\n-\t\treturn;\n+\tif (!c->protocol || !c->host || !c->username || !c->password)\n+\t\treturn EXIT_FAILURE;\n+\n+\tif (prepare_internet_password(c))\n+\t\treturn EXIT_FAILURE;\n \n \tif (SecKeychainAddInternetPassword(\n-\t      KEYCHAIN_ARGS,\n-\t      KEYCHAIN_ITEM(password),\n+\t      KEYCHAIN_ARGS(c),\n+\t      KEYCHAIN_ITEM(c->password),\n \t      NULL))\n-\t\treturn;\n-}\n+\t\treturn EXIT_FAILURE;\n \n-static void read_credential(void)\n-{\n-\tchar buf[1024];\n-\n-\twhile (fgets(buf, sizeof(buf), stdin)) {\n-\t\tchar *v;\n-\n-\t\tif (!strcmp(buf, \"\\n\"))\n-\t\t\tbreak;\n-\t\tbuf[strlen(buf)-1] = '\\0';\n-\n-\t\tv = strchr(buf, '=');\n-\t\tif (!v)\n-\t\t\tdie(\"bad input: %s\", buf);\n-\t\t*v++ = '\\0';\n-\n-\t\tif (!strcmp(buf, \"protocol\")) {\n-\t\t\tif (!strcmp(v, \"https\"))\n-\t\t\t\tprotocol = kSecProtocolTypeHTTPS;\n-\t\t\telse if (!strcmp(v, \"http\"))\n-\t\t\t\tprotocol = kSecProtocolTypeHTTP;\n-\t\t\telse /* we don't yet handle other protocols */\n-\t\t\t\texit(0);\n-\t\t}\n-\t\telse if (!strcmp(buf, \"host\")) {\n-\t\t\tchar *colon = strchr(v, ':');\n-\t\t\tif (colon) {\n-\t\t\t\t*colon++ = '\\0';\n-\t\t\t\tport = atoi(colon);\n-\t\t\t}\n-\t\t\thost = xstrdup(v);\n-\t\t}\n-\t\telse if (!strcmp(buf, \"path\"))\n-\t\t\tpath = xstrdup(v);\n-\t\telse if (!strcmp(buf, \"username\"))\n-\t\t\tusername = xstrdup(v);\n-\t\telse if (!strcmp(buf, \"password\"))\n-\t\t\tpassword = xstrdup(v);\n-\t}\n+\treturn EXIT_SUCCESS;\n }\n \n-int main(int argc, const char **argv)\n+/*\n+ * Table with helper operation callbacks, used by generic\n+ * credential helper main function.\n+ */\n+struct credential_operation const credential_helper_ops[] =\n {\n-\tconst char *usage =\n-\t\t\"Usage: git credential-osxkeychain <get|store|erase>\";\n-\n-\tif (!argv[1])\n-\t\tdie(usage);\n-\n-\tread_credential();\n-\n-\tif (!strcmp(argv[1], \"get\"))\n-\t\tfind_internet_password();\n-\telse if (!strcmp(argv[1], \"store\"))\n-\t\tadd_internet_password();\n-\telse if (!strcmp(argv[1], \"erase\"))\n-\t\tdelete_internet_password();\n-\t/* otherwise, ignore unknown action */\n-\n-\treturn 0;\n-}\n+\t{ \"get\",   find_internet_password   },\n+\t{ \"store\", add_internet_password    },\n+\t{ \"erase\", delete_internet_password },\n+\tCREDENTIAL_OP_END\n+};\n-- \n1.7.10.4\n"},{"id":"197786","messageId":"7vfw7cyx4n.fsf@alter.siamese.dyndns.org","threadId":"31334","inReplyTo":"1345741068-11004-1-git-send-email-pah@qo.cx","subject":"Re: [PATCH] contrib: GnomeKeyring support + generic helper implementation","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2012-08-24T18:15:36Z","receivedAt":"2012-08-24T18:15:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Philipp A. Hartmann\" <pah@qo.cx> writes:\n\n> All,\n>\n> the following patch series proposes enhancements to the credential helper\n> implementations in the contrib section.  The detailed development history\n> can be found at GitHub [1].\n>\n>   The first patch adds a GnomeKeyring credential backend.  The GnomeKeyring\n> specific parts are based on the work by John Szakmeister, who wrote the\n> helper originally for the initial, unpublished version of the credential\n> helper protocol.\n>\n>   The second patch adds a generic implementation of a credential helper\n> based on a simplified credential API and common helper functions.  Helpers\n> based on this implementation do not need to worry about the credential\n> protocol and only need to implement callback functions for the different\n> credential operations.\n>\n>   The third and fourth patches port the existing helpers to this generic\n> implementation.\n>\n> Looking forward to your thoughts.\n\nIt struck me somewhat odd to see a new one added as the first step\nin the series, and then \"the generic\", the third patch only to lose\ncode from the first one, and then use the same code reduction of\nexisting one with the last one in the series.  A natural progression\nwould have been the introduction of generic infrastructure\n(including the tiny bit this series had to add as part of 4/4) as\nthe first patch, update existing OSX one to it as the second, and\nthen build a new Gnome one on the infrastructure as the third and\nfinal patch; that way we have to review less code, too ;-).\n\nI gave it a cursory look; other than getting distracted by\ninconsistent coding styles here and there, the patches seemed\nreasonably clean and well thought out.\n\nThanks.\n"},{"id":"197796","messageId":"20120824213342.GB16285@sigill.intra.peff.net","threadId":"31334","inReplyTo":"7vfw7cyx4n.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH] contrib: GnomeKeyring support + generic helper implementation","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2012-08-24T21:33:42Z","receivedAt":"2012-08-24T21:33:42Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Aug 24, 2012 at 11:15:36AM -0700, Junio C Hamano wrote:\n\n> >   The third and fourth patches port the existing helpers to this generic\n> > implementation.\n> >\n> It struck me somewhat odd to see a new one added as the first step\n> in the series, and then \"the generic\", the third patch only to lose\n> code from the first one, and then use the same code reduction of\n> existing one with the last one in the series.  A natural progression\n> would have been the introduction of generic infrastructure\n> (including the tiny bit this series had to add as part of 4/4) as\n> the first patch, update existing OSX one to it as the second, and\n> then build a new Gnome one on the infrastructure as the third and\n> final patch; that way we have to review less code, too ;-).\n\nI think this is partially because I talked with Phillipp off-list and\nwas somewhat unsure how much we wanted to factor out of the helpers. My\ninitial thought was that the protocol would be sufficiently simple that\nhelpers would just be stand-alone and not need to share code with each\nother. Then the generic bits would not have to worry about being\ncross-platform compatible.\n\nHowever, the shared bits are simple enough that maybe that is not a\nconcern. An interesting test would be to add a 5/4 porting Erik's win32\ncredential helper, since that is the platform least like our other ones.\n\nSo I am OK with this series, but I am also OK with leaving it at patch\n1, and just keeping the implementations separate.\n\n-Peff\n"},{"id":"197797","messageId":"7vzk5kx8so.fsf@alter.siamese.dyndns.org","threadId":"31334","inReplyTo":"20120824213342.GB16285@sigill.intra.peff.net","subject":"Re: [PATCH] contrib: GnomeKeyring support + generic helper implementation","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2012-08-24T21:46:31Z","receivedAt":"2012-08-24T21:46:31Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> However, the shared bits are simple enough that maybe that is not a\n> concern. An interesting test would be to add a 5/4 porting Erik's win32\n> credential helper, since that is the platform least like our other ones.\n\nVery true.\n\n> So I am OK with this series, but I am also OK with leaving it at patch\n> 1, and just keeping the implementations separate.\n\nAmen.\n"},{"id":"197864","messageId":"7vr4qtv94h.fsf@alter.siamese.dyndns.org","threadId":"31334","inReplyTo":"7vzk5kx8so.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH] contrib: GnomeKeyring support + generic helper implementation","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2012-08-26T17:46:54Z","receivedAt":"2012-08-26T17:46:54Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Jeff King <peff@peff.net> writes:\n>\n>> However, the shared bits are simple enough that maybe that is not a\n>> concern. An interesting test would be to add a 5/4 porting Erik's win32\n>> credential helper, since that is the platform least like our other ones.\n>\n> Very true.\n>\n>> So I am OK with this series, but I am also OK with leaving it at patch\n>> 1, and just keeping the implementations separate.\n>\n> Amen.\n\nJust to make sure we do not leave loose ends, could somebody try to\nsee if the new \"generic helper\" infrastructure is useful to shrink\nErik's win32 credential helper implementation?\n\nIf we see much code reduction and improved clarity, this refactoring\nmay worth keeping.  Otherwise it may be sufficient to drop the later\nones in the series.  Without knowing which, it is hard to decide.\n\nThanks.\n"},{"id":"197866","messageId":"503A680C.3090406@qo.cx","threadId":"31334","inReplyTo":"7vr4qtv94h.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH] contrib: GnomeKeyring support + generic helper implementation","fromName":"Philipp A. Hartmann","fromEmail":"pah@qo.cx","sentAt":"2012-08-26T18:16:44Z","receivedAt":"2012-08-26T18:16:44Z","isPatch":true,"sender":{"key":"pah@qo.cx","avatar":"https://avatars.githubusercontent.com/u/177333?v=4"},"body":"n 26/08/12 19:46, Junio C Hamano wrote:\n> Junio C Hamano <gitster@pobox.com> writes:\n> \n>> Jeff King <peff@peff.net> writes:\n>>\n>>> However, the shared bits are simple enough that maybe that is not a\n>>> concern. An interesting test would be to add a 5/4 porting Erik's win32\n>>> credential helper, since that is the platform least like our other ones.\n>>\n>> Very true.\n\nIn principle, I agree that with the current set of helper\nimplementations, the generic infrastructure may not yet pay off.\n\n>>> So I am OK with this series, but I am also OK with leaving it at patch\n>>> 1, and just keeping the implementations separate.\n>>\n>> Amen.\n> \n> Just to make sure we do not leave loose ends, could somebody try to\n> see if the new \"generic helper\" infrastructure is useful to shrink\n> Erik's win32 credential helper implementation?\n\nI'll try to give it a shot now, although I won't be able to test it.\nI'll send the results as a single 5/4 addition to the previous series.\n\n> If we see much code reduction and improved clarity, this refactoring\n> may worth keeping.  Otherwise it may be sufficient to drop the later\n> ones in the series.  Without knowing which, it is hard to decide.\n\nIf we decide that the generic implementation is useful, I'll then resend\nthe reordered series adding the generic helper first, then refactoring\nthe existing ones and adding the new one for GnomeKeyring last.\n\nThanks,\n  Philipp\n"},{"id":"197876","messageId":"1346018649-3002-1-git-send-email-pah@qo.cx","threadId":"31334","inReplyTo":"503A680C.3090406@qo.cx","subject":"[PATCH 5/4] wincred: port to generic credential helper (UNTESTED)","fromName":"Philipp A. Hartmann","fromEmail":"pah@qo.cx","sentAt":"2012-08-26T22:04:09Z","receivedAt":"2012-08-26T22:04:09Z","isPatch":true,"sender":{"key":"pah@qo.cx","avatar":"https://avatars.githubusercontent.com/u/177333?v=4"},"body":"From: \"Philipp A. Hartmann\" <pah@qo.cx>\n\nThis patch is an experiment to port the wincred helper\nto the generic implementation.  As of know, it is\ncompletely untested.\n\nIn addition to porting the helper to the generic API,\nthis patch clears up all passwords from memory, which\nreduces the total amount to saved lines.\n\nSigned-off-by: Philipp A. Hartmann <pah@qo.cx>\n---\n\nThe porting was fairly easy, but due to the lack of a testing\nplatform, it is completely untested.\n\nErik: Can you try to have look?\n\nThe patch is against the current 'next' with my series applied.\nA github repository with all the helpers can also be found at\n  git://github.com/pah/git-credential-helper.git\n\nIn order to keep things simple, I decided to add the setup of\nthe binary pipes in the generic helper's main() already.  This\nwas the only platform-specific change needed in the helper.\n\nOther changes to the generic helper were\n - the addition of an URI field in the credential struct\n   (can be reused in the GNOME implementation)\n - the early stop in case of incomplete credentials\n   (simplifies handling this case in all three helpers)\n\nThe overall code reduction could be increased (also in\nthe osxkeychain helper), if we add init/cleanup functions\nto the generic helper.  I skipped this for now.\n\nSome additions to the wincred code were needed (or at least\nmotivated) to clean up the memory correctly.  Especially for\nthe password strings, this seems to be a sane thing to do.\nSince the wincred helper duplicates parts of these buffers to\ncomply with the wchar_t-based Windows API, this required more\nmanual free's than in the other helpers.\n\nIn summary, although the platform and API looked fairly different\nfrom the other platforms, the generic code could be used without\ntoo much pain.\n\nThanks,\n  Philipp\n\n contrib/credential/helper/credential_helper.c      |   41 ++-\n contrib/credential/helper/credential_helper.h      |    3 +-\n contrib/credential/wincred/.gitignore              |    1 +\n contrib/credential/wincred/Makefile                |   19 +-\n .../credential/wincred/git-credential-wincred.c    |  310 +++++++++-----------\n 5 files changed, 199 insertions(+), 175 deletions(-)\n create mode 100644 contrib/credential/wincred/.gitignore\n\ndiff --git a/contrib/credential/helper/credential_helper.c b/contrib/credential/helper/credential_helper.c\nindex e99c2ec..5ddf5a5 100644\n--- a/contrib/credential/helper/credential_helper.c\n+++ b/contrib/credential/helper/credential_helper.c\n@@ -11,6 +11,11 @@\n \n #include <credential_helper.h>\n \n+#ifdef WIN32\n+#include <fcntl.h>\n+#include <io.h>\n+#endif\n+\n void credential_init(struct credential *c)\n {\n \tmemset(c, 0, sizeof(*c));\n@@ -23,6 +28,7 @@ void credential_clear(struct credential *c)\n \tfree(c->path);\n \tfree(c->username);\n \tfree_password(c->password);\n+\tfree(c->uri);\n \n \tcredential_init(c);\n }\n@@ -79,6 +85,29 @@ int credential_read(struct credential *c)\n \t\t * learn new lines, and the helpers are updated to match.\n \t\t */\n \t}\n+\n+\t/* Rebuild URI from parts */\n+\t*buf = '\\0';\n+\tif (c->protocol) {\n+\t\tstrncat(buf, c->protocol, sizeof(buf));\n+\t\tstrncat(buf, \"://\", sizeof(buf));\n+\t}\n+\tif (c->username) {\n+\t\tstrncat(buf, c->username, sizeof(buf));\n+\t\tstrncat(buf, \"@\", sizeof(buf));\n+\t}\n+\tif (c->host)\n+\t\tstrncat(buf, c->host, sizeof(buf));\n+\tif (c->port) {\n+\t\tvalue = buf + strlen(buf);\n+\t\tsnprintf(value, sizeof(buf)-(value-buf), \":%hd\", c->port);\n+\t}\n+\tif (c->path) {\n+\t\tstrncat(buf, \"/\", sizeof(buf));\n+\t\tstrncat(buf, c->path, sizeof(buf));\n+\t}\n+\tc->uri = xstrdup(buf);\n+\n \treturn 0;\n }\n \n@@ -126,6 +155,12 @@ int main(int argc, char *argv[])\n \t\tgoto out;\n \t}\n \n+#ifdef WIN32\n+\t/* git on Windows uses binary pipes to avoid CRLF-issues */\n+\t_setmode(_fileno(stdin), _O_BINARY);\n+\t_setmode(_fileno(stdout), _O_BINARY);\n+#endif\n+\n \t/* lookup operation callback */\n \twhile(try_op->name && strcmp(argv[1], try_op->name))\n \t\ttry_op++;\n@@ -138,11 +173,15 @@ int main(int argc, char *argv[])\n \tif(ret)\n \t\tgoto out;\n \n+\tif (!cred.protocol || !(cred.host || cred.path)) {\n+\t\tret = EXIT_FAILURE;\n+\t\tgoto out;\n+\t}\n+\n \t/* perform credential operation */\n \tret = (*try_op->op)(&cred);\n \n \tcredential_write(&cred);\n-\n out:\n \tcredential_clear(&cred);\n \treturn ret;\ndiff --git a/contrib/credential/helper/credential_helper.h b/contrib/credential/helper/credential_helper.h\nindex 76b6e50..822b471 100644\n--- a/contrib/credential/helper/credential_helper.h\n+++ b/contrib/credential/helper/credential_helper.h\n@@ -25,10 +25,11 @@ struct credential\n \tchar          *path;\n \tchar          *username;\n \tchar          *password;\n+\tchar          *uri;     /* <protocol>://[username@][host[:port]][/path] */\n };\n \n #define CREDENTIAL_INIT \\\n-  { NULL,NULL,0,NULL,NULL,NULL }\n+  { NULL,NULL,0,NULL,NULL,NULL,NULL }\n \n void credential_init(struct credential *c);\n void credential_clear(struct credential *c);\ndiff --git a/contrib/credential/wincred/.gitignore b/contrib/credential/wincred/.gitignore\nnew file mode 100644\nindex 0000000..4780e4e\n--- /dev/null\n+++ b/contrib/credential/wincred/.gitignore\n@@ -0,0 +1 @@\n+git-credential-wincred.exe\ndiff --git a/contrib/credential/wincred/Makefile b/contrib/credential/wincred/Makefile\nindex bad45ca..ee7a8ef 100644\n--- a/contrib/credential/wincred/Makefile\n+++ b/contrib/credential/wincred/Makefile\n@@ -1,4 +1,5 @@\n-all: git-credential-wincred.exe\n+MAIN:=git-credential-wincred\n+all:: $(MAIN)\n \n CC = gcc\n RM = rm -f\n@@ -7,8 +8,18 @@ CFLAGS = -O2 -Wall\n -include ../../../config.mak.autogen\n -include ../../../config.mak\n \n-git-credential-wincred.exe : git-credential-wincred.c\n-\t$(LINK.c) $^ $(LOADLIBES) $(LDLIBS) -o $@\n+HELPER:=../helper\n+VPATH +=$(HELPER)\n+\n+SRCS:=$(MAIN).c\n+SRCS+=credential_helper.c\n+OBJS:=$(SRCS:.c=.o)\n+\n+%.o: %.c\n+\t$(CC) $(CFLAGS) $(CPPFLAGS) -I$(HELPER) -o $@ -c $<\n+\n+$(MAIN).exe: $(OBJS)\n+\t$(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@\n \n clean:\n-\t$(RM) git-credential-wincred.exe\n+\t$(RM) $(MAIN).exe $(OBJS)\ndiff --git a/contrib/credential/wincred/git-credential-wincred.c b/contrib/credential/wincred/git-credential-wincred.c\nindex cbaec5f..721e59f 100644\n--- a/contrib/credential/wincred/git-credential-wincred.c\n+++ b/contrib/credential/wincred/git-credential-wincred.c\n@@ -7,37 +7,6 @@\n #include <io.h>\n #include <fcntl.h>\n \n-/* common helpers */\n-\n-static void die(const char *err, ...)\n-{\n-\tchar msg[4096];\n-\tva_list params;\n-\tva_start(params, err);\n-\tvsnprintf(msg, sizeof(msg), err, params);\n-\tfprintf(stderr, \"%s\\n\", msg);\n-\tva_end(params);\n-\texit(1);\n-}\n-\n-static void *xmalloc(size_t size)\n-{\n-\tvoid *ret = malloc(size);\n-\tif (!ret && !size)\n-\t\tret = malloc(1);\n-\tif (!ret)\n-\t\t die(\"Out of memory\");\n-\treturn ret;\n-}\n-\n-static char *xstrdup(const char *str)\n-{\n-\tchar *ret = strdup(str);\n-\tif (!ret)\n-\t\tdie(\"Out of memory\");\n-\treturn ret;\n-}\n-\n /* MinGW doesn't have wincred.h, so we need to define stuff */\n \n typedef struct _CREDENTIAL_ATTRIBUTEW {\n@@ -84,13 +53,16 @@ static CredPackAuthenticationBufferWT CredPackAuthenticationBufferW;\n static CredFreeT CredFree;\n static CredDeleteWT CredDeleteW;\n \n-static void load_cred_funcs(void)\n+\n+static int load_cred_funcs(void)\n {\n \t/* load DLLs */\n \tadvapi = LoadLibrary(\"advapi32.dll\");\n \tcredui = LoadLibrary(\"credui.dll\");\n-\tif (!advapi || !credui)\n-\t\tdie(\"failed to load DLLs\");\n+\tif (!advapi || !credui) {\n+\t\terror(\"failed to load DLLs\");\n+\t\treturn EXIT_FAILURE;\n+\t}\n \n \t/* get function pointers */\n \tCredWriteW = (CredWriteWT)GetProcAddress(advapi, \"CredWriteW\");\n@@ -104,28 +76,58 @@ static void load_cred_funcs(void)\n \tCredDeleteW = (CredDeleteWT)GetProcAddress(advapi, \"CredDeleteW\");\n \tif (!CredWriteW || !CredUnPackAuthenticationBufferW ||\n \t    !CredEnumerateW || !CredPackAuthenticationBufferW || !CredFree ||\n-\t    !CredDeleteW)\n-\t\tdie(\"failed to load functions\");\n+\t    !CredDeleteW) {\n+\t\terror(\"failed to load functions\");\n+\t\treturn EXIT_FAILURE;\n+\t}\n+\treturn EXIT_SUCCESS;\n }\n \n static char target_buf[1024];\n-static char *protocol, *host, *path, *username;\n-static WCHAR *wusername, *password, *target;\n+static char port_buf[8];\n+static WCHAR *wusername, *wpassword;\n \n-static void write_item(const char *what, WCHAR *wbuf)\n+static WCHAR *utf8_to_utf16_dup(const char *str)\n {\n-\tchar *buf;\n-\tint len = WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, NULL, 0, NULL,\n-\t    FALSE);\n-\tbuf = xmalloc(len);\n-\n-\tif (!WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, buf, len, NULL, FALSE))\n-\t\tdie(\"WideCharToMultiByte failed!\");\n-\n-\tprintf(\"%s=\", what);\n-\tfwrite(buf, 1, len - 1, stdout);\n-\tputchar('\\n');\n-\tfree(buf);\n+\tint wlen = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);\n+\tWCHAR *wstr = xmalloc(sizeof(WCHAR) * wlen);\n+\tif (!MultiByteToWideChar(CP_UTF8, 0, str, -1, wstr, wlen))\n+\t\tdie(\"MultiByteToWideChar failed\");\n+\treturn wstr;\n+}\n+\n+static char *utf16_to_utf8_dup(const WCHAR *wstr)\n+{\n+\tint len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL,\n+\t\tFALSE);\n+\tchar *str = xmalloc(len);\n+\tif (!WideCharToMultiByte(CP_UTF8, 0, wstr, -1, str, len, NULL, FALSE))\n+\t\tdie(\"WideCharToMultiByte failed\");\n+\treturn str;\n+}\n+\n+static void free_wpassword(WCHAR *pass)\n+{\n+\tWCHAR *w = pass;\n+\tif(!pass)\n+\t\treturn;\n+\twhile(*w) *w++ = L'\\0';\n+\tfree(pass);\n+}\n+\n+static int prepare_credential(struct credential *c)\n+{\n+\tif (load_cred_funcs() )\n+\t\treturn EXIT_FAILURE;\n+\n+\tif (c->username)\n+\t\twusername = utf8_to_utf16_dup(c->username);\n+\tif (c->password)\n+\t\twpassword = utf8_to_utf16_dup(c->password);\n+\tif (c->port) {\n+\t\tsnprintf(port_buf,\"%hd\",c->port);\n+\t}\n+\treturn EXIT_SUCCESS;\n }\n \n static int match_attr(const CREDENTIALW *cred, const WCHAR *keyword,\n@@ -143,24 +145,28 @@ static int match_attr(const CREDENTIALW *cred, const WCHAR *keyword,\n \treturn 0; /* not found */\n }\n \n-static int match_cred(const CREDENTIALW *cred)\n+static int match_cred(const CREDENTIALW *cred, const struct credential *c)\n {\n \treturn (!wusername || !wcscmp(wusername, cred->UserName)) &&\n-\t    match_attr(cred, L\"git_protocol\", protocol) &&\n-\t    match_attr(cred, L\"git_host\", host) &&\n-\t    match_attr(cred, L\"git_path\", path);\n+\t    match_attr(cred, L\"git_protocol\", c->protocol) &&\n+\t    match_attr(cred, L\"git_host\", c->host) &&\n+\t    (c->port && match_attr(cred, L\"git_port\", port_buf)) &&\n+\t    match_attr(cred, L\"git_path\", c->path);\n }\n \n-static void get_credential(void)\n+static int get_credential(struct credential *c)\n {\n-\tWCHAR *user_buf, *pass_buf;\n+\tWCHAR *user_buf = NULL, *pass_buf = NULL;\n \tDWORD user_buf_size = 0, pass_buf_size = 0;\n \tCREDENTIALW **creds, *cred = NULL;\n \tDWORD num_creds;\n-\tint i;\n+\tint i, ret = EXIT_SUCCESS;\n+\n+\tif (prepare_credential(c))\n+\t\treturn EXIT_FAILURE;\n \n \tif (!CredEnumerateW(L\"git:*\", 0, &num_creds, &creds))\n-\t\treturn;\n+\t\treturn EXIT_FAILURE;\n \n \t/* search for the first credential that matches username */\n \tfor (i = 0; i < num_creds; ++i)\n@@ -169,7 +175,7 @@ static void get_credential(void)\n \t\t\tbreak;\n \t\t}\n \tif (!cred)\n-\t\treturn;\n+\t\tgoto out;\n \n \tCredUnPackAuthenticationBufferW(0, cred->CredentialBlob,\n \t    cred->CredentialBlobSize, NULL, &user_buf_size, NULL, NULL,\n@@ -180,20 +186,29 @@ static void get_credential(void)\n \n \tif (!CredUnPackAuthenticationBufferW(0, cred->CredentialBlob,\n \t    cred->CredentialBlobSize, user_buf, &user_buf_size, NULL, NULL,\n-\t    pass_buf, &pass_buf_size))\n-\t\tdie(\"CredUnPackAuthenticationBuffer failed\");\n-\n-\tCredFree(creds);\n+\t    pass_buf, &pass_buf_size)) {\n+\t\terror(\"CredUnPackAuthenticationBuffer failed\");\n+\t\tret = EXIT_FAILURE;\n+\t\tgoto out;\n+\t}\n \n \t/* zero-terminate (sizes include zero-termination) */\n \tuser_buf[user_buf_size - 1] = L'\\0';\n \tpass_buf[pass_buf_size - 1] = L'\\0';\n \n-\twrite_item(\"username\", user_buf);\n-\twrite_item(\"password\", pass_buf);\n+\tif (!c->username)\n+\t\tc->username = utf16_to_utf8_dup(user_buf);\n+\tfree_password(c->password);\n+\tc->password = utf16_to_utf8_dup(pass_buf);\n+\n+out:\n+\tCredFree(creds);\n \n \tfree(user_buf);\n-\tfree(pass_buf);\n+\tfree(wusername);\n+\tfree_wpassword(pass_buf);\n+\n+\treturn ret;\n }\n \n static void write_attr(CREDENTIAL_ATTRIBUTEW *attr, const WCHAR *keyword,\n@@ -205,29 +220,42 @@ static void write_attr(CREDENTIAL_ATTRIBUTEW *attr, const WCHAR *keyword,\n \tattr->Value = (LPBYTE)value;\n }\n \n-static void store_credential(void)\n+static int store_credential(struct credential *c)\n {\n \tCREDENTIALW cred;\n \tBYTE *auth_buf;\n \tDWORD auth_buf_size = 0;\n \tCREDENTIAL_ATTRIBUTEW attrs[CRED_MAX_ATTRIBUTES];\n+\tWCHAR *wtarget;\n+\tint ret = EXIT_SUCCESS;\n \n-\tif (!wusername || !password)\n-\t\treturn;\n+\tif (prepare_credential(c))\n+\t\treturn EXIT_FAILURE;\n+\n+\t/* prepare 'target', the unique key for the credential */\n+\tstrncat(target_buf,\"git:\",sizeof(target_buf));\n+\tstrncat(target_buf,c->uri,sizeof(target_buf));\n+\twtarget = utf8_to_utf16_dup(target_buf);\n+\n+\tif (!wusername || !wpassword)\n+\t\treturn EXIT_FAILURE;\n \n \t/* query buffer size */\n-\tCredPackAuthenticationBufferW(0, wusername, password,\n+\tCredPackAuthenticationBufferW(0, wusername, wpassword,\n \t    NULL, &auth_buf_size);\n \n \tauth_buf = xmalloc(auth_buf_size);\n \n-\tif (!CredPackAuthenticationBufferW(0, wusername, password,\n-\t    auth_buf, &auth_buf_size))\n-\t\tdie(\"CredPackAuthenticationBuffer failed\");\n+\tif (!CredPackAuthenticationBufferW(0, wusername, wpassword,\n+\t    auth_buf, &auth_buf_size)) {\n+\t\terror(\"CredPackAuthenticationBuffer failed\");\n+\t\tret = EXIT_FAILURE;\n+\t\tgoto out;\n+\t}\n \n \tcred.Flags = 0;\n \tcred.Type = CRED_TYPE_GENERIC;\n-\tcred.TargetName = target;\n+\tcred.TargetName = wtarget;\n \tcred.Comment = L\"saved by git-credential-wincred\";\n \tcred.CredentialBlobSize = auth_buf_size;\n \tcred.CredentialBlob = auth_buf;\n@@ -237,121 +265,65 @@ static void store_credential(void)\n \tcred.TargetAlias = NULL;\n \tcred.UserName = wusername;\n \n-\twrite_attr(attrs, L\"git_protocol\", protocol);\n+\twrite_attr(attrs, L\"git_protocol\", c->protocol);\n \n-\tif (host) {\n-\t\twrite_attr(attrs + cred.AttributeCount, L\"git_host\", host);\n+\tif (c->host) {\n+\t\twrite_attr(attrs + cred.AttributeCount, L\"git_host\", c->host);\n \t\tcred.AttributeCount++;\n \t}\n \n-\tif (path) {\n-\t\twrite_attr(attrs + cred.AttributeCount, L\"git_path\", path);\n+\tif (c->port) {\n+\t\twrite_attr(attrs + cred.AttributeCount, L\"git_port\", port_buf);\n \t\tcred.AttributeCount++;\n \t}\n \n-\tif (!CredWriteW(&cred, 0))\n-\t\tdie(\"CredWrite failed\");\n+\tif (c->path) {\n+\t\twrite_attr(attrs + cred.AttributeCount, L\"git_path\", c->path);\n+\t\tcred.AttributeCount++;\n+\t}\n+\n+\tif (!CredWriteW(&cred, 0)) {\n+\t\terror(\"CredWrite failed\");\n+\t\tret = EXIT_FAILURE;\n+\t}\n+\n+out:\n+\tfree(auth_buf);\n+\tfree(wusername);\n+\tfree_wpassword(wpassword);\n+\tfree(wtarget);\n+\treturn ret;\n }\n \n-static void erase_credential(void)\n+static int erase_credential(struct credential *c)\n {\n \tCREDENTIALW **creds;\n \tDWORD num_creds;\n \tint i;\n \n+\tif (prepare_credential(c))\n+\t\treturn EXIT_FAILURE;\n+\n \tif (!CredEnumerateW(L\"git:*\", 0, &num_creds, &creds))\n-\t\treturn;\n+\t\treturn EXIT_FAILURE;\n \n \tfor (i = 0; i < num_creds; ++i) {\n-\t\tif (match_cred(creds[i]))\n+\t\tif (match_cred(creds[i],c))\n \t\t\tCredDeleteW(creds[i]->TargetName, creds[i]->Type, 0);\n \t}\n \n \tCredFree(creds);\n+\treturn EXIT_SUCCESS;\n }\n \n-static WCHAR *utf8_to_utf16_dup(const char *str)\n-{\n-\tint wlen = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);\n-\tWCHAR *wstr = xmalloc(sizeof(WCHAR) * wlen);\n-\tMultiByteToWideChar(CP_UTF8, 0, str, -1, wstr, wlen);\n-\treturn wstr;\n-}\n-\n-static void read_credential(void)\n-{\n-\tchar buf[1024];\n-\n-\twhile (fgets(buf, sizeof(buf), stdin)) {\n-\t\tchar *v;\n-\n-\t\tif (!strcmp(buf, \"\\n\"))\n-\t\t\tbreak;\n-\t\tbuf[strlen(buf)-1] = '\\0';\n-\n-\t\tv = strchr(buf, '=');\n-\t\tif (!v)\n-\t\t\tdie(\"bad input: %s\", buf);\n-\t\t*v++ = '\\0';\n-\n-\t\tif (!strcmp(buf, \"protocol\"))\n-\t\t\tprotocol = xstrdup(v);\n-\t\telse if (!strcmp(buf, \"host\"))\n-\t\t\thost = xstrdup(v);\n-\t\telse if (!strcmp(buf, \"path\"))\n-\t\t\tpath = xstrdup(v);\n-\t\telse if (!strcmp(buf, \"username\")) {\n-\t\t\tusername = xstrdup(v);\n-\t\t\twusername = utf8_to_utf16_dup(v);\n-\t\t} else if (!strcmp(buf, \"password\"))\n-\t\t\tpassword = utf8_to_utf16_dup(v);\n-\t\telse\n-\t\t\tdie(\"unrecognized input\");\n-\t}\n-}\n-\n-int main(int argc, char *argv[])\n+/*\n+ * Table with helper operation callbacks, used by generic\n+ * credential helper main function.\n+ */\n+struct credential_operation const credential_helper_ops[] =\n {\n-\tconst char *usage =\n-\t    \"Usage: git credential-wincred <get|store|erase>\\n\";\n-\n-\tif (!argv[1])\n-\t\tdie(usage);\n-\n-\t/* git use binary pipes to avoid CRLF-issues */\n-\t_setmode(_fileno(stdin), _O_BINARY);\n-\t_setmode(_fileno(stdout), _O_BINARY);\n-\n-\tread_credential();\n-\n-\tload_cred_funcs();\n-\n-\tif (!protocol || !(host || path))\n-\t\treturn 0;\n-\n-\t/* prepare 'target', the unique key for the credential */\n-\tstrncat(target_buf, \"git:\", sizeof(target_buf));\n-\tstrncat(target_buf, protocol, sizeof(target_buf));\n-\tstrncat(target_buf, \"://\", sizeof(target_buf));\n-\tif (username) {\n-\t\tstrncat(target_buf, username, sizeof(target_buf));\n-\t\tstrncat(target_buf, \"@\", sizeof(target_buf));\n-\t}\n-\tif (host)\n-\t\tstrncat(target_buf, host, sizeof(target_buf));\n-\tif (path) {\n-\t\tstrncat(target_buf, \"/\", sizeof(target_buf));\n-\t\tstrncat(target_buf, path, sizeof(target_buf));\n-\t}\n-\n-\ttarget = utf8_to_utf16_dup(target_buf);\n-\n-\tif (!strcmp(argv[1], \"get\"))\n-\t\tget_credential();\n-\telse if (!strcmp(argv[1], \"store\"))\n-\t\tstore_credential();\n-\telse if (!strcmp(argv[1], \"erase\"))\n-\t\terase_credential();\n-\t/* otherwise, ignore unknown action */\n-\treturn 0;\n-}\n+\t{ \"get\",   get_credential   },\n+\t{ \"store\", store_credential },\n+\t{ \"erase\", erase_credential },\n+\tCREDENTIAL_OP_END\n+};\n-- \n1.7.10.4\n"},{"id":"197879","messageId":"1346021111-3824-1-git-send-email-pah@qo.cx","threadId":"31334","inReplyTo":"1346018649-3002-1-git-send-email-pah@qo.cx","subject":"[PATCH 5/4 v2] wincred: port to generic credential helper (UNTESTED)","fromName":"Philipp A. Hartmann","fromEmail":"pah@qo.cx","sentAt":"2012-08-26T22:45:11Z","receivedAt":"2012-08-26T22:45:11Z","isPatch":true,"sender":{"key":"pah@qo.cx","avatar":"https://avatars.githubusercontent.com/u/177333?v=4"},"body":"From: \"Philipp A. Hartmann\" <pah@qo.cx>\n\nThis patch is an experiment to port the wincred helper\nto the generic implementation.  As of know, it is\ncompletely untested.\n\nIn addition to porting the helper to the generic API,\nthis patch clears up all passwords from memory, which\nreduces the total amount to saved lines.\n\nThis version adds the missing xmalloc() implementation.\n\nSigned-off-by: Philipp A. Hartmann <pah@qo.cx>\n---\n\nThe previous version of the patch somehow lacked the xmalloc\nimplementation moved to credential_helper.h.  Sorry for the\ninconvenience.\n\nApart from being still untested, this version at least matches\nmy tree at git://github.com/pah/git-credential-helper.git.\n\n contrib/credential/helper/credential_helper.c      |   41 ++-\n contrib/credential/helper/credential_helper.h      |   14 +-\n contrib/credential/wincred/.gitignore              |    1 +\n contrib/credential/wincred/Makefile                |   19 +-\n .../credential/wincred/git-credential-wincred.c    |  310 +++++++++-----------\n 5 files changed, 210 insertions(+), 175 deletions(-)\n create mode 100644 contrib/credential/wincred/.gitignore\n\ndiff --git a/contrib/credential/helper/credential_helper.c b/contrib/credential/helper/credential_helper.c\nindex e99c2ec..5ddf5a5 100644\n--- a/contrib/credential/helper/credential_helper.c\n+++ b/contrib/credential/helper/credential_helper.c\n@@ -11,6 +11,11 @@\n \n #include <credential_helper.h>\n \n+#ifdef WIN32\n+#include <fcntl.h>\n+#include <io.h>\n+#endif\n+\n void credential_init(struct credential *c)\n {\n \tmemset(c, 0, sizeof(*c));\n@@ -23,6 +28,7 @@ void credential_clear(struct credential *c)\n \tfree(c->path);\n \tfree(c->username);\n \tfree_password(c->password);\n+\tfree(c->uri);\n \n \tcredential_init(c);\n }\n@@ -79,6 +85,29 @@ int credential_read(struct credential *c)\n \t\t * learn new lines, and the helpers are updated to match.\n \t\t */\n \t}\n+\n+\t/* Rebuild URI from parts */\n+\t*buf = '\\0';\n+\tif (c->protocol) {\n+\t\tstrncat(buf, c->protocol, sizeof(buf));\n+\t\tstrncat(buf, \"://\", sizeof(buf));\n+\t}\n+\tif (c->username) {\n+\t\tstrncat(buf, c->username, sizeof(buf));\n+\t\tstrncat(buf, \"@\", sizeof(buf));\n+\t}\n+\tif (c->host)\n+\t\tstrncat(buf, c->host, sizeof(buf));\n+\tif (c->port) {\n+\t\tvalue = buf + strlen(buf);\n+\t\tsnprintf(value, sizeof(buf)-(value-buf), \":%hd\", c->port);\n+\t}\n+\tif (c->path) {\n+\t\tstrncat(buf, \"/\", sizeof(buf));\n+\t\tstrncat(buf, c->path, sizeof(buf));\n+\t}\n+\tc->uri = xstrdup(buf);\n+\n \treturn 0;\n }\n \n@@ -126,6 +155,12 @@ int main(int argc, char *argv[])\n \t\tgoto out;\n \t}\n \n+#ifdef WIN32\n+\t/* git on Windows uses binary pipes to avoid CRLF-issues */\n+\t_setmode(_fileno(stdin), _O_BINARY);\n+\t_setmode(_fileno(stdout), _O_BINARY);\n+#endif\n+\n \t/* lookup operation callback */\n \twhile(try_op->name && strcmp(argv[1], try_op->name))\n \t\ttry_op++;\n@@ -138,11 +173,15 @@ int main(int argc, char *argv[])\n \tif(ret)\n \t\tgoto out;\n \n+\tif (!cred.protocol || !(cred.host || cred.path)) {\n+\t\tret = EXIT_FAILURE;\n+\t\tgoto out;\n+\t}\n+\n \t/* perform credential operation */\n \tret = (*try_op->op)(&cred);\n \n \tcredential_write(&cred);\n-\n out:\n \tcredential_clear(&cred);\n \treturn ret;\ndiff --git a/contrib/credential/helper/credential_helper.h b/contrib/credential/helper/credential_helper.h\nindex 76b6e50..7e73fc6 100644\n--- a/contrib/credential/helper/credential_helper.h\n+++ b/contrib/credential/helper/credential_helper.h\n@@ -25,10 +25,11 @@ struct credential\n \tchar          *path;\n \tchar          *username;\n \tchar          *password;\n+\tchar          *uri;     /* <protocol>://[username@][host[:port]][/path] */\n };\n \n #define CREDENTIAL_INIT \\\n-  { NULL,NULL,0,NULL,NULL,NULL }\n+  { NULL,NULL,0,NULL,NULL,NULL,NULL }\n \n void credential_init(struct credential *c);\n void credential_clear(struct credential *c);\n@@ -104,6 +105,17 @@ static inline void die_errno(int err)\n \texit(EXIT_FAILURE);\n }\n \n+static inline void *xmalloc(size_t size)\n+{\n+  void *ret = malloc(size);\n+\tif (!ret && !size)\n+\t\tret = malloc(1);\n+\tif (!ret)\n+\t\t die_errno(errno);\n+\n+\treturn ret;\n+}\n+\n static inline char *xstrdup(const char *str)\n {\n \tchar *ret = strdup(str);\ndiff --git a/contrib/credential/wincred/.gitignore b/contrib/credential/wincred/.gitignore\nnew file mode 100644\nindex 0000000..4780e4e\n--- /dev/null\n+++ b/contrib/credential/wincred/.gitignore\n@@ -0,0 +1 @@\n+git-credential-wincred.exe\ndiff --git a/contrib/credential/wincred/Makefile b/contrib/credential/wincred/Makefile\nindex bad45ca..ee7a8ef 100644\n--- a/contrib/credential/wincred/Makefile\n+++ b/contrib/credential/wincred/Makefile\n@@ -1,4 +1,5 @@\n-all: git-credential-wincred.exe\n+MAIN:=git-credential-wincred\n+all:: $(MAIN)\n \n CC = gcc\n RM = rm -f\n@@ -7,8 +8,18 @@ CFLAGS = -O2 -Wall\n -include ../../../config.mak.autogen\n -include ../../../config.mak\n \n-git-credential-wincred.exe : git-credential-wincred.c\n-\t$(LINK.c) $^ $(LOADLIBES) $(LDLIBS) -o $@\n+HELPER:=../helper\n+VPATH +=$(HELPER)\n+\n+SRCS:=$(MAIN).c\n+SRCS+=credential_helper.c\n+OBJS:=$(SRCS:.c=.o)\n+\n+%.o: %.c\n+\t$(CC) $(CFLAGS) $(CPPFLAGS) -I$(HELPER) -o $@ -c $<\n+\n+$(MAIN).exe: $(OBJS)\n+\t$(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@\n \n clean:\n-\t$(RM) git-credential-wincred.exe\n+\t$(RM) $(MAIN).exe $(OBJS)\ndiff --git a/contrib/credential/wincred/git-credential-wincred.c b/contrib/credential/wincred/git-credential-wincred.c\nindex cbaec5f..721e59f 100644\n--- a/contrib/credential/wincred/git-credential-wincred.c\n+++ b/contrib/credential/wincred/git-credential-wincred.c\n@@ -7,37 +7,6 @@\n #include <io.h>\n #include <fcntl.h>\n \n-/* common helpers */\n-\n-static void die(const char *err, ...)\n-{\n-\tchar msg[4096];\n-\tva_list params;\n-\tva_start(params, err);\n-\tvsnprintf(msg, sizeof(msg), err, params);\n-\tfprintf(stderr, \"%s\\n\", msg);\n-\tva_end(params);\n-\texit(1);\n-}\n-\n-static void *xmalloc(size_t size)\n-{\n-\tvoid *ret = malloc(size);\n-\tif (!ret && !size)\n-\t\tret = malloc(1);\n-\tif (!ret)\n-\t\t die(\"Out of memory\");\n-\treturn ret;\n-}\n-\n-static char *xstrdup(const char *str)\n-{\n-\tchar *ret = strdup(str);\n-\tif (!ret)\n-\t\tdie(\"Out of memory\");\n-\treturn ret;\n-}\n-\n /* MinGW doesn't have wincred.h, so we need to define stuff */\n \n typedef struct _CREDENTIAL_ATTRIBUTEW {\n@@ -84,13 +53,16 @@ static CredPackAuthenticationBufferWT CredPackAuthenticationBufferW;\n static CredFreeT CredFree;\n static CredDeleteWT CredDeleteW;\n \n-static void load_cred_funcs(void)\n+\n+static int load_cred_funcs(void)\n {\n \t/* load DLLs */\n \tadvapi = LoadLibrary(\"advapi32.dll\");\n \tcredui = LoadLibrary(\"credui.dll\");\n-\tif (!advapi || !credui)\n-\t\tdie(\"failed to load DLLs\");\n+\tif (!advapi || !credui) {\n+\t\terror(\"failed to load DLLs\");\n+\t\treturn EXIT_FAILURE;\n+\t}\n \n \t/* get function pointers */\n \tCredWriteW = (CredWriteWT)GetProcAddress(advapi, \"CredWriteW\");\n@@ -104,28 +76,58 @@ static void load_cred_funcs(void)\n \tCredDeleteW = (CredDeleteWT)GetProcAddress(advapi, \"CredDeleteW\");\n \tif (!CredWriteW || !CredUnPackAuthenticationBufferW ||\n \t    !CredEnumerateW || !CredPackAuthenticationBufferW || !CredFree ||\n-\t    !CredDeleteW)\n-\t\tdie(\"failed to load functions\");\n+\t    !CredDeleteW) {\n+\t\terror(\"failed to load functions\");\n+\t\treturn EXIT_FAILURE;\n+\t}\n+\treturn EXIT_SUCCESS;\n }\n \n static char target_buf[1024];\n-static char *protocol, *host, *path, *username;\n-static WCHAR *wusername, *password, *target;\n+static char port_buf[8];\n+static WCHAR *wusername, *wpassword;\n \n-static void write_item(const char *what, WCHAR *wbuf)\n+static WCHAR *utf8_to_utf16_dup(const char *str)\n {\n-\tchar *buf;\n-\tint len = WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, NULL, 0, NULL,\n-\t    FALSE);\n-\tbuf = xmalloc(len);\n-\n-\tif (!WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, buf, len, NULL, FALSE))\n-\t\tdie(\"WideCharToMultiByte failed!\");\n-\n-\tprintf(\"%s=\", what);\n-\tfwrite(buf, 1, len - 1, stdout);\n-\tputchar('\\n');\n-\tfree(buf);\n+\tint wlen = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);\n+\tWCHAR *wstr = xmalloc(sizeof(WCHAR) * wlen);\n+\tif (!MultiByteToWideChar(CP_UTF8, 0, str, -1, wstr, wlen))\n+\t\tdie(\"MultiByteToWideChar failed\");\n+\treturn wstr;\n+}\n+\n+static char *utf16_to_utf8_dup(const WCHAR *wstr)\n+{\n+\tint len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL,\n+\t\tFALSE);\n+\tchar *str = xmalloc(len);\n+\tif (!WideCharToMultiByte(CP_UTF8, 0, wstr, -1, str, len, NULL, FALSE))\n+\t\tdie(\"WideCharToMultiByte failed\");\n+\treturn str;\n+}\n+\n+static void free_wpassword(WCHAR *pass)\n+{\n+\tWCHAR *w = pass;\n+\tif(!pass)\n+\t\treturn;\n+\twhile(*w) *w++ = L'\\0';\n+\tfree(pass);\n+}\n+\n+static int prepare_credential(struct credential *c)\n+{\n+\tif (load_cred_funcs() )\n+\t\treturn EXIT_FAILURE;\n+\n+\tif (c->username)\n+\t\twusername = utf8_to_utf16_dup(c->username);\n+\tif (c->password)\n+\t\twpassword = utf8_to_utf16_dup(c->password);\n+\tif (c->port) {\n+\t\tsnprintf(port_buf,\"%hd\",c->port);\n+\t}\n+\treturn EXIT_SUCCESS;\n }\n \n static int match_attr(const CREDENTIALW *cred, const WCHAR *keyword,\n@@ -143,24 +145,28 @@ static int match_attr(const CREDENTIALW *cred, const WCHAR *keyword,\n \treturn 0; /* not found */\n }\n \n-static int match_cred(const CREDENTIALW *cred)\n+static int match_cred(const CREDENTIALW *cred, const struct credential *c)\n {\n \treturn (!wusername || !wcscmp(wusername, cred->UserName)) &&\n-\t    match_attr(cred, L\"git_protocol\", protocol) &&\n-\t    match_attr(cred, L\"git_host\", host) &&\n-\t    match_attr(cred, L\"git_path\", path);\n+\t    match_attr(cred, L\"git_protocol\", c->protocol) &&\n+\t    match_attr(cred, L\"git_host\", c->host) &&\n+\t    (c->port && match_attr(cred, L\"git_port\", port_buf)) &&\n+\t    match_attr(cred, L\"git_path\", c->path);\n }\n \n-static void get_credential(void)\n+static int get_credential(struct credential *c)\n {\n-\tWCHAR *user_buf, *pass_buf;\n+\tWCHAR *user_buf = NULL, *pass_buf = NULL;\n \tDWORD user_buf_size = 0, pass_buf_size = 0;\n \tCREDENTIALW **creds, *cred = NULL;\n \tDWORD num_creds;\n-\tint i;\n+\tint i, ret = EXIT_SUCCESS;\n+\n+\tif (prepare_credential(c))\n+\t\treturn EXIT_FAILURE;\n \n \tif (!CredEnumerateW(L\"git:*\", 0, &num_creds, &creds))\n-\t\treturn;\n+\t\treturn EXIT_FAILURE;\n \n \t/* search for the first credential that matches username */\n \tfor (i = 0; i < num_creds; ++i)\n@@ -169,7 +175,7 @@ static void get_credential(void)\n \t\t\tbreak;\n \t\t}\n \tif (!cred)\n-\t\treturn;\n+\t\tgoto out;\n \n \tCredUnPackAuthenticationBufferW(0, cred->CredentialBlob,\n \t    cred->CredentialBlobSize, NULL, &user_buf_size, NULL, NULL,\n@@ -180,20 +186,29 @@ static void get_credential(void)\n \n \tif (!CredUnPackAuthenticationBufferW(0, cred->CredentialBlob,\n \t    cred->CredentialBlobSize, user_buf, &user_buf_size, NULL, NULL,\n-\t    pass_buf, &pass_buf_size))\n-\t\tdie(\"CredUnPackAuthenticationBuffer failed\");\n-\n-\tCredFree(creds);\n+\t    pass_buf, &pass_buf_size)) {\n+\t\terror(\"CredUnPackAuthenticationBuffer failed\");\n+\t\tret = EXIT_FAILURE;\n+\t\tgoto out;\n+\t}\n \n \t/* zero-terminate (sizes include zero-termination) */\n \tuser_buf[user_buf_size - 1] = L'\\0';\n \tpass_buf[pass_buf_size - 1] = L'\\0';\n \n-\twrite_item(\"username\", user_buf);\n-\twrite_item(\"password\", pass_buf);\n+\tif (!c->username)\n+\t\tc->username = utf16_to_utf8_dup(user_buf);\n+\tfree_password(c->password);\n+\tc->password = utf16_to_utf8_dup(pass_buf);\n+\n+out:\n+\tCredFree(creds);\n \n \tfree(user_buf);\n-\tfree(pass_buf);\n+\tfree(wusername);\n+\tfree_wpassword(pass_buf);\n+\n+\treturn ret;\n }\n \n static void write_attr(CREDENTIAL_ATTRIBUTEW *attr, const WCHAR *keyword,\n@@ -205,29 +220,42 @@ static void write_attr(CREDENTIAL_ATTRIBUTEW *attr, const WCHAR *keyword,\n \tattr->Value = (LPBYTE)value;\n }\n \n-static void store_credential(void)\n+static int store_credential(struct credential *c)\n {\n \tCREDENTIALW cred;\n \tBYTE *auth_buf;\n \tDWORD auth_buf_size = 0;\n \tCREDENTIAL_ATTRIBUTEW attrs[CRED_MAX_ATTRIBUTES];\n+\tWCHAR *wtarget;\n+\tint ret = EXIT_SUCCESS;\n \n-\tif (!wusername || !password)\n-\t\treturn;\n+\tif (prepare_credential(c))\n+\t\treturn EXIT_FAILURE;\n+\n+\t/* prepare 'target', the unique key for the credential */\n+\tstrncat(target_buf,\"git:\",sizeof(target_buf));\n+\tstrncat(target_buf,c->uri,sizeof(target_buf));\n+\twtarget = utf8_to_utf16_dup(target_buf);\n+\n+\tif (!wusername || !wpassword)\n+\t\treturn EXIT_FAILURE;\n \n \t/* query buffer size */\n-\tCredPackAuthenticationBufferW(0, wusername, password,\n+\tCredPackAuthenticationBufferW(0, wusername, wpassword,\n \t    NULL, &auth_buf_size);\n \n \tauth_buf = xmalloc(auth_buf_size);\n \n-\tif (!CredPackAuthenticationBufferW(0, wusername, password,\n-\t    auth_buf, &auth_buf_size))\n-\t\tdie(\"CredPackAuthenticationBuffer failed\");\n+\tif (!CredPackAuthenticationBufferW(0, wusername, wpassword,\n+\t    auth_buf, &auth_buf_size)) {\n+\t\terror(\"CredPackAuthenticationBuffer failed\");\n+\t\tret = EXIT_FAILURE;\n+\t\tgoto out;\n+\t}\n \n \tcred.Flags = 0;\n \tcred.Type = CRED_TYPE_GENERIC;\n-\tcred.TargetName = target;\n+\tcred.TargetName = wtarget;\n \tcred.Comment = L\"saved by git-credential-wincred\";\n \tcred.CredentialBlobSize = auth_buf_size;\n \tcred.CredentialBlob = auth_buf;\n@@ -237,121 +265,65 @@ static void store_credential(void)\n \tcred.TargetAlias = NULL;\n \tcred.UserName = wusername;\n \n-\twrite_attr(attrs, L\"git_protocol\", protocol);\n+\twrite_attr(attrs, L\"git_protocol\", c->protocol);\n \n-\tif (host) {\n-\t\twrite_attr(attrs + cred.AttributeCount, L\"git_host\", host);\n+\tif (c->host) {\n+\t\twrite_attr(attrs + cred.AttributeCount, L\"git_host\", c->host);\n \t\tcred.AttributeCount++;\n \t}\n \n-\tif (path) {\n-\t\twrite_attr(attrs + cred.AttributeCount, L\"git_path\", path);\n+\tif (c->port) {\n+\t\twrite_attr(attrs + cred.AttributeCount, L\"git_port\", port_buf);\n \t\tcred.AttributeCount++;\n \t}\n \n-\tif (!CredWriteW(&cred, 0))\n-\t\tdie(\"CredWrite failed\");\n+\tif (c->path) {\n+\t\twrite_attr(attrs + cred.AttributeCount, L\"git_path\", c->path);\n+\t\tcred.AttributeCount++;\n+\t}\n+\n+\tif (!CredWriteW(&cred, 0)) {\n+\t\terror(\"CredWrite failed\");\n+\t\tret = EXIT_FAILURE;\n+\t}\n+\n+out:\n+\tfree(auth_buf);\n+\tfree(wusername);\n+\tfree_wpassword(wpassword);\n+\tfree(wtarget);\n+\treturn ret;\n }\n \n-static void erase_credential(void)\n+static int erase_credential(struct credential *c)\n {\n \tCREDENTIALW **creds;\n \tDWORD num_creds;\n \tint i;\n \n+\tif (prepare_credential(c))\n+\t\treturn EXIT_FAILURE;\n+\n \tif (!CredEnumerateW(L\"git:*\", 0, &num_creds, &creds))\n-\t\treturn;\n+\t\treturn EXIT_FAILURE;\n \n \tfor (i = 0; i < num_creds; ++i) {\n-\t\tif (match_cred(creds[i]))\n+\t\tif (match_cred(creds[i],c))\n \t\t\tCredDeleteW(creds[i]->TargetName, creds[i]->Type, 0);\n \t}\n \n \tCredFree(creds);\n+\treturn EXIT_SUCCESS;\n }\n \n-static WCHAR *utf8_to_utf16_dup(const char *str)\n-{\n-\tint wlen = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);\n-\tWCHAR *wstr = xmalloc(sizeof(WCHAR) * wlen);\n-\tMultiByteToWideChar(CP_UTF8, 0, str, -1, wstr, wlen);\n-\treturn wstr;\n-}\n-\n-static void read_credential(void)\n-{\n-\tchar buf[1024];\n-\n-\twhile (fgets(buf, sizeof(buf), stdin)) {\n-\t\tchar *v;\n-\n-\t\tif (!strcmp(buf, \"\\n\"))\n-\t\t\tbreak;\n-\t\tbuf[strlen(buf)-1] = '\\0';\n-\n-\t\tv = strchr(buf, '=');\n-\t\tif (!v)\n-\t\t\tdie(\"bad input: %s\", buf);\n-\t\t*v++ = '\\0';\n-\n-\t\tif (!strcmp(buf, \"protocol\"))\n-\t\t\tprotocol = xstrdup(v);\n-\t\telse if (!strcmp(buf, \"host\"))\n-\t\t\thost = xstrdup(v);\n-\t\telse if (!strcmp(buf, \"path\"))\n-\t\t\tpath = xstrdup(v);\n-\t\telse if (!strcmp(buf, \"username\")) {\n-\t\t\tusername = xstrdup(v);\n-\t\t\twusername = utf8_to_utf16_dup(v);\n-\t\t} else if (!strcmp(buf, \"password\"))\n-\t\t\tpassword = utf8_to_utf16_dup(v);\n-\t\telse\n-\t\t\tdie(\"unrecognized input\");\n-\t}\n-}\n-\n-int main(int argc, char *argv[])\n+/*\n+ * Table with helper operation callbacks, used by generic\n+ * credential helper main function.\n+ */\n+struct credential_operation const credential_helper_ops[] =\n {\n-\tconst char *usage =\n-\t    \"Usage: git credential-wincred <get|store|erase>\\n\";\n-\n-\tif (!argv[1])\n-\t\tdie(usage);\n-\n-\t/* git use binary pipes to avoid CRLF-issues */\n-\t_setmode(_fileno(stdin), _O_BINARY);\n-\t_setmode(_fileno(stdout), _O_BINARY);\n-\n-\tread_credential();\n-\n-\tload_cred_funcs();\n-\n-\tif (!protocol || !(host || path))\n-\t\treturn 0;\n-\n-\t/* prepare 'target', the unique key for the credential */\n-\tstrncat(target_buf, \"git:\", sizeof(target_buf));\n-\tstrncat(target_buf, protocol, sizeof(target_buf));\n-\tstrncat(target_buf, \"://\", sizeof(target_buf));\n-\tif (username) {\n-\t\tstrncat(target_buf, username, sizeof(target_buf));\n-\t\tstrncat(target_buf, \"@\", sizeof(target_buf));\n-\t}\n-\tif (host)\n-\t\tstrncat(target_buf, host, sizeof(target_buf));\n-\tif (path) {\n-\t\tstrncat(target_buf, \"/\", sizeof(target_buf));\n-\t\tstrncat(target_buf, path, sizeof(target_buf));\n-\t}\n-\n-\ttarget = utf8_to_utf16_dup(target_buf);\n-\n-\tif (!strcmp(argv[1], \"get\"))\n-\t\tget_credential();\n-\telse if (!strcmp(argv[1], \"store\"))\n-\t\tstore_credential();\n-\telse if (!strcmp(argv[1], \"erase\"))\n-\t\terase_credential();\n-\t/* otherwise, ignore unknown action */\n-\treturn 0;\n-}\n+\t{ \"get\",   get_credential   },\n+\t{ \"store\", store_credential },\n+\t{ \"erase\", erase_credential },\n+\tCREDENTIAL_OP_END\n+};\n-- \n1.7.10.4\n"},{"id":"198121","messageId":"CABPQNSZsXnf2kjcN+Qma8pApjGRF6SD5iJjB2Ow6GuGts0Z=Kg@mail.gmail.com","threadId":"31334","inReplyTo":"1346018649-3002-1-git-send-email-pah@qo.cx","subject":"Re: [PATCH 5/4] wincred: port to generic credential helper (UNTESTED)","fromName":"Erik Faye-Lund","fromEmail":"kusmabite@gmail.com","sentAt":"2012-08-30T18:27:02Z","receivedAt":"2012-08-30T18:27:02Z","isPatch":true,"sender":{"key":"kusmabite@gmail.com","avatar":"https://avatars.githubusercontent.com/u/47073?v=4"},"body":"On Mon, Aug 27, 2012 at 12:04 AM, Philipp A. Hartmann <pah@qo.cx> wrote:\n> From: \"Philipp A. Hartmann\" <pah@qo.cx>\n>\n> This patch is an experiment to port the wincred helper\n> to the generic implementation.  As of know, it is\n> completely untested.\n>\n> In addition to porting the helper to the generic API,\n> this patch clears up all passwords from memory, which\n> reduces the total amount to saved lines.\n>\n> Signed-off-by: Philipp A. Hartmann <pah@qo.cx>\n> ---\n>\n> The porting was fairly easy, but due to the lack of a testing\n> platform, it is completely untested.\n>\n> Erik: Can you try to have look?\n\nSorry for the late reply, I'm currently in bed with pneumonia.\n\nBut I gave it a quick go, but as-is it's a NACK; a wall of warnings and errors.\n\nBut with this patch on top, it seems to at least compile OK:\n\n---8<---\ndiff --git a/contrib/credential/helper/credential_helper.h\nb/contrib/credential/helper/credential_helper.h\nindex 7e73fc6..13b611e 100644\n--- a/contrib/credential/helper/credential_helper.h\n+++ b/contrib/credential/helper/credential_helper.h\n@@ -125,6 +125,7 @@ static inline char *xstrdup(const char *str)\n \treturn ret;\n }\n\n+#ifndef NO_STRNDUP\n static inline char *xstrndup(const char *str, size_t len)\n {\n \tchar *ret = strndup(str,len);\n@@ -133,5 +134,6 @@ static inline char *xstrndup(const char *str, size_t len)\n\n \treturn ret;\n }\n+#endif\n\n #endif /* CREDENTIAL_HELPER_H_INCLUDED_ */\ndiff --git a/contrib/credential/wincred/Makefile\nb/contrib/credential/wincred/Makefile\nindex ee7a8ef..3900322 100644\n--- a/contrib/credential/wincred/Makefile\n+++ b/contrib/credential/wincred/Makefile\n@@ -1,9 +1,10 @@\n MAIN:=git-credential-wincred\n-all:: $(MAIN)\n+all:: $(MAIN).exe\n\n CC = gcc\n RM = rm -f\n CFLAGS = -O2 -Wall\n+CPPFLAGS = -DNO_STRNDUP\n\n -include ../../../config.mak.autogen\n -include ../../../config.mak\ndiff --git a/contrib/credential/wincred/git-credential-wincred.c\nb/contrib/credential/wincred/git-credential-wincred.c\nindex 721e59f..e26ba47 100644\n--- a/contrib/credential/wincred/git-credential-wincred.c\n+++ b/contrib/credential/wincred/git-credential-wincred.c\n@@ -6,6 +6,7 @@\n #include <stdio.h>\n #include <io.h>\n #include <fcntl.h>\n+#include <credential_helper.h>\n\n /* MinGW doesn't have wincred.h, so we need to define stuff */\n\n@@ -124,9 +125,8 @@ static int prepare_credential(struct credential *c)\n \t\twusername = utf8_to_utf16_dup(c->username);\n \tif (c->password)\n \t\twpassword = utf8_to_utf16_dup(c->password);\n-\tif (c->port) {\n-\t\tsnprintf(port_buf,\"%hd\",c->port);\n-\t}\n+\tif (c->port)\n+\t\tsnprintf(port_buf, sizeof(port_buf), \"%hd\", c->port);\n \treturn EXIT_SUCCESS;\n }\n\n@@ -170,7 +170,7 @@ static int get_credential(struct credential *c)\n\n \t/* search for the first credential that matches username */\n \tfor (i = 0; i < num_creds; ++i)\n-\t\tif (match_cred(creds[i])) {\n+\t\tif (match_cred(creds[i], c)) {\n \t\t\tcred = creds[i];\n \t\t\tbreak;\n \t\t}\n---8<---\n\nHowever, I haven't been able to successfully run the tests on the\nresult. When I did, I got this error:\n\n---8<---\nrm: cannot remove `t/trash directory.t0303-credential-external/stderr': Permissi\non denied\nrm: cannot remove `t/trash directory.t0303-credential-external/stdout': Permissi\non denied\nrm: cannot remove directory `t/trash directory.t0303-credential-external': Direc\ntory not empty\n---8<---\n\nAnd I'm not currently feeling up to debugging stuck processes or whatever it is.\n"},{"id":"198123","messageId":"7vpq68qgwx.fsf@alter.siamese.dyndns.org","threadId":"31334","inReplyTo":"CABPQNSZsXnf2kjcN+Qma8pApjGRF6SD5iJjB2Ow6GuGts0Z=Kg@mail.gmail.com","subject":"Re: [PATCH 5/4] wincred: port to generic credential helper (UNTESTED)","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2012-08-30T20:11:10Z","receivedAt":"2012-08-30T20:11:10Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Erik Faye-Lund <kusmabite@gmail.com> writes:\n\n> On Mon, Aug 27, 2012 at 12:04 AM, Philipp A. Hartmann <pah@qo.cx> wrote:\n>> From: \"Philipp A. Hartmann\" <pah@qo.cx>\n>>\n>> This patch is an experiment to port the wincred helper\n>> to the generic implementation.  As of know, it is\n>> completely untested.\n>>\n>> In addition to porting the helper to the generic API,\n>> this patch clears up all passwords from memory, which\n>> reduces the total amount to saved lines.\n>>\n>> Signed-off-by: Philipp A. Hartmann <pah@qo.cx>\n>> ---\n>>\n>> The porting was fairly easy, but due to the lack of a testing\n>> platform, it is completely untested.\n>>\n>> Erik: Can you try to have look?\n>\n> Sorry for the late reply, I'm currently in bed with pneumonia.\n>\n> But I gave it a quick go, but as-is it's a NACK; a wall of warnings and errors.\n\nThanks, and get well soon.\n\nFor now, let's not worry about merging the \"let's share code across\nhelpers\" bit that comes later in Philipp's series.\n"},{"id":"198150","messageId":"CABPQNSZg=--RifuBCAbxuiQ0Pdx7B0ew=FVDPutccZkvsjTQGg@mail.gmail.com","threadId":"31334","inReplyTo":"CABPQNSZsXnf2kjcN+Qma8pApjGRF6SD5iJjB2Ow6GuGts0Z=Kg@mail.gmail.com","subject":"Re: [PATCH 5/4] wincred: port to generic credential helper (UNTESTED)","fromName":"Erik Faye-Lund","fromEmail":"kusmabite@gmail.com","sentAt":"2012-08-31T15:44:47Z","receivedAt":"2012-08-31T15:44:47Z","isPatch":true,"sender":{"key":"kusmabite@gmail.com","avatar":"https://avatars.githubusercontent.com/u/47073?v=4"},"body":"On Thu, Aug 30, 2012 at 8:27 PM, Erik Faye-Lund <kusmabite@gmail.com> wrote:\n> On Mon, Aug 27, 2012 at 12:04 AM, Philipp A. Hartmann <pah@qo.cx> wrote:\n>> From: \"Philipp A. Hartmann\" <pah@qo.cx>\n>>\n>> This patch is an experiment to port the wincred helper\n>> to the generic implementation.  As of know, it is\n>> completely untested.\n>>\n>> In addition to porting the helper to the generic API,\n>> this patch clears up all passwords from memory, which\n>> reduces the total amount to saved lines.\n>>\n>> Signed-off-by: Philipp A. Hartmann <pah@qo.cx>\n>> ---\n>>\n>> The porting was fairly easy, but due to the lack of a testing\n>> platform, it is completely untested.\n>>\n>> Erik: Can you try to have look?\n>\n> Sorry for the late reply, I'm currently in bed with pneumonia.\n>\n> But I gave it a quick go, but as-is it's a NACK; a wall of warnings and errors.\n>\n> But with this patch on top, it seems to at least compile OK:\n>\n> ---8<---\n> diff --git a/contrib/credential/helper/credential_helper.h\n> b/contrib/credential/helper/credential_helper.h\n> index 7e73fc6..13b611e 100644\n> --- a/contrib/credential/helper/credential_helper.h\n> +++ b/contrib/credential/helper/credential_helper.h\n> @@ -125,6 +125,7 @@ static inline char *xstrdup(const char *str)\n>         return ret;\n>  }\n>\n> +#ifndef NO_STRNDUP\n>  static inline char *xstrndup(const char *str, size_t len)\n>  {\n>         char *ret = strndup(str,len);\n> @@ -133,5 +134,6 @@ static inline char *xstrndup(const char *str, size_t len)\n>\n>         return ret;\n>  }\n> +#endif\n>\n>  #endif /* CREDENTIAL_HELPER_H_INCLUDED_ */\n> diff --git a/contrib/credential/wincred/Makefile\n> b/contrib/credential/wincred/Makefile\n> index ee7a8ef..3900322 100644\n> --- a/contrib/credential/wincred/Makefile\n> +++ b/contrib/credential/wincred/Makefile\n> @@ -1,9 +1,10 @@\n>  MAIN:=git-credential-wincred\n> -all:: $(MAIN)\n> +all:: $(MAIN).exe\n>\n>  CC = gcc\n>  RM = rm -f\n>  CFLAGS = -O2 -Wall\n> +CPPFLAGS = -DNO_STRNDUP\n>\n>  -include ../../../config.mak.autogen\n>  -include ../../../config.mak\n> diff --git a/contrib/credential/wincred/git-credential-wincred.c\n> b/contrib/credential/wincred/git-credential-wincred.c\n> index 721e59f..e26ba47 100644\n> --- a/contrib/credential/wincred/git-credential-wincred.c\n> +++ b/contrib/credential/wincred/git-credential-wincred.c\n> @@ -6,6 +6,7 @@\n>  #include <stdio.h>\n>  #include <io.h>\n>  #include <fcntl.h>\n> +#include <credential_helper.h>\n>\n>  /* MinGW doesn't have wincred.h, so we need to define stuff */\n>\n> @@ -124,9 +125,8 @@ static int prepare_credential(struct credential *c)\n>                 wusername = utf8_to_utf16_dup(c->username);\n>         if (c->password)\n>                 wpassword = utf8_to_utf16_dup(c->password);\n> -       if (c->port) {\n> -               snprintf(port_buf,\"%hd\",c->port);\n> -       }\n> +       if (c->port)\n> +               snprintf(port_buf, sizeof(port_buf), \"%hd\", c->port);\n>         return EXIT_SUCCESS;\n>  }\n>\n> @@ -170,7 +170,7 @@ static int get_credential(struct credential *c)\n>\n>         /* search for the first credential that matches username */\n>         for (i = 0; i < num_creds; ++i)\n> -               if (match_cred(creds[i])) {\n> +               if (match_cred(creds[i], c)) {\n>                         cred = creds[i];\n>                         break;\n>                 }\n> ---8<---\n>\n> However, I haven't been able to successfully run the tests on the\n> result. When I did, I got this error:\n>\n> ---8<---\n> rm: cannot remove `t/trash directory.t0303-credential-external/stderr': Permissi\n> on denied\n> rm: cannot remove `t/trash directory.t0303-credential-external/stdout': Permissi\n> on denied\n> rm: cannot remove directory `t/trash directory.t0303-credential-external': Direc\n> tory not empty\n> ---8<---\n>\n> And I'm not currently feeling up to debugging stuck processes or whatever it is.\n\nOK, that was due to a stuck process, and after killing it I got to\ntest properly. However, three tests fail now:\n\n\n$ (cd t && GIT_TEST_CREDENTIAL_HELPER=wincred ./t0303-credential-external.sh)\nok 1 - helper (wincred) has no existing data\nok 2 - helper (wincred) stores password\nnot ok - 3 helper (wincred) can retrieve password\n#\n#                       check fill $HELPER <<-\\EOF\n#                       protocol=https\n#                       host=example.com\n#                       --\n#                       protocol=https\n#                       host=example.com\n#                       username=store-user\n#                       password=store-pass\n#                       --\n#                       EOF\n#\nok 4 - helper (wincred) requires matching protocol\nok 5 - helper (wincred) requires matching host\nok 6 - helper (wincred) requires matching username\nok 7 - helper (wincred) requires matching path\nok 8 - helper (wincred) can forget host\nnot ok - 9 helper (wincred) can store multiple users\n#\n#                       check approve $HELPER <<-\\EOF &&\n#                       protocol=https\n#                       host=example.com\n#                       username=user1\n#                       password=pass1\n#                       EOF\n#                       check approve $HELPER <<-\\EOF &&\n#                       protocol=https\n#                       host=example.com\n#                       username=user2\n#                       password=pass2\n#                       EOF\n#                       check fill $HELPER <<-\\EOF &&\n#                       protocol=https\n#                       host=example.com\n#                       username=user1\n#                       --\n#                       protocol=https\n#                       host=example.com\n#                       username=user1\n#                       password=pass1\n#                       EOF\n#                       check fill $HELPER <<-\\EOF\n#                       protocol=https\n#                       host=example.com\n#                       username=user2\n#                       --\n#                       protocol=https\n#                       host=example.com\n#                       username=user2\n#                       password=pass2\n#                       EOF\n#\nok 10 - helper (wincred) can forget user\nnot ok - 11 helper (wincred) remembers other user\n#\n#                       check fill $HELPER <<-\\EOF\n#                       protocol=https\n#                       host=example.com\n#                       username=user2\n#                       --\n#                       protocol=https\n#                       host=example.com\n#                       username=user2\n#                       password=pass2\n#                       EOF\n#\n# skipping timeout tests (GIT_TEST_CREDENTIAL_HELPER_TIMEOUT not set)\n# failed 3 among 11 test(s)\n1..11\n\nSo, something else is up as well.\n"},{"id":"198188","messageId":"1346503354-18464-1-git-send-email-pah@qo.cx","threadId":"31334","inReplyTo":"CABPQNSZg=--RifuBCAbxuiQ0Pdx7B0ew=FVDPutccZkvsjTQGg@mail.gmail.com","subject":"[PATCH 5/4 v3] wincred: port to generic credential helper","fromName":"Philipp A. Hartmann","fromEmail":"pah@qo.cx","sentAt":"2012-09-01T12:42:34Z","receivedAt":"2012-09-01T12:42:34Z","isPatch":true,"sender":{"key":"pah@qo.cx","avatar":"https://avatars.githubusercontent.com/u/177333?v=4"},"body":"From: \"Philipp A. Hartmann\" <pah@qo.cx>\n\nIn addition to porting the helper to the generic API,\nthis patch clears up all passwords from memory, which\nreduces the total amount to saved lines.\n\nThis version will now pass t0303 if you do\n\n   GIT_TEST_CREDENTIAL_HELPER=wincred \\\n   ./t0303-credential-external.sh\n\nSigned-off-by: Philipp A. Hartmann <pah@qo.cx>\n---\n\nErik, thanks for testing!\n\nI finally managed to test the wincred implementation against t0303\nand found another stupid bug in the credential matching of the port:\n\n  -      (c->port && match_attr(cred, L\"git_port\", port_buf)) &&\n  +      (!c->port || match_attr(cred, L\"git_port\", port_buf)) &&\n\nTogether with the fixes from Erik, the tests (and additional manual tests\nwith explicit ports, etc) now pass.\n\nIf this makes the generic implementation useful enough, I'd re-roll at least\nthe patches #2-#5 (#1 is already merged to next)  to a new series by adding\nthe \"final\" generic helper in one step and adding the ports afterwards.\n\n[Apologies for reposting, I somehow broke the \"From:\" header in my\n original mail.]\n\n contrib/credential/helper/credential_helper.c      |   41 ++-\n contrib/credential/helper/credential_helper.h      |   16 +-\n contrib/credential/wincred/.gitignore              |    1 +\n contrib/credential/wincred/Makefile                |   20 +-\n .../credential/wincred/git-credential-wincred.c    |  312 +++++++++-----------\n 5 files changed, 214 insertions(+), 176 deletions(-)\n create mode 100644 contrib/credential/wincred/.gitignore\n\ndiff --git a/contrib/credential/helper/credential_helper.c b/contrib/credential/helper/credential_helper.c\nindex e99c2ec..15536a6 100644\n--- a/contrib/credential/helper/credential_helper.c\n+++ b/contrib/credential/helper/credential_helper.c\n@@ -11,6 +11,11 @@\n \n #include <credential_helper.h>\n \n+#ifdef WIN32\n+#include <fcntl.h>\n+#include <io.h>\n+#endif\n+\n void credential_init(struct credential *c)\n {\n \tmemset(c, 0, sizeof(*c));\n@@ -23,6 +28,7 @@ void credential_clear(struct credential *c)\n \tfree(c->path);\n \tfree(c->username);\n \tfree_password(c->password);\n+\tfree(c->url);\n \n \tcredential_init(c);\n }\n@@ -79,6 +85,29 @@ int credential_read(struct credential *c)\n \t\t * learn new lines, and the helpers are updated to match.\n \t\t */\n \t}\n+\n+\t/* Rebuild URI from parts */\n+\t*buf = '\\0';\n+\tif (c->protocol) {\n+\t\tstrncat(buf, c->protocol, sizeof(buf));\n+\t\tstrncat(buf, \"://\", sizeof(buf));\n+\t}\n+\tif (c->username) {\n+\t\tstrncat(buf, c->username, sizeof(buf));\n+\t\tstrncat(buf, \"@\", sizeof(buf));\n+\t}\n+\tif (c->host)\n+\t\tstrncat(buf, c->host, sizeof(buf));\n+\tif (c->port) {\n+\t\tvalue = buf + strlen(buf);\n+\t\tsnprintf(value, sizeof(buf)-(value-buf), \":%hd\", c->port);\n+\t}\n+\tif (c->path) {\n+\t\tstrncat(buf, \"/\", sizeof(buf));\n+\t\tstrncat(buf, c->path, sizeof(buf));\n+\t}\n+\tc->url = xstrdup(buf);\n+\n \treturn 0;\n }\n \n@@ -126,6 +155,12 @@ int main(int argc, char *argv[])\n \t\tgoto out;\n \t}\n \n+#ifdef WIN32\n+\t/* git on Windows uses binary pipes to avoid CRLF-issues */\n+\t_setmode(_fileno(stdin), _O_BINARY);\n+\t_setmode(_fileno(stdout), _O_BINARY);\n+#endif\n+\n \t/* lookup operation callback */\n \twhile(try_op->name && strcmp(argv[1], try_op->name))\n \t\ttry_op++;\n@@ -138,11 +173,15 @@ int main(int argc, char *argv[])\n \tif(ret)\n \t\tgoto out;\n \n+\tif (!cred.protocol || !(cred.host || cred.path)) {\n+\t\tret = EXIT_FAILURE;\n+\t\tgoto out;\n+\t}\n+\n \t/* perform credential operation */\n \tret = (*try_op->op)(&cred);\n \n \tcredential_write(&cred);\n-\n out:\n \tcredential_clear(&cred);\n \treturn ret;\ndiff --git a/contrib/credential/helper/credential_helper.h b/contrib/credential/helper/credential_helper.h\nindex 76b6e50..b5ffa5b 100644\n--- a/contrib/credential/helper/credential_helper.h\n+++ b/contrib/credential/helper/credential_helper.h\n@@ -25,10 +25,11 @@ struct credential\n \tchar          *path;\n \tchar          *username;\n \tchar          *password;\n+\tchar          *url;     /* <protocol>://[username@][host[:port]][/path] */\n };\n \n #define CREDENTIAL_INIT \\\n-  { NULL,NULL,0,NULL,NULL,NULL }\n+  { NULL,NULL,0,NULL,NULL,NULL,NULL }\n \n void credential_init(struct credential *c);\n void credential_clear(struct credential *c);\n@@ -104,6 +105,17 @@ static inline void die_errno(int err)\n \texit(EXIT_FAILURE);\n }\n \n+static inline void *xmalloc(size_t size)\n+{\n+  void *ret = malloc(size);\n+\tif (!ret && !size)\n+\t\tret = malloc(1);\n+\tif (!ret)\n+\t\t die_errno(errno);\n+\n+\treturn ret;\n+}\n+\n static inline char *xstrdup(const char *str)\n {\n \tchar *ret = strdup(str);\n@@ -113,6 +125,7 @@ static inline char *xstrdup(const char *str)\n \treturn ret;\n }\n \n+#ifndef NO_STRNDUP\n static inline char *xstrndup(const char *str, size_t len)\n {\n \tchar *ret = strndup(str,len);\n@@ -121,5 +134,6 @@ static inline char *xstrndup(const char *str, size_t len)\n \n \treturn ret;\n }\n+#endif\n \n #endif /* CREDENTIAL_HELPER_H_INCLUDED_ */\ndiff --git a/contrib/credential/wincred/.gitignore b/contrib/credential/wincred/.gitignore\nnew file mode 100644\nindex 0000000..4780e4e\n--- /dev/null\n+++ b/contrib/credential/wincred/.gitignore\n@@ -0,0 +1 @@\n+git-credential-wincred.exe\ndiff --git a/contrib/credential/wincred/Makefile b/contrib/credential/wincred/Makefile\nindex bad45ca..3900322 100644\n--- a/contrib/credential/wincred/Makefile\n+++ b/contrib/credential/wincred/Makefile\n@@ -1,14 +1,26 @@\n-all: git-credential-wincred.exe\n+MAIN:=git-credential-wincred\n+all:: $(MAIN).exe\n \n CC = gcc\n RM = rm -f\n CFLAGS = -O2 -Wall\n+CPPFLAGS = -DNO_STRNDUP\n \n -include ../../../config.mak.autogen\n -include ../../../config.mak\n \n-git-credential-wincred.exe : git-credential-wincred.c\n-\t$(LINK.c) $^ $(LOADLIBES) $(LDLIBS) -o $@\n+HELPER:=../helper\n+VPATH +=$(HELPER)\n+\n+SRCS:=$(MAIN).c\n+SRCS+=credential_helper.c\n+OBJS:=$(SRCS:.c=.o)\n+\n+%.o: %.c\n+\t$(CC) $(CFLAGS) $(CPPFLAGS) -I$(HELPER) -o $@ -c $<\n+\n+$(MAIN).exe: $(OBJS)\n+\t$(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@\n \n clean:\n-\t$(RM) git-credential-wincred.exe\n+\t$(RM) $(MAIN).exe $(OBJS)\ndiff --git a/contrib/credential/wincred/git-credential-wincred.c b/contrib/credential/wincred/git-credential-wincred.c\nindex cbaec5f..6dabeb7 100644\n--- a/contrib/credential/wincred/git-credential-wincred.c\n+++ b/contrib/credential/wincred/git-credential-wincred.c\n@@ -6,37 +6,7 @@\n #include <stdio.h>\n #include <io.h>\n #include <fcntl.h>\n-\n-/* common helpers */\n-\n-static void die(const char *err, ...)\n-{\n-\tchar msg[4096];\n-\tva_list params;\n-\tva_start(params, err);\n-\tvsnprintf(msg, sizeof(msg), err, params);\n-\tfprintf(stderr, \"%s\\n\", msg);\n-\tva_end(params);\n-\texit(1);\n-}\n-\n-static void *xmalloc(size_t size)\n-{\n-\tvoid *ret = malloc(size);\n-\tif (!ret && !size)\n-\t\tret = malloc(1);\n-\tif (!ret)\n-\t\t die(\"Out of memory\");\n-\treturn ret;\n-}\n-\n-static char *xstrdup(const char *str)\n-{\n-\tchar *ret = strdup(str);\n-\tif (!ret)\n-\t\tdie(\"Out of memory\");\n-\treturn ret;\n-}\n+#include <credential_helper.h>\n \n /* MinGW doesn't have wincred.h, so we need to define stuff */\n \n@@ -84,13 +54,16 @@ static CredPackAuthenticationBufferWT CredPackAuthenticationBufferW;\n static CredFreeT CredFree;\n static CredDeleteWT CredDeleteW;\n \n-static void load_cred_funcs(void)\n+\n+static int load_cred_funcs(void)\n {\n \t/* load DLLs */\n \tadvapi = LoadLibrary(\"advapi32.dll\");\n \tcredui = LoadLibrary(\"credui.dll\");\n-\tif (!advapi || !credui)\n-\t\tdie(\"failed to load DLLs\");\n+\tif (!advapi || !credui) {\n+\t\terror(\"failed to load DLLs\");\n+\t\treturn EXIT_FAILURE;\n+\t}\n \n \t/* get function pointers */\n \tCredWriteW = (CredWriteWT)GetProcAddress(advapi, \"CredWriteW\");\n@@ -104,28 +77,57 @@ static void load_cred_funcs(void)\n \tCredDeleteW = (CredDeleteWT)GetProcAddress(advapi, \"CredDeleteW\");\n \tif (!CredWriteW || !CredUnPackAuthenticationBufferW ||\n \t    !CredEnumerateW || !CredPackAuthenticationBufferW || !CredFree ||\n-\t    !CredDeleteW)\n-\t\tdie(\"failed to load functions\");\n+\t    !CredDeleteW) {\n+\t\terror(\"failed to load functions\");\n+\t\treturn EXIT_FAILURE;\n+\t}\n+\treturn EXIT_SUCCESS;\n }\n \n static char target_buf[1024];\n-static char *protocol, *host, *path, *username;\n-static WCHAR *wusername, *password, *target;\n+static char port_buf[8];\n+static WCHAR *wusername, *wpassword;\n \n-static void write_item(const char *what, WCHAR *wbuf)\n+static WCHAR *utf8_to_utf16_dup(const char *str)\n {\n-\tchar *buf;\n-\tint len = WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, NULL, 0, NULL,\n-\t    FALSE);\n-\tbuf = xmalloc(len);\n-\n-\tif (!WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, buf, len, NULL, FALSE))\n-\t\tdie(\"WideCharToMultiByte failed!\");\n-\n-\tprintf(\"%s=\", what);\n-\tfwrite(buf, 1, len - 1, stdout);\n-\tputchar('\\n');\n-\tfree(buf);\n+\tint wlen = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);\n+\tWCHAR *wstr = xmalloc(sizeof(WCHAR) * wlen);\n+\tif (!MultiByteToWideChar(CP_UTF8, 0, str, -1, wstr, wlen))\n+\t\tdie(\"MultiByteToWideChar failed\");\n+\treturn wstr;\n+}\n+\n+static char *utf16_to_utf8_dup(const WCHAR *wstr)\n+{\n+\tint len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL,\n+\t\tFALSE);\n+\tchar *str = xmalloc(len);\n+\tif (!WideCharToMultiByte(CP_UTF8, 0, wstr, -1, str, len, NULL, FALSE))\n+\t\tdie(\"WideCharToMultiByte failed\");\n+\treturn str;\n+}\n+\n+static void free_wpassword(WCHAR *pass)\n+{\n+\tWCHAR *w = pass;\n+\tif(!pass)\n+\t\treturn;\n+\twhile(*w) *w++ = L'\\0';\n+\tfree(pass);\n+}\n+\n+static int prepare_credential(struct credential *c)\n+{\n+\tif (load_cred_funcs() )\n+\t\treturn EXIT_FAILURE;\n+\n+\tif (c->username)\n+\t\twusername = utf8_to_utf16_dup(c->username);\n+\tif (c->password)\n+\t\twpassword = utf8_to_utf16_dup(c->password);\n+\tif (c->port)\n+\t\tsnprintf(port_buf, sizeof(port_buf), \"%hd\", c->port);\n+\treturn EXIT_SUCCESS;\n }\n \n static int match_attr(const CREDENTIALW *cred, const WCHAR *keyword,\n@@ -143,33 +145,37 @@ static int match_attr(const CREDENTIALW *cred, const WCHAR *keyword,\n \treturn 0; /* not found */\n }\n \n-static int match_cred(const CREDENTIALW *cred)\n+static int match_cred(const CREDENTIALW *cred, const struct credential *c)\n {\n \treturn (!wusername || !wcscmp(wusername, cred->UserName)) &&\n-\t    match_attr(cred, L\"git_protocol\", protocol) &&\n-\t    match_attr(cred, L\"git_host\", host) &&\n-\t    match_attr(cred, L\"git_path\", path);\n+\t    match_attr(cred, L\"git_protocol\", c->protocol) &&\n+\t    match_attr(cred, L\"git_host\", c->host) &&\n+\t    (!c->port || match_attr(cred, L\"git_port\", port_buf)) &&\n+\t    match_attr(cred, L\"git_path\", c->path);\n }\n \n-static void get_credential(void)\n+static int get_credential(struct credential *c)\n {\n-\tWCHAR *user_buf, *pass_buf;\n+\tWCHAR *user_buf = NULL, *pass_buf = NULL;\n \tDWORD user_buf_size = 0, pass_buf_size = 0;\n \tCREDENTIALW **creds, *cred = NULL;\n \tDWORD num_creds;\n-\tint i;\n+\tint i, ret = EXIT_SUCCESS;\n+\n+\tif (prepare_credential(c))\n+\t\treturn EXIT_FAILURE;\n \n \tif (!CredEnumerateW(L\"git:*\", 0, &num_creds, &creds))\n-\t\treturn;\n+\t\treturn EXIT_FAILURE;\n \n \t/* search for the first credential that matches username */\n \tfor (i = 0; i < num_creds; ++i)\n-\t\tif (match_cred(creds[i])) {\n+\t\tif (match_cred(creds[i], c)) {\n \t\t\tcred = creds[i];\n \t\t\tbreak;\n \t\t}\n \tif (!cred)\n-\t\treturn;\n+\t\tgoto out;\n \n \tCredUnPackAuthenticationBufferW(0, cred->CredentialBlob,\n \t    cred->CredentialBlobSize, NULL, &user_buf_size, NULL, NULL,\n@@ -180,20 +186,29 @@ static void get_credential(void)\n \n \tif (!CredUnPackAuthenticationBufferW(0, cred->CredentialBlob,\n \t    cred->CredentialBlobSize, user_buf, &user_buf_size, NULL, NULL,\n-\t    pass_buf, &pass_buf_size))\n-\t\tdie(\"CredUnPackAuthenticationBuffer failed\");\n-\n-\tCredFree(creds);\n+\t    pass_buf, &pass_buf_size)) {\n+\t\terror(\"CredUnPackAuthenticationBuffer failed\");\n+\t\tret = EXIT_FAILURE;\n+\t\tgoto out;\n+\t}\n \n \t/* zero-terminate (sizes include zero-termination) */\n \tuser_buf[user_buf_size - 1] = L'\\0';\n \tpass_buf[pass_buf_size - 1] = L'\\0';\n \n-\twrite_item(\"username\", user_buf);\n-\twrite_item(\"password\", pass_buf);\n+\tif (!c->username)\n+\t\tc->username = utf16_to_utf8_dup(user_buf);\n+\tfree_password(c->password);\n+\tc->password = utf16_to_utf8_dup(pass_buf);\n+\n+out:\n+\tCredFree(creds);\n \n \tfree(user_buf);\n-\tfree(pass_buf);\n+\tfree(wusername);\n+\tfree_wpassword(pass_buf);\n+\n+\treturn ret;\n }\n \n static void write_attr(CREDENTIAL_ATTRIBUTEW *attr, const WCHAR *keyword,\n@@ -205,29 +220,42 @@ static void write_attr(CREDENTIAL_ATTRIBUTEW *attr, const WCHAR *keyword,\n \tattr->Value = (LPBYTE)value;\n }\n \n-static void store_credential(void)\n+static int store_credential(struct credential *c)\n {\n \tCREDENTIALW cred;\n \tBYTE *auth_buf;\n \tDWORD auth_buf_size = 0;\n \tCREDENTIAL_ATTRIBUTEW attrs[CRED_MAX_ATTRIBUTES];\n+\tWCHAR *wtarget;\n+\tint ret = EXIT_SUCCESS;\n \n-\tif (!wusername || !password)\n-\t\treturn;\n+\tif (prepare_credential(c))\n+\t\treturn EXIT_FAILURE;\n+\n+\tif (!wusername || !wpassword)\n+\t\treturn EXIT_FAILURE;\n+\n+\t/* prepare 'target', the unique key for the credential */\n+\tstrncat(target_buf,\"git:\",sizeof(target_buf));\n+\tstrncat(target_buf,c->url,sizeof(target_buf));\n+\twtarget = utf8_to_utf16_dup(target_buf);\n \n \t/* query buffer size */\n-\tCredPackAuthenticationBufferW(0, wusername, password,\n+\tCredPackAuthenticationBufferW(0, wusername, wpassword,\n \t    NULL, &auth_buf_size);\n \n \tauth_buf = xmalloc(auth_buf_size);\n \n-\tif (!CredPackAuthenticationBufferW(0, wusername, password,\n-\t    auth_buf, &auth_buf_size))\n-\t\tdie(\"CredPackAuthenticationBuffer failed\");\n+\tif (!CredPackAuthenticationBufferW(0, wusername, wpassword,\n+\t    auth_buf, &auth_buf_size)) {\n+\t\terror(\"CredPackAuthenticationBuffer failed\");\n+\t\tret = EXIT_FAILURE;\n+\t\tgoto out;\n+\t}\n \n \tcred.Flags = 0;\n \tcred.Type = CRED_TYPE_GENERIC;\n-\tcred.TargetName = target;\n+\tcred.TargetName = wtarget;\n \tcred.Comment = L\"saved by git-credential-wincred\";\n \tcred.CredentialBlobSize = auth_buf_size;\n \tcred.CredentialBlob = auth_buf;\n@@ -237,121 +265,65 @@ static void store_credential(void)\n \tcred.TargetAlias = NULL;\n \tcred.UserName = wusername;\n \n-\twrite_attr(attrs, L\"git_protocol\", protocol);\n+\twrite_attr(attrs, L\"git_protocol\", c->protocol);\n+\n+\tif (c->host) {\n+\t\twrite_attr(attrs + cred.AttributeCount, L\"git_host\", c->host);\n+\t\tcred.AttributeCount++;\n+\t}\n \n-\tif (host) {\n-\t\twrite_attr(attrs + cred.AttributeCount, L\"git_host\", host);\n+\tif (c->port) {\n+\t\twrite_attr(attrs + cred.AttributeCount, L\"git_port\", port_buf);\n \t\tcred.AttributeCount++;\n \t}\n \n-\tif (path) {\n-\t\twrite_attr(attrs + cred.AttributeCount, L\"git_path\", path);\n+\tif (c->path) {\n+\t\twrite_attr(attrs + cred.AttributeCount, L\"git_path\", c->path);\n \t\tcred.AttributeCount++;\n \t}\n \n-\tif (!CredWriteW(&cred, 0))\n-\t\tdie(\"CredWrite failed\");\n+\tif (!CredWriteW(&cred, 0)) {\n+\t\terror(\"CredWrite failed\");\n+\t\tret = EXIT_FAILURE;\n+\t}\n+\n+out:\n+\tfree(auth_buf);\n+\tfree(wusername);\n+\tfree_wpassword(wpassword);\n+\tfree(wtarget);\n+\treturn ret;\n }\n \n-static void erase_credential(void)\n+static int erase_credential(struct credential *c)\n {\n \tCREDENTIALW **creds;\n \tDWORD num_creds;\n \tint i;\n \n+\tif (prepare_credential(c))\n+\t\treturn EXIT_FAILURE;\n+\n \tif (!CredEnumerateW(L\"git:*\", 0, &num_creds, &creds))\n-\t\treturn;\n+\t\treturn EXIT_FAILURE;\n \n \tfor (i = 0; i < num_creds; ++i) {\n-\t\tif (match_cred(creds[i]))\n+\t\tif (match_cred(creds[i],c))\n \t\t\tCredDeleteW(creds[i]->TargetName, creds[i]->Type, 0);\n \t}\n \n \tCredFree(creds);\n+\treturn EXIT_SUCCESS;\n }\n \n-static WCHAR *utf8_to_utf16_dup(const char *str)\n-{\n-\tint wlen = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);\n-\tWCHAR *wstr = xmalloc(sizeof(WCHAR) * wlen);\n-\tMultiByteToWideChar(CP_UTF8, 0, str, -1, wstr, wlen);\n-\treturn wstr;\n-}\n-\n-static void read_credential(void)\n-{\n-\tchar buf[1024];\n-\n-\twhile (fgets(buf, sizeof(buf), stdin)) {\n-\t\tchar *v;\n-\n-\t\tif (!strcmp(buf, \"\\n\"))\n-\t\t\tbreak;\n-\t\tbuf[strlen(buf)-1] = '\\0';\n-\n-\t\tv = strchr(buf, '=');\n-\t\tif (!v)\n-\t\t\tdie(\"bad input: %s\", buf);\n-\t\t*v++ = '\\0';\n-\n-\t\tif (!strcmp(buf, \"protocol\"))\n-\t\t\tprotocol = xstrdup(v);\n-\t\telse if (!strcmp(buf, \"host\"))\n-\t\t\thost = xstrdup(v);\n-\t\telse if (!strcmp(buf, \"path\"))\n-\t\t\tpath = xstrdup(v);\n-\t\telse if (!strcmp(buf, \"username\")) {\n-\t\t\tusername = xstrdup(v);\n-\t\t\twusername = utf8_to_utf16_dup(v);\n-\t\t} else if (!strcmp(buf, \"password\"))\n-\t\t\tpassword = utf8_to_utf16_dup(v);\n-\t\telse\n-\t\t\tdie(\"unrecognized input\");\n-\t}\n-}\n-\n-int main(int argc, char *argv[])\n+/*\n+ * Table with helper operation callbacks, used by generic\n+ * credential helper main function.\n+ */\n+struct credential_operation const credential_helper_ops[] =\n {\n-\tconst char *usage =\n-\t    \"Usage: git credential-wincred <get|store|erase>\\n\";\n-\n-\tif (!argv[1])\n-\t\tdie(usage);\n-\n-\t/* git use binary pipes to avoid CRLF-issues */\n-\t_setmode(_fileno(stdin), _O_BINARY);\n-\t_setmode(_fileno(stdout), _O_BINARY);\n-\n-\tread_credential();\n-\n-\tload_cred_funcs();\n-\n-\tif (!protocol || !(host || path))\n-\t\treturn 0;\n-\n-\t/* prepare 'target', the unique key for the credential */\n-\tstrncat(target_buf, \"git:\", sizeof(target_buf));\n-\tstrncat(target_buf, protocol, sizeof(target_buf));\n-\tstrncat(target_buf, \"://\", sizeof(target_buf));\n-\tif (username) {\n-\t\tstrncat(target_buf, username, sizeof(target_buf));\n-\t\tstrncat(target_buf, \"@\", sizeof(target_buf));\n-\t}\n-\tif (host)\n-\t\tstrncat(target_buf, host, sizeof(target_buf));\n-\tif (path) {\n-\t\tstrncat(target_buf, \"/\", sizeof(target_buf));\n-\t\tstrncat(target_buf, path, sizeof(target_buf));\n-\t}\n-\n-\ttarget = utf8_to_utf16_dup(target_buf);\n-\n-\tif (!strcmp(argv[1], \"get\"))\n-\t\tget_credential();\n-\telse if (!strcmp(argv[1], \"store\"))\n-\t\tstore_credential();\n-\telse if (!strcmp(argv[1], \"erase\"))\n-\t\terase_credential();\n-\t/* otherwise, ignore unknown action */\n-\treturn 0;\n-}\n+\t{ \"get\",   get_credential   },\n+\t{ \"store\", store_credential },\n+\t{ \"erase\", erase_credential },\n+\tCREDENTIAL_OP_END\n+};\n-- \n1.7.10.4\n"}]}