{"thread":{"id":"29128","subject":"[PATCHv3 0/13] credential helpers","startedAt":"2011-12-10T10:28:28Z","lastAt":"2012-01-17T06:51:54Z","messageCount":54,"participants":["Jeff King","Jakub Narebski","Johannes Sixt","Junio C Hamano","Pete Wyckoff","Jonathan Nieder"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"180752","messageId":"20111210102827.GA16460@sigill.intra.peff.net","threadId":"29128","inReplyTo":null,"subject":"[PATCHv3 0/13] credential helpers","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-12-10T10:28:28Z","receivedAt":"2011-12-10T10:28:28Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"Here's the latest re-roll of the credential helpers series. I think this\none is probably ready to go to 'next'.\n\nIt's rebased on the latest tip of 'master' (applying it to an older\ncommit will get you a minor textual conflict in strbuf.c). It\nincorporates the erase-safety we discussed, fixes a few commit message\ntypos, and tweaks the test scripts to make testing the external OS X\nhelper a little easier.\n\n  [01/13]: test-lib: add test_config_global variant\n  [02/13]: t5550: fix typo\n  [03/13]: introduce credentials API\n  [04/13]: credential: add function for parsing url components\n  [05/13]: http: use credential API to get passwords\n  [06/13]: credential: apply helper config\n  [07/13]: credential: add credential.*.username\n  [08/13]: credential: make relevance of http path configurable\n  [09/13]: docs: end-user documentation for the credential subsystem\n  [10/13]: credentials: add \"cache\" helper\n  [11/13]: strbuf: add strbuf_add*_urlencode\n  [12/13]: credentials: add \"store\" helper\n  [13/13]: t: add test harness for external credential helpers\n\n-Peff\n"},{"id":"180753","messageId":"20111210103006.GA16529@sigill.intra.peff.net","threadId":"29128","inReplyTo":"20111210102827.GA16460@sigill.intra.peff.net","subject":"[PATCHv3 01/13] test-lib: add test_config_global variant","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-12-10T10:30:06Z","receivedAt":"2011-12-10T10:30:06Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"The point of test_config is to simultaneously set a config\nvariable and register its cleanup handler, like:\n\n  test_config core.foo bar\n\nHowever, it stupidly assumes that $1 contained the name of\nthe variable, which means it won't work for:\n\n  test_config --global core.foo bar\n\nWe could try to parse the command-line ourselves and figure\nout which parts need to be fed to test_unconfig. But since\nthis is likely the most common variant, it's much simpler\nand less error-prone to simply add a new function.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n t/test-lib.sh |    5 +++++\n 1 files changed, 5 insertions(+), 0 deletions(-)\n\ndiff --git a/t/test-lib.sh b/t/test-lib.sh\nindex bdd9513..160479b 100644\n--- a/t/test-lib.sh\n+++ b/t/test-lib.sh\n@@ -379,6 +379,11 @@ test_config () {\n \tgit config \"$@\"\n }\n \n+test_config_global () {\n+\ttest_when_finished \"test_unconfig --global '$1'\" &&\n+\tgit config --global \"$@\"\n+}\n+\n # Use test_set_prereq to tell that a particular prerequisite is available.\n # The prerequisite can later be checked for in two ways:\n #\n-- \n1.7.8.rc2.40.gaf387\n"},{"id":"180754","messageId":"20111210103010.GB16529@sigill.intra.peff.net","threadId":"29128","inReplyTo":"20111210102827.GA16460@sigill.intra.peff.net","subject":"[PATCHv3 02/13] t5550: fix typo","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-12-10T10:30:10Z","receivedAt":"2011-12-10T10:30:10Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"This didn't have an impact, because it was just setting up\nan \"expect\" file that happened to be identical to the one in\nthe test before it.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n t/t5550-http-fetch.sh |    2 +-\n 1 files changed, 1 insertions(+), 1 deletions(-)\n\ndiff --git a/t/t5550-http-fetch.sh b/t/t5550-http-fetch.sh\nindex 311a33c..3d6e871 100755\n--- a/t/t5550-http-fetch.sh\n+++ b/t/t5550-http-fetch.sh\n@@ -66,7 +66,7 @@ test_expect_success 'cloning password-protected repository can fail' '\n \n test_expect_success 'http auth can use user/pass in URL' '\n \t>askpass-query &&\n-\techo wrong >askpass-reponse &&\n+\techo wrong >askpass-response &&\n \tgit clone \"$HTTPD_URL_USER_PASS/auth/repo.git\" clone-auth-none &&\n \ttest_cmp askpass-expect-none askpass-query\n '\n-- \n1.7.8.rc2.40.gaf387\n"},{"id":"180755","messageId":"20111210103111.GC16529@sigill.intra.peff.net","threadId":"29128","inReplyTo":"20111210102827.GA16460@sigill.intra.peff.net","subject":"[PATCHv3 03/13] introduce credentials API","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-12-10T10:31:11Z","receivedAt":"2011-12-10T10:31:11Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"There are a few places in git that need to get a username\nand password credential from the user; the most notable one\nis HTTP authentication for smart-http pushing.\n\nRight now the only choices for providing credentials are to\nput them plaintext into your ~/.netrc, or to have git prompt\nyou (either on the terminal or via an askpass program). The\nformer is not very secure, and the latter is not very\nconvenient.\n\nUnfortunately, there is no \"always best\" solution for\npassword management. The details will depend on the tradeoff\nyou want between security and convenience, as well as how\ngit can integrate with other security systems (e.g., many\noperating systems provide a keychain or password wallet for\nsingle sign-on).\n\nThis patch provides an abstract notion of credentials as a\ndata item, and provides three basic operations:\n\n  - fill (i.e., acquire from external storage or from the\n    user)\n\n  - approve (mark a credential as \"working\" for further\n    storage)\n\n  - reject (mark a credential as \"not working\", so it can\n    be removed from storage)\n\nThese operations can be backed by external helper processes\nthat interact with system- or user-specific secure storage.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n .gitignore                                  |    1 +\n Documentation/technical/api-credentials.txt |  241 +++++++++++++++++++++++++++\n Makefile                                    |    3 +\n credential.c                                |  234 ++++++++++++++++++++++++++\n credential.h                                |   28 +++\n t/lib-credential.sh                         |   33 ++++\n t/t0300-credentials.sh                      |  195 ++++++++++++++++++++++\n test-credential.c                           |   38 +++++\n 8 files changed, 773 insertions(+), 0 deletions(-)\n create mode 100644 Documentation/technical/api-credentials.txt\n create mode 100644 credential.c\n create mode 100644 credential.h\n create mode 100755 t/lib-credential.sh\n create mode 100755 t/t0300-credentials.sh\n create mode 100644 test-credential.c\n\ndiff --git a/.gitignore b/.gitignore\nindex 8572c8c..7d2fefc 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -167,6 +167,7 @@\n /gitweb/static/gitweb.js\n /gitweb/static/gitweb.min.*\n /test-chmtime\n+/test-credential\n /test-ctype\n /test-date\n /test-delta\ndiff --git a/Documentation/technical/api-credentials.txt b/Documentation/technical/api-credentials.txt\nnew file mode 100644\nindex 0000000..f624aef\n--- /dev/null\n+++ b/Documentation/technical/api-credentials.txt\n@@ -0,0 +1,241 @@\n+credentials API\n+===============\n+\n+The credentials API provides an abstracted way of gathering username and\n+password credentials from the user (even though credentials in the wider\n+world can take many forms, in this document the word \"credential\" always\n+refers to a username and password pair).\n+\n+Data Structures\n+---------------\n+\n+`struct credential`::\n+\n+\tThis struct represents a single username/password combination\n+\talong with any associated context. All string fields should be\n+\theap-allocated (or NULL if they are not known or not applicable).\n+\tThe meaning of the individual context fields is the same as\n+\ttheir counterparts in the helper protocol; see the section below\n+\tfor a description of each field.\n++\n+The `helpers` member of the struct is a `string_list` of helpers.  Each\n+string specifies an external helper which will be run, in order, to\n+either acquire or store credentials. See the section on credential\n+helpers below.\n++\n+This struct should always be initialized with `CREDENTIAL_INIT` or\n+`credential_init`.\n+\n+\n+Functions\n+---------\n+\n+`credential_init`::\n+\n+\tInitialize a credential structure, setting all fields to empty.\n+\n+`credential_clear`::\n+\n+\tFree any resources associated with the credential structure,\n+\treturning it to a pristine initialized state.\n+\n+`credential_fill`::\n+\n+\tInstruct the credential subsystem to fill the username and\n+\tpassword fields of the passed credential struct by first\n+\tconsulting helpers, then asking the user. After this function\n+\treturns, the username and password fields of the credential are\n+\tguaranteed to be non-NULL. If an error occurs, the function will\n+\tdie().\n+\n+`credential_reject`::\n+\n+\tInform the credential subsystem that the provided credentials\n+\thave been rejected. This will cause the credential subsystem to\n+\tnotify any helpers of the rejection (which allows them, for\n+\texample, to purge the invalid credentials from storage).  It\n+\twill also free() the username and password fields of the\n+\tcredential and set them to NULL (readying the credential for\n+\tanother call to `credential_fill`). Any errors from helpers are\n+\tignored.\n+\n+`credential_approve`::\n+\n+\tInform the credential subsystem that the provided credentials\n+\twere successfully used for authentication.  This will cause the\n+\tcredential subsystem to notify any helpers of the approval, so\n+\tthat they may store the result to be used again.  Any errors\n+\tfrom helpers are ignored.\n+\n+Example\n+-------\n+\n+The example below shows how the functions of the credential API could be\n+used to login to a fictitious \"foo\" service on a remote host:\n+\n+-----------------------------------------------------------------------\n+int foo_login(struct foo_connection *f)\n+{\n+\tint status;\n+\t/*\n+\t * Create a credential with some context; we don't yet know the\n+\t * username or password.\n+\t */\n+\n+\tstruct credential c = CREDENTIAL_INIT;\n+\tc.protocol = xstrdup(\"foo\");\n+\tc.host = xstrdup(f->hostname);\n+\n+\t/*\n+\t * Fill in the username and password fields by contacting\n+\t * helpers and/or asking the user. The function will die if it\n+\t * fails.\n+\t */\n+\tcredential_fill(&c);\n+\n+\t/*\n+\t * Otherwise, we have a username and password. Try to use it.\n+\t */\n+\tstatus = send_foo_login(f, c.username, c.password);\n+\tswitch (status) {\n+\tcase FOO_OK:\n+\t\t/* It worked. Store the credential for later use. */\n+\t\tcredential_accept(&c);\n+\t\tbreak;\n+\tcase FOO_BAD_LOGIN:\n+\t\t/* Erase the credential from storage so we don't try it\n+\t\t * again. */\n+\t\tcredential_reject(&c);\n+\t\tbreak;\n+\tdefault:\n+\t\t/*\n+\t\t * Some other error occured. We don't know if the\n+\t\t * credential is good or bad, so report nothing to the\n+\t\t * credential subsystem.\n+\t\t */\n+\t}\n+\n+\t/* Free any associated resources. */\n+\tcredential_clear(&c);\n+\n+\treturn status;\n+}\n+-----------------------------------------------------------------------\n+\n+\n+Credential Helpers\n+------------------\n+\n+Credential helpers are programs executed by git to fetch or save\n+credentials from and to long-term storage (where \"long-term\" is simply\n+longer than a single git process; e.g., credentials may be stored\n+in-memory for a few minutes, or indefinitely on disk).\n+\n+Each helper is specified by a single string. The string is transformed\n+by git into a command to be executed using these rules:\n+\n+  1. If the helper string begins with \"!\", it is considered a shell\n+     snippet, and everything after the \"!\" becomes the command.\n+\n+  2. Otherwise, if the helper string begins with an absolute path, the\n+     verbatim helper string becomes the command.\n+\n+  3. Otherwise, the string \"git credential-\" is prepended to the helper\n+     string, and the result becomes the command.\n+\n+The resulting command then has an \"operation\" argument appended to it\n+(see below for details), and the result is executed by the shell.\n+\n+Here are some example specifications:\n+\n+----------------------------------------------------\n+# run \"git credential-foo\"\n+foo\n+\n+# same as above, but pass an argument to the helper\n+foo --bar=baz\n+\n+# the arguments are parsed by the shell, so use shell\n+# quoting if necessary\n+foo --bar=\"whitespace arg\"\n+\n+# you can also use an absolute path, which will not use the git wrapper\n+/path/to/my/helper --with-arguments\n+\n+# or you can specify your own shell snippet\n+!f() { echo \"password=`cat $HOME/.secret`\"; }; f\n+----------------------------------------------------\n+\n+Generally speaking, rule (3) above is the simplest for users to specify.\n+Authors of credential helpers should make an effort to assist their\n+users by naming their program \"git-credential-$NAME\", and putting it in\n+the $PATH or $GIT_EXEC_PATH during installation, which will allow a user\n+to enable it with `git config credential.helper $NAME`.\n+\n+When a helper is executed, it will have one \"operation\" argument\n+appended to its command line, which is one of:\n+\n+`get`::\n+\n+\tReturn a matching credential, if any exists.\n+\n+`store`::\n+\n+\tStore the credential, if applicable to the helper.\n+\n+`erase`::\n+\n+\tRemove a matching credential, if any, from the helper's storage.\n+\n+The details of the credential will be provided on the helper's stdin\n+stream. The credential is split into a set of named attributes.\n+Attributes are provided to the helper, one per line. Each attribute is\n+specified by a key-value pair, separated by an `=` (equals) sign,\n+followed by a newline. The key may contain any bytes except `=`,\n+newline, or NUL. The value may contain any bytes except newline or NUL.\n+In both cases, all bytes are treated as-is (i.e., there is no quoting,\n+and one cannot transmit a value with newline or NUL in it). The list of\n+attributes is terminated by a blank line or end-of-file.\n+\n+Git will send the following attributes (but may not send all of\n+them for a given credential; for example, a `host` attribute makes no\n+sense when dealing with a non-network protocol):\n+\n+`protocol`::\n+\n+\tThe protocol over which the credential will be used (e.g.,\n+\t`https`).\n+\n+`host`::\n+\n+\tThe remote hostname for a network credential.\n+\n+`path`::\n+\n+\tThe path with which the credential will be used. E.g., for\n+\taccessing a remote https repository, this will be the\n+\trepository's path on the server.\n+\n+`username`::\n+\n+\tThe credential's username, if we already have one (e.g., from a\n+\tURL, from the user, or from a previously run helper).\n+\n+`password`::\n+\n+\tThe credential's password, if we are asking it to be stored.\n+\n+For a `get` operation, the helper should produce a list of attributes\n+on stdout in the same format. A helper is free to produce a subset, or\n+even no values at all if it has nothing useful to provide. Any provided\n+attributes will overwrite those already known about by git.\n+\n+For a `store` or `erase` operation, the helper's output is ignored.\n+If it fails to perform the requested operation, it may complain to\n+stderr to inform the user. If it does not support the requested\n+operation (e.g., a read-only store), it should silently ignore the\n+request.\n+\n+If a helper receives any other operation, it should silently ignore the\n+request. This leaves room for future operations to be added (older\n+helpers will just ignore the new requests).\ndiff --git a/Makefile b/Makefile\nindex ed82320..d69f1dd 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -431,6 +431,7 @@ PROGRAM_OBJS += sh-i18n--envsubst.o\n PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS))\n \n TEST_PROGRAMS_NEED_X += test-chmtime\n+TEST_PROGRAMS_NEED_X += test-credential\n TEST_PROGRAMS_NEED_X += test-ctype\n TEST_PROGRAMS_NEED_X += test-date\n TEST_PROGRAMS_NEED_X += test-delta\n@@ -525,6 +526,7 @@ LIB_H += compat/win32/poll.h\n LIB_H += compat/win32/dirent.h\n LIB_H += connected.h\n LIB_H += convert.h\n+LIB_H += credential.h\n LIB_H += csum-file.h\n LIB_H += decorate.h\n LIB_H += delta.h\n@@ -611,6 +613,7 @@ LIB_OBJS += connect.o\n LIB_OBJS += connected.o\n LIB_OBJS += convert.o\n LIB_OBJS += copy.o\n+LIB_OBJS += credential.o\n LIB_OBJS += csum-file.o\n LIB_OBJS += ctype.o\n LIB_OBJS += date.o\ndiff --git a/credential.c b/credential.c\nnew file mode 100644\nindex 0000000..86397f3\n--- /dev/null\n+++ b/credential.c\n@@ -0,0 +1,234 @@\n+#include \"cache.h\"\n+#include \"credential.h\"\n+#include \"string-list.h\"\n+#include \"run-command.h\"\n+\n+void credential_init(struct credential *c)\n+{\n+\tmemset(c, 0, sizeof(*c));\n+\tc->helpers.strdup_strings = 1;\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(c->password);\n+\tstring_list_clear(&c->helpers, 0);\n+\n+\tcredential_init(c);\n+}\n+\n+static void credential_describe(struct credential *c, struct strbuf *out)\n+{\n+\tif (!c->protocol)\n+\t\treturn;\n+\tstrbuf_addf(out, \"%s://\", c->protocol);\n+\tif (c->username && *c->username)\n+\t\tstrbuf_addf(out, \"%s@\", c->username);\n+\tif (c->host)\n+\t\tstrbuf_addstr(out, c->host);\n+\tif (c->path)\n+\t\tstrbuf_addf(out, \"/%s\", c->path);\n+}\n+\n+static char *credential_ask_one(const char *what, struct credential *c)\n+{\n+\tstruct strbuf desc = STRBUF_INIT;\n+\tstruct strbuf prompt = STRBUF_INIT;\n+\tchar *r;\n+\n+\tcredential_describe(c, &desc);\n+\tif (desc.len)\n+\t\tstrbuf_addf(&prompt, \"%s for '%s': \", what, desc.buf);\n+\telse\n+\t\tstrbuf_addf(&prompt, \"%s: \", what);\n+\n+\t/* FIXME: for usernames, we should do something less magical that\n+\t * actually echoes the characters. However, we need to read from\n+\t * /dev/tty and not stdio, which is not portable (but getpass will do\n+\t * it for us). http.c uses the same workaround. */\n+\tr = git_getpass(prompt.buf);\n+\n+\tstrbuf_release(&desc);\n+\tstrbuf_release(&prompt);\n+\treturn xstrdup(r);\n+}\n+\n+static void credential_getpass(struct credential *c)\n+{\n+\tif (!c->username)\n+\t\tc->username = credential_ask_one(\"Username\", c);\n+\tif (!c->password)\n+\t\tc->password = credential_ask_one(\"Password\", c);\n+}\n+\n+int credential_read(struct credential *c, FILE *fp)\n+{\n+\tstruct strbuf line = STRBUF_INIT;\n+\n+\twhile (strbuf_getline(&line, fp, '\\n') != EOF) {\n+\t\tchar *key = line.buf;\n+\t\tchar *value = strchr(key, '=');\n+\n+\t\tif (!line.len)\n+\t\t\tbreak;\n+\n+\t\tif (!value) {\n+\t\t\twarning(\"invalid credential line: %s\", key);\n+\t\t\tstrbuf_release(&line);\n+\t\t\treturn -1;\n+\t\t}\n+\t\t*value++ = '\\0';\n+\n+\t\tif (!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(c->password);\n+\t\t\tc->password = xstrdup(value);\n+\t\t} else if (!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} else if (!strcmp(key, \"path\")) {\n+\t\t\tfree(c->path);\n+\t\t\tc->path = xstrdup(value);\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+\n+\tstrbuf_release(&line);\n+\treturn 0;\n+}\n+\n+static 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+static void credential_write(const struct credential *c, FILE *fp)\n+{\n+\tcredential_write_item(fp, \"protocol\", c->protocol);\n+\tcredential_write_item(fp, \"host\", c->host);\n+\tcredential_write_item(fp, \"path\", c->path);\n+\tcredential_write_item(fp, \"username\", c->username);\n+\tcredential_write_item(fp, \"password\", c->password);\n+}\n+\n+static int run_credential_helper(struct credential *c,\n+\t\t\t\t const char *cmd,\n+\t\t\t\t int want_output)\n+{\n+\tstruct child_process helper;\n+\tconst char *argv[] = { NULL, NULL };\n+\tFILE *fp;\n+\n+\tmemset(&helper, 0, sizeof(helper));\n+\targv[0] = cmd;\n+\thelper.argv = argv;\n+\thelper.use_shell = 1;\n+\thelper.in = -1;\n+\tif (want_output)\n+\t\thelper.out = -1;\n+\telse\n+\t\thelper.no_stdout = 1;\n+\n+\tif (start_command(&helper) < 0)\n+\t\treturn -1;\n+\n+\tfp = xfdopen(helper.in, \"w\");\n+\tcredential_write(c, fp);\n+\tfclose(fp);\n+\n+\tif (want_output) {\n+\t\tint r;\n+\t\tfp = xfdopen(helper.out, \"r\");\n+\t\tr = credential_read(c, fp);\n+\t\tfclose(fp);\n+\t\tif (r < 0) {\n+\t\t\tfinish_command(&helper);\n+\t\t\treturn -1;\n+\t\t}\n+\t}\n+\n+\tif (finish_command(&helper))\n+\t\treturn -1;\n+\treturn 0;\n+}\n+\n+static int credential_do(struct credential *c, const char *helper,\n+\t\t\t const char *operation)\n+{\n+\tstruct strbuf cmd = STRBUF_INIT;\n+\tint r;\n+\n+\tif (helper[0] == '!')\n+\t\tstrbuf_addstr(&cmd, helper + 1);\n+\telse if (is_absolute_path(helper))\n+\t\tstrbuf_addstr(&cmd, helper);\n+\telse\n+\t\tstrbuf_addf(&cmd, \"git credential-%s\", helper);\n+\n+\tstrbuf_addf(&cmd, \" %s\", operation);\n+\tr = run_credential_helper(c, cmd.buf, !strcmp(operation, \"get\"));\n+\n+\tstrbuf_release(&cmd);\n+\treturn r;\n+}\n+\n+void credential_fill(struct credential *c)\n+{\n+\tint i;\n+\n+\tif (c->username && c->password)\n+\t\treturn;\n+\n+\tfor (i = 0; i < c->helpers.nr; i++) {\n+\t\tcredential_do(c, c->helpers.items[i].string, \"get\");\n+\t\tif (c->username && c->password)\n+\t\t\treturn;\n+\t}\n+\n+\tcredential_getpass(c);\n+\tif (!c->username && !c->password)\n+\t\tdie(\"unable to get password from user\");\n+}\n+\n+void credential_approve(struct credential *c)\n+{\n+\tint i;\n+\n+\tif (c->approved)\n+\t\treturn;\n+\tif (!c->username || !c->password)\n+\t\treturn;\n+\n+\tfor (i = 0; i < c->helpers.nr; i++)\n+\t\tcredential_do(c, c->helpers.items[i].string, \"store\");\n+\tc->approved = 1;\n+}\n+\n+void credential_reject(struct credential *c)\n+{\n+\tint i;\n+\n+\tfor (i = 0; i < c->helpers.nr; i++)\n+\t\tcredential_do(c, c->helpers.items[i].string, \"erase\");\n+\n+\tfree(c->username);\n+\tc->username = NULL;\n+\tfree(c->password);\n+\tc->password = NULL;\n+\tc->approved = 0;\n+}\ndiff --git a/credential.h b/credential.h\nnew file mode 100644\nindex 0000000..2ea7d49\n--- /dev/null\n+++ b/credential.h\n@@ -0,0 +1,28 @@\n+#ifndef CREDENTIAL_H\n+#define CREDENTIAL_H\n+\n+#include \"string-list.h\"\n+\n+struct credential {\n+\tstruct string_list helpers;\n+\tunsigned approved:1;\n+\n+\tchar *username;\n+\tchar *password;\n+\tchar *protocol;\n+\tchar *host;\n+\tchar *path;\n+};\n+\n+#define CREDENTIAL_INIT { STRING_LIST_INIT_DUP }\n+\n+void credential_init(struct credential *);\n+void credential_clear(struct credential *);\n+\n+void credential_fill(struct credential *);\n+void credential_approve(struct credential *);\n+void credential_reject(struct credential *);\n+\n+int credential_read(struct credential *, FILE *);\n+\n+#endif /* CREDENTIAL_H */\ndiff --git a/t/lib-credential.sh b/t/lib-credential.sh\nnew file mode 100755\nindex 0000000..54ae1f4\n--- /dev/null\n+++ b/t/lib-credential.sh\n@@ -0,0 +1,33 @@\n+#!/bin/sh\n+\n+# Try a set of credential helpers; the expected stdin,\n+# stdout and stderr should be provided on stdin,\n+# separated by \"--\".\n+check() {\n+\tread_chunk >stdin &&\n+\tread_chunk >expect-stdout &&\n+\tread_chunk >expect-stderr &&\n+\ttest-credential \"$@\" <stdin >stdout 2>stderr &&\n+\ttest_cmp expect-stdout stdout &&\n+\ttest_cmp expect-stderr stderr\n+}\n+\n+read_chunk() {\n+\twhile read line; do\n+\t\tcase \"$line\" in\n+\t\t--) break ;;\n+\t\t*) echo \"$line\" ;;\n+\t\tesac\n+\tdone\n+}\n+\n+\n+cat >askpass <<\\EOF\n+#!/bin/sh\n+echo >&2 askpass: $*\n+what=`echo $1 | cut -d\" \" -f1 | tr A-Z a-z | tr -cd a-z`\n+echo \"askpass-$what\"\n+EOF\n+chmod +x askpass\n+GIT_ASKPASS=\"$PWD/askpass\"\n+export GIT_ASKPASS\ndiff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh\nnew file mode 100755\nindex 0000000..81a455f\n--- /dev/null\n+++ b/t/t0300-credentials.sh\n@@ -0,0 +1,195 @@\n+#!/bin/sh\n+\n+test_description='basic credential helper tests'\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-credential.sh\n+\n+test_expect_success 'setup helper scripts' '\n+\tcat >dump <<-\\EOF &&\n+\twhoami=`echo $0 | sed s/.*git-credential-//`\n+\techo >&2 \"$whoami: $*\"\n+\twhile IFS== read key value; do\n+\t\techo >&2 \"$whoami: $key=$value\"\n+\t\teval \"$key=$value\"\n+\tdone\n+\tEOF\n+\n+\tcat >git-credential-useless <<-\\EOF &&\n+\t#!/bin/sh\n+\t. ./dump\n+\texit 0\n+\tEOF\n+\tchmod +x git-credential-useless &&\n+\n+\tcat >git-credential-verbatim <<-\\EOF &&\n+\t#!/bin/sh\n+\tuser=$1; shift\n+\tpass=$1; shift\n+\t. ./dump\n+\ttest -z \"$user\" || echo username=$user\n+\ttest -z \"$pass\" || echo password=$pass\n+\tEOF\n+\tchmod +x git-credential-verbatim &&\n+\n+\tPATH=\"$PWD:$PATH\"\n+'\n+\n+test_expect_success 'credential_fill invokes helper' '\n+\tcheck fill \"verbatim foo bar\" <<-\\EOF\n+\t--\n+\tusername=foo\n+\tpassword=bar\n+\t--\n+\tverbatim: get\n+\tEOF\n+'\n+\n+test_expect_success 'credential_fill invokes multiple helpers' '\n+\tcheck fill useless \"verbatim foo bar\" <<-\\EOF\n+\t--\n+\tusername=foo\n+\tpassword=bar\n+\t--\n+\tuseless: get\n+\tverbatim: get\n+\tEOF\n+'\n+\n+test_expect_success 'credential_fill stops when we get a full response' '\n+\tcheck fill \"verbatim one two\" \"verbatim three four\" <<-\\EOF\n+\t--\n+\tusername=one\n+\tpassword=two\n+\t--\n+\tverbatim: get\n+\tEOF\n+'\n+\n+test_expect_success 'credential_fill continues through partial response' '\n+\tcheck fill \"verbatim one \\\"\\\"\" \"verbatim two three\" <<-\\EOF\n+\t--\n+\tusername=two\n+\tpassword=three\n+\t--\n+\tverbatim: get\n+\tverbatim: get\n+\tverbatim: username=one\n+\tEOF\n+'\n+\n+test_expect_success 'credential_fill passes along metadata' '\n+\tcheck fill \"verbatim one two\" <<-\\EOF\n+\tprotocol=ftp\n+\thost=example.com\n+\tpath=foo.git\n+\t--\n+\tusername=one\n+\tpassword=two\n+\t--\n+\tverbatim: get\n+\tverbatim: protocol=ftp\n+\tverbatim: host=example.com\n+\tverbatim: path=foo.git\n+\tEOF\n+'\n+\n+test_expect_success 'credential_approve calls all helpers' '\n+\tcheck approve useless \"verbatim one two\" <<-\\EOF\n+\tusername=foo\n+\tpassword=bar\n+\t--\n+\t--\n+\tuseless: store\n+\tuseless: username=foo\n+\tuseless: password=bar\n+\tverbatim: store\n+\tverbatim: username=foo\n+\tverbatim: password=bar\n+\tEOF\n+'\n+\n+test_expect_success 'do not bother storing password-less credential' '\n+\tcheck approve useless <<-\\EOF\n+\tusername=foo\n+\t--\n+\t--\n+\tEOF\n+'\n+\n+\n+test_expect_success 'credential_reject calls all helpers' '\n+\tcheck reject useless \"verbatim one two\" <<-\\EOF\n+\tusername=foo\n+\tpassword=bar\n+\t--\n+\t--\n+\tuseless: erase\n+\tuseless: username=foo\n+\tuseless: password=bar\n+\tverbatim: erase\n+\tverbatim: username=foo\n+\tverbatim: password=bar\n+\tEOF\n+'\n+\n+test_expect_success 'usernames can be preserved' '\n+\tcheck fill \"verbatim \\\"\\\" three\" <<-\\EOF\n+\tusername=one\n+\t--\n+\tusername=one\n+\tpassword=three\n+\t--\n+\tverbatim: get\n+\tverbatim: username=one\n+\tEOF\n+'\n+\n+test_expect_success 'usernames can be overridden' '\n+\tcheck fill \"verbatim two three\" <<-\\EOF\n+\tusername=one\n+\t--\n+\tusername=two\n+\tpassword=three\n+\t--\n+\tverbatim: get\n+\tverbatim: username=one\n+\tEOF\n+'\n+\n+test_expect_success 'do not bother completing already-full credential' '\n+\tcheck fill \"verbatim three four\" <<-\\EOF\n+\tusername=one\n+\tpassword=two\n+\t--\n+\tusername=one\n+\tpassword=two\n+\t--\n+\tEOF\n+'\n+\n+# We can't test the basic terminal password prompt here because\n+# getpass() tries too hard to find the real terminal. But if our\n+# askpass helper is run, we know the internal getpass is working.\n+test_expect_success 'empty helper list falls back to internal getpass' '\n+\tcheck fill <<-\\EOF\n+\t--\n+\tusername=askpass-username\n+\tpassword=askpass-password\n+\t--\n+\taskpass: Username:\n+\taskpass: Password:\n+\tEOF\n+'\n+\n+test_expect_success 'internal getpass does not ask for known username' '\n+\tcheck fill <<-\\EOF\n+\tusername=foo\n+\t--\n+\tusername=foo\n+\tpassword=askpass-password\n+\t--\n+\taskpass: Password:\n+\tEOF\n+'\n+\n+test_done\ndiff --git a/test-credential.c b/test-credential.c\nnew file mode 100644\nindex 0000000..dee200e\n--- /dev/null\n+++ b/test-credential.c\n@@ -0,0 +1,38 @@\n+#include \"cache.h\"\n+#include \"credential.h\"\n+#include \"string-list.h\"\n+\n+static const char usage_msg[] =\n+\"test-credential <fill|approve|reject> [helper...]\";\n+\n+int main(int argc, const char **argv)\n+{\n+\tconst char *op;\n+\tstruct credential c = CREDENTIAL_INIT;\n+\tint i;\n+\n+\top = argv[1];\n+\tif (!op)\n+\t\tusage(usage_msg);\n+\tfor (i = 2; i < argc; i++)\n+\t\tstring_list_append(&c.helpers, argv[i]);\n+\n+\tif (credential_read(&c, stdin) < 0)\n+\t\tdie(\"unable to read credential from stdin\");\n+\n+\tif (!strcmp(op, \"fill\")) {\n+\t\tcredential_fill(&c);\n+\t\tif (c.username)\n+\t\t\tprintf(\"username=%s\\n\", c.username);\n+\t\tif (c.password)\n+\t\t\tprintf(\"password=%s\\n\", c.password);\n+\t}\n+\telse if (!strcmp(op, \"approve\"))\n+\t\tcredential_approve(&c);\n+\telse if (!strcmp(op, \"reject\"))\n+\t\tcredential_reject(&c);\n+\telse\n+\t\tusage(usage_msg);\n+\n+\treturn 0;\n+}\n-- \n1.7.8.rc2.40.gaf387\n"},{"id":"180756","messageId":"20111210103117.GD16529@sigill.intra.peff.net","threadId":"29128","inReplyTo":"20111210102827.GA16460@sigill.intra.peff.net","subject":"[PATCHv3 04/13] credential: add function for parsing url components","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-12-10T10:31:17Z","receivedAt":"2011-12-10T10:31:17Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"All of the components of a credential struct can be found in\na URL.  For example, the URL:\n\n  http://foo:bar@example.com/repo.git\n\ncontains:\n\n  protocol=http\n  host=example.com\n  path=repo.git\n  username=foo\n  password=bar\n\nWe want to be able to turn URLs into broken-down credential\nstructs so that we know two things:\n\n  1. Which parts of the username/password we still need\n\n  2. What the context of the request is (for prompting or\n     as a key for storing credentials).\n\nThis code is based on http_auth_init in http.c, but needed a\nfew modifications in order to get all of the components that\nthe credential object is interested in.\n\nOnce the http code is switched over to the credential API,\nthen http_auth_init can just go away.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n Documentation/technical/api-credentials.txt |    4 ++\n credential.c                                |   52 +++++++++++++++++++++++++++\n credential.h                                |    1 +\n 3 files changed, 57 insertions(+), 0 deletions(-)\n\ndiff --git a/Documentation/technical/api-credentials.txt b/Documentation/technical/api-credentials.txt\nindex f624aef..21ca6a2 100644\n--- a/Documentation/technical/api-credentials.txt\n+++ b/Documentation/technical/api-credentials.txt\n@@ -67,6 +67,10 @@ Functions\n \tthat they may store the result to be used again.  Any errors\n \tfrom helpers are ignored.\n \n+`credential_from_url`::\n+\n+\tParse a URL into broken-down credential fields.\n+\n Example\n -------\n \ndiff --git a/credential.c b/credential.c\nindex 86397f3..c349b9a 100644\n--- a/credential.c\n+++ b/credential.c\n@@ -2,6 +2,7 @@\n #include \"credential.h\"\n #include \"string-list.h\"\n #include \"run-command.h\"\n+#include \"url.h\"\n \n void credential_init(struct credential *c)\n {\n@@ -232,3 +233,54 @@ void credential_reject(struct credential *c)\n \tc->password = NULL;\n \tc->approved = 0;\n }\n+\n+void credential_from_url(struct credential *c, const char *url)\n+{\n+\tconst char *at, *colon, *cp, *slash, *host, *proto_end;\n+\n+\tcredential_clear(c);\n+\n+\t/*\n+\t * Match one of:\n+\t *   (1) proto://<host>/...\n+\t *   (2) proto://<user>@<host>/...\n+\t *   (3) proto://<user>:<pass>@<host>/...\n+\t */\n+\tproto_end = strstr(url, \"://\");\n+\tif (!proto_end)\n+\t\treturn;\n+\tcp = proto_end + 3;\n+\tat = strchr(cp, '@');\n+\tcolon = strchr(cp, ':');\n+\tslash = strchrnul(cp, '/');\n+\n+\tif (!at || slash <= at) {\n+\t\t/* Case (1) */\n+\t\thost = cp;\n+\t}\n+\telse if (!colon || at <= colon) {\n+\t\t/* Case (2) */\n+\t\tc->username = url_decode_mem(cp, at - cp);\n+\t\thost = at + 1;\n+\t} else {\n+\t\t/* Case (3) */\n+\t\tc->username = url_decode_mem(cp, colon - cp);\n+\t\tc->password = url_decode_mem(colon + 1, at - (colon + 1));\n+\t\thost = at + 1;\n+\t}\n+\n+\tif (proto_end - url > 0)\n+\t\tc->protocol = xmemdupz(url, proto_end - url);\n+\tif (slash - host > 0)\n+\t\tc->host = url_decode_mem(host, slash - host);\n+\t/* Trim leading and trailing slashes from path */\n+\twhile (*slash == '/')\n+\t\tslash++;\n+\tif (*slash) {\n+\t\tchar *p;\n+\t\tc->path = url_decode(slash);\n+\t\tp = c->path + strlen(c->path) - 1;\n+\t\twhile (p > c->path && *p == '/')\n+\t\t\t*p-- = '\\0';\n+\t}\n+}\ndiff --git a/credential.h b/credential.h\nindex 2ea7d49..8a6d162 100644\n--- a/credential.h\n+++ b/credential.h\n@@ -24,5 +24,6 @@ struct credential {\n void credential_reject(struct credential *);\n \n int credential_read(struct credential *, FILE *);\n+void credential_from_url(struct credential *, const char *url);\n \n #endif /* CREDENTIAL_H */\n-- \n1.7.8.rc2.40.gaf387\n"},{"id":"180758","messageId":"20111210103121.GE16529@sigill.intra.peff.net","threadId":"29128","inReplyTo":"20111210102827.GA16460@sigill.intra.peff.net","subject":"[PATCHv3 05/13] http: use credential API to get passwords","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-12-10T10:31:21Z","receivedAt":"2011-12-10T10:31:21Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"This patch converts the http code to use the new credential\nAPI, both for http authentication as well as for getting\ncertificate passwords.\n\nMost of the code change is simply variable naming (the\npasswords are now contained inside the credential struct)\nor deletion of obsolete code (the credential code handles\nURL parsing and prompting for us).\n\nThe behavior should be the same, with one exception: the\ncredential code will prompt with a description based on the\ncredential components. Therefore, the old prompt of:\n\n  Username for 'example.com':\n  Password for 'example.com':\n\nnow looks like:\n\n  Username for 'https://example.com/repo.git':\n  Password for 'https://user@example.com/repo.git':\n\nNote that we include more information in each line,\nspecifically:\n\n  1. We now include the protocol. While more noisy, this is\n     an important part of knowing what you are accessing\n     (especially if you care about http vs https).\n\n  2. We include the username in the password prompt. This is\n     not a big deal when you have just been prompted for it,\n     but the username may also come from the remote's URL\n     (and after future patches, from configuration or\n     credential helpers).  In that case, it's a nice\n     reminder of the user for which you're giving the\n     password.\n\n  3. We include the path component of the URL. In many\n     cases, the user won't care about this and it's simply\n     noise (i.e., they'll use the same credential for a\n     whole site). However, that is part of a larger\n     question, which is whether path components should be\n     part of credential context, both for prompting and for\n     lookup by storage helpers. That issue will be addressed\n     as a whole in a future patch.\n\nSimilarly, for unlocking certificates, we used to say:\n\n  Certificate Password for 'example.com':\n\nand we now say:\n\n  Password for 'cert:///path/to/certificate':\n\nShowing the path to the client certificate makes more sense,\nas that is what you are unlocking, not \"example.com\".\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n http.c                |  113 +++++++++++--------------------------------------\n t/t5550-http-fetch.sh |   38 ++++++++++++-----\n 2 files changed, 52 insertions(+), 99 deletions(-)\n\ndiff --git a/http.c b/http.c\nindex 44fcc4d..8e72664 100644\n--- a/http.c\n+++ b/http.c\n@@ -3,6 +3,7 @@\n #include \"sideband.h\"\n #include \"run-command.h\"\n #include \"url.h\"\n+#include \"credential.h\"\n \n int active_requests;\n int http_is_verbose;\n@@ -41,7 +42,7 @@\n static int curl_ftp_no_epsv;\n static const char *curl_http_proxy;\n static const char *curl_cookie_file;\n-static char *user_name, *user_pass, *description;\n+static struct credential http_auth = CREDENTIAL_INIT;\n static const char *user_agent;\n \n #if LIBCURL_VERSION_NUM >= 0x071700\n@@ -52,7 +53,7 @@\n #define CURLOPT_KEYPASSWD CURLOPT_SSLCERTPASSWD\n #endif\n \n-static char *ssl_cert_password;\n+static struct credential cert_auth = CREDENTIAL_INIT;\n static int ssl_cert_password_required;\n \n static struct curl_slist *pragma_header;\n@@ -136,27 +137,6 @@ static void process_curl_messages(void)\n }\n #endif\n \n-static char *git_getpass_with_description(const char *what, const char *desc)\n-{\n-\tstruct strbuf prompt = STRBUF_INIT;\n-\tchar *r;\n-\n-\tif (desc)\n-\t\tstrbuf_addf(&prompt, \"%s for '%s': \", what, desc);\n-\telse\n-\t\tstrbuf_addf(&prompt, \"%s: \", what);\n-\t/*\n-\t * NEEDSWORK: for usernames, we should do something less magical that\n-\t * actually echoes the characters. However, we need to read from\n-\t * /dev/tty and not stdio, which is not portable (but getpass will do\n-\t * it for us). http.c uses the same workaround.\n-\t */\n-\tr = git_getpass(prompt.buf);\n-\n-\tstrbuf_release(&prompt);\n-\treturn xstrdup(r);\n-}\n-\n static int http_options(const char *var, const char *value, void *cb)\n {\n \tif (!strcmp(\"http.sslverify\", var)) {\n@@ -229,11 +209,11 @@ static int http_options(const char *var, const char *value, void *cb)\n \n static void init_curl_http_auth(CURL *result)\n {\n-\tif (user_name) {\n+\tif (http_auth.username) {\n \t\tstruct strbuf up = STRBUF_INIT;\n-\t\tif (!user_pass)\n-\t\t\tuser_pass = xstrdup(git_getpass_with_description(\"Password\", description));\n-\t\tstrbuf_addf(&up, \"%s:%s\", user_name, user_pass);\n+\t\tcredential_fill(&http_auth);\n+\t\tstrbuf_addf(&up, \"%s:%s\",\n+\t\t\t    http_auth.username, http_auth.password);\n \t\tcurl_easy_setopt(result, CURLOPT_USERPWD,\n \t\t\t\t strbuf_detach(&up, NULL));\n \t}\n@@ -241,18 +221,14 @@ static void init_curl_http_auth(CURL *result)\n \n static int has_cert_password(void)\n {\n-\tif (ssl_cert_password != NULL)\n-\t\treturn 1;\n \tif (ssl_cert == NULL || ssl_cert_password_required != 1)\n \t\treturn 0;\n-\t/* Only prompt the user once. */\n-\tssl_cert_password_required = -1;\n-\tssl_cert_password = git_getpass_with_description(\"Certificate Password\", description);\n-\tif (ssl_cert_password != NULL) {\n-\t\tssl_cert_password = xstrdup(ssl_cert_password);\n-\t\treturn 1;\n-\t} else\n-\t\treturn 0;\n+\tif (!cert_auth.password) {\n+\t\tcert_auth.protocol = xstrdup(\"cert\");\n+\t\tcert_auth.path = xstrdup(ssl_cert);\n+\t\tcredential_fill(&cert_auth);\n+\t}\n+\treturn 1;\n }\n \n static CURL *get_curl_handle(void)\n@@ -279,7 +255,7 @@ static int has_cert_password(void)\n \tif (ssl_cert != NULL)\n \t\tcurl_easy_setopt(result, CURLOPT_SSLCERT, ssl_cert);\n \tif (has_cert_password())\n-\t\tcurl_easy_setopt(result, CURLOPT_KEYPASSWD, ssl_cert_password);\n+\t\tcurl_easy_setopt(result, CURLOPT_KEYPASSWD, cert_auth.password);\n #if LIBCURL_VERSION_NUM >= 0x070903\n \tif (ssl_key != NULL)\n \t\tcurl_easy_setopt(result, CURLOPT_SSLKEY, ssl_key);\n@@ -321,42 +297,6 @@ static int has_cert_password(void)\n \treturn result;\n }\n \n-static void http_auth_init(const char *url)\n-{\n-\tconst char *at, *colon, *cp, *slash, *host;\n-\n-\tcp = strstr(url, \"://\");\n-\tif (!cp)\n-\t\treturn;\n-\n-\t/*\n-\t * Ok, the URL looks like \"proto://something\".  Which one?\n-\t * \"proto://<user>:<pass>@<host>/...\",\n-\t * \"proto://<user>@<host>/...\", or just\n-\t * \"proto://<host>/...\"?\n-\t */\n-\tcp += 3;\n-\tat = strchr(cp, '@');\n-\tcolon = strchr(cp, ':');\n-\tslash = strchrnul(cp, '/');\n-\tif (!at || slash <= at) {\n-\t\t/* No credentials, but we may have to ask for some later */\n-\t\thost = cp;\n-\t}\n-\telse if (!colon || at <= colon) {\n-\t\t/* Only username */\n-\t\tuser_name = url_decode_mem(cp, at - cp);\n-\t\tuser_pass = NULL;\n-\t\thost = at + 1;\n-\t} else {\n-\t\tuser_name = url_decode_mem(cp, colon - cp);\n-\t\tuser_pass = url_decode_mem(colon + 1, at - (colon + 1));\n-\t\thost = at + 1;\n-\t}\n-\n-\tdescription = url_decode_mem(host, slash - host);\n-}\n-\n static void set_from_env(const char **var, const char *envname)\n {\n \tconst char *val = getenv(envname);\n@@ -429,7 +369,7 @@ void http_init(struct remote *remote, const char *url)\n \t\tcurl_ftp_no_epsv = 1;\n \n \tif (url) {\n-\t\thttp_auth_init(url);\n+\t\tcredential_from_url(&http_auth, url);\n \t\tif (!ssl_cert_password_required &&\n \t\t    getenv(\"GIT_SSL_CERT_PASSWORD_PROTECTED\") &&\n \t\t    !prefixcmp(url, \"https://\"))\n@@ -478,10 +418,10 @@ void http_cleanup(void)\n \t\tcurl_http_proxy = NULL;\n \t}\n \n-\tif (ssl_cert_password != NULL) {\n-\t\tmemset(ssl_cert_password, 0, strlen(ssl_cert_password));\n-\t\tfree(ssl_cert_password);\n-\t\tssl_cert_password = NULL;\n+\tif (cert_auth.password != NULL) {\n+\t\tmemset(cert_auth.password, 0, strlen(cert_auth.password));\n+\t\tfree(cert_auth.password);\n+\t\tcert_auth.password = NULL;\n \t}\n \tssl_cert_password_required = 0;\n }\n@@ -837,17 +777,11 @@ static int http_request(const char *url, void *result, int target, int options)\n \t\telse if (missing_target(&results))\n \t\t\tret = HTTP_MISSING_TARGET;\n \t\telse if (results.http_code == 401) {\n-\t\t\tif (user_name && user_pass) {\n+\t\t\tif (http_auth.username && http_auth.password) {\n+\t\t\t\tcredential_reject(&http_auth);\n \t\t\t\tret = HTTP_NOAUTH;\n \t\t\t} else {\n-\t\t\t\t/*\n-\t\t\t\t * git_getpass is needed here because its very likely stdin/stdout are\n-\t\t\t\t * pipes to our parent process.  So we instead need to use /dev/tty,\n-\t\t\t\t * but that is non-portable.  Using git_getpass() can at least be stubbed\n-\t\t\t\t * on other platforms with a different implementation if/when necessary.\n-\t\t\t\t */\n-\t\t\t\tif (!user_name)\n-\t\t\t\t\tuser_name = xstrdup(git_getpass_with_description(\"Username\", description));\n+\t\t\t\tcredential_fill(&http_auth);\n \t\t\t\tinit_curl_http_auth(slot->curl);\n \t\t\t\tret = HTTP_REAUTH;\n \t\t\t}\n@@ -866,6 +800,9 @@ static int http_request(const char *url, void *result, int target, int options)\n \tcurl_slist_free_all(headers);\n \tstrbuf_release(&buf);\n \n+\tif (ret == HTTP_OK)\n+\t\tcredential_approve(&http_auth);\n+\n \treturn ret;\n }\n \ndiff --git a/t/t5550-http-fetch.sh b/t/t5550-http-fetch.sh\nindex 3d6e871..398a2d2 100755\n--- a/t/t5550-http-fetch.sh\n+++ b/t/t5550-http-fetch.sh\n@@ -49,40 +49,56 @@ test_expect_success 'setup askpass helpers' '\n \tEOF\n \tchmod +x askpass &&\n \tGIT_ASKPASS=\"$PWD/askpass\" &&\n-\texport GIT_ASKPASS &&\n-\t>askpass-expect-none &&\n-\techo \"askpass: Password for '\\''$HTTPD_DEST'\\'': \" >askpass-expect-pass &&\n-\t{ echo \"askpass: Username for '\\''$HTTPD_DEST'\\'': \" &&\n-\t  cat askpass-expect-pass\n-\t} >askpass-expect-both\n-'\n+\texport GIT_ASKPASS\n+'\n+\n+expect_askpass() {\n+\tdest=$HTTPD_DEST/auth/repo.git\n+\t{\n+\t\tcase \"$1\" in\n+\t\tnone)\n+\t\t\t;;\n+\t\tpass)\n+\t\t\techo \"askpass: Password for 'http://$2@$dest': \"\n+\t\t\t;;\n+\t\tboth)\n+\t\t\techo \"askpass: Username for 'http://$dest': \"\n+\t\t\techo \"askpass: Password for 'http://$2@$dest': \"\n+\t\t\t;;\n+\t\t*)\n+\t\t\tfalse\n+\t\t\t;;\n+\t\tesac\n+\t} >askpass-expect &&\n+\ttest_cmp askpass-expect askpass-query\n+}\n \n test_expect_success 'cloning password-protected repository can fail' '\n \t>askpass-query &&\n \techo wrong >askpass-response &&\n \ttest_must_fail git clone \"$HTTPD_URL/auth/repo.git\" clone-auth-fail &&\n-\ttest_cmp askpass-expect-both askpass-query\n+\texpect_askpass both wrong\n '\n \n test_expect_success 'http auth can use user/pass in URL' '\n \t>askpass-query &&\n \techo wrong >askpass-response &&\n \tgit clone \"$HTTPD_URL_USER_PASS/auth/repo.git\" clone-auth-none &&\n-\ttest_cmp askpass-expect-none askpass-query\n+\texpect_askpass none\n '\n \n test_expect_success 'http auth can use just user in URL' '\n \t>askpass-query &&\n \techo user@host >askpass-response &&\n \tgit clone \"$HTTPD_URL_USER/auth/repo.git\" clone-auth-pass &&\n-\ttest_cmp askpass-expect-pass askpass-query\n+\texpect_askpass pass user@host\n '\n \n test_expect_success 'http auth can request both user and pass' '\n \t>askpass-query &&\n \techo user@host >askpass-response &&\n \tgit clone \"$HTTPD_URL/auth/repo.git\" clone-auth-both &&\n-\ttest_cmp askpass-expect-both askpass-query\n+\texpect_askpass both user@host\n '\n \n test_expect_success 'fetch changes via http' '\n-- \n1.7.8.rc2.40.gaf387\n"},{"id":"180757","messageId":"20111210103124.GF16529@sigill.intra.peff.net","threadId":"29128","inReplyTo":"20111210102827.GA16460@sigill.intra.peff.net","subject":"[PATCHv3 06/13] credential: apply helper config","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-12-10T10:31:24Z","receivedAt":"2011-12-10T10:31:24Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"The functionality for credential storage helpers is already\nthere; we just need to give the users a way to turn it on.\nThis patch provides a \"credential.helper\" configuration\nvariable which allows the user to provide one or more helper\nstrings.\n\nRather than simply matching credential.helper, we will also\ncompare URLs in subsection headings to the current context.\nThis means you can apply configuration to a subset of\ncredentials. For example:\n\n  [credential \"https://example.com\"]\n\thelper = foo\n\nwould match a request for \"https://example.com/foo.git\", but\nnot one for \"https://kernel.org/foo.git\".\n\nThis is overkill for the \"helper\" variable, since users are\nunlikely to want different helpers for different sites (and\nsince helpers run arbitrary code, they could do the matching\nthemselves anyway).\n\nHowever, future patches will add new config variables where\nthis extra feature will be more useful.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n credential.c           |   61 ++++++++++++++++++++++++++++++++++++++++++++++++\n credential.h           |    5 +++-\n t/t0300-credentials.sh |   42 +++++++++++++++++++++++++++++++++\n t/t5550-http-fetch.sh  |   12 +++++++++\n 4 files changed, 119 insertions(+), 1 deletions(-)\n\ndiff --git a/credential.c b/credential.c\nindex c349b9a..96be1c2 100644\n--- a/credential.c\n+++ b/credential.c\n@@ -22,6 +22,61 @@ void credential_clear(struct credential *c)\n \tcredential_init(c);\n }\n \n+int credential_match(const struct credential *want,\n+\t\t     const struct credential *have)\n+{\n+#define CHECK(x) (!want->x || (have->x && !strcmp(want->x, have->x)))\n+\treturn CHECK(protocol) &&\n+\t       CHECK(host) &&\n+\t       CHECK(path) &&\n+\t       CHECK(username);\n+#undef CHECK\n+}\n+\n+static int credential_config_callback(const char *var, const char *value,\n+\t\t\t\t      void *data)\n+{\n+\tstruct credential *c = data;\n+\tconst char *key, *dot;\n+\n+\tkey = skip_prefix(var, \"credential.\");\n+\tif (!key)\n+\t\treturn 0;\n+\n+\tif (!value)\n+\t\treturn config_error_nonbool(var);\n+\n+\tdot = strrchr(key, '.');\n+\tif (dot) {\n+\t\tstruct credential want = CREDENTIAL_INIT;\n+\t\tchar *url = xmemdupz(key, dot - key);\n+\t\tint matched;\n+\n+\t\tcredential_from_url(&want, url);\n+\t\tmatched = credential_match(&want, c);\n+\n+\t\tcredential_clear(&want);\n+\t\tfree(url);\n+\n+\t\tif (!matched)\n+\t\t\treturn 0;\n+\t\tkey = dot + 1;\n+\t}\n+\n+\tif (!strcmp(key, \"helper\"))\n+\t\tstring_list_append(&c->helpers, value);\n+\n+\treturn 0;\n+}\n+\n+static void credential_apply_config(struct credential *c)\n+{\n+\tif (c->configured)\n+\t\treturn;\n+\tgit_config(credential_config_callback, c);\n+\tc->configured = 1;\n+}\n+\n static void credential_describe(struct credential *c, struct strbuf *out)\n {\n \tif (!c->protocol)\n@@ -195,6 +250,8 @@ void credential_fill(struct credential *c)\n \tif (c->username && c->password)\n \t\treturn;\n \n+\tcredential_apply_config(c);\n+\n \tfor (i = 0; i < c->helpers.nr; i++) {\n \t\tcredential_do(c, c->helpers.items[i].string, \"get\");\n \t\tif (c->username && c->password)\n@@ -215,6 +272,8 @@ void credential_approve(struct credential *c)\n \tif (!c->username || !c->password)\n \t\treturn;\n \n+\tcredential_apply_config(c);\n+\n \tfor (i = 0; i < c->helpers.nr; i++)\n \t\tcredential_do(c, c->helpers.items[i].string, \"store\");\n \tc->approved = 1;\n@@ -224,6 +283,8 @@ void credential_reject(struct credential *c)\n {\n \tint i;\n \n+\tcredential_apply_config(c);\n+\n \tfor (i = 0; i < c->helpers.nr; i++)\n \t\tcredential_do(c, c->helpers.items[i].string, \"erase\");\n \ndiff --git a/credential.h b/credential.h\nindex 8a6d162..e504272 100644\n--- a/credential.h\n+++ b/credential.h\n@@ -5,7 +5,8 @@\n \n struct credential {\n \tstruct string_list helpers;\n-\tunsigned approved:1;\n+\tunsigned approved:1,\n+\t\t configured:1;\n \n \tchar *username;\n \tchar *password;\n@@ -25,5 +26,7 @@ struct credential {\n \n int credential_read(struct credential *, FILE *);\n void credential_from_url(struct credential *, const char *url);\n+int credential_match(const struct credential *have,\n+\t\t     const struct credential *want);\n \n #endif /* CREDENTIAL_H */\ndiff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh\nindex 81a455f..42d0f5b 100755\n--- a/t/t0300-credentials.sh\n+++ b/t/t0300-credentials.sh\n@@ -192,4 +192,46 @@ test_expect_success 'internal getpass does not ask for known username' '\n \tEOF\n '\n \n+HELPER=\"!f() {\n+\t\tcat >/dev/null\n+\t\techo username=foo\n+\t\techo password=bar\n+\t}; f\"\n+test_expect_success 'respect configured credentials' '\n+\ttest_config credential.helper \"$HELPER\" &&\n+\tcheck fill <<-\\EOF\n+\t--\n+\tusername=foo\n+\tpassword=bar\n+\t--\n+\tEOF\n+'\n+\n+test_expect_success 'match configured credential' '\n+\ttest_config credential.https://example.com.helper \"$HELPER\" &&\n+\tcheck fill <<-\\EOF\n+\tprotocol=https\n+\thost=example.com\n+\tpath=repo.git\n+\t--\n+\tusername=foo\n+\tpassword=bar\n+\t--\n+\tEOF\n+'\n+\n+test_expect_success 'do not match configured credential' '\n+\ttest_config credential.https://foo.helper \"$HELPER\" &&\n+\tcheck fill <<-\\EOF\n+\tprotocol=https\n+\thost=bar\n+\t--\n+\tusername=askpass-username\n+\tpassword=askpass-password\n+\t--\n+\taskpass: Username for '\\''https://bar'\\'':\n+\taskpass: Password for '\\''https://askpass-username@bar'\\'':\n+\tEOF\n+'\n+\n test_done\ndiff --git a/t/t5550-http-fetch.sh b/t/t5550-http-fetch.sh\nindex 398a2d2..c59908f 100755\n--- a/t/t5550-http-fetch.sh\n+++ b/t/t5550-http-fetch.sh\n@@ -101,6 +101,18 @@ test_expect_success 'http auth can request both user and pass' '\n \texpect_askpass both user@host\n '\n \n+test_expect_success 'http auth respects credential helper config' '\n+\ttest_config_global credential.helper \"!f() {\n+\t\tcat >/dev/null\n+\t\techo username=user@host\n+\t\techo password=user@host\n+\t}; f\" &&\n+\t>askpass-query &&\n+\techo wrong >askpass-response &&\n+\tgit clone \"$HTTPD_URL/auth/repo.git\" clone-auth-helper &&\n+\texpect_askpass none\n+'\n+\n test_expect_success 'fetch changes via http' '\n \techo content >>file &&\n \tgit commit -a -m two &&\n-- \n1.7.8.rc2.40.gaf387\n"},{"id":"180759","messageId":"20111210103129.GG16529@sigill.intra.peff.net","threadId":"29128","inReplyTo":"20111210102827.GA16460@sigill.intra.peff.net","subject":"[PATCHv3 07/13] credential: add credential.*.username","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-12-10T10:31:30Z","receivedAt":"2011-12-10T10:31:30Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"Credential helpers can help users avoid having to type their\nusername and password over and over. However, some users may\nnot want a helper for their password, or they may be running\na helper which caches for a short time. In this case, it is\nconvenient to provide the non-secret username portion of\ntheir credential via config.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n credential.c           |    4 ++++\n t/t0300-credentials.sh |   13 +++++++++++++\n t/t5550-http-fetch.sh  |   16 ++++++++++++++++\n 3 files changed, 33 insertions(+), 0 deletions(-)\n\ndiff --git a/credential.c b/credential.c\nindex 96be1c2..3c17ea1 100644\n--- a/credential.c\n+++ b/credential.c\n@@ -65,6 +65,10 @@ static int credential_config_callback(const char *var, const char *value,\n \n \tif (!strcmp(key, \"helper\"))\n \t\tstring_list_append(&c->helpers, value);\n+\telse if (!strcmp(key, \"username\")) {\n+\t\tif (!c->username)\n+\t\t\tc->username = xstrdup(value);\n+\t}\n \n \treturn 0;\n }\ndiff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh\nindex 42d0f5b..53e94bc 100755\n--- a/t/t0300-credentials.sh\n+++ b/t/t0300-credentials.sh\n@@ -234,4 +234,17 @@ test_expect_success 'do not match configured credential' '\n \tEOF\n '\n \n+test_expect_success 'pull username from config' '\n+\ttest_config credential.https://example.com.username foo &&\n+\tcheck fill <<-\\EOF\n+\tprotocol=https\n+\thost=example.com\n+\t--\n+\tusername=foo\n+\tpassword=askpass-password\n+\t--\n+\taskpass: Password for '\\''https://foo@example.com'\\'':\n+\tEOF\n+'\n+\n test_done\ndiff --git a/t/t5550-http-fetch.sh b/t/t5550-http-fetch.sh\nindex c59908f..3262f90 100755\n--- a/t/t5550-http-fetch.sh\n+++ b/t/t5550-http-fetch.sh\n@@ -113,6 +113,22 @@ test_expect_success 'http auth respects credential helper config' '\n \texpect_askpass none\n '\n \n+test_expect_success 'http auth can get username from config' '\n+\ttest_config_global \"credential.$HTTPD_URL.username\" user@host &&\n+\t>askpass-query &&\n+\techo user@host >askpass-response &&\n+\tgit clone \"$HTTPD_URL/auth/repo.git\" clone-auth-user &&\n+\texpect_askpass pass user@host\n+'\n+\n+test_expect_success 'configured username does not override URL' '\n+\ttest_config_global \"credential.$HTTPD_URL.username\" wrong &&\n+\t>askpass-query &&\n+\techo user@host >askpass-response &&\n+\tgit clone \"$HTTPD_URL_USER/auth/repo.git\" clone-auth-user2 &&\n+\texpect_askpass pass user@host\n+'\n+\n test_expect_success 'fetch changes via http' '\n \techo content >>file &&\n \tgit commit -a -m two &&\n-- \n1.7.8.rc2.40.gaf387\n"},{"id":"180760","messageId":"20111210103133.GH16529@sigill.intra.peff.net","threadId":"29128","inReplyTo":"20111210102827.GA16460@sigill.intra.peff.net","subject":"[PATCHv3 08/13] credential: make relevance of http path configurable","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-12-10T10:31:34Z","receivedAt":"2011-12-10T10:31:34Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"When parsing a URL into a credential struct, we carefully\nrecord each part of the URL, including the path on the\nremote host, and use the result as part of the credential\ncontext.\n\nThis had two practical implications:\n\n  1. Credential helpers which store a credential for later\n     access are likely to use the \"path\" portion as part of\n     the storage key. That means that a request to\n\n       https://example.com/foo.git\n\n     would not use the same credential that was stored in an\n     earlier request for:\n\n       https://example.com/bar.git\n\n  2. The prompt shown to the user includes all relevant\n     context, including the path.\n\nIn most cases, however, users will have a single password\nper host. The behavior in (1) will be inconvenient, and the\nprompt in (2) will be overly long.\n\nThis patch introduces a config option to toggle the\nrelevance of http paths. When turned on, we use the path as\nbefore. When turned off, we drop the path component from the\ncontext: helpers don't see it, and it does not appear in the\nprompt.\n\nThis is nothing you couldn't do with a clever credential\nhelper at the start of your stack, like:\n\n  [credential \"http://\"]\n\thelper = \"!f() { grep -v ^path= ; }; f\"\n\thelper = your_real_helper\n\nBut doing this:\n\n  [credential]\n\tuseHttpPath = false\n\nis way easier and more readable. Furthermore, since most\nusers will want the \"off\" behavior, that is the new default.\nUsers who want it \"on\" can set the variable (either for all\ncredentials, or just for a subset using\ncredential.*.useHttpPath).\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n credential.c           |   14 ++++++++++++++\n credential.h           |    3 ++-\n t/t0300-credentials.sh |   29 +++++++++++++++++++++++++++++\n t/t5550-http-fetch.sh  |    2 +-\n 4 files changed, 46 insertions(+), 2 deletions(-)\n\ndiff --git a/credential.c b/credential.c\nindex 3c17ea1..a17eafe 100644\n--- a/credential.c\n+++ b/credential.c\n@@ -69,16 +69,30 @@ static int credential_config_callback(const char *var, const char *value,\n \t\tif (!c->username)\n \t\t\tc->username = xstrdup(value);\n \t}\n+\telse if (!strcmp(key, \"usehttppath\"))\n+\t\tc->use_http_path = git_config_bool(var, value);\n \n \treturn 0;\n }\n \n+static int proto_is_http(const char *s)\n+{\n+\tif (!s)\n+\t\treturn 0;\n+\treturn !strcmp(s, \"https\") || !strcmp(s, \"http\");\n+}\n+\n static void credential_apply_config(struct credential *c)\n {\n \tif (c->configured)\n \t\treturn;\n \tgit_config(credential_config_callback, c);\n \tc->configured = 1;\n+\n+\tif (!c->use_http_path && proto_is_http(c->protocol)) {\n+\t\tfree(c->path);\n+\t\tc->path = NULL;\n+\t}\n }\n \n static void credential_describe(struct credential *c, struct strbuf *out)\ndiff --git a/credential.h b/credential.h\nindex e504272..96ea41b 100644\n--- a/credential.h\n+++ b/credential.h\n@@ -6,7 +6,8 @@\n struct credential {\n \tstruct string_list helpers;\n \tunsigned approved:1,\n-\t\t configured:1;\n+\t\t configured:1,\n+\t\t use_http_path:1;\n \n \tchar *username;\n \tchar *password;\ndiff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh\nindex 53e94bc..885af8f 100755\n--- a/t/t0300-credentials.sh\n+++ b/t/t0300-credentials.sh\n@@ -247,4 +247,33 @@ test_expect_success 'pull username from config' '\n \tEOF\n '\n \n+test_expect_success 'http paths can be part of context' '\n+\tcheck fill \"verbatim foo bar\" <<-\\EOF &&\n+\tprotocol=https\n+\thost=example.com\n+\tpath=foo.git\n+\t--\n+\tusername=foo\n+\tpassword=bar\n+\t--\n+\tverbatim: get\n+\tverbatim: protocol=https\n+\tverbatim: host=example.com\n+\tEOF\n+\ttest_config credential.https://example.com.useHttpPath true &&\n+\tcheck fill \"verbatim foo bar\" <<-\\EOF\n+\tprotocol=https\n+\thost=example.com\n+\tpath=foo.git\n+\t--\n+\tusername=foo\n+\tpassword=bar\n+\t--\n+\tverbatim: get\n+\tverbatim: protocol=https\n+\tverbatim: host=example.com\n+\tverbatim: path=foo.git\n+\tEOF\n+'\n+\n test_done\ndiff --git a/t/t5550-http-fetch.sh b/t/t5550-http-fetch.sh\nindex 3262f90..95a133d 100755\n--- a/t/t5550-http-fetch.sh\n+++ b/t/t5550-http-fetch.sh\n@@ -53,7 +53,7 @@ test_expect_success 'setup askpass helpers' '\n '\n \n expect_askpass() {\n-\tdest=$HTTPD_DEST/auth/repo.git\n+\tdest=$HTTPD_DEST\n \t{\n \t\tcase \"$1\" in\n \t\tnone)\n-- \n1.7.8.rc2.40.gaf387\n"},{"id":"180761","messageId":"20111210103137.GI16529@sigill.intra.peff.net","threadId":"29128","inReplyTo":"20111210102827.GA16460@sigill.intra.peff.net","subject":"[PATCHv3 09/13] docs: end-user documentation for the credential subsystem","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-12-10T10:31:38Z","receivedAt":"2011-12-10T10:31:38Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"The credential API and helper format is already defined in\ntechnical/api-credentials.txt.  This presents the end-user\nview.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n Documentation/Makefile           |    1 +\n Documentation/config.txt         |   23 +++++\n Documentation/gitcredentials.txt |  171 ++++++++++++++++++++++++++++++++++++++\n 3 files changed, 195 insertions(+), 0 deletions(-)\n create mode 100644 Documentation/gitcredentials.txt\n\ndiff --git a/Documentation/Makefile b/Documentation/Makefile\nindex 304b31e..116f175 100644\n--- a/Documentation/Makefile\n+++ b/Documentation/Makefile\n@@ -7,6 +7,7 @@ MAN5_TXT=gitattributes.txt gitignore.txt gitmodules.txt githooks.txt \\\n MAN7_TXT=gitcli.txt gittutorial.txt gittutorial-2.txt \\\n \tgitcvs-migration.txt gitcore-tutorial.txt gitglossary.txt \\\n \tgitdiffcore.txt gitnamespaces.txt gitrevisions.txt gitworkflows.txt\n+MAN7_TXT += gitcredentials.txt\n \n MAN_TXT = $(MAN1_TXT) $(MAN5_TXT) $(MAN7_TXT)\n MAN_XML=$(patsubst %.txt,%.xml,$(MAN_TXT))\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 8a7d2d4..c6630c7 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -834,6 +834,29 @@ commit.template::\n \t\"{tilde}/\" is expanded to the value of `$HOME` and \"{tilde}user/\" to the\n \tspecified user's home directory.\n \n+credential.helper::\n+\tSpecify an external helper to be called when a username or\n+\tpassword credential is needed; the helper may consult external\n+\tstorage to avoid prompting the user for the credentials. See\n+\tlinkgit:gitcredentials[7] for details.\n+\n+credential.useHttpPath::\n+\tWhen acquiring credentials, consider the \"path\" component of an http\n+\tor https URL to be important. Defaults to false. See\n+\tlinkgit:gitcredentials[7] for more information.\n+\n+credential.username::\n+\tIf no username is set for a network authentication, use this username\n+\tby default. See credential.<context>.* below, and\n+\tlinkgit:gitcredentials[7].\n+\n+credential.<url>.*::\n+\tAny of the credential.* options above can be applied selectively to\n+\tsome credentials. For example \"credential.https://example.com.username\"\n+\twould set the default username only for https connections to\n+\texample.com. See linkgit:gitcredentials[7] for details on how URLs are\n+\tmatched.\n+\n include::diff-config.txt[]\n \n difftool.<tool>.path::\ndiff --git a/Documentation/gitcredentials.txt b/Documentation/gitcredentials.txt\nnew file mode 100644\nindex 0000000..07f6596\n--- /dev/null\n+++ b/Documentation/gitcredentials.txt\n@@ -0,0 +1,171 @@\n+gitcredentials(7)\n+=================\n+\n+NAME\n+----\n+gitcredentials - providing usernames and passwords to git\n+\n+SYNOPSIS\n+--------\n+------------------\n+git config credential.https://example.com.username myusername\n+git config credential.helper \"$helper $options\"\n+------------------\n+\n+DESCRIPTION\n+-----------\n+\n+Git will sometimes need credentials from the user in order to perform\n+operations; for example, it may need to ask for a username and password\n+in order to access a remote repository over HTTP. This manual describes\n+the mechanisms git uses to request these credentials, as well as some\n+features to avoid inputting these credentials repeatedly.\n+\n+REQUESTING CREDENTIALS\n+----------------------\n+\n+Without any credential helpers defined, git will try the following\n+strategies to ask the user for usernames and passwords:\n+\n+1. If the `GIT_ASKPASS` environment variable is set, the program\n+   specified by the variable is invoked. A suitable prompt is provided\n+   to the program on the command line, and the user's input is read\n+   from its standard output.\n+\n+2. Otherwise, if the `core.askpass` configuration variable is set, its\n+   value is used as above.\n+\n+3. Otherwise, if the `SSH_ASKPASS` environment variable is set, its\n+   value is used as above.\n+\n+4. Otherwise, the user is prompted on the terminal.\n+\n+AVOIDING REPETITION\n+-------------------\n+\n+It can be cumbersome to input the same credentials over and over.  Git\n+provides two methods to reduce this annoyance:\n+\n+1. Static configuration of usernames for a given authentication context.\n+\n+2. Credential helpers to cache or store passwords, or to interact with\n+   a system password wallet or keychain.\n+\n+The first is simple and appropriate if you do not have secure storage available\n+for a password. It is generally configured by adding this to your config:\n+\n+---------------------------------------\n+[credential \"https://example.com\"]\n+\tusername = me\n+---------------------------------------\n+\n+Credential helpers, on the other hand, are external programs from which git can\n+request both usernames and passwords; they typically interface with secure\n+storage provided by the OS or other programs.\n+\n+To use a helper, you must first select one to use.  Git does not yet\n+include any credential helpers, but you may have third-party helpers\n+installed; search for `credential-*` in the output of `git help -a`, and\n+consult the documentation of individual helpers.  Once you have selected\n+a helper, you can tell git to use it by putting its name into the\n+credential.helper variable.\n+\n+1. Find a helper.\n++\n+-------------------------------------------\n+$ git help -a | grep credential-\n+credential-foo\n+-------------------------------------------\n+\n+2. Read its description.\n++\n+-------------------------------------------\n+$ git help credential-foo\n+-------------------------------------------\n+\n+3. Tell git to use it.\n++\n+-------------------------------------------\n+$ git config --global credential.helper foo\n+-------------------------------------------\n+\n+If there are multiple instances of the `credential.helper` configuration\n+variable, each helper will be tried in turn, and may provide a username,\n+password, or nothing. Once git has acquired both a username and a\n+password, no more helpers will be tried.\n+\n+\n+CREDENTIAL CONTEXTS\n+-------------------\n+\n+Git considers each credential to have a context defined by a URL. This context\n+is used to look up context-specific configuration, and is passed to any\n+helpers, which may use it as an index into secure storage.\n+\n+For instance, imagine we are accessing `https://example.com/foo.git`. When git\n+looks into a config file to see if a section matches this context, it will\n+consider the two a match if the context is a more-specific subset of the\n+pattern in the config file. For example, if you have this in your config file:\n+\n+--------------------------------------\n+[credential \"https://example.com\"]\n+\tusername = foo\n+--------------------------------------\n+\n+then we will match: both protocols are the same, both hosts are the same, and\n+the \"pattern\" URL does not care about the path component at all. However, this\n+context would not match:\n+\n+--------------------------------------\n+[credential \"https://kernel.org\"]\n+\tusername = foo\n+--------------------------------------\n+\n+because the hostnames differ. Nor would it match `foo.example.com`; git\n+compares hostnames exactly, without considering whether two hosts are part of\n+the same domain. Likewise, a config entry for `http://example.com` would not\n+match: git compares the protocols exactly.\n+\n+\n+CONFIGURATION OPTIONS\n+---------------------\n+\n+Options for a credential context can be configured either in\n+`credential.\\*` (which applies to all credentials), or\n+`credential.<url>.\\*`, where <url> matches the context as described\n+above.\n+\n+The following options are available in either location:\n+\n+helper::\n+\n+\tThe name of an external credential helper, and any associated options.\n+\tIf the helper name is not an absolute path, then the string `git\n+\tcredential-` is prepended. The resulting string is executed by the\n+\tshell (so, for example, setting this to `foo --option=bar` will execute\n+\t`git credential-foo --option=bar` via the shell. See the manual of\n+\tspecific helpers for examples of their use.\n+\n+username::\n+\n+\tA default username, if one is not provided in the URL.\n+\n+useHttpPath::\n+\n+\tBy default, git does not consider the \"path\" component of an http URL\n+\tto be worth matching via external helpers. This means that a credential\n+\tstored for `https://example.com/foo.git` will also be used for\n+\t`https://example.com/bar.git`. If you do want to distinguish these\n+\tcases, set this option to `true`.\n+\n+\n+CUSTOM HELPERS\n+--------------\n+\n+You can write your own custom helpers to interface with any system in\n+which you keep credentials. See the documentation for git's\n+link:technical/api-credentials.html[credentials API] for details.\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\n-- \n1.7.8.rc2.40.gaf387\n"},{"id":"180762","messageId":"20111210103407.GJ16529@sigill.intra.peff.net","threadId":"29128","inReplyTo":"20111210102827.GA16460@sigill.intra.peff.net","subject":"[PATCHv3 10/13] credentials: add \"cache\" helper","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-12-10T10:34:14Z","receivedAt":"2011-12-10T10:34:14Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"If you access repositories over smart-http using http\nauthentication, then it can be annoying to have git ask you\nfor your password repeatedly. We cache credentials in\nmemory, of course, but git is composed of many small\nprograms. Having to input your password for each one can be\nfrustrating.\n\nThis patch introduces a credential helper that will cache\npasswords in memory for a short period of time.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\nSince the previous round, the tests have been tweaked to remove the use\nof the \"ftp\" protocol. While this worked just fine for our cache and\nstore helpers, git doesn't actually generate protocol lines like that,\nand some helpers may not understand it (e.g., the OS X helper has to\nconvert the protocol strings into numeric constants, and doesn't bother\nconverting things that git doesn't generate). And we use the same\nbattery of tests for the internal helpers as for external ones.\n\n .gitignore                                     |    2 +\n Documentation/git-credential-cache--daemon.txt |   26 +++\n Documentation/git-credential-cache.txt         |   77 +++++++\n Documentation/gitcredentials.txt               |   17 +-\n Makefile                                       |    3 +\n credential-cache--daemon.c                     |  269 ++++++++++++++++++++++++\n credential-cache.c                             |  120 +++++++++++\n git-compat-util.h                              |    1 +\n t/lib-credential.sh                            |  221 +++++++++++++++++++\n t/t0301-credential-cache.sh                    |   18 ++\n unix-socket.c                                  |   56 +++++\n unix-socket.h                                  |    7 +\n 12 files changed, 812 insertions(+), 5 deletions(-)\n create mode 100644 Documentation/git-credential-cache--daemon.txt\n create mode 100644 Documentation/git-credential-cache.txt\n create mode 100644 credential-cache--daemon.c\n create mode 100644 credential-cache.c\n create mode 100755 t/t0301-credential-cache.sh\n create mode 100644 unix-socket.c\n create mode 100644 unix-socket.h\n\ndiff --git a/.gitignore b/.gitignore\nindex 7d2fefc..a6b0bd4 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -30,6 +30,8 @@\n /git-commit-tree\n /git-config\n /git-count-objects\n+/git-credential-cache\n+/git-credential-cache--daemon\n /git-cvsexportcommit\n /git-cvsimport\n /git-cvsserver\ndiff --git a/Documentation/git-credential-cache--daemon.txt b/Documentation/git-credential-cache--daemon.txt\nnew file mode 100644\nindex 0000000..11edc5a\n--- /dev/null\n+++ b/Documentation/git-credential-cache--daemon.txt\n@@ -0,0 +1,26 @@\n+git-credential-cache--daemon(1)\n+===============================\n+\n+NAME\n+----\n+git-credential-cache--daemon - temporarily store user credentials in memory\n+\n+SYNOPSIS\n+--------\n+[verse]\n+git credential-cache--daemon <socket>\n+\n+DESCRIPTION\n+-----------\n+\n+NOTE: You probably don't want to invoke this command yourself; it is\n+started automatically when you use linkgit:git-credential-cache[1].\n+\n+This command listens on the Unix domain socket specified by `<socket>`\n+for `git-credential-cache` clients. Clients may store and retrieve\n+credentials. Each credential is held for a timeout specified by the\n+client; once no credentials are held, the daemon exits.\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\ndiff --git a/Documentation/git-credential-cache.txt b/Documentation/git-credential-cache.txt\nnew file mode 100644\nindex 0000000..f3d09c5\n--- /dev/null\n+++ b/Documentation/git-credential-cache.txt\n@@ -0,0 +1,77 @@\n+git-credential-cache(1)\n+=======================\n+\n+NAME\n+----\n+git-credential-cache - helper to temporarily store passwords in memory\n+\n+SYNOPSIS\n+--------\n+-----------------------------\n+git config credential.helper 'cache [options]'\n+-----------------------------\n+\n+DESCRIPTION\n+-----------\n+\n+This command caches credentials in memory for use by future git\n+programs. The stored credentials never touch the disk, and are forgotten\n+after a configurable timeout.  The cache is accessible over a Unix\n+domain socket, restricted to the current user by filesystem permissions.\n+\n+You probably don't want to invoke this command directly; it is meant to\n+be used as a credential helper by other parts of git. See\n+linkgit:gitcredentials[7] or `EXAMPLES` below.\n+\n+OPTIONS\n+-------\n+\n+--timeout <seconds>::\n+\n+\tNumber of seconds to cache credentials (default: 900).\n+\n+--socket <path>::\n+\n+\tUse `<path>` to contact a running cache daemon (or start a new\n+\tcache daemon if one is not started). Defaults to\n+\t`~/.git-credential-cache/socket`. If your home directory is on a\n+\tnetwork-mounted filesystem, you may need to change this to a\n+\tlocal filesystem.\n+\n+CONTROLLING THE DAEMON\n+----------------------\n+\n+If you would like the daemon to exit early, forgetting all cached\n+credentials before their timeout, you can issue an `exit` action:\n+\n+--------------------------------------\n+git credential-cache exit\n+--------------------------------------\n+\n+EXAMPLES\n+--------\n+\n+The point of this helper is to reduce the number of times you must type\n+your username or password. For example:\n+\n+------------------------------------\n+$ git config credential.helper cache\n+$ git push http://example.com/repo.git\n+Username: <type your username>\n+Password: <type your password>\n+\n+[work for 5 more minutes]\n+$ git push http://example.com/repo.git\n+[your credentials are used automatically]\n+------------------------------------\n+\n+You can provide options via the credential.helper configuration\n+variable (this example drops the cache time to 5 minutes):\n+\n+-------------------------------------------------------\n+$ git config credential.helper 'cache --timeout=300'\n+-------------------------------------------------------\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\ndiff --git a/Documentation/gitcredentials.txt b/Documentation/gitcredentials.txt\nindex 07f6596..4e3f860 100644\n--- a/Documentation/gitcredentials.txt\n+++ b/Documentation/gitcredentials.txt\n@@ -63,11 +63,18 @@ Credential helpers, on the other hand, are external programs from which git can\n request both usernames and passwords; they typically interface with secure\n storage provided by the OS or other programs.\n \n-To use a helper, you must first select one to use.  Git does not yet\n-include any credential helpers, but you may have third-party helpers\n-installed; search for `credential-*` in the output of `git help -a`, and\n-consult the documentation of individual helpers.  Once you have selected\n-a helper, you can tell git to use it by putting its name into the\n+To use a helper, you must first select one to use. Git currently\n+includes the following helpers:\n+\n+cache::\n+\n+\tCache credentials in memory for a short period of time. See\n+\tlinkgit:git-credential-cache[1] for details.\n+\n+You may also have third-party helpers installed; search for\n+`credential-*` in the output of `git help -a`, and consult the\n+documentation of individual helpers.  Once you have selected a helper,\n+you can tell git to use it by putting its name into the\n credential.helper variable.\n \n 1. Find a helper.\ndiff --git a/Makefile b/Makefile\nindex d69f1dd..7ad2fe3 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -427,6 +427,8 @@ PROGRAM_OBJS += show-index.o\n PROGRAM_OBJS += upload-pack.o\n PROGRAM_OBJS += http-backend.o\n PROGRAM_OBJS += sh-i18n--envsubst.o\n+PROGRAM_OBJS += credential-cache.o\n+PROGRAM_OBJS += credential-cache--daemon.o\n \n PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS))\n \n@@ -702,6 +704,7 @@ LIB_OBJS += transport-helper.o\n LIB_OBJS += tree-diff.o\n LIB_OBJS += tree.o\n LIB_OBJS += tree-walk.o\n+LIB_OBJS += unix-socket.o\n LIB_OBJS += unpack-trees.o\n LIB_OBJS += url.o\n LIB_OBJS += usage.o\ndiff --git a/credential-cache--daemon.c b/credential-cache--daemon.c\nnew file mode 100644\nindex 0000000..390f194\n--- /dev/null\n+++ b/credential-cache--daemon.c\n@@ -0,0 +1,269 @@\n+#include \"cache.h\"\n+#include \"credential.h\"\n+#include \"unix-socket.h\"\n+#include \"sigchain.h\"\n+\n+static const char *socket_path;\n+\n+static void cleanup_socket(void)\n+{\n+\tif (socket_path)\n+\t\tunlink(socket_path);\n+}\n+\n+static void cleanup_socket_on_signal(int sig)\n+{\n+\tcleanup_socket();\n+\tsigchain_pop(sig);\n+\traise(sig);\n+}\n+\n+struct credential_cache_entry {\n+\tstruct credential item;\n+\tunsigned long expiration;\n+};\n+static struct credential_cache_entry *entries;\n+static int entries_nr;\n+static int entries_alloc;\n+\n+static void cache_credential(struct credential *c, int timeout)\n+{\n+\tstruct credential_cache_entry *e;\n+\n+\tALLOC_GROW(entries, entries_nr + 1, entries_alloc);\n+\te = &entries[entries_nr++];\n+\n+\t/* take ownership of pointers */\n+\tmemcpy(&e->item, c, sizeof(*c));\n+\tmemset(c, 0, sizeof(*c));\n+\te->expiration = time(NULL) + timeout;\n+}\n+\n+static struct credential_cache_entry *lookup_credential(const struct credential *c)\n+{\n+\tint i;\n+\tfor (i = 0; i < entries_nr; i++) {\n+\t\tstruct credential *e = &entries[i].item;\n+\t\tif (credential_match(c, e))\n+\t\t\treturn &entries[i];\n+\t}\n+\treturn NULL;\n+}\n+\n+static void remove_credential(const struct credential *c)\n+{\n+\tstruct credential_cache_entry *e;\n+\n+\te = lookup_credential(c);\n+\tif (e)\n+\t\te->expiration = 0;\n+}\n+\n+static int check_expirations(void)\n+{\n+\tstatic unsigned long wait_for_entry_until;\n+\tint i = 0;\n+\tunsigned long now = time(NULL);\n+\tunsigned long next = (unsigned long)-1;\n+\n+\t/*\n+\t * Initially give the client 30 seconds to actually contact us\n+\t * and store a credential before we decide there's no point in\n+\t * keeping the daemon around.\n+\t */\n+\tif (!wait_for_entry_until)\n+\t\twait_for_entry_until = now + 30;\n+\n+\twhile (i < entries_nr) {\n+\t\tif (entries[i].expiration <= now) {\n+\t\t\tentries_nr--;\n+\t\t\tcredential_clear(&entries[i].item);\n+\t\t\tif (i != entries_nr)\n+\t\t\t\tmemcpy(&entries[i], &entries[entries_nr], sizeof(*entries));\n+\t\t\t/*\n+\t\t\t * Stick around 30 seconds in case a new credential\n+\t\t\t * shows up (e.g., because we just removed a failed\n+\t\t\t * one, and we will soon get the correct one).\n+\t\t\t */\n+\t\t\twait_for_entry_until = now + 30;\n+\t\t}\n+\t\telse {\n+\t\t\tif (entries[i].expiration < next)\n+\t\t\t\tnext = entries[i].expiration;\n+\t\t\ti++;\n+\t\t}\n+\t}\n+\n+\tif (!entries_nr) {\n+\t\tif (wait_for_entry_until <= now)\n+\t\t\treturn 0;\n+\t\tnext = wait_for_entry_until;\n+\t}\n+\n+\treturn next - now;\n+}\n+\n+static int read_request(FILE *fh, struct credential *c,\n+\t\t\tstruct strbuf *action, int *timeout) {\n+\tstatic struct strbuf item = STRBUF_INIT;\n+\tconst char *p;\n+\n+\tstrbuf_getline(&item, fh, '\\n');\n+\tp = skip_prefix(item.buf, \"action=\");\n+\tif (!p)\n+\t\treturn error(\"client sent bogus action line: %s\", item.buf);\n+\tstrbuf_addstr(action, p);\n+\n+\tstrbuf_getline(&item, fh, '\\n');\n+\tp = skip_prefix(item.buf, \"timeout=\");\n+\tif (!p)\n+\t\treturn error(\"client sent bogus timeout line: %s\", item.buf);\n+\t*timeout = atoi(p);\n+\n+\tif (credential_read(c, fh) < 0)\n+\t\treturn -1;\n+\treturn 0;\n+}\n+\n+static void serve_one_client(FILE *in, FILE *out)\n+{\n+\tstruct credential c = CREDENTIAL_INIT;\n+\tstruct strbuf action = STRBUF_INIT;\n+\tint timeout = -1;\n+\n+\tif (read_request(in, &c, &action, &timeout) < 0)\n+\t\t/* ignore error */ ;\n+\telse if (!strcmp(action.buf, \"get\")) {\n+\t\tstruct credential_cache_entry *e = lookup_credential(&c);\n+\t\tif (e) {\n+\t\t\tfprintf(out, \"username=%s\\n\", e->item.username);\n+\t\t\tfprintf(out, \"password=%s\\n\", e->item.password);\n+\t\t}\n+\t}\n+\telse if (!strcmp(action.buf, \"exit\"))\n+\t\texit(0);\n+\telse if (!strcmp(action.buf, \"erase\"))\n+\t\tremove_credential(&c);\n+\telse if (!strcmp(action.buf, \"store\")) {\n+\t\tif (timeout < 0)\n+\t\t\twarning(\"cache client didn't specify a timeout\");\n+\t\telse if (!c.username || !c.password)\n+\t\t\twarning(\"cache client gave us a partial credential\");\n+\t\telse {\n+\t\t\tremove_credential(&c);\n+\t\t\tcache_credential(&c, timeout);\n+\t\t}\n+\t}\n+\telse\n+\t\twarning(\"cache client sent unknown action: %s\", action.buf);\n+\n+\tcredential_clear(&c);\n+\tstrbuf_release(&action);\n+}\n+\n+static int serve_cache_loop(int fd)\n+{\n+\tstruct pollfd pfd;\n+\tunsigned long wakeup;\n+\n+\twakeup = check_expirations();\n+\tif (!wakeup)\n+\t\treturn 0;\n+\n+\tpfd.fd = fd;\n+\tpfd.events = POLLIN;\n+\tif (poll(&pfd, 1, 1000 * wakeup) < 0) {\n+\t\tif (errno != EINTR)\n+\t\t\tdie_errno(\"poll failed\");\n+\t\treturn 1;\n+\t}\n+\n+\tif (pfd.revents & POLLIN) {\n+\t\tint client, client2;\n+\t\tFILE *in, *out;\n+\n+\t\tclient = accept(fd, NULL, NULL);\n+\t\tif (client < 0) {\n+\t\t\twarning(\"accept failed: %s\", strerror(errno));\n+\t\t\treturn 1;\n+\t\t}\n+\t\tclient2 = dup(client);\n+\t\tif (client2 < 0) {\n+\t\t\twarning(\"dup failed: %s\", strerror(errno));\n+\t\t\tclose(client);\n+\t\t\treturn 1;\n+\t\t}\n+\n+\t\tin = xfdopen(client, \"r\");\n+\t\tout = xfdopen(client2, \"w\");\n+\t\tserve_one_client(in, out);\n+\t\tfclose(in);\n+\t\tfclose(out);\n+\t}\n+\treturn 1;\n+}\n+\n+static void serve_cache(const char *socket_path)\n+{\n+\tint fd;\n+\n+\tfd = unix_stream_listen(socket_path);\n+\tif (fd < 0)\n+\t\tdie_errno(\"unable to bind to '%s'\", socket_path);\n+\n+\tprintf(\"ok\\n\");\n+\tfclose(stdout);\n+\n+\twhile (serve_cache_loop(fd))\n+\t\t; /* nothing */\n+\n+\tclose(fd);\n+\tunlink(socket_path);\n+}\n+\n+static const char permissions_advice[] =\n+\"The permissions on your socket directory are too loose; other\\n\"\n+\"users may be able to read your cached credentials. Consider running:\\n\"\n+\"\\n\"\n+\"\tchmod 0700 %s\";\n+static void check_socket_directory(const char *path)\n+{\n+\tstruct stat st;\n+\tchar *path_copy = xstrdup(path);\n+\tchar *dir = dirname(path_copy);\n+\n+\tif (!stat(dir, &st)) {\n+\t\tif (st.st_mode & 077)\n+\t\t\tdie(permissions_advice, dir);\n+\t\tfree(path_copy);\n+\t\treturn;\n+\t}\n+\n+\t/*\n+\t * We must be sure to create the directory with the correct mode,\n+\t * not just chmod it after the fact; otherwise, there is a race\n+\t * condition in which somebody can chdir to it, sleep, then try to open\n+\t * our protected socket.\n+\t */\n+\tif (safe_create_leading_directories_const(dir) < 0)\n+\t\tdie_errno(\"unable to create directories for '%s'\", dir);\n+\tif (mkdir(dir, 0700) < 0)\n+\t\tdie_errno(\"unable to mkdir '%s'\", dir);\n+\tfree(path_copy);\n+}\n+\n+int main(int argc, const char **argv)\n+{\n+\tsocket_path = argv[1];\n+\n+\tif (!socket_path)\n+\t\tdie(\"usage: git-credential-cache--daemon <socket_path>\");\n+\tcheck_socket_directory(socket_path);\n+\n+\tatexit(cleanup_socket);\n+\tsigchain_push_common(cleanup_socket_on_signal);\n+\n+\tserve_cache(socket_path);\n+\n+\treturn 0;\n+}\ndiff --git a/credential-cache.c b/credential-cache.c\nnew file mode 100644\nindex 0000000..dc98372\n--- /dev/null\n+++ b/credential-cache.c\n@@ -0,0 +1,120 @@\n+#include \"cache.h\"\n+#include \"credential.h\"\n+#include \"string-list.h\"\n+#include \"parse-options.h\"\n+#include \"unix-socket.h\"\n+#include \"run-command.h\"\n+\n+#define FLAG_SPAWN 0x1\n+#define FLAG_RELAY 0x2\n+\n+static int send_request(const char *socket, const struct strbuf *out)\n+{\n+\tint got_data = 0;\n+\tint fd = unix_stream_connect(socket);\n+\n+\tif (fd < 0)\n+\t\treturn -1;\n+\n+\tif (write_in_full(fd, out->buf, out->len) < 0)\n+\t\tdie_errno(\"unable to write to cache daemon\");\n+\tshutdown(fd, SHUT_WR);\n+\n+\twhile (1) {\n+\t\tchar in[1024];\n+\t\tint r;\n+\n+\t\tr = read_in_full(fd, in, sizeof(in));\n+\t\tif (r == 0)\n+\t\t\tbreak;\n+\t\tif (r < 0)\n+\t\t\tdie_errno(\"read error from cache daemon\");\n+\t\twrite_or_die(1, in, r);\n+\t\tgot_data = 1;\n+\t}\n+\treturn got_data;\n+}\n+\n+static void spawn_daemon(const char *socket)\n+{\n+\tstruct child_process daemon;\n+\tconst char *argv[] = { NULL, NULL, NULL };\n+\tchar buf[128];\n+\tint r;\n+\n+\tmemset(&daemon, 0, sizeof(daemon));\n+\targv[0] = \"git-credential-cache--daemon\";\n+\targv[1] = socket;\n+\tdaemon.argv = argv;\n+\tdaemon.no_stdin = 1;\n+\tdaemon.out = -1;\n+\n+\tif (start_command(&daemon))\n+\t\tdie_errno(\"unable to start cache daemon\");\n+\tr = read_in_full(daemon.out, buf, sizeof(buf));\n+\tif (r < 0)\n+\t\tdie_errno(\"unable to read result code from cache daemon\");\n+\tif (r != 3 || memcmp(buf, \"ok\\n\", 3))\n+\t\tdie(\"cache daemon did not start: %.*s\", r, buf);\n+\tclose(daemon.out);\n+}\n+\n+static void do_cache(const char *socket, const char *action, int timeout,\n+\t\t     int flags)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf, \"action=%s\\n\", action);\n+\tstrbuf_addf(&buf, \"timeout=%d\\n\", timeout);\n+\tif (flags & FLAG_RELAY) {\n+\t\tif (strbuf_read(&buf, 0, 0) < 0)\n+\t\t\tdie_errno(\"unable to relay credential\");\n+\t}\n+\n+\tif (!send_request(socket, &buf))\n+\t\treturn;\n+\tif (flags & FLAG_SPAWN) {\n+\t\tspawn_daemon(socket);\n+\t\tsend_request(socket, &buf);\n+\t}\n+\tstrbuf_release(&buf);\n+}\n+\n+int main(int argc, const char **argv)\n+{\n+\tchar *socket_path = NULL;\n+\tint timeout = 900;\n+\tconst char *op;\n+\tconst char * const usage[] = {\n+\t\t\"git credential-cache [options] <action>\",\n+\t\tNULL\n+\t};\n+\tstruct option options[] = {\n+\t\tOPT_INTEGER(0, \"timeout\", &timeout,\n+\t\t\t    \"number of seconds to cache credentials\"),\n+\t\tOPT_STRING(0, \"socket\", &socket_path, \"path\",\n+\t\t\t   \"path of cache-daemon socket\"),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, NULL, options, usage, 0);\n+\tif (!argc)\n+\t\tusage_with_options(usage, options);\n+\top = argv[0];\n+\n+\tif (!socket_path)\n+\t\tsocket_path = expand_user_path(\"~/.git-credential-cache/socket\");\n+\tif (!socket_path)\n+\t\tdie(\"unable to find a suitable socket path; use --socket\");\n+\n+\tif (!strcmp(op, \"exit\"))\n+\t\tdo_cache(socket_path, op, timeout, 0);\n+\telse if (!strcmp(op, \"get\") || !strcmp(op, \"erase\"))\n+\t\tdo_cache(socket_path, op, timeout, FLAG_RELAY);\n+\telse if (!strcmp(op, \"store\"))\n+\t\tdo_cache(socket_path, op, timeout, FLAG_RELAY|FLAG_SPAWN);\n+\telse\n+\t\t; /* ignore unknown operation */\n+\n+\treturn 0;\n+}\ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex 77062ed..8f3972c 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -135,6 +135,7 @@\n #include <arpa/inet.h>\n #include <netdb.h>\n #include <pwd.h>\n+#include <sys/un.h>\n #ifndef NO_INTTYPES_H\n #include <inttypes.h>\n #else\ndiff --git a/t/lib-credential.sh b/t/lib-credential.sh\nindex 54ae1f4..4a37cd7 100755\n--- a/t/lib-credential.sh\n+++ b/t/lib-credential.sh\n@@ -21,6 +21,227 @@ read_chunk() {\n \tdone\n }\n \n+# Clear any residual data from previous tests. We only\n+# need this when testing third-party helpers which read and\n+# write outside of our trash-directory sandbox.\n+#\n+# Don't bother checking for success here, as it is\n+# outside the scope of tests and represents a best effort to\n+# clean up after ourselves.\n+helper_test_clean() {\n+\treject $1 https example.com store-user\n+\treject $1 https example.com user1\n+\treject $1 https example.com user2\n+\treject $1 http path.tld user\n+\treject $1 https timeout.tld user\n+}\n+\n+reject() {\n+\t(\n+\t\techo protocol=$2\n+\t\techo host=$3\n+\t\techo username=$4\n+\t) | test-credential reject $1\n+}\n+\n+helper_test() {\n+\tHELPER=$1\n+\n+\ttest_expect_success \"helper ($HELPER) has no existing data\" '\n+\t\tcheck fill $HELPER <<-\\EOF\n+\t\tprotocol=https\n+\t\thost=example.com\n+\t\t--\n+\t\tusername=askpass-username\n+\t\tpassword=askpass-password\n+\t\t--\n+\t\taskpass: Username for '\\''https://example.com'\\'':\n+\t\taskpass: Password for '\\''https://askpass-username@example.com'\\'':\n+\t\tEOF\n+\t'\n+\n+\ttest_expect_success \"helper ($HELPER) stores password\" '\n+\t\tcheck approve $HELPER <<-\\EOF\n+\t\tprotocol=https\n+\t\thost=example.com\n+\t\tusername=store-user\n+\t\tpassword=store-pass\n+\t\tEOF\n+\t'\n+\n+\ttest_expect_success \"helper ($HELPER) can retrieve password\" '\n+\t\tcheck fill $HELPER <<-\\EOF\n+\t\tprotocol=https\n+\t\thost=example.com\n+\t\t--\n+\t\tusername=store-user\n+\t\tpassword=store-pass\n+\t\t--\n+\t\tEOF\n+\t'\n+\n+\ttest_expect_success \"helper ($HELPER) requires matching protocol\" '\n+\t\tcheck fill $HELPER <<-\\EOF\n+\t\tprotocol=http\n+\t\thost=example.com\n+\t\t--\n+\t\tusername=askpass-username\n+\t\tpassword=askpass-password\n+\t\t--\n+\t\taskpass: Username for '\\''http://example.com'\\'':\n+\t\taskpass: Password for '\\''http://askpass-username@example.com'\\'':\n+\t\tEOF\n+\t'\n+\n+\ttest_expect_success \"helper ($HELPER) requires matching host\" '\n+\t\tcheck fill $HELPER <<-\\EOF\n+\t\tprotocol=https\n+\t\thost=other.tld\n+\t\t--\n+\t\tusername=askpass-username\n+\t\tpassword=askpass-password\n+\t\t--\n+\t\taskpass: Username for '\\''https://other.tld'\\'':\n+\t\taskpass: Password for '\\''https://askpass-username@other.tld'\\'':\n+\t\tEOF\n+\t'\n+\n+\ttest_expect_success \"helper ($HELPER) requires matching username\" '\n+\t\tcheck fill $HELPER <<-\\EOF\n+\t\tprotocol=https\n+\t\thost=example.com\n+\t\tusername=other\n+\t\t--\n+\t\tusername=other\n+\t\tpassword=askpass-password\n+\t\t--\n+\t\taskpass: Password for '\\''https://other@example.com'\\'':\n+\t\tEOF\n+\t'\n+\n+\ttest_expect_success \"helper ($HELPER) requires matching path\" '\n+\t\ttest_config credential.usehttppath true &&\n+\t\tcheck approve $HELPER <<-\\EOF &&\n+\t\tprotocol=http\n+\t\thost=path.tld\n+\t\tpath=foo.git\n+\t\tusername=user\n+\t\tpassword=pass\n+\t\tEOF\n+\t\tcheck fill $HELPER <<-\\EOF\n+\t\tprotocol=http\n+\t\thost=path.tld\n+\t\tpath=bar.git\n+\t\t--\n+\t\tusername=askpass-username\n+\t\tpassword=askpass-password\n+\t\t--\n+\t\taskpass: Username for '\\''http://path.tld/bar.git'\\'':\n+\t\taskpass: Password for '\\''http://askpass-username@path.tld/bar.git'\\'':\n+\t\tEOF\n+\t'\n+\n+\ttest_expect_success \"helper ($HELPER) can forget host\" '\n+\t\tcheck reject $HELPER <<-\\EOF &&\n+\t\tprotocol=https\n+\t\thost=example.com\n+\t\tEOF\n+\t\tcheck fill $HELPER <<-\\EOF\n+\t\tprotocol=https\n+\t\thost=example.com\n+\t\t--\n+\t\tusername=askpass-username\n+\t\tpassword=askpass-password\n+\t\t--\n+\t\taskpass: Username for '\\''https://example.com'\\'':\n+\t\taskpass: Password for '\\''https://askpass-username@example.com'\\'':\n+\t\tEOF\n+\t'\n+\n+\ttest_expect_success \"helper ($HELPER) can store multiple users\" '\n+\t\tcheck approve $HELPER <<-\\EOF &&\n+\t\tprotocol=https\n+\t\thost=example.com\n+\t\tusername=user1\n+\t\tpassword=pass1\n+\t\tEOF\n+\t\tcheck approve $HELPER <<-\\EOF &&\n+\t\tprotocol=https\n+\t\thost=example.com\n+\t\tusername=user2\n+\t\tpassword=pass2\n+\t\tEOF\n+\t\tcheck fill $HELPER <<-\\EOF &&\n+\t\tprotocol=https\n+\t\thost=example.com\n+\t\tusername=user1\n+\t\t--\n+\t\tusername=user1\n+\t\tpassword=pass1\n+\t\tEOF\n+\t\tcheck fill $HELPER <<-\\EOF\n+\t\tprotocol=https\n+\t\thost=example.com\n+\t\tusername=user2\n+\t\t--\n+\t\tusername=user2\n+\t\tpassword=pass2\n+\t\tEOF\n+\t'\n+\n+\ttest_expect_success \"helper ($HELPER) can forget user\" '\n+\t\tcheck reject $HELPER <<-\\EOF &&\n+\t\tprotocol=https\n+\t\thost=example.com\n+\t\tusername=user1\n+\t\tEOF\n+\t\tcheck fill $HELPER <<-\\EOF\n+\t\tprotocol=https\n+\t\thost=example.com\n+\t\tusername=user1\n+\t\t--\n+\t\tusername=user1\n+\t\tpassword=askpass-password\n+\t\t--\n+\t\taskpass: Password for '\\''https://user1@example.com'\\'':\n+\t\tEOF\n+\t'\n+\n+\ttest_expect_success \"helper ($HELPER) remembers other user\" '\n+\t\tcheck fill $HELPER <<-\\EOF\n+\t\tprotocol=https\n+\t\thost=example.com\n+\t\tusername=user2\n+\t\t--\n+\t\tusername=user2\n+\t\tpassword=pass2\n+\t\tEOF\n+\t'\n+}\n+\n+helper_test_timeout() {\n+\tHELPER=\"$*\"\n+\n+\ttest_expect_success \"helper ($HELPER) times out\" '\n+\t\tcheck approve \"$HELPER\" <<-\\EOF &&\n+\t\tprotocol=https\n+\t\thost=timeout.tld\n+\t\tusername=user\n+\t\tpassword=pass\n+\t\tEOF\n+\t\tsleep 2 &&\n+\t\tcheck fill \"$HELPER\" <<-\\EOF\n+\t\tprotocol=https\n+\t\thost=timeout.tld\n+\t\t--\n+\t\tusername=askpass-username\n+\t\tpassword=askpass-password\n+\t\t--\n+\t\taskpass: Username for '\\''https://timeout.tld'\\'':\n+\t\taskpass: Password for '\\''https://askpass-username@timeout.tld'\\'':\n+\t\tEOF\n+\t'\n+}\n \n cat >askpass <<\\EOF\n #!/bin/sh\ndiff --git a/t/t0301-credential-cache.sh b/t/t0301-credential-cache.sh\nnew file mode 100755\nindex 0000000..3a65f99\n--- /dev/null\n+++ b/t/t0301-credential-cache.sh\n@@ -0,0 +1,18 @@\n+#!/bin/sh\n+\n+test_description='credential-cache tests'\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-credential.sh\n+\n+# don't leave a stale daemon running\n+trap 'code=$?; git credential-cache exit; (exit $code); die' EXIT\n+\n+helper_test cache\n+helper_test_timeout cache --timeout=1\n+\n+# we can't rely on our \"trap\" above working after test_done,\n+# as test_done will delete the trash directory containing\n+# our socket, leaving us with no way to access the daemon.\n+git credential-cache exit\n+\n+test_done\ndiff --git a/unix-socket.c b/unix-socket.c\nnew file mode 100644\nindex 0000000..84b1509\n--- /dev/null\n+++ b/unix-socket.c\n@@ -0,0 +1,56 @@\n+#include \"cache.h\"\n+#include \"unix-socket.h\"\n+\n+static int unix_stream_socket(void)\n+{\n+\tint fd = socket(AF_UNIX, SOCK_STREAM, 0);\n+\tif (fd < 0)\n+\t\tdie_errno(\"unable to create socket\");\n+\treturn fd;\n+}\n+\n+static void unix_sockaddr_init(struct sockaddr_un *sa, const char *path)\n+{\n+\tint size = strlen(path) + 1;\n+\tif (size > sizeof(sa->sun_path))\n+\t\tdie(\"socket path is too long to fit in sockaddr\");\n+\tmemset(sa, 0, sizeof(*sa));\n+\tsa->sun_family = AF_UNIX;\n+\tmemcpy(sa->sun_path, path, size);\n+}\n+\n+int unix_stream_connect(const char *path)\n+{\n+\tint fd;\n+\tstruct sockaddr_un sa;\n+\n+\tunix_sockaddr_init(&sa, path);\n+\tfd = unix_stream_socket();\n+\tif (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {\n+\t\tclose(fd);\n+\t\treturn -1;\n+\t}\n+\treturn fd;\n+}\n+\n+int unix_stream_listen(const char *path)\n+{\n+\tint fd;\n+\tstruct sockaddr_un sa;\n+\n+\tunix_sockaddr_init(&sa, path);\n+\tfd = unix_stream_socket();\n+\n+\tunlink(path);\n+\tif (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {\n+\t\tclose(fd);\n+\t\treturn -1;\n+\t}\n+\n+\tif (listen(fd, 5) < 0) {\n+\t\tclose(fd);\n+\t\treturn -1;\n+\t}\n+\n+\treturn fd;\n+}\ndiff --git a/unix-socket.h b/unix-socket.h\nnew file mode 100644\nindex 0000000..e271aee\n--- /dev/null\n+++ b/unix-socket.h\n@@ -0,0 +1,7 @@\n+#ifndef UNIX_SOCKET_H\n+#define UNIX_SOCKET_H\n+\n+int unix_stream_connect(const char *path);\n+int unix_stream_listen(const char *path);\n+\n+#endif /* UNIX_SOCKET_H */\n-- \n1.7.8.rc2.40.gaf387\n"},{"id":"180763","messageId":"20111210103420.GK16529@sigill.intra.peff.net","threadId":"29128","inReplyTo":"20111210102827.GA16460@sigill.intra.peff.net","subject":"[PATCHv3 11/13] strbuf: add strbuf_add*_urlencode","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-12-10T10:34:20Z","receivedAt":"2011-12-10T10:34:20Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"This just follows the rfc3986 rules for percent-encoding\nurl data into a strbuf.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n strbuf.c |   37 +++++++++++++++++++++++++++++++++++++\n strbuf.h |    5 +++++\n 2 files changed, 42 insertions(+), 0 deletions(-)\n\ndiff --git a/strbuf.c b/strbuf.c\nindex a849705..ff0b96b 100644\n--- a/strbuf.c\n+++ b/strbuf.c\n@@ -411,3 +411,40 @@ void strbuf_add_lines(struct strbuf *out, const char *prefix,\n \t}\n \tstrbuf_complete_line(out);\n }\n+\n+static int is_rfc3986_reserved(char ch)\n+{\n+\tswitch (ch) {\n+\t\tcase '!': case '*': case '\\'': case '(': case ')': case ';':\n+\t\tcase ':': case '@': case '&': case '=': case '+': case '$':\n+\t\tcase ',': case '/': case '?': case '#': case '[': case ']':\n+\t\t\treturn 1;\n+\t}\n+\treturn 0;\n+}\n+\n+static int is_rfc3986_unreserved(char ch)\n+{\n+\treturn isalnum(ch) ||\n+\t\tch == '-' || ch == '_' || ch == '.' || ch == '~';\n+}\n+\n+void strbuf_add_urlencode(struct strbuf *sb, const char *s, size_t len,\n+\t\t\t  int reserved)\n+{\n+\tstrbuf_grow(sb, len);\n+\twhile (len--) {\n+\t\tchar ch = *s++;\n+\t\tif (is_rfc3986_unreserved(ch) ||\n+\t\t    (!reserved && is_rfc3986_reserved(ch)))\n+\t\t\tstrbuf_addch(sb, ch);\n+\t\telse\n+\t\t\tstrbuf_addf(sb, \"%%%02x\", ch);\n+\t}\n+}\n+\n+void strbuf_addstr_urlencode(struct strbuf *sb, const char *s,\n+\t\t\t     int reserved)\n+{\n+\tstrbuf_add_urlencode(sb, s, strlen(s), reserved);\n+}\ndiff --git a/strbuf.h b/strbuf.h\nindex 08fc13d..fbf059f 100644\n--- a/strbuf.h\n+++ b/strbuf.h\n@@ -123,4 +123,9 @@ static inline void strbuf_complete_line(struct strbuf *sb)\n extern int strbuf_branchname(struct strbuf *sb, const char *name);\n extern int strbuf_check_branch_ref(struct strbuf *sb, const char *name);\n \n+extern void strbuf_add_urlencode(struct strbuf *, const char *, size_t,\n+\t\t\t\t int reserved);\n+extern void strbuf_addstr_urlencode(struct strbuf *, const char *,\n+\t\t\t\t    int reserved);\n+\n #endif /* STRBUF_H */\n-- \n1.7.8.rc2.40.gaf387\n"},{"id":"180764","messageId":"20111210103444.GL16529@sigill.intra.peff.net","threadId":"29128","inReplyTo":"20111210102827.GA16460@sigill.intra.peff.net","subject":"[PATCHv3 12/13] credentials: add \"store\" helper","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-12-10T10:34:44Z","receivedAt":"2011-12-10T10:34:44Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"This is like \"cache\", except that we actually put the\ncredentials on disk. This can be terribly insecure, of\ncourse, but we do what we can to protect them by filesystem\npermissions, and we warn the user in the documentation.\n\nThis is not unlike using .netrc to store entries, but it's a\nlittle more user-friendly. Instead of putting credentials in\nplace ahead of time, we transparently store them after\nprompting the user for them once.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\nThis version has the \"don't erase everything\" protection we discussed.\n\n .gitignore                             |    1 +\n Documentation/git-credential-store.txt |   75 +++++++++++++++\n Documentation/gitcredentials.txt       |    5 +\n Makefile                               |    1 +\n credential-store.c                     |  157 ++++++++++++++++++++++++++++++++\n t/t0302-credential-store.sh            |    9 ++\n 6 files changed, 248 insertions(+), 0 deletions(-)\n create mode 100644 Documentation/git-credential-store.txt\n create mode 100644 credential-store.c\n create mode 100755 t/t0302-credential-store.sh\n\ndiff --git a/.gitignore b/.gitignore\nindex a6b0bd4..2b7a3f9 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -32,6 +32,7 @@\n /git-count-objects\n /git-credential-cache\n /git-credential-cache--daemon\n+/git-credential-store\n /git-cvsexportcommit\n /git-cvsimport\n /git-cvsserver\ndiff --git a/Documentation/git-credential-store.txt b/Documentation/git-credential-store.txt\nnew file mode 100644\nindex 0000000..3109346\n--- /dev/null\n+++ b/Documentation/git-credential-store.txt\n@@ -0,0 +1,75 @@\n+git-credential-store(1)\n+=======================\n+\n+NAME\n+----\n+git-credential-store - helper to store credentials on disk\n+\n+SYNOPSIS\n+--------\n+-------------------\n+git config credential.helper 'store [options]'\n+-------------------\n+\n+DESCRIPTION\n+-----------\n+\n+NOTE: Using this helper will store your passwords unencrypted on disk,\n+protected only by filesystem permissions. If this is not an acceptable\n+security tradeoff, try linkgit:git-credential-cache[1], or find a helper\n+that integrates with secure storage provided by your operating system.\n+\n+This command stores credentials indefinitely on disk for use by future\n+git programs.\n+\n+You probably don't want to invoke this command directly; it is meant to\n+be used as a credential helper by other parts of git. See\n+linkgit:gitcredentials[7] or `EXAMPLES` below.\n+\n+OPTIONS\n+-------\n+\n+--store=<path>::\n+\n+\tUse `<path>` to store credentials. The file will have its\n+\tfilesystem permissions set to prevent other users on the system\n+\tfrom reading it, but will not be encrypted or otherwise\n+\tprotected. Defaults to `~/.git-credentials`.\n+\n+EXAMPLES\n+--------\n+\n+The point of this helper is to reduce the number of times you must type\n+your username or password. For example:\n+\n+------------------------------------------\n+$ git config credential.helper store\n+$ git push http://example.com/repo.git\n+Username: <type your username>\n+Password: <type your password>\n+\n+[several days later]\n+$ git push http://example.com/repo.git\n+[your credentials are used automatically]\n+------------------------------------------\n+\n+STORAGE FORMAT\n+--------------\n+\n+The `.git-credentials` file is stored in plaintext. Each credential is\n+stored on its own line as a URL like:\n+\n+------------------------------\n+https://user:pass@example.com\n+------------------------------\n+\n+When git needs authentication for a particular URL context,\n+credential-store will consider that context a pattern to match against\n+each entry in the credentials file.  If the protocol, hostname, and\n+username (if we already have one) match, then the password is returned\n+to git. See the discussion of configuration in linkgit:gitcredentials[7]\n+for more information.\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\ndiff --git a/Documentation/gitcredentials.txt b/Documentation/gitcredentials.txt\nindex 4e3f860..066f825 100644\n--- a/Documentation/gitcredentials.txt\n+++ b/Documentation/gitcredentials.txt\n@@ -71,6 +71,11 @@ cache::\n \tCache credentials in memory for a short period of time. See\n \tlinkgit:git-credential-cache[1] for details.\n \n+store::\n+\n+\tStore credentials indefinitely on disk. See\n+\tlinkgit:git-credential-store[1] for details.\n+\n You may also have third-party helpers installed; search for\n `credential-*` in the output of `git help -a`, and consult the\n documentation of individual helpers.  Once you have selected a helper,\ndiff --git a/Makefile b/Makefile\nindex 7ad2fe3..b144f1a 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -429,6 +429,7 @@ PROGRAM_OBJS += http-backend.o\n PROGRAM_OBJS += sh-i18n--envsubst.o\n PROGRAM_OBJS += credential-cache.o\n PROGRAM_OBJS += credential-cache--daemon.o\n+PROGRAM_OBJS += credential-store.o\n \n PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS))\n \ndiff --git a/credential-store.c b/credential-store.c\nnew file mode 100644\nindex 0000000..26f7589\n--- /dev/null\n+++ b/credential-store.c\n@@ -0,0 +1,157 @@\n+#include \"cache.h\"\n+#include \"credential.h\"\n+#include \"string-list.h\"\n+#include \"parse-options.h\"\n+\n+static struct lock_file credential_lock;\n+\n+static void parse_credential_file(const char *fn,\n+\t\t\t\t  struct credential *c,\n+\t\t\t\t  void (*match_cb)(struct credential *),\n+\t\t\t\t  void (*other_cb)(struct strbuf *))\n+{\n+\tFILE *fh;\n+\tstruct strbuf line = STRBUF_INIT;\n+\tstruct credential entry = CREDENTIAL_INIT;\n+\n+\tfh = fopen(fn, \"r\");\n+\tif (!fh) {\n+\t\tif (errno != ENOENT)\n+\t\t\tdie_errno(\"unable to open %s\", fn);\n+\t\treturn;\n+\t}\n+\n+\twhile (strbuf_getline(&line, fh, '\\n') != EOF) {\n+\t\tcredential_from_url(&entry, line.buf);\n+\t\tif (entry.username && entry.password &&\n+\t\t    credential_match(c, &entry)) {\n+\t\t\tif (match_cb) {\n+\t\t\t\tmatch_cb(&entry);\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\t\telse if (other_cb)\n+\t\t\tother_cb(&line);\n+\t}\n+\n+\tcredential_clear(&entry);\n+\tstrbuf_release(&line);\n+\tfclose(fh);\n+}\n+\n+static void print_entry(struct credential *c)\n+{\n+\tprintf(\"username=%s\\n\", c->username);\n+\tprintf(\"password=%s\\n\", c->password);\n+}\n+\n+static void print_line(struct strbuf *buf)\n+{\n+\tstrbuf_addch(buf, '\\n');\n+\twrite_or_die(credential_lock.fd, buf->buf, buf->len);\n+}\n+\n+static void rewrite_credential_file(const char *fn, struct credential *c,\n+\t\t\t\t    struct strbuf *extra)\n+{\n+\tif (hold_lock_file_for_update(&credential_lock, fn, 0) < 0)\n+\t\tdie_errno(\"unable to get credential storage lock\");\n+\tif (extra)\n+\t\tprint_line(extra);\n+\tparse_credential_file(fn, c, NULL, print_line);\n+\tif (commit_lock_file(&credential_lock) < 0)\n+\t\tdie_errno(\"unable to commit credential store\");\n+}\n+\n+static void store_credential(const char *fn, struct credential *c)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\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;\n+\n+\tstrbuf_addf(&buf, \"%s://\", c->protocol);\n+\tstrbuf_addstr_urlencode(&buf, c->username, 1);\n+\tstrbuf_addch(&buf, ':');\n+\tstrbuf_addstr_urlencode(&buf, c->password, 1);\n+\tstrbuf_addch(&buf, '@');\n+\tif (c->host)\n+\t\tstrbuf_addstr_urlencode(&buf, c->host, 1);\n+\tif (c->path) {\n+\t\tstrbuf_addch(&buf, '/');\n+\t\tstrbuf_addstr_urlencode(&buf, c->path, 0);\n+\t}\n+\n+\trewrite_credential_file(fn, c, &buf);\n+\tstrbuf_release(&buf);\n+}\n+\n+static void remove_credential(const char *fn, struct credential *c)\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\trewrite_credential_file(fn, c, NULL);\n+}\n+\n+static int lookup_credential(const char *fn, struct credential *c)\n+{\n+\tparse_credential_file(fn, c, print_entry, NULL);\n+\treturn c->username && c->password;\n+}\n+\n+int main(int argc, const char **argv)\n+{\n+\tconst char * const usage[] = {\n+\t\t\"git credential-store [options] <action>\",\n+\t\tNULL\n+\t};\n+\tconst char *op;\n+\tstruct credential c = CREDENTIAL_INIT;\n+\tchar *file = NULL;\n+\tstruct option options[] = {\n+\t\tOPT_STRING(0, \"file\", &file, \"path\",\n+\t\t\t   \"fetch and store credentials in <path>\"),\n+\t\tOPT_END()\n+\t};\n+\n+\tumask(077);\n+\n+\targc = parse_options(argc, argv, NULL, options, usage, 0);\n+\tif (argc != 1)\n+\t\tusage_with_options(usage, options);\n+\top = argv[0];\n+\n+\tif (!file)\n+\t\tfile = expand_user_path(\"~/.git-credentials\");\n+\tif (!file)\n+\t\tdie(\"unable to set up default path; use --file\");\n+\n+\tif (credential_read(&c, stdin) < 0)\n+\t\tdie(\"unable to read credential\");\n+\n+\tif (!strcmp(op, \"get\"))\n+\t\tlookup_credential(file, &c);\n+\telse if (!strcmp(op, \"erase\"))\n+\t\tremove_credential(file, &c);\n+\telse if (!strcmp(op, \"store\"))\n+\t\tstore_credential(file, &c);\n+\telse\n+\t\t; /* Ignore unknown operation. */\n+\n+\treturn 0;\n+}\ndiff --git a/t/t0302-credential-store.sh b/t/t0302-credential-store.sh\nnew file mode 100755\nindex 0000000..f61b40c\n--- /dev/null\n+++ b/t/t0302-credential-store.sh\n@@ -0,0 +1,9 @@\n+#!/bin/sh\n+\n+test_description='credential-store tests'\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-credential.sh\n+\n+helper_test store\n+\n+test_done\n-- \n1.7.8.rc2.40.gaf387\n"},{"id":"180766","messageId":"20111210103554.GM16529@sigill.intra.peff.net","threadId":"29128","inReplyTo":"20111210102827.GA16460@sigill.intra.peff.net","subject":"[PATCHv3 13/13] t: add test harness for external credential helpers","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-12-10T10:35:55Z","receivedAt":"2011-12-10T10:35:55Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"We already have tests for the internal helpers, but it's\nnice to give authors of external tools an easy way to\nsanity-check their helpers.\n\nIf you have written the \"git-credential-foo\" helper, you can\ndo so with:\n\n  GIT_TEST_CREDENTIAL_HELPER=foo \\\n  make t0303-credential-external.sh\n\nThis assumes that your helper is capable of both storing and\nretrieving credentials (some helpers may be read-only, and\nthey will fail these tests).\n\nIf your helper supports time-based expiration with a\nconfigurable timeout, you can test that feature like this:\n\n  GIT_TEST_CREDENTIAL_HELPER_TIMEOUT=\"foo --timeout=1\" \\\n  make t0303-credential-external.sh\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\nThis version adds GIT_TEST_CREDENTIAL_HELPER_SETUP, which is an\nunfortunate hack needed for the OS X helper (I'll post that patch in a\nfew minutes).\n\n t/t0303-credential-external.sh |   39 +++++++++++++++++++++++++++++++++++++++\n 1 files changed, 39 insertions(+), 0 deletions(-)\n create mode 100755 t/t0303-credential-external.sh\n\ndiff --git a/t/t0303-credential-external.sh b/t/t0303-credential-external.sh\nnew file mode 100755\nindex 0000000..267f4c8\n--- /dev/null\n+++ b/t/t0303-credential-external.sh\n@@ -0,0 +1,39 @@\n+#!/bin/sh\n+\n+test_description='external credential helper tests'\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-credential.sh\n+\n+pre_test() {\n+\ttest -z \"$GIT_TEST_CREDENTIAL_HELPER_SETUP\" ||\n+\teval \"$GIT_TEST_CREDENTIAL_HELPER_SETUP\"\n+\n+\t# clean before the test in case there is cruft left\n+\t# over from a previous run that would impact results\n+\thelper_test_clean \"$GIT_TEST_CREDENTIAL_HELPER\"\n+}\n+\n+post_test() {\n+\t# clean afterwards so that we are good citizens\n+\t# and don't leave cruft in the helper's storage, which\n+\t# might be long-term system storage\n+\thelper_test_clean \"$GIT_TEST_CREDENTIAL_HELPER\"\n+}\n+\n+if test -z \"$GIT_TEST_CREDENTIAL_HELPER\"; then\n+\tsay \"# skipping external helper tests (set GIT_TEST_CREDENTIAL_HELPER)\"\n+else\n+\tpre_test\n+\thelper_test \"$GIT_TEST_CREDENTIAL_HELPER\"\n+\tpost_test\n+fi\n+\n+if test -z \"$GIT_TEST_CREDENTIAL_HELPER_TIMEOUT\"; then\n+\tsay \"# skipping external helper timeout tests\"\n+else\n+\tpre_test\n+\thelper_test_timeout \"$GIT_TEST_CREDENTIAL_HELPER_TIMEOUT\"\n+\tpost_test\n+fi\n+\n+test_done\n-- \n1.7.8.rc2.40.gaf387\n"},{"id":"180767","messageId":"20111210103943.GA16478@sigill.intra.peff.net","threadId":"29128","inReplyTo":"20111210102827.GA16460@sigill.intra.peff.net","subject":"[PATCHv2 0/9] echo characters in username prompt","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-12-10T10:39:44Z","receivedAt":"2011-12-10T10:39:44Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sat, Dec 10, 2011 at 05:28:27AM -0500, Jeff King wrote:\n\n> Here's the latest re-roll of the credential helpers series. I think this\n> one is probably ready to go to 'next'.\n\nAnd here's the prompt/terminal refactoring to go on top of it. This\nisn't as well reviewed as the main credential series, but I think it's\nreasonably solid.\n\nSince the last round, I fixed the bone-head NO_DEV_TTY/HAVE_DEV_TTY\nmixup that Jakub pointed out. And I split the \"Linux has /dev/tty\" patch\nout so I could test to make sure it isn't accidentally turned on until\nwe actually enable it.\n\nI also tested it on OS X, and it works fine there.\n\n  [1/9]: imap-send: avoid buffer overflow\n  [2/9]: imap-send: don't check return value of git_getpass\n  [3/9]: move git_getpass to its own source file\n  [4/9]: refactor git_getpass into generic prompt function\n  [5/9]: add generic terminal prompt function\n  [6/9]: prompt: use git_terminal_prompt\n  [7/9]: credential: use git_prompt instead of git_getpass\n  [8/9]: Makefile: linux has /dev/tty\n  [9/9]: Makefile: OS X has /dev/tty\n\n-Peff\n"},{"id":"180768","messageId":"20111210104045.GA16648@sigill.intra.peff.net","threadId":"29128","inReplyTo":"20111210103943.GA16478@sigill.intra.peff.net","subject":"[PATCHv2 1/9] imap-send: avoid buffer overflow","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-12-10T10:40:45Z","receivedAt":"2011-12-10T10:40:45Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"We format the password prompt in an 80-character static\nbuffer. It contains the remote host and username, so it's\nunlikely to overflow (or be exploitable by a remote\nattacker), but there's no reason not to be careful and use\na strbuf.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n imap-send.c |    7 ++++---\n 1 files changed, 4 insertions(+), 3 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex e1ad1a4..4c1e897 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1209,9 +1209,10 @@ static int auth_cram_md5(struct imap_store *ctx, struct imap_cmd *cmd, const cha\n \t\t\tgoto bail;\n \t\t}\n \t\tif (!srvc->pass) {\n-\t\t\tchar prompt[80];\n-\t\t\tsprintf(prompt, \"Password (%s@%s): \", srvc->user, srvc->host);\n-\t\t\targ = git_getpass(prompt);\n+\t\t\tstruct strbuf prompt = STRBUF_INIT;\n+\t\t\tstrbuf_addf(&prompt, \"Password (%s@%s): \", srvc->user, srvc->host);\n+\t\t\targ = git_getpass(prompt.buf);\n+\t\t\tstrbuf_release(&prompt);\n \t\t\tif (!arg) {\n \t\t\t\tperror(\"getpass\");\n \t\t\t\texit(1);\n-- \n1.7.8.rc2.40.gaf387\n"},{"id":"180769","messageId":"20111210104049.GB16648@sigill.intra.peff.net","threadId":"29128","inReplyTo":"20111210103943.GA16478@sigill.intra.peff.net","subject":"[PATCHv2 2/9] imap-send: don't check return value of git_getpass","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-12-10T10:40:49Z","receivedAt":"2011-12-10T10:40:49Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"git_getpass will always die() if we weren't able to get\ninput, so there's no point looking for NULL.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n imap-send.c |    4 ----\n 1 files changed, 0 insertions(+), 4 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 4c1e897..227253e 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1213,10 +1213,6 @@ static int auth_cram_md5(struct imap_store *ctx, struct imap_cmd *cmd, const cha\n \t\t\tstrbuf_addf(&prompt, \"Password (%s@%s): \", srvc->user, srvc->host);\n \t\t\targ = git_getpass(prompt.buf);\n \t\t\tstrbuf_release(&prompt);\n-\t\t\tif (!arg) {\n-\t\t\t\tperror(\"getpass\");\n-\t\t\t\texit(1);\n-\t\t\t}\n \t\t\tif (!*arg) {\n \t\t\t\tfprintf(stderr, \"Skipping account %s@%s, no password\\n\", srvc->user, srvc->host);\n \t\t\t\tgoto bail;\n-- \n1.7.8.rc2.40.gaf387\n"},{"id":"180770","messageId":"20111210104054.GC16648@sigill.intra.peff.net","threadId":"29128","inReplyTo":"20111210103943.GA16478@sigill.intra.peff.net","subject":"[PATCHv2 3/9] move git_getpass to its own source file","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-12-10T10:40:54Z","receivedAt":"2011-12-10T10:40:54Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"This is currently in connect.c, but really has nothing to\ndo with the git protocol itself. Let's make a new source\nfile all about prompting the user, which will make it\ncleaner to refactor.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n Makefile     |    2 ++\n cache.h      |    1 -\n connect.c    |   44 --------------------------------------------\n credential.c |    1 +\n imap-send.c  |    1 +\n prompt.c     |   48 ++++++++++++++++++++++++++++++++++++++++++++++++\n prompt.h     |    6 ++++++\n 7 files changed, 58 insertions(+), 45 deletions(-)\n create mode 100644 prompt.c\n create mode 100644 prompt.h\n\ndiff --git a/Makefile b/Makefile\nindex b144f1a..5201bea 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -565,6 +565,7 @@ LIB_H += parse-options.h\n LIB_H += patch-ids.h\n LIB_H += pkt-line.h\n LIB_H += progress.h\n+LIB_H += prompt.h\n LIB_H += quote.h\n LIB_H += reflog-walk.h\n LIB_H += refs.h\n@@ -672,6 +673,7 @@ LIB_OBJS += pkt-line.o\n LIB_OBJS += preload-index.o\n LIB_OBJS += pretty.o\n LIB_OBJS += progress.o\n+LIB_OBJS += prompt.o\n LIB_OBJS += quote.o\n LIB_OBJS += reachable.o\n LIB_OBJS += read-cache.o\ndiff --git a/cache.h b/cache.h\nindex 8c98d05..bb26c2f 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1025,7 +1025,6 @@ struct ref {\n extern struct ref *find_ref_by_name(const struct ref *list, const char *name);\n \n #define CONNECT_VERBOSE       (1u << 0)\n-extern char *git_getpass(const char *prompt);\n extern struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags);\n extern int finish_connect(struct child_process *conn);\n extern int git_connection_is_socket(struct child_process *conn);\ndiff --git a/connect.c b/connect.c\nindex 51990fa..519e527 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -619,47 +619,3 @@ int finish_connect(struct child_process *conn)\n \tfree(conn);\n \treturn code;\n }\n-\n-char *git_getpass(const char *prompt)\n-{\n-\tconst char *askpass;\n-\tstruct child_process pass;\n-\tconst char *args[3];\n-\tstatic struct strbuf buffer = STRBUF_INIT;\n-\n-\taskpass = getenv(\"GIT_ASKPASS\");\n-\tif (!askpass)\n-\t\taskpass = askpass_program;\n-\tif (!askpass)\n-\t\taskpass = getenv(\"SSH_ASKPASS\");\n-\tif (!askpass || !(*askpass)) {\n-\t\tchar *result = getpass(prompt);\n-\t\tif (!result)\n-\t\t\tdie_errno(\"Could not read password\");\n-\t\treturn result;\n-\t}\n-\n-\targs[0] = askpass;\n-\targs[1]\t= prompt;\n-\targs[2] = NULL;\n-\n-\tmemset(&pass, 0, sizeof(pass));\n-\tpass.argv = args;\n-\tpass.out = -1;\n-\n-\tif (start_command(&pass))\n-\t\texit(1);\n-\n-\tstrbuf_reset(&buffer);\n-\tif (strbuf_read(&buffer, pass.out, 20) < 0)\n-\t\tdie(\"failed to read password from %s\\n\", askpass);\n-\n-\tclose(pass.out);\n-\n-\tif (finish_command(&pass))\n-\t\texit(1);\n-\n-\tstrbuf_setlen(&buffer, strcspn(buffer.buf, \"\\r\\n\"));\n-\n-\treturn buffer.buf;\n-}\ndiff --git a/credential.c b/credential.c\nindex a17eafe..fbb7231 100644\n--- a/credential.c\n+++ b/credential.c\n@@ -3,6 +3,7 @@\n #include \"string-list.h\"\n #include \"run-command.h\"\n #include \"url.h\"\n+#include \"prompt.h\"\n \n void credential_init(struct credential *c)\n {\ndiff --git a/imap-send.c b/imap-send.c\nindex 227253e..43588e8 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -25,6 +25,7 @@\n #include \"cache.h\"\n #include \"exec_cmd.h\"\n #include \"run-command.h\"\n+#include \"prompt.h\"\n #ifdef NO_OPENSSL\n typedef void *SSL;\n #else\ndiff --git a/prompt.c b/prompt.c\nnew file mode 100644\nindex 0000000..42a1c9f\n--- /dev/null\n+++ b/prompt.c\n@@ -0,0 +1,48 @@\n+#include \"cache.h\"\n+#include \"run-command.h\"\n+#include \"strbuf.h\"\n+#include \"prompt.h\"\n+\n+char *git_getpass(const char *prompt)\n+{\n+\tconst char *askpass;\n+\tstruct child_process pass;\n+\tconst char *args[3];\n+\tstatic struct strbuf buffer = STRBUF_INIT;\n+\n+\taskpass = getenv(\"GIT_ASKPASS\");\n+\tif (!askpass)\n+\t\taskpass = askpass_program;\n+\tif (!askpass)\n+\t\taskpass = getenv(\"SSH_ASKPASS\");\n+\tif (!askpass || !(*askpass)) {\n+\t\tchar *result = getpass(prompt);\n+\t\tif (!result)\n+\t\t\tdie_errno(\"Could not read password\");\n+\t\treturn result;\n+\t}\n+\n+\targs[0] = askpass;\n+\targs[1]\t= prompt;\n+\targs[2] = NULL;\n+\n+\tmemset(&pass, 0, sizeof(pass));\n+\tpass.argv = args;\n+\tpass.out = -1;\n+\n+\tif (start_command(&pass))\n+\t\texit(1);\n+\n+\tstrbuf_reset(&buffer);\n+\tif (strbuf_read(&buffer, pass.out, 20) < 0)\n+\t\tdie(\"failed to read password from %s\\n\", askpass);\n+\n+\tclose(pass.out);\n+\n+\tif (finish_command(&pass))\n+\t\texit(1);\n+\n+\tstrbuf_setlen(&buffer, strcspn(buffer.buf, \"\\r\\n\"));\n+\n+\treturn buffer.buf;\n+}\ndiff --git a/prompt.h b/prompt.h\nnew file mode 100644\nindex 0000000..0fd7bd9\n--- /dev/null\n+++ b/prompt.h\n@@ -0,0 +1,6 @@\n+#ifndef PROMPT_H\n+#define PROMPT_H\n+\n+char *git_getpass(const char *prompt);\n+\n+#endif /* PROMPT_H */\n-- \n1.7.8.rc2.40.gaf387\n"},{"id":"180771","messageId":"20111210104057.GD16648@sigill.intra.peff.net","threadId":"29128","inReplyTo":"20111210103943.GA16478@sigill.intra.peff.net","subject":"[PATCHv2 4/9] refactor git_getpass into generic prompt function","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-12-10T10:40:57Z","receivedAt":"2011-12-10T10:40:57Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"This will allow callers to specify more options (e.g.,\nleaving echo on). The original git_getpass becomes a slim\nwrapper around the new function.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n prompt.c |   46 ++++++++++++++++++++++++++++++----------------\n prompt.h |    3 +++\n 2 files changed, 33 insertions(+), 16 deletions(-)\n\ndiff --git a/prompt.c b/prompt.c\nindex 42a1c9f..2002644 100644\n--- a/prompt.c\n+++ b/prompt.c\n@@ -3,26 +3,13 @@\n #include \"strbuf.h\"\n #include \"prompt.h\"\n \n-char *git_getpass(const char *prompt)\n+static char *do_askpass(const char *cmd, const char *prompt)\n {\n-\tconst char *askpass;\n \tstruct child_process pass;\n \tconst char *args[3];\n \tstatic struct strbuf buffer = STRBUF_INIT;\n \n-\taskpass = getenv(\"GIT_ASKPASS\");\n-\tif (!askpass)\n-\t\taskpass = askpass_program;\n-\tif (!askpass)\n-\t\taskpass = getenv(\"SSH_ASKPASS\");\n-\tif (!askpass || !(*askpass)) {\n-\t\tchar *result = getpass(prompt);\n-\t\tif (!result)\n-\t\t\tdie_errno(\"Could not read password\");\n-\t\treturn result;\n-\t}\n-\n-\targs[0] = askpass;\n+\targs[0] = cmd;\n \targs[1]\t= prompt;\n \targs[2] = NULL;\n \n@@ -35,7 +22,7 @@\n \n \tstrbuf_reset(&buffer);\n \tif (strbuf_read(&buffer, pass.out, 20) < 0)\n-\t\tdie(\"failed to read password from %s\\n\", askpass);\n+\t\tdie(\"failed to get '%s' from %s\\n\", prompt, cmd);\n \n \tclose(pass.out);\n \n@@ -46,3 +33,30 @@\n \n \treturn buffer.buf;\n }\n+\n+char *git_prompt(const char *prompt, int flags)\n+{\n+\tchar *r;\n+\n+\tif (flags & PROMPT_ASKPASS) {\n+\t\tconst char *askpass;\n+\n+\t\taskpass = getenv(\"GIT_ASKPASS\");\n+\t\tif (!askpass)\n+\t\t\taskpass = askpass_program;\n+\t\tif (!askpass)\n+\t\t\taskpass = getenv(\"SSH_ASKPASS\");\n+\t\tif (askpass && *askpass)\n+\t\t\treturn do_askpass(askpass, prompt);\n+\t}\n+\n+\tr = getpass(prompt);\n+\tif (!r)\n+\t\tdie_errno(\"could not read '%s'\", prompt);\n+\treturn r;\n+}\n+\n+char *git_getpass(const char *prompt)\n+{\n+\treturn git_prompt(prompt, PROMPT_ASKPASS);\n+}\ndiff --git a/prompt.h b/prompt.h\nindex 0fd7bd9..9ab85a7 100644\n--- a/prompt.h\n+++ b/prompt.h\n@@ -1,6 +1,9 @@\n #ifndef PROMPT_H\n #define PROMPT_H\n \n+#define PROMPT_ASKPASS (1<<0)\n+\n+char *git_prompt(const char *prompt, int flags);\n char *git_getpass(const char *prompt);\n \n #endif /* PROMPT_H */\n-- \n1.7.8.rc2.40.gaf387\n"},{"id":"180772","messageId":"20111210104101.GE16648@sigill.intra.peff.net","threadId":"29128","inReplyTo":"20111210103943.GA16478@sigill.intra.peff.net","subject":"[PATCHv2 5/9] add generic terminal prompt function","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-12-10T10:41:01Z","receivedAt":"2011-12-10T10:41:01Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"When we need to prompt the user for input interactively, we\nwant to access their terminal directly. We can't rely on\nstdio because it may be connected to pipes or files, rather\nthan the terminal. Instead, we use \"getpass()\", because it\nabstracts the idea of prompting and reading from the\nterminal.  However, it has some problems:\n\n  1. It never echoes the typed characters, which makes it OK\n     for passwords but annoying for other input (like usernames).\n\n  2. Some implementations of getpass() have an extremely\n     small input buffer (e.g., Solaris 8 is reported to\n     support only 8 characters).\n\n  3. Some implementations of getpass() will fall back to\n     reading from stdin (e.g., glibc). We explicitly don't\n     want this, because our stdin may be connected to a pipe\n     speaking a particular protocol, and reading will\n     disrupt the protocol flow (e.g., the remote-curl\n     helper).\n\n  4. Some implementations of getpass() turn off signals, so\n     that hitting \"^C\" on the terminal does not break out of\n     the password prompt. This can be a mild annoyance.\n\nInstead, let's provide an abstract \"git_terminal_prompt\"\nfunction that addresses these concerns. This patch includes\nan implementation based on /dev/tty, enabled by setting\nHAVE_DEV_TTY. The fallback is to use getpass() as before.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n Makefile          |    9 ++++++\n compat/terminal.c |   81 +++++++++++++++++++++++++++++++++++++++++++++++++++++\n compat/terminal.h |    6 ++++\n 3 files changed, 96 insertions(+), 0 deletions(-)\n create mode 100644 compat/terminal.c\n create mode 100644 compat/terminal.h\n\ndiff --git a/Makefile b/Makefile\nindex 5201bea..4ef6ba5 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -227,6 +227,9 @@ all::\n #\n # Define NO_REGEX if you have no or inferior regex support in your C library.\n #\n+# Define HAVE_DEV_TTY if your system can open /dev/tty to interact with the\n+# user.\n+#\n # Define GETTEXT_POISON if you are debugging the choice of strings marked\n # for translation.  In a GETTEXT_POISON build, you can turn all strings marked\n # for translation into gibberish by setting the GIT_GETTEXT_POISON variable\n@@ -523,6 +526,7 @@ LIB_H += compat/bswap.h\n LIB_H += compat/cygwin.h\n LIB_H += compat/mingw.h\n LIB_H += compat/obstack.h\n+LIB_H += compat/terminal.h\n LIB_H += compat/win32/pthread.h\n LIB_H += compat/win32/syslog.h\n LIB_H += compat/win32/poll.h\n@@ -612,6 +616,7 @@ LIB_OBJS += color.o\n LIB_OBJS += combine-diff.o\n LIB_OBJS += commit.o\n LIB_OBJS += compat/obstack.o\n+LIB_OBJS += compat/terminal.o\n LIB_OBJS += config.o\n LIB_OBJS += connect.o\n LIB_OBJS += connected.o\n@@ -1642,6 +1647,10 @@ ifdef HAVE_PATHS_H\n \tBASIC_CFLAGS += -DHAVE_PATHS_H\n endif\n \n+ifdef HAVE_DEV_TTY\n+\tBASIC_CFLAGS += -DHAVE_DEV_TTY\n+endif\n+\n ifdef DIR_HAS_BSD_GROUP_SEMANTICS\n \tCOMPAT_CFLAGS += -DDIR_HAS_BSD_GROUP_SEMANTICS\n endif\ndiff --git a/compat/terminal.c b/compat/terminal.c\nnew file mode 100644\nindex 0000000..6d16c8f\n--- /dev/null\n+++ b/compat/terminal.c\n@@ -0,0 +1,81 @@\n+#include \"git-compat-util.h\"\n+#include \"compat/terminal.h\"\n+#include \"sigchain.h\"\n+#include \"strbuf.h\"\n+\n+#ifdef HAVE_DEV_TTY\n+\n+static int term_fd = -1;\n+static struct termios old_term;\n+\n+static void restore_term(void)\n+{\n+\tif (term_fd < 0)\n+\t\treturn;\n+\n+\ttcsetattr(term_fd, TCSAFLUSH, &old_term);\n+\tterm_fd = -1;\n+}\n+\n+static void restore_term_on_signal(int sig)\n+{\n+\trestore_term();\n+\tsigchain_pop(sig);\n+\traise(sig);\n+}\n+\n+char *git_terminal_prompt(const char *prompt, int echo)\n+{\n+\tstatic struct strbuf buf = STRBUF_INIT;\n+\tint r;\n+\tFILE *fh;\n+\n+\tfh = fopen(\"/dev/tty\", \"w+\");\n+\tif (!fh)\n+\t\treturn NULL;\n+\n+\tif (!echo) {\n+\t\tstruct termios t;\n+\n+\t\tif (tcgetattr(fileno(fh), &t) < 0) {\n+\t\t\tfclose(fh);\n+\t\t\treturn NULL;\n+\t\t}\n+\n+\t\told_term = t;\n+\t\tterm_fd = fileno(fh);\n+\t\tsigchain_push_common(restore_term_on_signal);\n+\n+\t\tt.c_lflag &= ~ECHO;\n+\t\tif (tcsetattr(fileno(fh), TCSAFLUSH, &t) < 0) {\n+\t\t\tterm_fd = -1;\n+\t\t\tfclose(fh);\n+\t\t\treturn NULL;\n+\t\t}\n+\t}\n+\n+\tfputs(prompt, fh);\n+\tfflush(fh);\n+\n+\tr = strbuf_getline(&buf, fh, '\\n');\n+\tif (!echo) {\n+\t\tputc('\\n', fh);\n+\t\tfflush(fh);\n+\t}\n+\n+\trestore_term();\n+\tfclose(fh);\n+\n+\tif (r == EOF)\n+\t\treturn NULL;\n+\treturn buf.buf;\n+}\n+\n+#else\n+\n+char *git_terminal_prompt(const char *prompt, int echo)\n+{\n+\treturn getpass(prompt);\n+}\n+\n+#endif\ndiff --git a/compat/terminal.h b/compat/terminal.h\nnew file mode 100644\nindex 0000000..97db7cd\n--- /dev/null\n+++ b/compat/terminal.h\n@@ -0,0 +1,6 @@\n+#ifndef COMPAT_TERMINAL_H\n+#define COMPAT_TERMINAL_H\n+\n+char *git_terminal_prompt(const char *prompt, int echo);\n+\n+#endif /* COMPAT_TERMINAL_H */\n-- \n1.7.8.rc2.40.gaf387\n"},{"id":"180773","messageId":"20111210104108.GF16648@sigill.intra.peff.net","threadId":"29128","inReplyTo":"20111210103943.GA16478@sigill.intra.peff.net","subject":"[PATCHv2 6/9] prompt: use git_terminal_prompt","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-12-10T10:41:08Z","receivedAt":"2011-12-10T10:41:08Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"Our custom implementation of git_terminal_prompt has many\nadvantages over regular getpass(), as described in the prior\ncommit.\n\nThis also lets us implement a PROMPT_ECHO flag for callers\nwho want it.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n prompt.c |    3 ++-\n prompt.h |    1 +\n 2 files changed, 3 insertions(+), 1 deletions(-)\n\ndiff --git a/prompt.c b/prompt.c\nindex 2002644..72ab9de 100644\n--- a/prompt.c\n+++ b/prompt.c\n@@ -2,6 +2,7 @@\n #include \"run-command.h\"\n #include \"strbuf.h\"\n #include \"prompt.h\"\n+#include \"compat/terminal.h\"\n \n static char *do_askpass(const char *cmd, const char *prompt)\n {\n@@ -50,7 +51,7 @@\n \t\t\treturn do_askpass(askpass, prompt);\n \t}\n \n-\tr = getpass(prompt);\n+\tr = git_terminal_prompt(prompt, flags & PROMPT_ECHO);\n \tif (!r)\n \t\tdie_errno(\"could not read '%s'\", prompt);\n \treturn r;\ndiff --git a/prompt.h b/prompt.h\nindex 9ab85a7..04f321a 100644\n--- a/prompt.h\n+++ b/prompt.h\n@@ -2,6 +2,7 @@\n #define PROMPT_H\n \n #define PROMPT_ASKPASS (1<<0)\n+#define PROMPT_ECHO    (1<<1)\n \n char *git_prompt(const char *prompt, int flags);\n char *git_getpass(const char *prompt);\n-- \n1.7.8.rc2.40.gaf387\n"},{"id":"180774","messageId":"20111210104123.GG16648@sigill.intra.peff.net","threadId":"29128","inReplyTo":"20111210103943.GA16478@sigill.intra.peff.net","subject":"[PATCHv2 7/9] credential: use git_prompt instead of git_getpass","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-12-10T10:41:23Z","receivedAt":"2011-12-10T10:41:23Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"We use git_getpass to retrieve the username and password\nfrom the terminal. However, git_getpass will not echo the\nusername as the user types. We can fix this by using the\nmore generic git_prompt, which underlies git_getpass but\nlets us specify an \"echo\" option.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n credential.c |   15 +++++++--------\n 1 files changed, 7 insertions(+), 8 deletions(-)\n\ndiff --git a/credential.c b/credential.c\nindex fbb7231..62d1c56 100644\n--- a/credential.c\n+++ b/credential.c\n@@ -109,7 +109,8 @@ static void credential_describe(struct credential *c, struct strbuf *out)\n \t\tstrbuf_addf(out, \"/%s\", c->path);\n }\n \n-static char *credential_ask_one(const char *what, struct credential *c)\n+static char *credential_ask_one(const char *what, struct credential *c,\n+\t\t\t\tint flags)\n {\n \tstruct strbuf desc = STRBUF_INIT;\n \tstruct strbuf prompt = STRBUF_INIT;\n@@ -121,11 +122,7 @@ static void credential_describe(struct credential *c, struct strbuf *out)\n \telse\n \t\tstrbuf_addf(&prompt, \"%s: \", what);\n \n-\t/* FIXME: for usernames, we should do something less magical that\n-\t * actually echoes the characters. However, we need to read from\n-\t * /dev/tty and not stdio, which is not portable (but getpass will do\n-\t * it for us). http.c uses the same workaround. */\n-\tr = git_getpass(prompt.buf);\n+\tr = git_prompt(prompt.buf, flags);\n \n \tstrbuf_release(&desc);\n \tstrbuf_release(&prompt);\n@@ -135,9 +132,11 @@ static void credential_describe(struct credential *c, struct strbuf *out)\n static void credential_getpass(struct credential *c)\n {\n \tif (!c->username)\n-\t\tc->username = credential_ask_one(\"Username\", c);\n+\t\tc->username = credential_ask_one(\"Username\", c,\n+\t\t\t\t\t\t PROMPT_ASKPASS|PROMPT_ECHO);\n \tif (!c->password)\n-\t\tc->password = credential_ask_one(\"Password\", c);\n+\t\tc->password = credential_ask_one(\"Password\", c,\n+\t\t\t\t\t\t PROMPT_ASKPASS);\n }\n \n int credential_read(struct credential *c, FILE *fp)\n-- \n1.7.8.rc2.40.gaf387\n"},{"id":"180775","messageId":"20111210104127.GH16648@sigill.intra.peff.net","threadId":"29128","inReplyTo":"20111210103943.GA16478@sigill.intra.peff.net","subject":"[PATCHv2 8/9] Makefile: linux has /dev/tty","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-12-10T10:41:27Z","receivedAt":"2011-12-10T10:41:27Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"Therefore we can turn on our custom prompt function instead\nof relying on getpass.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n Makefile |    1 +\n 1 files changed, 1 insertions(+), 0 deletions(-)\n\ndiff --git a/Makefile b/Makefile\nindex 4ef6ba5..198fc9b 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -841,6 +841,7 @@ ifeq ($(uname_S),Linux)\n \tNO_STRLCPY = YesPlease\n \tNO_MKSTEMPS = YesPlease\n \tHAVE_PATHS_H = YesPlease\n+\tHAVE_DEV_TTY = YesPlease\n endif\n ifeq ($(uname_S),GNU/kFreeBSD)\n \tNO_STRLCPY = YesPlease\n-- \n1.7.8.rc2.40.gaf387\n"},{"id":"180776","messageId":"20111210104130.GI16648@sigill.intra.peff.net","threadId":"29128","inReplyTo":"20111210103943.GA16478@sigill.intra.peff.net","subject":"[PATCHv2 9/9] Makefile: OS X has /dev/tty","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-12-10T10:41:30Z","receivedAt":"2011-12-10T10:41:30Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"We can use our enhanced getpass(). Tested by me.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n Makefile |    1 +\n 1 files changed, 1 insertions(+), 0 deletions(-)\n\ndiff --git a/Makefile b/Makefile\nindex 198fc9b..1b3f5f0 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -902,6 +902,7 @@ ifeq ($(uname_S),Darwin)\n \tendif\n \tNO_MEMMEM = YesPlease\n \tUSE_ST_TIMESPEC = YesPlease\n+\tHAVE_DEV_TTY = YesPlease\n endif\n ifeq ($(uname_S),SunOS)\n \tNEEDS_SOCKET = YesPlease\n-- \n1.7.8.rc2.40.gaf387\n"},{"id":"180777","messageId":"20111210105314.GB16478@sigill.intra.peff.net","threadId":"29128","inReplyTo":"20111210102827.GA16460@sigill.intra.peff.net","subject":"[PATCH 1/1] contrib: add credential helper for OS X Keychain","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-12-10T10:53:14Z","receivedAt":"2011-12-10T10:53:14Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sat, Dec 10, 2011 at 05:28:27AM -0500, Jeff King wrote:\n\n> Here's the latest re-roll of the credential helpers series. I think this\n> one is probably ready to go to 'next'.\n> \n> It's rebased on the latest tip of 'master' (applying it to an older\n> commit will get you a minor textual conflict in strbuf.c). It\n> incorporates the erase-safety we discussed, fixes a few commit message\n> typos, and tweaks the test scripts to make testing the external OS X\n> helper a little easier.\n\nAnd here is that helper.\n\n-- >8 --\nSubject: [PATCH] contrib: add credential helper for OS X Keychain\n\nWith this installed in your $PATH, you can store\ngit-over-http passwords in your keychain by doing:\n\n  git config credential.helper osxkeychain\n\nThe code is based in large part on the work of Jay Soffian,\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=osxkeychain \\\n  GIT_TEST_CREDENTIAL_HELPER_SETUP=\"export HOME=$HOME\" \\\n  ./t0303-credential-external.sh\n\nThe \"HOME\" setup is unfortunately necessary. The test\nscripts set HOME to the trash directory, but this causes the\nkeychain API to complain.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\nI tried clever workarounds for the HOME thing, like running \"security\ncreate-keychain\" in the trash directory. But if I try to create the\n\"login\" keychain, it says it's already there! So it's like we're in a\nweird limbo where we both have and do not have access to the keychains.\n\nI'm not too concerned with the hackishness of my solution, though; this\nisn't even a part of the regular test scripts, but just a thing you can\ndo manually to test the helper from contrib.\n\nMany thanks to Jay for the initial version; even though this version is\nquite chopped-up, having somebody else provide a nice example of how to\nuse the SecKeychain functions made it much easier. I tried to trim the\ncode down to the bare essentials to glue our input into the SecKeychain\nAPI, and to show good practices for other helpers to follow.\n\nI dropped the python version, as it is redundant and I didn't feel like\nporting it to the new interface. It could be added on top if somebody\nfeels like converting it as an exercise. :)\n\n contrib/credential/osxkeychain/.gitignore          |    1 +\n contrib/credential/osxkeychain/Makefile            |   14 ++\n .../osxkeychain/git-credential-osxkeychain.c       |  173 ++++++++++++++++++++\n 3 files changed, 188 insertions(+), 0 deletions(-)\n create mode 100644 contrib/credential/osxkeychain/.gitignore\n create mode 100644 contrib/credential/osxkeychain/Makefile\n create mode 100644 contrib/credential/osxkeychain/git-credential-osxkeychain.c\n\ndiff --git a/contrib/credential/osxkeychain/.gitignore b/contrib/credential/osxkeychain/.gitignore\nnew file mode 100644\nindex 0000000..6c5b702\n--- /dev/null\n+++ b/contrib/credential/osxkeychain/.gitignore\n@@ -0,0 +1 @@\n+git-credential-osxkeychain\ndiff --git a/contrib/credential/osxkeychain/Makefile b/contrib/credential/osxkeychain/Makefile\nnew file mode 100644\nindex 0000000..75c07f8\n--- /dev/null\n+++ b/contrib/credential/osxkeychain/Makefile\n@@ -0,0 +1,14 @@\n+all:: git-credential-osxkeychain\n+\n+CC = gcc\n+RM = rm -f\n+CFLAGS = -g -Wall\n+\n+git-credential-osxkeychain: git-credential-osxkeychain.o\n+\t$(CC) -o $@ $< -Wl,-framework -Wl,Security\n+\n+git-credential-osxkeychain.o: git-credential-osxkeychain.c\n+\t$(CC) -c $(CFLAGS) $<\n+\n+clean:\n+\t$(RM) git-credential-osxkeychain git-credential-osxkeychain.o\ndiff --git a/contrib/credential/osxkeychain/git-credential-osxkeychain.c b/contrib/credential/osxkeychain/git-credential-osxkeychain.c\nnew file mode 100644\nindex 0000000..6beed12\n--- /dev/null\n+++ b/contrib/credential/osxkeychain/git-credential-osxkeychain.c\n@@ -0,0 +1,173 @@\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+\tNULL, /* default keychain */ \\\n+\tKEYCHAIN_ITEM(host), \\\n+\t0, NULL, /* account domain */ \\\n+\tKEYCHAIN_ITEM(username), \\\n+\tKEYCHAIN_ITEM(path), \\\n+\tport, \\\n+\tprotocol, \\\n+\tkSecAuthenticationTypeDefault\n+\n+static void write_item(const char *what, const char *buf, int len)\n+{\n+\tprintf(\"%s=\", what);\n+\tfwrite(buf, 1, len, stdout);\n+\tputchar('\\n');\n+}\n+\n+static void find_username_in_item(SecKeychainItemRef item)\n+{\n+\tSecKeychainAttributeList list;\n+\tSecKeychainAttribute attr;\n+\n+\tlist.count = 1;\n+\tlist.attr = &attr;\n+\tattr.tag = kSecAccountItemAttr;\n+\n+\tif (SecKeychainItemCopyContent(item, NULL, &list, NULL, NULL))\n+\t\treturn;\n+\n+\twrite_item(\"username\", attr.data, attr.length);\n+\tSecKeychainItemFreeContent(&list, NULL);\n+}\n+\n+static void find_internet_password(void)\n+{\n+\tvoid *buf;\n+\tUInt32 len;\n+\tSecKeychainItemRef item;\n+\n+\tif (SecKeychainFindInternetPassword(KEYCHAIN_ARGS, &len, &buf, &item))\n+\t\treturn;\n+\n+\twrite_item(\"password\", buf, len);\n+\tif (!username)\n+\t\tfind_username_in_item(item);\n+\n+\tSecKeychainItemFreeContent(NULL, buf);\n+}\n+\n+static void delete_internet_password(void)\n+{\n+\tSecKeychainItemRef item;\n+\n+\t/*\n+\t * Require at least a protocol and host for removal, which is what git\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+\n+\tif (SecKeychainFindInternetPassword(KEYCHAIN_ARGS, 0, NULL, &item))\n+\t\treturn;\n+\n+\tSecKeychainItemDelete(item);\n+}\n+\n+static void add_internet_password(void)\n+{\n+\t/* Only store complete credentials */\n+\tif (!protocol || !host || !username || !password)\n+\t\treturn;\n+\n+\tif (SecKeychainAddInternetPassword(\n+\t      KEYCHAIN_ARGS,\n+\t      KEYCHAIN_ITEM(password),\n+\t      NULL))\n+\t\treturn;\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\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+}\n+\n+int main(int argc, const char **argv)\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-- \n1.7.8.rc2.40.gaf387\n"},{"id":"180780","messageId":"m3r50chcvm.fsf@localhost.localdomain","threadId":"29128","inReplyTo":"20111210103111.GC16529@sigill.intra.peff.net","subject":"Re: [PATCHv3 03/13] introduce credentials API","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2011-12-10T11:43:01Z","receivedAt":"2011-12-10T11:43:01Z","isPatch":false,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> There are a few places in git that need to get a username\n> and password credential from the user; the most notable one\n> is HTTP authentication for smart-http pushing.\n\nA question: does it work also for access via SSH, either without\npublic key set up (i.e. 'keyboard-interactive'), or with encrypted\nprivate key without ssh-agent set up?\n\nIt would probably require URL i.e. ssh://git.example.com/srv/scm/repo.git\nor git+ssh://git.example.com/srv/scm/repo.git and not scp-like\naddress i.e. user@git.example.com:/srv/scm/repo.git, isn't it?\n\n-- \nJakub Narębski\n"},{"id":"180781","messageId":"m3mxb0hcjs.fsf@localhost.localdomain","threadId":"29128","inReplyTo":"20111210103133.GH16529@sigill.intra.peff.net","subject":"Re: [PATCHv3 08/13] credential: make relevance of http path configurable","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2011-12-10T11:50:11Z","receivedAt":"2011-12-10T11:50:11Z","isPatch":false,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n[...]\n> This is nothing you couldn't do with a clever credential\n> helper at the start of your stack, like:\n> \n>   [credential \"http://\"]\n> \thelper = \"!f() { grep -v ^path= ; }; f\"\n> \thelper = your_real_helper\n> \n> But doing this:\n> \n>   [credential]\n> \tuseHttpPath = false\n\nShouldn't this be 'usePath' or 'usePathComponent' or 'useRepositoryPath',\netc.?  Because if^W when remote helper for Subversion is complete, you\ncould have svn://svnserve.example.com/path/to/repo as an URL... which\nwould be not HTTP(S). \n\n-- \nJakub Narębski\n"},{"id":"180782","messageId":"m3iplohc6s.fsf@localhost.localdomain","threadId":"29128","inReplyTo":"20111210103420.GK16529@sigill.intra.peff.net","subject":"Re: [PATCHv3 11/13] strbuf: add strbuf_add*_urlencode","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2011-12-10T11:57:59Z","receivedAt":"2011-12-10T11:57:59Z","isPatch":false,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> +void strbuf_add_urlencode(struct strbuf *sb, const char *s, size_t len,\n> +\t\t\t  int reserved)\n> +{\n> +\tstrbuf_grow(sb, len);\n\nWhat is this `reserved` flag for, and when should one use it?\nIt would be nice to have a short comment...\n\nBTW. was it meant to be aligned like this?\n\n> +void strbuf_add_urlencode(struct strbuf *sb, const char *s, size_t len,\n> +\t\t\t     int reserved)\n\n\n-- \nJakub Narębski\n"},{"id":"180808","messageId":"20111210194801.GA17999@sigill.intra.peff.net","threadId":"29128","inReplyTo":"m3r50chcvm.fsf@localhost.localdomain","subject":"Re: [PATCHv3 03/13] introduce credentials API","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-12-10T19:48:01Z","receivedAt":"2011-12-10T19:48:01Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sat, Dec 10, 2011 at 03:43:01AM -0800, Jakub Narebski wrote:\n\n> Jeff King <peff@peff.net> writes:\n> \n> > There are a few places in git that need to get a username\n> > and password credential from the user; the most notable one\n> > is HTTP authentication for smart-http pushing.\n> \n> A question: does it work also for access via SSH, either without\n> public key set up (i.e. 'keyboard-interactive'), or with encrypted\n> private key without ssh-agent set up?\n\nNo. ssh handles its own password querying, and contacts the user\ndirectly via the terminal. And there's not much point in using a\npassword helper with ssh; if you don't want to type your password, set\nup a key and use ssh-agent.\n\n> It would probably require URL i.e. ssh://git.example.com/srv/scm/repo.git\n> or git+ssh://git.example.com/srv/scm/repo.git and not scp-like\n> address i.e. user@git.example.com:/srv/scm/repo.git, isn't it?\n\nIt's not a matter of recognizing the URL; it's that we hand off the\nauthentication problem to ssh, which takes care of it entirely itself.\n\n-Peff\n"},{"id":"180809","messageId":"20111210195033.GB17999@sigill.intra.peff.net","threadId":"29128","inReplyTo":"m3mxb0hcjs.fsf@localhost.localdomain","subject":"Re: [PATCHv3 08/13] credential: make relevance of http path configurable","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-12-10T19:50:33Z","receivedAt":"2011-12-10T19:50:33Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sat, Dec 10, 2011 at 03:50:11AM -0800, Jakub Narebski wrote:\n\n> > This is nothing you couldn't do with a clever credential\n> > helper at the start of your stack, like:\n> > \n> >   [credential \"http://\"]\n> > \thelper = \"!f() { grep -v ^path= ; }; f\"\n> > \thelper = your_real_helper\n> > \n> > But doing this:\n> > \n> >   [credential]\n> > \tuseHttpPath = false\n> \n> Shouldn't this be 'usePath' or 'usePathComponent' or 'useRepositoryPath',\n> etc.?  Because if^W when remote helper for Subversion is complete, you\n> could have svn://svnserve.example.com/path/to/repo as an URL... which\n> would be not HTTP(S).\n\nIt must be per-protocol, because paths will be relevant for some\nprotocols (e.g., unlocking certificates in the local filesystem).\nSo if anything, it would be \"useNetworkPath\" or something, if we wanted\nto unify svn and http. But it would also be OK to have a separate flag\nfor http and svn, which is more flexible (and most users aren't going to\nset it at all, so they won't notice).\n\n-Peff\n"},{"id":"180810","messageId":"20111210200909.GC17999@sigill.intra.peff.net","threadId":"29128","inReplyTo":"m3iplohc6s.fsf@localhost.localdomain","subject":"Re: [PATCHv3 11/13] strbuf: add strbuf_add*_urlencode","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-12-10T20:09:09Z","receivedAt":"2011-12-10T20:09:09Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sat, Dec 10, 2011 at 03:57:59AM -0800, Jakub Narebski wrote:\n\n> Jeff King <peff@peff.net> writes:\n> \n> > +void strbuf_add_urlencode(struct strbuf *sb, const char *s, size_t len,\n> > +\t\t\t  int reserved)\n> > +{\n> > +\tstrbuf_grow(sb, len);\n> \n> What is this `reserved` flag for, and when should one use it?\n> It would be nice to have a short comment...\n\nIt indicates whether one should encode rfc3986 reserved characters. You\nwant to use it when encoding the host, username, and password portions\nof a URL (i.e., before the \"/\"), but not the path (since you don't want\nto encode all of the slashes). If you were breaking down the path more\n(e.g., into a \"query\" and \"fragment\" portion), you would care about more\nspecific quoting there, but we don't; we treat everything after the\nslash as an opaque blob of path.\n\nPatch to the strbuf api documentation is below. I think it should be\nsquashed into patch 12/13.\n\n> BTW. was it meant to be aligned like this?\n> \n> > +void strbuf_add_urlencode(struct strbuf *sb, const char *s, size_t len,\n> > +\t\t\t     int reserved)\n\nIt is aligned correctly. When you ident by tabs, the \"+\" of the diff\ngets soaked in the first tabstop, so it is off-by-one with respect to\nnon-tabbed input (it is off even more in the quoted section above,\nbecause \"> > +\" gets soaked into the first tabstop). You can see your\nversion above also is misaligned when I quote it. :)\n\nIf you apply the diff, the result is as you expected.\n\n-Peff\n\n---\ndiff --git a/Documentation/technical/api-strbuf.txt b/Documentation/technical/api-strbuf.txt\nindex afe2759..e1ab6c5 100644\n--- a/Documentation/technical/api-strbuf.txt\n+++ b/Documentation/technical/api-strbuf.txt\n@@ -270,3 +270,14 @@ same behaviour as well.\n \tthird argument can be used to set the environment which the editor is\n \trun in. If the buffer is NULL the editor is launched as usual but the\n \tfile's contents are not read into the buffer upon completion.\n+\n+`strbuf_add_urlencode`::\n+\n+\tCopy data to the end of the buffer, percent-encoding it as per\n+\trfc3986. If the reserved flag is non-zero, then characters in\n+\tthe rfc3986 reserved list are percent-encoded; otherwise, they\n+\tare copied literally. Characters in the rfc3986 unreserved list\n+\tare always copied literally. All other characters are\n+\tpercent-encoded. Typically, one would use the reserved flag for\n+\tthe host and user-info sections of a URL, but leave special\n+\tcharacters in the path untouched.\n"},{"id":"180965","messageId":"4EE66DAB.5070407@kdbg.org","threadId":"29128","inReplyTo":"20111210104130.GI16648@sigill.intra.peff.net","subject":"[PATCH 1/2] Makefile: Windows lacks /dev/tty","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2011-12-12T21:10:03Z","receivedAt":"2011-12-12T21:10:03Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Signed-off-by: Johannes Sixt <j6t@kdbg.org>\n---\nThese two patches go on top of jk/credentials.\n\n Makefile |    2 ++\n 1 files changed, 2 insertions(+), 0 deletions(-)\n\ndiff --git a/Makefile b/Makefile\nindex 2c506b3..5b7f6a8 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1139,6 +1139,7 @@ ifeq ($(uname_S),Windows)\n \tNO_CURL = YesPlease\n \tNO_PYTHON = YesPlease\n \tBLK_SHA1 = YesPlease\n+\tNO_DEV_TTY = YesPlease\n \tNO_POSIX_GOODIES = UnfortunatelyYes\n \tNATIVE_CRLF = YesPlease\n \n@@ -1232,6 +1233,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))\n \tETAGS_TARGET = ETAGS\n \tNO_INET_PTON = YesPlease\n \tNO_INET_NTOP = YesPlease\n+\tNO_DEV_TTY = YesPlease\n \tNO_POSIX_GOODIES = UnfortunatelyYes\n \tCOMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/win32\n \tCOMPAT_CFLAGS += -DSTRIP_EXTENSION=\\\".exe\\\"\n-- \n1.7.8.216.g2e426\n"},{"id":"180966","messageId":"4EE66E58.6040404@kdbg.org","threadId":"29128","inReplyTo":"4EE66DAB.5070407@kdbg.org","subject":"[PATCH 2/2] Makefile: optionally exclude code that needs Unix sockets","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2011-12-12T21:12:56Z","receivedAt":"2011-12-12T21:12:56Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Introduce a configuration option NO_UNIX_SOCKETS to exclude code that\ndepends on Unix sockets and use it in MSVC and MinGW builds.\n\nNotice that unix-socket.h was missing from LIB_H before; fix that, too.\n\nSigned-off-by: Johannes Sixt <j6t@kdbg.org>\n---\nAll or most of the tests introduced by the series fail on Windows.\nWhat is the preferred way to exclude them? Mark the all with a\nprerequisite? Or exit early at the beginning of the test file?\n\n Makefile |   13 ++++++++++---\n 1 files changed, 10 insertions(+), 3 deletions(-)\n\ndiff --git a/Makefile b/Makefile\nindex 5b7f6a8..77c85db 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -143,6 +143,8 @@ all::\n #\n # Define NO_IPV6 if you lack IPv6 support and getaddrinfo().\n #\n+# Define NO_UNIX_SOCKETS if your system does not offer unix sockets.\n+#\n # Define NO_SOCKADDR_STORAGE if your platform does not have struct\n # sockaddr_storage.\n #\n@@ -430,8 +432,6 @@ PROGRAM_OBJS += show-index.o\n PROGRAM_OBJS += upload-pack.o\n PROGRAM_OBJS += http-backend.o\n PROGRAM_OBJS += sh-i18n--envsubst.o\n-PROGRAM_OBJS += credential-cache.o\n-PROGRAM_OBJS += credential-cache--daemon.o\n PROGRAM_OBJS += credential-store.o\n \n PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS))\n@@ -709,7 +709,6 @@ LIB_OBJS += transport-helper.o\n LIB_OBJS += tree-diff.o\n LIB_OBJS += tree.o\n LIB_OBJS += tree-walk.o\n-LIB_OBJS += unix-socket.o\n LIB_OBJS += unpack-trees.o\n LIB_OBJS += url.o\n LIB_OBJS += usage.o\n@@ -1112,6 +1111,7 @@ ifeq ($(uname_S),Windows)\n \tNO_SYS_POLL_H = YesPlease\n \tNO_SYMLINK_HEAD = YesPlease\n \tNO_IPV6 = YesPlease\n+\tNO_UNIX_SOCKETS = YesPlease\n \tNO_SETENV = YesPlease\n \tNO_UNSETENV = YesPlease\n \tNO_STRCASESTR = YesPlease\n@@ -1206,6 +1206,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))\n \tNO_LIBGEN_H = YesPlease\n \tNO_SYS_POLL_H = YesPlease\n \tNO_SYMLINK_HEAD = YesPlease\n+\tNO_UNIX_SOCKETS = YesPlease\n \tNO_SETENV = YesPlease\n \tNO_UNSETENV = YesPlease\n \tNO_STRCASESTR = YesPlease\n@@ -1584,6 +1585,12 @@ ifdef NO_INET_PTON\n \tLIB_OBJS += compat/inet_pton.o\n \tBASIC_CFLAGS += -DNO_INET_PTON\n endif\n+ifndef NO_UNIX_SOCKETS\n+\tLIB_OBJS += unix-socket.o\n+\tLIB_H += unix-socket.h\n+\tPROGRAM_OBJS += credential-cache.o\n+\tPROGRAM_OBJS += credential-cache--daemon.o\n+endif\n \n ifdef NO_ICONV\n \tBASIC_CFLAGS += -DNO_ICONV\n-- \n1.7.8.216.g2e426\n"},{"id":"180972","messageId":"20111212211801.GA9754@sigill.intra.peff.net","threadId":"29128","inReplyTo":"4EE66DAB.5070407@kdbg.org","subject":"Re: [PATCH 1/2] Makefile: Windows lacks /dev/tty","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-12-12T21:18:02Z","receivedAt":"2011-12-12T21:18:02Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Dec 12, 2011 at 10:10:03PM +0100, Johannes Sixt wrote:\n\n> diff --git a/Makefile b/Makefile\n> index 2c506b3..5b7f6a8 100644\n> --- a/Makefile\n> +++ b/Makefile\n> @@ -1139,6 +1139,7 @@ ifeq ($(uname_S),Windows)\n>  \tNO_CURL = YesPlease\n>  \tNO_PYTHON = YesPlease\n>  \tBLK_SHA1 = YesPlease\n> +\tNO_DEV_TTY = YesPlease\n>  \tNO_POSIX_GOODIES = UnfortunatelyYes\n>  \tNATIVE_CRLF = YesPlease\n>  \n> @@ -1232,6 +1233,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))\n>  \tETAGS_TARGET = ETAGS\n>  \tNO_INET_PTON = YesPlease\n>  \tNO_INET_NTOP = YesPlease\n> +\tNO_DEV_TTY = YesPlease\n>  \tNO_POSIX_GOODIES = UnfortunatelyYes\n>  \tCOMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/win32\n>  \tCOMPAT_CFLAGS += -DSTRIP_EXTENSION=\\\".exe\\\"\n\nUnless I've bungled something, these should be no-ops, shouldn't they?\nThe most recent version of the prompt series has platforms opting into\nthe replacement with HAVE_DEV_TTY.\n\n-Peff\n"},{"id":"180974","messageId":"20111212213951.GB9754@sigill.intra.peff.net","threadId":"29128","inReplyTo":"4EE66E58.6040404@kdbg.org","subject":"Re: [PATCH 2/2] Makefile: optionally exclude code that needs Unix sockets","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-12-12T21:39:51Z","receivedAt":"2011-12-12T21:39:51Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Dec 12, 2011 at 10:12:56PM +0100, Johannes Sixt wrote:\n\n> Introduce a configuration option NO_UNIX_SOCKETS to exclude code that\n> depends on Unix sockets and use it in MSVC and MinGW builds.\n\nMakes sense, thanks.\n\n> Notice that unix-socket.h was missing from LIB_H before; fix that, too.\n\nOops, thanks.\n\n> All or most of the tests introduced by the series fail on Windows.\n> What is the preferred way to exclude them? Mark the all with a\n> prerequisite? Or exit early at the beginning of the test file?\n\nThe cache daemon is the only thing that uses sockets. I would suggest\njust exiting early from its script (patch below).\n\nThe rest of the tests should pass under Windows. If they don't, then\nthey should be fixed (I'm happy to work on that, with the limitation\nthat I don't actually have a Windows box, and if at all possible I'd\nlike to keep it that way).\n\nIn theory we should also disable the documentation for credential-cache.\nBut that means surgery not only to Documentation/Makefile, but figuring\nout how to pass the flags down to the actual asciidoc process (since\ngitcredentials(7) mentions it in the text). Certainly possible, but I\ndon't know if it's worth the effort or not.\n\n---\ndiff --git a/Makefile b/Makefile\nindex e3ee884..6e7e190 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2220,6 +2220,7 @@ ifdef GIT_TEST_CMP_USE_COPIED_CONTEXT\n \t@echo GIT_TEST_CMP_USE_COPIED_CONTEXT=YesPlease >>$@\n endif\n \t@echo GETTEXT_POISON=\\''$(subst ','\\'',$(subst ','\\'',$(GETTEXT_POISON)))'\\' >>$@\n+\t@echo NO_UNIX_SOCKETS=\\''$(subst ','\\'',$(subst ','\\'',$(NO_UNIX_SOCKETS)))'\\' >>$@\n \n ### Detect Tck/Tk interpreter path changes\n ifndef NO_TCLTK\ndiff --git a/t/t0301-credential-cache.sh b/t/t0301-credential-cache.sh\nindex 3a65f99..82c8411 100755\n--- a/t/t0301-credential-cache.sh\n+++ b/t/t0301-credential-cache.sh\n@@ -4,6 +4,11 @@ test_description='credential-cache tests'\n . ./test-lib.sh\n . \"$TEST_DIRECTORY\"/lib-credential.sh\n \n+test -z \"$NO_UNIX_SOCKETS\" || {\n+\tskip_all='skipping credential-cache tests, unix sockets not available'\n+\ttest_done\n+}\n+\n # don't leave a stale daemon running\n trap 'code=$?; git credential-cache exit; (exit $code); die' EXIT\n \n"},{"id":"180976","messageId":"4EE6778B.4000803@kdbg.org","threadId":"29128","inReplyTo":"20111212211801.GA9754@sigill.intra.peff.net","subject":"Re: [PATCH 1/2] Makefile: Windows lacks /dev/tty","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2011-12-12T21:52:11Z","receivedAt":"2011-12-12T21:52:11Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 12.12.2011 22:18, schrieb Jeff King:\n> The most recent version of the prompt series has platforms opting into\n> the replacement with HAVE_DEV_TTY.\n\nI see. Obviously, I've tested an earlier iteration. This patch can be\ndropped then.\n\n-- Hannes\n"},{"id":"180990","messageId":"7v1us91i5b.fsf@alter.siamese.dyndns.org","threadId":"29128","inReplyTo":"20111212213951.GB9754@sigill.intra.peff.net","subject":"Re: [PATCH 2/2] Makefile: optionally exclude code that needs Unix sockets","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-12-12T23:31:44Z","receivedAt":"2011-12-12T23:31:44Z","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> In theory we should also disable the documentation for credential-cache.\n> But that means surgery not only to Documentation/Makefile, but figuring\n> out how to pass the flags down to the actual asciidoc process (since\n> gitcredentials(7) mentions it in the text). Certainly possible, but I\n> don't know if it's worth the effort or not.\n\nI do not think it matters that much. We've been shipping documentation for\nstuff like remote archiver and daemon without conditional compilation, no?\n"},{"id":"181009","messageId":"7vzkexwb7m.fsf@alter.siamese.dyndns.org","threadId":"29128","inReplyTo":"20111212213951.GB9754@sigill.intra.peff.net","subject":"Re: [PATCH 2/2] Makefile: optionally exclude code that needs Unix sockets","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-12-13T00:45:49Z","receivedAt":"2011-12-13T00:45:49Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"I'll queue a single patch that is a squash between 2/2 and Peff's test\nupdates between \"credentials: add \"cache\" helper\" and \"strbuf: add\nstrbuf_add*_urlencode\" in the series.\n\nThanks.\n"},{"id":"181013","messageId":"20111213005812.GA3947@sigill.intra.peff.net","threadId":"29128","inReplyTo":"7vzkexwb7m.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH 2/2] Makefile: optionally exclude code that needs Unix sockets","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-12-13T00:58:12Z","receivedAt":"2011-12-13T00:58:12Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Dec 12, 2011 at 03:31:44PM -0800, Junio C Hamano wrote:\n\n> > In theory we should also disable the documentation for credential-cache.\n> > But that means surgery not only to Documentation/Makefile, but figuring\n> > out how to pass the flags down to the actual asciidoc process (since\n> > gitcredentials(7) mentions it in the text). Certainly possible, but I\n> > don't know if it's worth the effort or not.\n> \n> I do not think it matters that much. We've been shipping documentation for\n> stuff like remote archiver and daemon without conditional compilation, no?\n\nTrue. Let's not worry about it, then.\n\n> I'll queue a single patch that is a squash between 2/2 and Peff's test\n> updates between \"credentials: add \"cache\" helper\" and \"strbuf: add\n> strbuf_add*_urlencode\" in the series.\n\nThat's perfect. Thanks.\n\n-Peff\n"},{"id":"181068","messageId":"4EE7AEDA.6090509@kdbg.org","threadId":"29128","inReplyTo":"7vzkexwb7m.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH 2/2] Makefile: optionally exclude code that needs Unix sockets","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2011-12-13T20:00:26Z","receivedAt":"2011-12-13T20:00:26Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 13.12.2011 01:45, schrieb Junio C Hamano:\n> I'll queue a single patch that is a squash between 2/2 and Peff's test\n> updates between \"credentials: add \"cache\" helper\" and \"strbuf: add\n> strbuf_add*_urlencode\" in the series.\n\nThanks. The resulting series builds fine on Windows and passes/skips the\nnew tests in the expected manner.\n\n-- Hannes\n"},{"id":"181101","messageId":"7vhb14t3g5.fsf@alter.siamese.dyndns.org","threadId":"29128","inReplyTo":"4EE7AEDA.6090509@kdbg.org","subject":"Re: [PATCH 2/2] Makefile: optionally exclude code that needs Unix sockets","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-12-14T00:14:02Z","receivedAt":"2011-12-14T00:14:02Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Sixt <j6t@kdbg.org> writes:\n\n> Am 13.12.2011 01:45, schrieb Junio C Hamano:\n>> I'll queue a single patch that is a squash between 2/2 and Peff's test\n>> updates between \"credentials: add \"cache\" helper\" and \"strbuf: add\n>> strbuf_add*_urlencode\" in the series.\n>\n> Thanks. The resulting series builds fine on Windows and passes/skips the\n> new tests in the expected manner.\n\nThanks. Let's advance the topic forward then.\n"},{"id":"181223","messageId":"20111215124851.GA6907@padd.com","threadId":"29128","inReplyTo":"20111210104101.GE16648@sigill.intra.peff.net","subject":"Re: [PATCHv2 5/9] add generic terminal prompt function","fromName":"Pete Wyckoff","fromEmail":"pw@padd.com","sentAt":"2011-12-15T12:48:51Z","receivedAt":"2011-12-15T12:48:51Z","isPatch":false,"sender":{"key":"pw@padd.com","avatar":null},"body":"peff@peff.net wrote on Sat, 10 Dec 2011 05:41 -0500:\n> +static struct termios old_term;\n> +\n> +static void restore_term(void)\n> +{\n> +\tif (term_fd < 0)\n> +\t\treturn;\n> +\n> +\ttcsetattr(term_fd, TCSAFLUSH, &old_term);\n> +\tterm_fd = -1;\n> +}\n\nRestores from static old_term.\n\n> +char *git_terminal_prompt(const char *prompt, int echo)\n> +{\n> +\tstatic struct strbuf buf = STRBUF_INIT;\n> +\tint r;\n> +\tFILE *fh;\n> +\n> +\tfh = fopen(\"/dev/tty\", \"w+\");\n> +\tif (!fh)\n> +\t\treturn NULL;\n> +\n> +\tif (!echo) {\n> +\t\tstruct termios t;\n> +\n> +\t\tif (tcgetattr(fileno(fh), &t) < 0) {\n> +\t\t\tfclose(fh);\n> +\t\t\treturn NULL;\n> +\t\t}\n> +\n> +\t\told_term = t;\n\nWhich is only saved if echo is true.\n\n> +\t\tterm_fd = fileno(fh);\n> +\t\tsigchain_push_common(restore_term_on_signal);\n> +\n> +\t\tt.c_lflag &= ~ECHO;\n> +\t\tif (tcsetattr(fileno(fh), TCSAFLUSH, &t) < 0) {\n> +\t\t\tterm_fd = -1;\n> +\t\t\tfclose(fh);\n> +\t\t\treturn NULL;\n> +\t\t}\n> +\t}\n> +\n> +\tfputs(prompt, fh);\n> +\tfflush(fh);\n> +\n> +\tr = strbuf_getline(&buf, fh, '\\n');\n> +\tif (!echo) {\n> +\t\tputc('\\n', fh);\n> +\t\tfflush(fh);\n> +\t}\n> +\n> +\trestore_term();\n\nPerhaps this line should go in !echo.\n\nAnd why no sigchain_pop() for the signal handler?\n\n\t\t-- Pete\n"},{"id":"181224","messageId":"20111215133939.GA2241@sigill.intra.peff.net","threadId":"29128","inReplyTo":"20111215124851.GA6907@padd.com","subject":"Re: [PATCHv2 5/9] add generic terminal prompt function","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-12-15T13:39:39Z","receivedAt":"2011-12-15T13:39:39Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Dec 15, 2011 at 07:48:51AM -0500, Pete Wyckoff wrote:\n\n> peff@peff.net wrote on Sat, 10 Dec 2011 05:41 -0500:\n> > +static struct termios old_term;\n> > +\n> > +static void restore_term(void)\n> > +{\n> > +\tif (term_fd < 0)\n> > +\t\treturn;\n> > +\n> > +\ttcsetattr(term_fd, TCSAFLUSH, &old_term);\n> > +\tterm_fd = -1;\n> > +}\n> \n> Restores from static old_term.\n\nRight. But note that it is protected by term_fd being set.\n\n> > +char *git_terminal_prompt(const char *prompt, int echo)\n> > +{\n> > +\tstatic struct strbuf buf = STRBUF_INIT;\n> > +\tint r;\n> > +\tFILE *fh;\n> > +\n> > +\tfh = fopen(\"/dev/tty\", \"w+\");\n> > +\tif (!fh)\n> > +\t\treturn NULL;\n> > +\n> > +\tif (!echo) {\n> > +\t\tstruct termios t;\n> > +\n> > +\t\tif (tcgetattr(fileno(fh), &t) < 0) {\n> > +\t\t\tfclose(fh);\n> > +\t\t\treturn NULL;\n> > +\t\t}\n> > +\n> > +\t\told_term = t;\n> \n> Which is only saved if echo is true.\n\nYes, but just below:\n\n> > +\t\tterm_fd = fileno(fh);\n> > +\t\tsigchain_push_common(restore_term_on_signal);\n\nWe set up term_fd, and then:\n\n> > +\t\tt.c_lflag &= ~ECHO;\n> > +\t\tif (tcsetattr(fileno(fh), TCSAFLUSH, &t) < 0) {\n> > +\t\t\tterm_fd = -1;\n> > +\t\t\tfclose(fh);\n> > +\t\t\treturn NULL;\n> > +\t\t}\n\nOn error, disable it again.\n\n> > +\t}\n> > +\n> > +\tfputs(prompt, fh);\n> > +\tfflush(fh);\n> > +\n> > +\tr = strbuf_getline(&buf, fh, '\\n');\n> > +\tif (!echo) {\n> > +\t\tputc('\\n', fh);\n> > +\t\tfflush(fh);\n> > +\t}\n> > +\n> > +\trestore_term();\n> \n> Perhaps this line should go in !echo.\n\nIt could, but it's a no-op as-is, as term_fd will be -1.\n\nI agree it might be a little more obvious to put it there (I think what\nhappened is my initial revision did not look at \"echo\" ever again, and\nthen that conditional was added later when I realized that the \"!echo\"\ncase needed us to print the newline manually).\n\n> And why no sigchain_pop() for the signal handler?\n\nBecause I used sigchain_push_common, which has no pop_common analog. But\nit's OK, because calling restore_term sets term_fd to -1, making further\ncalls a no-op. So leaving the handler in place is fine.\n\nAnother option would be to add sigchain_pop_common, which pops the\nsame signals from push_common.\n\n-Peff\n"},{"id":"181263","messageId":"20111215215903.GA1917@padd.com","threadId":"29128","inReplyTo":"20111215133939.GA2241@sigill.intra.peff.net","subject":"Re: [PATCHv2 5/9] add generic terminal prompt function","fromName":"Pete Wyckoff","fromEmail":"pw@padd.com","sentAt":"2011-12-15T21:59:03Z","receivedAt":"2011-12-15T21:59:03Z","isPatch":false,"sender":{"key":"pw@padd.com","avatar":null},"body":"peff@peff.net wrote on Thu, 15 Dec 2011 08:39 -0500:\n> I agree it might be a little more obvious to put it there (I think what\n> happened is my initial revision did not look at \"echo\" ever again, and\n> then that conditional was added later when I realized that the \"!echo\"\n> case needed us to print the newline manually).\n> \n> > And why no sigchain_pop() for the signal handler?\n> \n> Because I used sigchain_push_common, which has no pop_common analog. But\n> it's OK, because calling restore_term sets term_fd to -1, making further\n> calls a no-op. So leaving the handler in place is fine.\n> \n> Another option would be to add sigchain_pop_common, which pops the\n> same signals from push_common.\n\nThanks for the detailed explanation.  It was indeed the lack of\nsymmetry that set me off.  I missed the term_fd check for both.\n\n\t\t-- Pete\n"},{"id":"182197","messageId":"20120110015038.GA17754@burratino","threadId":"29128","inReplyTo":"20111210103407.GJ16529@sigill.intra.peff.net","subject":"Re: [PATCHv3 10/13] credentials: add \"cache\" helper","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2012-01-10T01:50:38Z","receivedAt":"2012-01-10T01:50:38Z","isPatch":false,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Hi,\n\nJeff King wrote:\n\n> This patch introduces a credential helper that will cache\n> passwords in memory for a short period of time.\n>\n> Signed-off-by: Jeff King <peff@peff.net>\n[...]\n> t/t0301-credential-cache.sh                    |   18 ++\n\nThis test is failing for me:\n\n\texpecting success: \n\t\t\tcheck fill $HELPER <<-\\EOF\n\t\t\tprotocol=https\n\t\t\thost=example.com\n\t\t\t--\n\t\t\tusername=askpass-username\n\t\t\tpassword=askpass-password\n\t\t\t--\n\t\t\taskpass: Username for 'https://example.com':\n\t\t\taskpass: Password for 'https://askpass-username@example.com':\n\t\t\tEOF\n\n\t--- expect-stderr       2012-01-10 00:07:02.398365248 +0000\n\t+++ stderr      2012-01-10 00:07:02.418364996 +0000\n\t@@ -1,2 +1,3 @@\n\t+fatal: socket path is too long to fit in sockaddr\n\t askpass: Username for 'https://example.com':\n\t askpass: Password for 'https://askpass-username@example.com':\n\tnot ok - 1 helper (cache) has no existing data\n\nI didn't notice until recently because typically the cwd is short.\nThe sun_path[] array on this machine is 108 bytes; POSIX informs me\nthat some platforms make it as small as 96 bytes and there's no\nguaranteed lower limit.  The machines[*] triggering it were running\ntests in a chroot, hence the long path.\n\nSo you should be able to reproduce this with\n\n\tlongpath=foo/bar; # > 6 chars\n\tlongpath=$longpath/$longpath/$longpath/$longpath; # > 24\n\tlongpath=$longpath/$longpath/$longpath/$longpath; # > 96\n\tlongpath=/tmp/$longpath/$longpath\n\tmkdir -p $longpath\n\tsh t0301-credential-cache.sh --root=$longpath\n\nIdeas?\n---\n[*] https://buildd.debian.org/status/logs.php?pkg=git&ver=1%3A1.7.9~rc0-1\n\n credential-cache.c |   18 +++++++++++++-----\n 1 files changed, 13 insertions(+), 5 deletions(-)\n\ndiff --git a/credential-cache.c b/credential-cache.c\nindex dc98372e..fd9304e6 100644\n--- a/credential-cache.c\n+++ b/credential-cache.c\n@@ -82,7 +82,7 @@ static void do_cache(const char *socket, const char *action, int timeout,\n \n int main(int argc, const char **argv)\n {\n-\tchar *socket_path = NULL;\n+\tconst char *socket_path = NULL;\n \tint timeout = 900;\n \tconst char *op;\n \tconst char * const usage[] = {\n@@ -102,10 +102,18 @@ int main(int argc, const char **argv)\n \t\tusage_with_options(usage, options);\n \top = argv[0];\n \n-\tif (!socket_path)\n-\t\tsocket_path = expand_user_path(\"~/.git-credential-cache/socket\");\n-\tif (!socket_path)\n-\t\tdie(\"unable to find a suitable socket path; use --socket\");\n+\tif (!socket_path) {\n+\t\tchar *home = expand_user_path(\"~\");\n+\t\tif (!home)\n+\t\t\tdie(\"unable to find a suitable socket path; use --socket\");\n+\n+\t\tif (!chdir(home))\n+\t\t\tsocket_path = \".git-credential-cache/socket\";\n+\t\telse if (errno == ENOENT && !strcmp(op, \"exit\"))\n+\t\t\treturn 0;\n+\t\telse\n+\t\t\tdie(\"cannot enter home directory\");\n+\t}\n \n \tif (!strcmp(op, \"exit\"))\n \t\tdo_cache(socket_path, op, timeout, 0);\n-- \n1.7.8.2\n"},{"id":"182203","messageId":"20120110044430.GA23619@sigill.intra.peff.net","threadId":"29128","inReplyTo":"20120110015038.GA17754@burratino","subject":"Re: [PATCHv3 10/13] credentials: add \"cache\" helper","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2012-01-10T04:44:30Z","receivedAt":"2012-01-10T04:44:30Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Jan 09, 2012 at 07:50:38PM -0600, Jonathan Nieder wrote:\n\n> \t--- expect-stderr       2012-01-10 00:07:02.398365248 +0000\n> \t+++ stderr      2012-01-10 00:07:02.418364996 +0000\n> \t@@ -1,2 +1,3 @@\n> \t+fatal: socket path is too long to fit in sockaddr\n> \t askpass: Username for 'https://example.com':\n> \t askpass: Password for 'https://askpass-username@example.com':\n> \tnot ok - 1 helper (cache) has no existing data\n> \n> I didn't notice until recently because typically the cwd is short.\n> The sun_path[] array on this machine is 108 bytes; POSIX informs me\n> that some platforms make it as small as 96 bytes and there's no\n> guaranteed lower limit.  The machines[*] triggering it were running\n> tests in a chroot, hence the long path.\n\nUgh. Some days I really love working on Unix systems. And then some days\nyou run across junk like this.\n\nPresumably Linux has kept to 108 to avoid breaking older programs.  So\nit's not as if we can assume it will be changed soon, or just write this\noff as a limitation for old crappy systems.\n\nGoogling around, I've seen some indication that you can simply\nover-allocate the sockaddr_un and pass a longer length to connect.\nHowever that just seems to yield EINVAL on Linux (and even if it did\nwork on Linux, I'd be surprised if it worked everywhere).\n\nWhich really leaves us with only one option: chdir and bind to a\nrelative path, as you did below.\n\n> -\tif (!socket_path)\n> -\t\tsocket_path = expand_user_path(\"~/.git-credential-cache/socket\");\n> -\tif (!socket_path)\n> -\t\tdie(\"unable to find a suitable socket path; use --socket\");\n> +\tif (!socket_path) {\n> +\t\tchar *home = expand_user_path(\"~\");\n> +\t\tif (!home)\n> +\t\t\tdie(\"unable to find a suitable socket path; use --socket\");\n> +\n> +\t\tif (!chdir(home))\n> +\t\t\tsocket_path = \".git-credential-cache/socket\";\n> +\t\telse if (errno == ENOENT && !strcmp(op, \"exit\"))\n> +\t\t\treturn 0;\n> +\t\telse\n> +\t\t\tdie(\"cannot enter home directory\");\n> +\t}\n\nI think this is the right approach, but the wrong place to do it. Your\npatch only helps people using the default path (so passing\n--socket=$longpath would still be broken). And we'd need a similar fix\nfor the binding side in credential-cache--daemon.\n\nHere's a patch which passes your $longpath test.\n\n-- >8 --\nSubject: [PATCH] unix-socket: handle long socket pathnames\n\nOn many systems, the sockaddr_un.sun_path field is quite\nsmall. Even on Linux, it is only 108 characters. A user of\nthe credential-cache daemon can easily surpass this,\nespecially if their home directory is in a deep directory\ntree (since the default location expands ~/.git-credentials).\n\nWe can hack around this in the unix-socket.[ch] code by\ndoing a chdir() to the enclosing directory, feeding the\nrelative basename to the socket functions, and then\nrestoring the working directory.\n\nThis introduces several new possible error cases for\ncreating a socket, including an irrecoverable one in the\ncase that we can't restore the working directory. In the\ncase of the credential-cache code, we could perhaps get away\nwith simply chdir()-ing to the socket directory and never\ncoming back. However, I'd rather do it at the lower level\nfor a few reasons:\n\n  1. It keeps the hackery behind an opaque interface instead\n     of polluting the main program logic.\n\n  2. A hack in credential-cache won't help any unix-socket\n     users who come along later.\n\n  3. The chdir trickery isn't that likely to fail (basically\n     it's only a problem if your cwd is missing or goes away\n     while you're running).  And because we only enable the\n     hack when we get a too-long name, it can only fail in\n     cases that would have failed under the previous code\n     anyway.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n unix-socket.c |   74 +++++++++++++++++++++++++++++++++++++++++++++++++++++----\n 1 files changed, 69 insertions(+), 5 deletions(-)\n\ndiff --git a/unix-socket.c b/unix-socket.c\nindex 84b1509..664782a 100644\n--- a/unix-socket.c\n+++ b/unix-socket.c\n@@ -9,27 +9,86 @@ static int unix_stream_socket(void)\n \treturn fd;\n }\n \n-static void unix_sockaddr_init(struct sockaddr_un *sa, const char *path)\n+static int chdir_len(const char *orig, int len)\n+{\n+\tchar *path = xmemdupz(orig, len);\n+\tint r = chdir(path);\n+\tfree(path);\n+\treturn r;\n+}\n+\n+struct unix_sockaddr_context {\n+\tchar orig_dir[PATH_MAX];\n+};\n+\n+static void unix_sockaddr_cleanup(struct unix_sockaddr_context *ctx)\n+{\n+\tif (!ctx->orig_dir[0])\n+\t\treturn;\n+\t/*\n+\t * If we fail, we can't just return an error, since we have\n+\t * moved the cwd of the whole process, which could confuse calling\n+\t * code.  We are better off to just die.\n+\t */\n+\tif (chdir(ctx->orig_dir) < 0)\n+\t\tdie(\"unable to restore original working directory\");\n+}\n+\n+static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,\n+\t\t\t      struct unix_sockaddr_context *ctx)\n {\n \tint size = strlen(path) + 1;\n-\tif (size > sizeof(sa->sun_path))\n-\t\tdie(\"socket path is too long to fit in sockaddr\");\n+\n+\tctx->orig_dir[0] = '\\0';\n+\tif (size > sizeof(sa->sun_path)) {\n+\t\tconst char *slash = find_last_dir_sep(path);\n+\t\tconst char *dir;\n+\t\tint dir_len;\n+\n+\t\tif (!slash) {\n+\t\t\terrno = ENAMETOOLONG;\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tdir = path;\n+\t\tdir_len = slash - path;\n+\n+\t\tpath = slash + 1;\n+\t\tsize = strlen(path) + 1;\n+\t\tif (size > sizeof(sa->sun_path)) {\n+\t\t\terrno = ENAMETOOLONG;\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tif (!getcwd(ctx->orig_dir, sizeof(ctx->orig_dir))) {\n+\t\t\terrno = ENAMETOOLONG;\n+\t\t\treturn -1;\n+\t\t}\n+\t\tif (chdir_len(dir, dir_len) < 0)\n+\t\t\treturn -1;\n+\t}\n+\n \tmemset(sa, 0, sizeof(*sa));\n \tsa->sun_family = AF_UNIX;\n \tmemcpy(sa->sun_path, path, size);\n+\treturn 0;\n }\n \n int unix_stream_connect(const char *path)\n {\n \tint fd;\n \tstruct sockaddr_un sa;\n+\tstruct unix_sockaddr_context ctx;\n \n-\tunix_sockaddr_init(&sa, path);\n+\tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n+\t\treturn -1;\n \tfd = unix_stream_socket();\n \tif (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {\n+\t\tunix_sockaddr_cleanup(&ctx);\n \t\tclose(fd);\n \t\treturn -1;\n \t}\n+\tunix_sockaddr_cleanup(&ctx);\n \treturn fd;\n }\n \n@@ -37,20 +96,25 @@ int unix_stream_listen(const char *path)\n {\n \tint fd;\n \tstruct sockaddr_un sa;\n+\tstruct unix_sockaddr_context ctx;\n \n-\tunix_sockaddr_init(&sa, path);\n+\tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n+\t\treturn -1;\n \tfd = unix_stream_socket();\n \n \tunlink(path);\n \tif (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {\n+\t\tunix_sockaddr_cleanup(&ctx);\n \t\tclose(fd);\n \t\treturn -1;\n \t}\n \n \tif (listen(fd, 5) < 0) {\n+\t\tunix_sockaddr_cleanup(&ctx);\n \t\tclose(fd);\n \t\treturn -1;\n \t}\n \n+\tunix_sockaddr_cleanup(&ctx);\n \treturn fd;\n }\n-- \n1.7.9.rc0.33.g15ced5\n"},{"id":"182206","messageId":"20120110045733.GA12460@sigill.intra.peff.net","threadId":"29128","inReplyTo":"20120110044430.GA23619@sigill.intra.peff.net","subject":"Re: [PATCHv3 10/13] credentials: add \"cache\" helper","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2012-01-10T04:57:33Z","receivedAt":"2012-01-10T04:57:33Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Jan 09, 2012 at 11:44:30PM -0500, Jeff King wrote:\n\n> Subject: [PATCH] unix-socket: handle long socket pathnames\n\nAnd I think this should go on top. You were lucky enough that I used a\ndie() in the original code for your condition (because I thought it was\na \"this could never happen, right?\" condition). Had it simply returned\nan error, the cache-daemon would have silently failed to do anything,\nwhich would have been much more confusing for you. :)\n\nWe probably should have done this as part of Clemens' 98c2924, but I\ndidn't think of it then (but the same reasoning applies to both\npatches).\n\n-- >8 --\nSubject: [PATCH] credential-cache: report more daemon connection errors\n\nOriginally, this code remained relatively silent when we\nfailed to connect to the cache. The idea was that it was\nsimply a cache, and we didn't want to bother the user with\ntemporary failures (the worst case is that we would simply\nask their password again).\n\nHowever, if you have a configuration failure or other\nproblem, it is helpful for the daemon to report those\nproblems. Git will happily ignore the failed error code, but\nthe extra information to stderr can help the user diagnose\nthe problem.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n credential-cache.c |   10 +++++++---\n 1 files changed, 7 insertions(+), 3 deletions(-)\n\ndiff --git a/credential-cache.c b/credential-cache.c\nindex b15a9a7..1933018 100644\n--- a/credential-cache.c\n+++ b/credential-cache.c\n@@ -71,10 +71,14 @@ static void do_cache(const char *socket, const char *action, int timeout,\n \t\t\tdie_errno(\"unable to relay credential\");\n \t}\n \n-\tif (send_request(socket, &buf) < 0 && (flags & FLAG_SPAWN)) {\n-\t\tspawn_daemon(socket);\n-\t\tif (send_request(socket, &buf) < 0)\n+\tif (send_request(socket, &buf) < 0) {\n+\t\tif (errno != ENOENT)\n \t\t\tdie_errno(\"unable to connect to cache daemon\");\n+\t\tif (flags & FLAG_SPAWN) {\n+\t\t\tspawn_daemon(socket);\n+\t\t\tif (send_request(socket, &buf) < 0)\n+\t\t\t\tdie_errno(\"unable to connect to cache daemon\");\n+\t\t}\n \t}\n \tstrbuf_release(&buf);\n }\n-- \n1.7.9.rc0.33.g15ced5\n"},{"id":"182254","messageId":"7v62gjbiie.fsf@alter.siamese.dyndns.org","threadId":"29128","inReplyTo":"20120110045733.GA12460@sigill.intra.peff.net","subject":"Re: [PATCHv3 10/13] credentials: add \"cache\" helper","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2012-01-10T16:59:53Z","receivedAt":"2012-01-10T16:59:53Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Thanks, these look sane.\n\nWill queue both to 'next' aiming for 'master' before 1.7.9-rc1.\n"},{"id":"182257","messageId":"20120110174420.GA22184@burratino","threadId":"29128","inReplyTo":"20120110044430.GA23619@sigill.intra.peff.net","subject":"Re: [PATCHv3 10/13] credentials: add \"cache\" helper","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2012-01-10T17:44:21Z","receivedAt":"2012-01-10T17:44:21Z","isPatch":false,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Jeff King wrote:\n\n>   2. A hack in credential-cache won't help any unix-socket\n>      users who come along later.\n>\n>   3. The chdir trickery isn't that likely to fail (basically\n>      it's only a problem if your cwd is missing or goes away\n>      while you're running).  And because we only enable the\n>      hack when we get a too-long name, it can only fail in\n>      cases that would have failed under the previous code\n>      anyway.\n>\n> Signed-off-by: Jeff King <peff@peff.net>\n\nA part of me worries that this assumption (3), and the additional\nassumption that nothing running concurrently cares about the cwd,\nmight come back to bite us when the future (2) arrives.  But I don't\nsee a better approach.\n\nFollow-on ideas: on platforms that support it, it would be nice to use\n\n\tctx->orig_dirfd = open(\".\", O_RDONLY);\n\tif (ctx->orig_dirfd < 0)\n\t\t... error handling ...\n\t...\n\tif (ctx->orig_dirfd >= 0) {\n\t\tif (fchdir(ctx->orig_dirfd))\n\t\t\tdie_errno(\"unable to restore original working directory\");\n\t\tclose(ctx->orig_dirfd);\n\t}\n\n> --- a/unix-socket.c\n> +++ b/unix-socket.c\n> @@ -9,27 +9,86 @@ static int unix_stream_socket(void)\n[...]\n> +\t\tdir = path;\n> +\t\tdir_len = slash - path;\n[...]\n> +\t\tif (chdir_len(dir, dir_len) < 0)\n> +\t\t\treturn -1;\n\nCould avoid some complication by eliminating the dir_len var.\n\n\t\tif (chdir_len(dir, slash - dir))\n\t\t\treturn -1;\n\nNeither seems worth holding up the patch, so\nReviewed-by: Jonathan Nieder <jrnieder@gmail.com>\n"},{"id":"182258","messageId":"20120110175312.GA7289@sigill.intra.peff.net","threadId":"29128","inReplyTo":"20120110174420.GA22184@burratino","subject":"Re: [PATCHv3 10/13] credentials: add \"cache\" helper","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2012-01-10T17:53:12Z","receivedAt":"2012-01-10T17:53:12Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Jan 10, 2012 at 11:44:21AM -0600, Jonathan Nieder wrote:\n\n> Jeff King wrote:\n> \n> >   2. A hack in credential-cache won't help any unix-socket\n> >      users who come along later.\n> >\n> >   3. The chdir trickery isn't that likely to fail (basically\n> >      it's only a problem if your cwd is missing or goes away\n> >      while you're running).  And because we only enable the\n> >      hack when we get a too-long name, it can only fail in\n> >      cases that would have failed under the previous code\n> >      anyway.\n> >\n> > Signed-off-by: Jeff King <peff@peff.net>\n> \n> A part of me worries that this assumption (3), and the additional\n> assumption that nothing running concurrently cares about the cwd,\n> might come back to bite us when the future (2) arrives.  But I don't\n> see a better approach.\n\nThe problem is that when future (2) arrives, a credential-cache specific\nhack won't be helping it at all. :)\n\nTo be honest, I don't really expect a lot of future unix-socket users.\nIt's not portable, and most of our IPC happens over pipes. The design of\nthe cache daemon is very specific in requiring a true\nmany-clients-to-one-server model, but also caring deeply about access\ncontrols (making TCP sockets less good[1]).\n\n[1] One could in theory do a loopback TCP socket and provide a random\n    token read from an access-controlled file. But that ends up a lot\n    more complicated and introduces new attack surfaces. Which is a bad\n    thing for security-sensitive code like this.\n\n> Follow-on ideas: on platforms that support it, it would be nice to use\n> \n> \tctx->orig_dirfd = open(\".\", O_RDONLY);\n> \tif (ctx->orig_dirfd < 0)\n> \t\t... error handling ...\n> \t...\n> \tif (ctx->orig_dirfd >= 0) {\n> \t\tif (fchdir(ctx->orig_dirfd))\n> \t\t\tdie_errno(\"unable to restore original working directory\");\n> \t\tclose(ctx->orig_dirfd);\n> \t}\n\nYeah, I started by using fchdir, but noticed that we don't use it\nanywhere else and didn't want to create a portability problem. It does\nfix the \"somebody deleted your cwd while you were gone from it\" problem.\nBut if you have no cwd at all, the open call will still fail.\n\nStill, it would be slightly more robust. I wonder how portable fchdir\nis in practice (I guess we could always fall back to the getcwd code\npath). Do you want to prepare a patch on top?\n\n> [...]\n> > +\t\tdir = path;\n> > +\t\tdir_len = slash - path;\n> [...]\n> > +\t\tif (chdir_len(dir, dir_len) < 0)\n> > +\t\t\treturn -1;\n> \n> Could avoid some complication by eliminating the dir_len var.\n> \n> \t\tif (chdir_len(dir, slash - dir))\n> \t\t\treturn -1;\n\nAh, yeah. Originally I had written it so that \"slash\" didn't survive\nuntouched to there, but in the current code, that would work.\n\nJunio, if you haven't merged it to next yet, it might be worth squashing\nin the patch below. Otherwise I wouldn't worry about it.\n\n> Neither seems worth holding up the patch, so\n> Reviewed-by: Jonathan Nieder <jrnieder@gmail.com>\n\nThanks for the review (and for, as usual, a well-written bug report).\n\n---\ndiff --git a/unix-socket.c b/unix-socket.c\nindex 91ed9dc..e8f19c6 100644\n--- a/unix-socket.c\n+++ b/unix-socket.c\n@@ -43,7 +43,6 @@ static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,\n \tif (size > sizeof(sa->sun_path)) {\n \t\tconst char *slash = find_last_dir_sep(path);\n \t\tconst char *dir;\n-\t\tint dir_len;\n \n \t\tif (!slash) {\n \t\t\terrno = ENAMETOOLONG;\n@@ -51,8 +50,6 @@ static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,\n \t\t}\n \n \t\tdir = path;\n-\t\tdir_len = slash - path;\n-\n \t\tpath = slash + 1;\n \t\tsize = strlen(path) + 1;\n \t\tif (size > sizeof(sa->sun_path)) {\n@@ -64,7 +61,7 @@ static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,\n \t\t\terrno = ENAMETOOLONG;\n \t\t\treturn -1;\n \t\t}\n-\t\tif (chdir_len(dir, dir_len) < 0)\n+\t\tif (chdir_len(dir, slash - dir) < 0)\n \t\t\treturn -1;\n \t}\n \n"},{"id":"182390","messageId":"20120111235009.GB30243@burratino","threadId":"29128","inReplyTo":"20120110175312.GA7289@sigill.intra.peff.net","subject":"Re: [PATCHv3 10/13] credentials: add \"cache\" helper","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2012-01-11T23:50:10Z","receivedAt":"2012-01-11T23:50:10Z","isPatch":false,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Jeff King wrote:\n\n> Still, it would be slightly more robust. I wonder how portable fchdir\n> is in practice (I guess we could always fall back to the getcwd code\n> path). Do you want to prepare a patch on top?\n\nI've been wanting to get around to doing something similar for setup.c\nfor a while.  I'm happy enough to forget about it for now. ;-)\n\nThanks again for the fix.  Here's another quick nit.\n\n-- >8 --\nSubject: unix-socket: do not let close() or chdir() clobber errno during cleanup\n\nunix_stream_connect and unix_stream_listen return -1 on error, with\nerrno set by the failing underlying call to allow the caller to write\na useful diagnosis.\n\nUnfortunately the error path involves a few system calls itself, such\nas close(), that can themselves touch errno.\n\nThis is not as worrisome as it might sound.  If close() fails, this\njust means substituting one meaningful error message for another,\nwhich is perfectly fine.  However, when the call _succeeds_, it is\nallowed to (and sometimes might) clobber errno along the way with some\nundefined value, so it is good higiene to save errno and restore it\nimmediately before returning to the caller.  Do so.\n\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n unix-socket.c |   39 ++++++++++++++++++++++-----------------\n 1 files changed, 22 insertions(+), 17 deletions(-)\n\ndiff --git a/unix-socket.c b/unix-socket.c\nindex 7d8bec61..01f119f9 100644\n--- a/unix-socket.c\n+++ b/unix-socket.c\n@@ -73,25 +73,29 @@ static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,\n \n int unix_stream_connect(const char *path)\n {\n-\tint fd;\n+\tint fd, saved_errno;\n \tstruct sockaddr_un sa;\n \tstruct unix_sockaddr_context ctx;\n \n \tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n \t\treturn -1;\n \tfd = unix_stream_socket();\n-\tif (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {\n-\t\tunix_sockaddr_cleanup(&ctx);\n-\t\tclose(fd);\n-\t\treturn -1;\n-\t}\n+\tif (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)\n+\t\tgoto fail;\n \tunix_sockaddr_cleanup(&ctx);\n \treturn fd;\n+\n+fail:\n+\tsaved_errno = errno;\n+\tunix_sockaddr_cleanup(&ctx);\n+\tclose(fd);\n+\terrno = saved_errno;\n+\treturn -1;\n }\n \n int unix_stream_listen(const char *path)\n {\n-\tint fd;\n+\tint fd, saved_errno;\n \tstruct sockaddr_un sa;\n \tstruct unix_sockaddr_context ctx;\n \n@@ -100,18 +104,19 @@ int unix_stream_listen(const char *path)\n \tfd = unix_stream_socket();\n \n \tunlink(path);\n-\tif (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {\n-\t\tunix_sockaddr_cleanup(&ctx);\n-\t\tclose(fd);\n-\t\treturn -1;\n-\t}\n+\tif (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)\n+\t\tgoto fail;\n \n-\tif (listen(fd, 5) < 0) {\n-\t\tunix_sockaddr_cleanup(&ctx);\n-\t\tclose(fd);\n-\t\treturn -1;\n-\t}\n+\tif (listen(fd, 5) < 0)\n+\t\tgoto fail;\n \n \tunix_sockaddr_cleanup(&ctx);\n \treturn fd;\n+\n+fail:\n+\tsaved_errno = errno;\n+\tunix_sockaddr_cleanup(&ctx);\n+\tclose(fd);\n+\terrno = saved_errno;\n+\treturn -1;\n }\n-- \n1.7.8.3\n"},{"id":"182411","messageId":"20120112030746.GB26363@sigill.intra.peff.net","threadId":"29128","inReplyTo":"20120111235009.GB30243@burratino","subject":"Re: [PATCHv3 10/13] credentials: add \"cache\" helper","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2012-01-12T03:07:46Z","receivedAt":"2012-01-12T03:07:46Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Jan 11, 2012 at 05:50:10PM -0600, Jonathan Nieder wrote:\n\n> Thanks again for the fix.  Here's another quick nit.\n\nMy favorite form for nits to come in: a patch.\n\n> -- >8 --\n> Subject: unix-socket: do not let close() or chdir() clobber errno during cleanup\n> [...]\n\nLooks good to me. Thanks.\n\n-Peff\n"},{"id":"182669","messageId":"20120117060232.GA27343@sigill.intra.peff.net","threadId":"29128","inReplyTo":"20120110045733.GA12460@sigill.intra.peff.net","subject":"Re: [PATCHv3 10/13] credentials: add \"cache\" helper","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2012-01-17T06:02:32Z","receivedAt":"2012-01-17T06:02:32Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Jan 09, 2012 at 11:57:33PM -0500, Jeff King wrote:\n\n> Subject: [PATCH] credential-cache: report more daemon connection errors\n> \n> Originally, this code remained relatively silent when we\n> failed to connect to the cache. The idea was that it was\n> simply a cache, and we didn't want to bother the user with\n> temporary failures (the worst case is that we would simply\n> ask their password again).\n> \n> However, if you have a configuration failure or other\n> problem, it is helpful for the daemon to report those\n> problems. Git will happily ignore the failed error code, but\n> the extra information to stderr can help the user diagnose\n> the problem.\n\nThis actually has a minor regression, fixed below.\n\n-- >8 --\nSubject: [PATCH] credential-cache: ignore \"connection refused\" errors\n\nThe credential-cache helper will try to connect to its\ndaemon over a unix socket. Originally, a failure to do so\nwas silently ignored, and we would either give up (if\nperforming a \"get\" or \"erase\" operation), or spawn a new\ndaemon (for a \"store\" operation).\n\nBut since 8ec6c8d, we try to report more errors. We detect a\nmissing daemon by checking for ENOENT on our connection\nattempt.  If the daemon is missing, we continue as before\n(giving up or spawning a new daemon). For any other error,\nwe die and report the problem.\n\nHowever, checking for ENOENT is not sufficient for a missing\ndaemon. We might also get ECONNREFUSED if a dead daemon\nprocess left a stale socket. This generally shouldn't\nhappen, as the daemon cleans up after itself, but the daemon\nmay not always be given a chance to do so (e.g., power loss,\n\"kill -9\").\n\nThe resulting state is annoying not just because the helper\noutputs an extra useless message, but because it actually\nblocks the helper from spawning a new daemon to replace the\nstale socket.\n\nFix it by checking for ECONNREFUSED.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\nIf we really want to go belt-and-suspenders, the logic should perhaps be\nchanged to:\n\n  if (send_request(socket, &buf < 0) {\n          /* if we're starting a new one, who cares why it didn't work */\n          if (flags & FLAG_SPAWN) {\n                  spawn_daemon(socket);\n                  if (send_request(socket, &buf) < 0)\n                          die_errno(\"unable to connect to spawned daemon\");\n          }\n          /* otherwise, report any non-minor errors */\n          else if(errno != ENOENT && errno != ECONNREFUSED)\n                  die_errno(\"unable to connect to cache daemon\");\n          /* otherwise we are just missing the daemon, and we can ignore */\n  }\n\nbut that implies there is some condition besides ENOENT and ECONNREFUSED\nwhere actually starting a new daemon (which will try to unlink whatever\nis there now!) would be a good idea. I'd rather be conservative and\nsee if anybody reports a real-world case.\n\n credential-cache.c |    2 +-\n 1 files changed, 1 insertions(+), 1 deletions(-)\n\ndiff --git a/credential-cache.c b/credential-cache.c\nindex 1933018..9a03792 100644\n--- a/credential-cache.c\n+++ b/credential-cache.c\n@@ -72,7 +72,7 @@ static void do_cache(const char *socket, const char *action, int timeout,\n \t}\n \n \tif (send_request(socket, &buf) < 0) {\n-\t\tif (errno != ENOENT)\n+\t\tif (errno != ENOENT && errno != ECONNREFUSED)\n \t\t\tdie_errno(\"unable to connect to cache daemon\");\n \t\tif (flags & FLAG_SPAWN) {\n \t\t\tspawn_daemon(socket);\n-- \n1.7.9.rc0.33.gd3c17\n"},{"id":"182674","messageId":"7vy5t6stx1.fsf@alter.siamese.dyndns.org","threadId":"29128","inReplyTo":"20120117060232.GA27343@sigill.intra.peff.net","subject":"Re: [PATCHv3 10/13] credentials: add \"cache\" helper","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2012-01-17T06:51:54Z","receivedAt":"2012-01-17T06:51:54Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Thanks, really appreciated.\n"}]}