{"thread":{"id":"27841","subject":"[RFC/PATCH 0/14] less annoying http authentication","startedAt":"2011-07-18T07:46:42Z","lastAt":"2011-08-26T15:29:50Z","messageCount":33,"participants":["Jeff King","Junio C Hamano","Ted Zlatanov"],"isPatch":true,"patchVersion":1,"patchTotal":14},"messages":[{"id":"171545","messageId":"20110718074642.GA11678@sigill.intra.peff.net","threadId":"27841","inReplyTo":null,"subject":"[RFC/PATCH 0/14] less annoying http authentication","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-07-18T07:46:42Z","receivedAt":"2011-07-18T07:46:42Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"Using smart-http with a server that does http authentication can involve\ntyping your username and password a lot. This is annoying, and is a\nfrequent complaint of people using smart-http at GitHub.\n\nStoring passwords is tricky, of course, because of the security\nconcerns. Fortunately this is a problem that has been solved by a\nmultitude of password wallet and keychain systems. We just have to hook\ninto whatever secure storage the user is already using.\n\nSo the goals of the series are:\n\n  1. Provide an interface to request usernames and passwords from\n     external helpers. By making them an external program that we spawn,\n     it's easy to write custom helpers for whatever wallet program you\n     want. Somebody has already expressed interest in making a helper\n     for freedesktop.org's \"Secrets API\" to do one. Somebody at GitHub\n     will probably do an OS X Keychain one.\n\n  2. For users on systems with no secure storage, provide a\n     time-limited, in-memory cache that is persistent between program\n     invocations. So if you do:\n\n       $ git fetch https://user@example.com/repo.git\n       $ hack hack hack\n       $ git push https://user@example.com/repo.git\n\n     or:\n\n       $ git push\n       $ hack hack hack\n       $ git push\n\n     we can use the same password both times.\n\n  3. For users on systems with no secure storage who want to take the\n     risk of plaintext storage, provide a persistent disk cache (i.e.,\n     prompt once and then store it in a config file). This is what SVN\n     does. I think it's a horrible security policy to have by default,\n     but it is simple, and it's good enough for many people.\n\n  4. Allow associating usernames with remote hosts. Even if you do want\n     to type your password each time, typing your username every time is\n     annoying. Two things you can already do are:\n\n       a. Use user@host in your remote URL. This has a few downsides,\n          though.\n\n          One is that git assumes that a URL with a user-portion is\n          going to want authentication. So now it will ask for your\n          password to fetch, even if the server doesn't actually care\n          (though we might want to change that, since handles HTTP 401s\n          properly these days).\n\n          Two is that it's much simpler to separate the authentication\n          from the repo name. Then you never have to worry about adding\n          in your username to a URL when cloning, or stripping out\n          somebody else's username from the URL they've shared with you.\n\n       b. Use netrc. We don't document anywhere that netrc exists, or\n          that we respect it. It's just a feature which comes along with\n          curl. And even if we did mention it, some systems don't\n          actually have a manpage for netrc. So we'd need to document\n          that we use it, and its format.\n\n          It also isn't very git-ish. You can't have per-repo versus\n          per-user values. You can't use \"git config\" to set and look at\n          it. It isn't carted around with your .gitconfig when you copy\n          it to a new system.\n\nThis is still in the RFC stage. I think the code is fine, and I've been\nusing it for a few weeks. I tried to keep the design of the credential\nhelper interface simple, but flexible enough to meet the needs of the\nvarious keychain systems. Aside from the usual, what I'd most like\nfeedback on is whether the interface is sufficient to actually integrate\nwith those systems. And I suspect we won't really know until people try\nto make helpers for their pet systems.\n\nThe patches are:\n\n  [01/14]: parse-options: add OPT_STRING_LIST helper\n\nThis one is actually a cherry-pick from jk/clone-cmdline-config, which\nwas marked as \"Will merge to master\" in the last What's Cooking. So it\nprobably makes sense to drop this and just build the rest of the series\non top of c8ba1639.\n\n  [02/14]: url: decode buffers that are not NUL-terminated\n  [03/14]: improve httpd auth tests\n  [04/14]: remote-curl: don't retry auth failures with dumb protocol\n  [05/14]: http: retry authentication failures for all http requests\n  [06/14]: introduce credentials API\n  [07/14]: http: use credential API to get passwords\n  [08/14]: look for credentials in config before prompting\n  [09/14]: allow the user to configure credential helpers\n  [10/14]: http: use hostname in credential description\n  [11/14]: docs: end-user documentation for the credential subsystem\n  [12/14]: credentials: add \"cache\" helper\n  [13/14]: credentials: add \"store\" helper\n  [14/14]: credentials: add \"getpass\" helper\n\n-Peff\n"},{"id":"171547","messageId":"20110718074819.GA12341@sigill.intra.peff.net","threadId":"27841","inReplyTo":"20110718074642.GA11678@sigill.intra.peff.net","subject":"[PATCH 01/14] parse-options: add OPT_STRING_LIST helper","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-07-18T07:48:19Z","receivedAt":"2011-07-18T07:48:19Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"This just adds repeated invocations of an option to a list\nof strings. Using the \"--no-<var>\" form will reset the list\nto empty.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\nAs I mentioned elsewhere, this is a cherry-pick of c8ba163 and can be\ndropped.\n\n parse-options.c          |   17 +++++++++++++++++\n parse-options.h          |    4 ++++\n t/t0040-parse-options.sh |   17 +++++++++++++++++\n test-parse-options.c     |    6 ++++++\n 4 files changed, 44 insertions(+), 0 deletions(-)\n\ndiff --git a/parse-options.c b/parse-options.c\nindex 73bd28a..879ea82 100644\n--- a/parse-options.c\n+++ b/parse-options.c\n@@ -3,6 +3,7 @@\n #include \"cache.h\"\n #include \"commit.h\"\n #include \"color.h\"\n+#include \"string-list.h\"\n \n static int parse_options_usage(struct parse_opt_ctx_t *ctx,\n \t\t\t       const char * const *usagestr,\n@@ -687,3 +688,19 @@ int parse_options_concat(struct option *dst, size_t dst_size, struct option *src\n \t}\n \treturn -1;\n }\n+\n+int parse_opt_string_list(const struct option *opt, const char *arg, int unset)\n+{\n+\tstruct string_list *v = opt->value;\n+\n+\tif (unset) {\n+\t\tstring_list_clear(v, 0);\n+\t\treturn 0;\n+\t}\n+\n+\tif (!arg)\n+\t\treturn -1;\n+\n+\tstring_list_append(v, xstrdup(arg));\n+\treturn 0;\n+}\ndiff --git a/parse-options.h b/parse-options.h\nindex d1b12fe..05eb09b 100644\n--- a/parse-options.h\n+++ b/parse-options.h\n@@ -130,6 +130,9 @@ struct option {\n \t\t\t\t      (h), PARSE_OPT_NOARG, NULL, (p) }\n #define OPT_INTEGER(s, l, v, h)     { OPTION_INTEGER, (s), (l), (v), \"n\", (h) }\n #define OPT_STRING(s, l, v, a, h)   { OPTION_STRING,  (s), (l), (v), (a), (h) }\n+#define OPT_STRING_LIST(s, l, v, a, h) \\\n+\t\t\t\t    { OPTION_CALLBACK, (s), (l), (v), (a), \\\n+\t\t\t\t      (h), 0, &parse_opt_string_list }\n #define OPT_UYN(s, l, v, h)         { OPTION_CALLBACK, (s), (l), (v), NULL, \\\n \t\t\t\t      (h), PARSE_OPT_NOARG, &parse_opt_tertiary }\n #define OPT_DATE(s, l, v, h) \\\n@@ -204,6 +207,7 @@ extern int parse_opt_color_flag_cb(const struct option *, const char *, int);\n extern int parse_opt_verbosity_cb(const struct option *, const char *, int);\n extern int parse_opt_with_commit(const struct option *, const char *, int);\n extern int parse_opt_tertiary(const struct option *, const char *, int);\n+extern int parse_opt_string_list(const struct option *, const char *, int);\n \n #define OPT__VERBOSE(var, h)  OPT_BOOLEAN('v', \"verbose\", (var), (h))\n #define OPT__QUIET(var, h)    OPT_BOOLEAN('q', \"quiet\",   (var), (h))\ndiff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh\nindex ae26614..007f39d 100755\n--- a/t/t0040-parse-options.sh\n+++ b/t/t0040-parse-options.sh\n@@ -28,6 +28,7 @@ String options\n     --st <st>             get another string (pervert ordering)\n     -o <str>              get another string\n     --default-string      set string to default\n+    --list <str>          add str to list\n \n Magic arguments\n     --quux                means --quux\n@@ -337,4 +338,20 @@ test_expect_success 'negation of OPT_NONEG flags is not ambiguous' '\n \ttest_cmp expect output\n '\n \n+cat >>expect <<'EOF'\n+list: foo\n+list: bar\n+list: baz\n+EOF\n+test_expect_success '--list keeps list of strings' '\n+\ttest-parse-options --list foo --list=bar --list=baz >output &&\n+\ttest_cmp expect output\n+'\n+\n+test_expect_success '--no-list resets list' '\n+\ttest-parse-options --list=other --list=irrelevant --list=options \\\n+\t\t--no-list --list=foo --list=bar --list=baz >output &&\n+\ttest_cmp expect output\n+'\n+\n test_done\ndiff --git a/test-parse-options.c b/test-parse-options.c\nindex 4e3710b..91a5701 100644\n--- a/test-parse-options.c\n+++ b/test-parse-options.c\n@@ -1,5 +1,6 @@\n #include \"cache.h\"\n #include \"parse-options.h\"\n+#include \"string-list.h\"\n \n static int boolean = 0;\n static int integer = 0;\n@@ -9,6 +10,7 @@ static int verbose = 0, dry_run = 0, quiet = 0;\n static char *string = NULL;\n static char *file = NULL;\n static int ambiguous;\n+static struct string_list list;\n \n static int length_callback(const struct option *opt, const char *arg, int unset)\n {\n@@ -54,6 +56,7 @@ int main(int argc, const char **argv)\n \t\tOPT_STRING('o', NULL, &string, \"str\", \"get another string\"),\n \t\tOPT_SET_PTR(0, \"default-string\", &string,\n \t\t\t\"set string to default\", (unsigned long)\"default\"),\n+\t\tOPT_STRING_LIST(0, \"list\", &list, \"str\", \"add str to list\"),\n \t\tOPT_GROUP(\"Magic arguments\"),\n \t\tOPT_ARGUMENT(\"quux\", \"means --quux\"),\n \t\tOPT_NUMBER_CALLBACK(&integer, \"set integer to NUM\",\n@@ -85,6 +88,9 @@ int main(int argc, const char **argv)\n \tprintf(\"dry run: %s\\n\", dry_run ? \"yes\" : \"no\");\n \tprintf(\"file: %s\\n\", file ? file : \"(not set)\");\n \n+\tfor (i = 0; i < list.nr; i++)\n+\t\tprintf(\"list: %s\\n\", list.items[i].string);\n+\n \tfor (i = 0; i < argc; i++)\n \t\tprintf(\"arg %02d: %s\\n\", i, argv[i]);\n \n-- \n1.7.6.rc1.12.g65e2\n"},{"id":"171548","messageId":"20110718074851.GB12341@sigill.intra.peff.net","threadId":"27841","inReplyTo":"20110718074642.GA11678@sigill.intra.peff.net","subject":"[PATCH 02/14] url: decode buffers that are not NUL-terminated","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-07-18T07:48:51Z","receivedAt":"2011-07-18T07:48:51Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"The url_decode function needs only minor tweaks to handle\narbitrary buffers. Let's do those tweaks, which cleans up an\nunreadable mess of temporary strings in http.c.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n http.c |   27 ++++-----------------------\n url.c  |   26 ++++++++++++++++++--------\n url.h  |    1 +\n 3 files changed, 23 insertions(+), 31 deletions(-)\n\ndiff --git a/http.c b/http.c\nindex a1ea3db..c93716c 100644\n--- a/http.c\n+++ b/http.c\n@@ -307,8 +307,7 @@ static CURL *get_curl_handle(void)\n \n static void http_auth_init(const char *url)\n {\n-\tchar *at, *colon, *cp, *slash, *decoded;\n-\tint len;\n+\tchar *at, *colon, *cp, *slash;\n \n \tcp = strstr(url, \"://\");\n \tif (!cp)\n@@ -328,29 +327,11 @@ static void http_auth_init(const char *url)\n \t\treturn; /* No credentials */\n \tif (!colon || at <= colon) {\n \t\t/* Only username */\n-\t\tlen = at - cp;\n-\t\tuser_name = xmalloc(len + 1);\n-\t\tmemcpy(user_name, cp, len);\n-\t\tuser_name[len] = '\\0';\n-\t\tdecoded = url_decode(user_name);\n-\t\tfree(user_name);\n-\t\tuser_name = decoded;\n+\t\tuser_name = url_decode_mem(cp, at - cp);\n \t\tuser_pass = NULL;\n \t} else {\n-\t\tlen = colon - cp;\n-\t\tuser_name = xmalloc(len + 1);\n-\t\tmemcpy(user_name, cp, len);\n-\t\tuser_name[len] = '\\0';\n-\t\tdecoded = url_decode(user_name);\n-\t\tfree(user_name);\n-\t\tuser_name = decoded;\n-\t\tlen = at - (colon + 1);\n-\t\tuser_pass = xmalloc(len + 1);\n-\t\tmemcpy(user_pass, colon + 1, len);\n-\t\tuser_pass[len] = '\\0';\n-\t\tdecoded = url_decode(user_pass);\n-\t\tfree(user_pass);\n-\t\tuser_pass = decoded;\n+\t\tuser_name = url_decode_mem(cp, colon - cp);\n+\t\tuser_pass = url_decode_mem(colon + 1, at - (colon + 1));\n \t}\n }\n \ndiff --git a/url.c b/url.c\nindex 3e06fd3..389d9da 100644\n--- a/url.c\n+++ b/url.c\n@@ -68,18 +68,20 @@ static int url_decode_char(const char *q)\n \treturn val;\n }\n \n-static char *url_decode_internal(const char **query, const char *stop_at,\n-\t\t\t\t struct strbuf *out, int decode_plus)\n+static char *url_decode_internal(const char **query, int len,\n+\t\t\t\t const char *stop_at, struct strbuf *out,\n+\t\t\t\t int decode_plus)\n {\n \tconst char *q = *query;\n \n-\tdo {\n+\twhile (len) {\n \t\tunsigned char c = *q;\n \n \t\tif (!c)\n \t\t\tbreak;\n \t\tif (stop_at && strchr(stop_at, c)) {\n \t\t\tq++;\n+\t\t\tlen--;\n \t\t\tbreak;\n \t\t}\n \n@@ -88,6 +90,7 @@ static char *url_decode_internal(const char **query, const char *stop_at,\n \t\t\tif (0 <= val) {\n \t\t\t\tstrbuf_addch(out, val);\n \t\t\t\tq += 3;\n+\t\t\t\tlen -= 3;\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t}\n@@ -97,34 +100,41 @@ static char *url_decode_internal(const char **query, const char *stop_at,\n \t\telse\n \t\t\tstrbuf_addch(out, c);\n \t\tq++;\n-\t} while (1);\n+\t\tlen--;\n+\t}\n \t*query = q;\n \treturn strbuf_detach(out, NULL);\n }\n \n char *url_decode(const char *url)\n {\n+\treturn url_decode_mem(url, strlen(url));\n+}\n+\n+char *url_decode_mem(const char *url, int len)\n+{\n \tstruct strbuf out = STRBUF_INIT;\n-\tconst char *colon = strchr(url, ':');\n+\tconst char *colon = memchr(url, ':', len);\n \n \t/* Skip protocol part if present */\n \tif (colon && url < colon) {\n \t\tstrbuf_add(&out, url, colon - url);\n+\t\tlen -= colon - url;\n \t\turl = colon;\n \t}\n-\treturn url_decode_internal(&url, NULL, &out, 0);\n+\treturn url_decode_internal(&url, len, NULL, &out, 0);\n }\n \n char *url_decode_parameter_name(const char **query)\n {\n \tstruct strbuf out = STRBUF_INIT;\n-\treturn url_decode_internal(query, \"&=\", &out, 1);\n+\treturn url_decode_internal(query, -1, \"&=\", &out, 1);\n }\n \n char *url_decode_parameter_value(const char **query)\n {\n \tstruct strbuf out = STRBUF_INIT;\n-\treturn url_decode_internal(query, \"&\", &out, 1);\n+\treturn url_decode_internal(query, -1, \"&\", &out, 1);\n }\n \n void end_url_with_slash(struct strbuf *buf, const char *url)\ndiff --git a/url.h b/url.h\nindex 7100e32..abdaf6f 100644\n--- a/url.h\n+++ b/url.h\n@@ -4,6 +4,7 @@\n extern int is_url(const char *url);\n extern int is_urlschemechar(int first_flag, int ch);\n extern char *url_decode(const char *url);\n+extern char *url_decode_mem(const char *url, int len);\n extern char *url_decode_parameter_name(const char **query);\n extern char *url_decode_parameter_value(const char **query);\n \n-- \n1.7.6.rc1.12.g65e2\n"},{"id":"171549","messageId":"20110718074912.GC12341@sigill.intra.peff.net","threadId":"27841","inReplyTo":"20110718074642.GA11678@sigill.intra.peff.net","subject":"[PATCH 03/14] improve httpd auth tests","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-07-18T07:49:12Z","receivedAt":"2011-07-18T07:49:12Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"These just checked that we could clone a repository when the\nusername and password were given in the URL; we should also\ncheck that git will prompt when no or partial credentials\nare given.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n t/lib-httpd.sh        |   10 +++++---\n t/t5550-http-fetch.sh |   51 +++++++++++++++++++++++++++++++++++++++++++++---\n 2 files changed, 53 insertions(+), 8 deletions(-)\n\ndiff --git a/t/lib-httpd.sh b/t/lib-httpd.sh\nindex b8996a3..f7dc078 100644\n--- a/t/lib-httpd.sh\n+++ b/t/lib-httpd.sh\n@@ -81,8 +81,7 @@ prepare_httpd() {\n \n \tif test -n \"$LIB_HTTPD_SSL\"\n \tthen\n-\t\tHTTPD_URL=https://127.0.0.1:$LIB_HTTPD_PORT\n-\t\tAUTH_HTTPD_URL=https://user%40host:user%40host@127.0.0.1:$LIB_HTTPD_PORT\n+\t\tHTTPD_PROTO=https\n \n \t\tRANDFILE_PATH=\"$HTTPD_ROOT_PATH\"/.rnd openssl req \\\n \t\t\t-config \"$TEST_PATH/ssl.cnf\" \\\n@@ -93,9 +92,12 @@ prepare_httpd() {\n \t\texport GIT_SSL_NO_VERIFY\n \t\tHTTPD_PARA=\"$HTTPD_PARA -DSSL\"\n \telse\n-\t\tHTTPD_URL=http://127.0.0.1:$LIB_HTTPD_PORT\n-\t\tAUTH_HTTPD_URL=http://user%40host:user%40host@127.0.0.1:$LIB_HTTPD_PORT\n+\t\tHTTPD_PROTO=http\n \tfi\n+\tHTTPD_DEST=127.0.0.1:$LIB_HTTPD_PORT\n+\tHTTPD_URL=$HTTPD_PROTO://$HTTPD_DEST\n+\tHTTPD_URL_USER=$HTTPD_PROTO://user%40host@$HTTPD_DEST\n+\tHTTPD_URL_USER_PASS=$HTTPD_PROTO://user%40host:user%40host@$HTTPD_DEST\n \n \tif test -n \"$LIB_HTTPD_DAV\" -o -n \"$LIB_HTTPD_SVN\"\n \tthen\ndiff --git a/t/t5550-http-fetch.sh b/t/t5550-http-fetch.sh\nindex a1883ca..ed4db09 100755\n--- a/t/t5550-http-fetch.sh\n+++ b/t/t5550-http-fetch.sh\n@@ -35,11 +35,54 @@ test_expect_success 'clone http repository' '\n \ttest_cmp file clone/file\n '\n \n-test_expect_success 'clone http repository with authentication' '\n+test_expect_success 'create password-protected repository' '\n \tmkdir \"$HTTPD_DOCUMENT_ROOT_PATH/auth/\" &&\n-\tcp -Rf \"$HTTPD_DOCUMENT_ROOT_PATH/repo.git\" \"$HTTPD_DOCUMENT_ROOT_PATH/auth/repo.git\" &&\n-\tgit clone $AUTH_HTTPD_URL/auth/repo.git clone-auth &&\n-\ttest_cmp file clone-auth/file\n+\tcp -Rf \"$HTTPD_DOCUMENT_ROOT_PATH/repo.git\" \\\n+\t       \"$HTTPD_DOCUMENT_ROOT_PATH/auth/repo.git\"\n+'\n+\n+test_expect_success 'setup askpass helpers' '\n+\tcat >askpass <<-EOF &&\n+\t#!/bin/sh\n+\techo >>\"$PWD/askpass-query\" \"askpass: \\$*\" &&\n+\tcat \"$PWD/askpass-response\"\n+\tEOF\n+\tchmod +x askpass &&\n+\tGIT_ASKPASS=\"$PWD/askpass\" &&\n+\texport GIT_ASKPASS &&\n+\t>askpass-expect-none &&\n+\techo \"askpass: Password: \" >askpass-expect-pass &&\n+\t{ echo \"askpass: Username: \" &&\n+\t  cat askpass-expect-pass\n+\t} >askpass-expect-both\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+'\n+\n+test_expect_success 'http auth can use user/pass in URL' '\n+\t>askpass-query &&\n+\techo wrong >askpass-reponse &&\n+\tgit clone \"$HTTPD_URL_USER_PASS/auth/repo.git\" clone-auth-none &&\n+\ttest_cmp askpass-expect-none askpass-query\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+'\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 '\n \n test_expect_success 'fetch changes via http' '\n-- \n1.7.6.rc1.12.g65e2\n"},{"id":"171550","messageId":"20110718074956.GD12341@sigill.intra.peff.net","threadId":"27841","inReplyTo":"20110718074642.GA11678@sigill.intra.peff.net","subject":"[PATCH 04/14] remote-curl: don't retry auth failures with dumb protocol","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-07-18T07:49:56Z","receivedAt":"2011-07-18T07:49:56Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"When fetching an http URL, we first try fetching info/refs\nwith an extra \"service\" parameter. This will work for a\nsmart-http server, or a dumb server which ignores extra\nparameters when fetching files. If that fails, we retry\nwithout the extra parameter to remain compatible with dumb\nservers which didn't like our first request.\n\nIf the server returned a \"401 Unauthorized\", indicating that\nthe credentials we provided were not good, there is not much\npoint in retrying. With the current code, we just waste an\nextra round trip to the HTTP server before failing.\n\nBut as the http code becomes smarter about throwing away\nrejected credentials and re-prompting the user for new ones\n(which it will later in this series), this will become more\nconfusing. At some point we will stop asking for credentials\nto retry smart http, and will be asking for credentials to\nretry dumb http. So now we're not only wasting an extra HTTP\nround trip for something that is unlikely to work, but we're\nmaking the user re-type their password for it.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n remote-curl.c |    2 +-\n 1 files changed, 1 insertions(+), 1 deletions(-)\n\ndiff --git a/remote-curl.c b/remote-curl.c\nindex b5be25c..42b02af 100644\n--- a/remote-curl.c\n+++ b/remote-curl.c\n@@ -115,7 +115,7 @@ static struct discovery* discover_refs(const char *service)\n \thttp_ret = http_get_strbuf(refs_url, &buffer, HTTP_NO_CACHE);\n \n \t/* try again with \"plain\" url (no ? or & appended) */\n-\tif (http_ret != HTTP_OK) {\n+\tif (http_ret != HTTP_OK && http_ret != HTTP_NOAUTH) {\n \t\tfree(refs_url);\n \t\tstrbuf_reset(&buffer);\n \n-- \n1.7.6.rc1.12.g65e2\n"},{"id":"171551","messageId":"20110718075014.GE12341@sigill.intra.peff.net","threadId":"27841","inReplyTo":"20110718074642.GA11678@sigill.intra.peff.net","subject":"[PATCH 05/14] http: retry authentication failures for all http requests","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-07-18T07:50:14Z","receivedAt":"2011-07-18T07:50:14Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"Commit 42653c0 (Prompt for a username when an HTTP request\n401s, 2010-04-01) changed http_get_strbuf to prompt for\ncredentials when we receive a 401, but didn't touch\nhttp_get_file. The latter is called only for dumb http;\nwhile it's usually the case that people don't use\nauthentication on top of dumb http, there is no reason not\nto allow both types of requests to use this feature.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n http.c |   17 +++++++++++------\n 1 files changed, 11 insertions(+), 6 deletions(-)\n\ndiff --git a/http.c b/http.c\nindex c93716c..89e3cf4 100644\n--- a/http.c\n+++ b/http.c\n@@ -846,13 +846,18 @@ static int http_request(const char *url, void *result, int target, int options)\n \treturn ret;\n }\n \n+static int http_request_reauth(const char *url, void *result, int target,\n+\t\t\t       int options)\n+{\n+\tint ret = http_request(url, result, target, options);\n+\tif (ret != HTTP_REAUTH)\n+\t\treturn ret;\n+\treturn http_request(url, result, target, options);\n+}\n+\n int http_get_strbuf(const char *url, struct strbuf *result, int options)\n {\n-\tint http_ret = http_request(url, result, HTTP_REQUEST_STRBUF, options);\n-\tif (http_ret == HTTP_REAUTH) {\n-\t\thttp_ret = http_request(url, result, HTTP_REQUEST_STRBUF, options);\n-\t}\n-\treturn http_ret;\n+\treturn http_request_reauth(url, result, HTTP_REQUEST_STRBUF, options);\n }\n \n /*\n@@ -875,7 +880,7 @@ static int http_get_file(const char *url, const char *filename, int options)\n \t\tgoto cleanup;\n \t}\n \n-\tret = http_request(url, result, HTTP_REQUEST_FILE, options);\n+\tret = http_request_reauth(url, result, HTTP_REQUEST_FILE, options);\n \tfclose(result);\n \n \tif ((ret == HTTP_OK) && move_temp_to_file(tmpfile.buf, filename))\n-- \n1.7.6.rc1.12.g65e2\n"},{"id":"171552","messageId":"20110718075034.GF12341@sigill.intra.peff.net","threadId":"27841","inReplyTo":"20110718074642.GA11678@sigill.intra.peff.net","subject":"[PATCH 06/14] introduce credentials API","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-07-18T07:50:34Z","receivedAt":"2011-07-18T07:50:34Z","isPatch":true,"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 abstracts the notion of gathering user\ncredentials into a few simple functions. These functions can\nbe backed by our internal git_getpass implementation (which\njust prompts the user), or by external helpers which are\nfree to consult system-specific password wallets, make\ncustom policy decisions on password caching and storage, or\nprompt the user in a non-traditional manner.\n\nThe helper protocol aims for simplicity of helper\nimplementation; see the newly added documentation for\ndetails.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n .gitignore                                  |    1 +\n Documentation/technical/api-credentials.txt |  113 ++++++++++++++++\n Makefile                                    |    3 +\n credential.c                                |  190 +++++++++++++++++++++++++++\n credential.h                                |   19 +++\n t/t0300-credentials.sh                      |  175 ++++++++++++++++++++++++\n test-credential.c                           |   47 +++++++\n 7 files changed, 548 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/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..880db92\n--- /dev/null\n+++ b/Documentation/technical/api-credentials.txt\n@@ -0,0 +1,113 @@\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+\tThe `username` and `password` fields should be heap-allocated\n+\tstrings (or NULL if they are not yet known). The `unique` field,\n+\tif non-NULL, should be a heap-allocated string indicating a\n+\tunique context for this credential (e.g., a protocol and server\n+\tname for a remote credential). The `description` field, if\n+\tnon-NULL, should point to a string containing a human-readable\n+\tdescription of this credential.\n+\n+`struct string_list methods`::\n+\n+\tThe credential functions take a `string_list` of methods for\n+\tacquiring credentials. Each string specifies an external\n+\thelper which will be run, in order, to acquire credentials,\n+\tuntil both a username and password have been acquired. A NULL or\n+\tempty methods list indicates that the internal\n+\t`credential_getpass` function should be used.\n+\n+\n+Functions\n+---------\n+\n+`credential_fill_gently`::\n+\n+\tAttempt to fill the username and password fields of the passed\n+\tcredential struct. If they cannot be filled after trying each\n+\tavailable method, returns -1. Otherwise, returns 0.\n+\n+`credential_fill`::\n+\n+\tLike `credential_fill_gently`, but `die()` if credentials cannot\n+\tbe gathered.\n+\n+`credential_reject`::\n+\n+\tInform the credential subsystem that the provided credentials\n+\thave been rejected. This will clear the username and password\n+\tfields in `struct credential`, as well as notify any helpers of\n+\tthe rejection (which may, for example, purge the invalid\n+\tcredentials from storage).\n+\n+`credential_getpass`::\n+\n+\tFetch credentials from the user either using an \"askpass\" helper\n+\t(see the discussion of core.askpass and GIT_ASKPASS in\n+\tlinkgit:git-config[1] and linkgit:git[1], respectively) or by\n+\tprompting the user via the terminal.\n+\n+\n+Credential Helpers\n+------------------\n+\n+Credential helpers are programs executed by git to fetch credentials\n+from storage or from the user. The default behavior when no helpers are\n+defined is to use the internal `credential_askpass` function.\n+\n+When a helper is executed, it may receive the following options on the\n+command line:\n+\n+`--reject`::\n+\n+\tSpecify that the provided credential has been rejected; the\n+\thelper may take appropriate action to purge any credential\n+\tstorage or cache. If this option is not given, the helper should\n+\tassume a credential is being requested.\n+\n+`--description=<X>`::\n+\n+\t`<X>` will contain a human-readable description of the\n+\tcredential being requested. If this option is not given, no\n+\tdescription is available.\n+\n+`--unique=<X>`::\n+\n+\t`<X>` will contain a token to uniquely identify the context of\n+\tthe credential (e.g., a host name for network authentication).\n+\tIf this option is not given, no context is available.\n+\n+`--username=<X>`::\n+\n+\t`<X>` will contain the username requested by the user. If this\n+\toption is not given, no username is available, and the helper\n+\tshould provide both a username and password.\n+\n+The helper should produce a list of items on stdout, each followed by a\n+newline character. Each item should consist of a key-value pair, separated\n+by an `=` (equals) sign. The value may contain any bytes except a\n+newline. When reading the response, git understands the following keys:\n+\n+`username`::\n+\n+\tThe username part of the credential. If a username was given to\n+\tthe helper via `--username`, the new value will override it.\n+\n+`password`::\n+\n+\tThe password part of the credential.\n+\n+It is perfectly acceptable for a helper to provide only part of a\n+credential, or nothing at all.\ndiff --git a/Makefile b/Makefile\nindex 46793d1..16515bf 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -421,6 +421,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@@ -511,6 +512,7 @@ LIB_H += compat/win32/pthread.h\n LIB_H += compat/win32/syslog.h\n LIB_H += compat/win32/sys/poll.h\n LIB_H += compat/win32/dirent.h\n+LIB_H += credential.h\n LIB_H += csum-file.h\n LIB_H += decorate.h\n LIB_H += delta.h\n@@ -590,6 +592,7 @@ LIB_OBJS += config.o\n LIB_OBJS += connect.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..c403289\n--- /dev/null\n+++ b/credential.c\n@@ -0,0 +1,190 @@\n+#include \"cache.h\"\n+#include \"credential.h\"\n+#include \"quote.h\"\n+#include \"string-list.h\"\n+#include \"run-command.h\"\n+\n+static char *credential_ask_one(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+\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(&prompt);\n+\treturn xstrdup(r);\n+}\n+\n+int credential_getpass(struct credential *c)\n+{\n+\n+\tif (!c->username)\n+\t\tc->username = credential_ask_one(\"Username\", c->description);\n+\tif (!c->password)\n+\t\tc->password = credential_ask_one(\"Password\", c->description);\n+\treturn 0;\n+}\n+\n+static int read_credential_response(struct credential *c, FILE *fp)\n+{\n+\tstruct strbuf response = STRBUF_INIT;\n+\n+\twhile (strbuf_getline(&response, fp, '\\n') != EOF) {\n+\t\tchar *key = response.buf;\n+\t\tchar *value = strchr(key, '=');\n+\n+\t\tif (!value) {\n+\t\t\twarning(\"bad output from credential helper: %s\", key);\n+\t\t\tstrbuf_release(&response);\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}\n+\t\telse if (!strcmp(key, \"password\")) {\n+\t\t\tfree(c->password);\n+\t\t\tc->password = xstrdup(value);\n+\t\t}\n+\t\t/* ignore other responses; we don't know what they mean */\n+\t}\n+\n+\tstrbuf_release(&response);\n+\treturn 0;\n+}\n+\n+static int run_credential_helper(struct credential *c, const char *cmd)\n+{\n+\tstruct child_process helper;\n+\tconst char *argv[] = { NULL, NULL };\n+\tFILE *fp;\n+\tint r;\n+\n+\tmemset(&helper, 0, sizeof(helper));\n+\targv[0] = cmd;\n+\thelper.argv = argv;\n+\thelper.use_shell = 1;\n+\thelper.no_stdin = 1;\n+\thelper.out = -1;\n+\n+\tif (start_command(&helper))\n+\t\treturn -1;\n+\tfp = xfdopen(helper.out, \"r\");\n+\n+\tr = read_credential_response(c, fp);\n+\n+\tfclose(fp);\n+\tif (finish_command(&helper))\n+\t\tr = -1;\n+\n+\treturn r;\n+}\n+\n+static void add_item(struct strbuf *out, const char *key, const char *value)\n+{\n+\tif (!value)\n+\t\treturn;\n+\tstrbuf_addf(out, \" --%s=\", key);\n+\tsq_quote_buf(out, value);\n+}\n+\n+static int first_word_is_alnum(const char *s)\n+{\n+\tfor (; *s && *s != ' '; s++)\n+\t\tif (!isalnum(*s))\n+\t\t\treturn 0;\n+\treturn 1;\n+}\n+\n+static int credential_do(struct credential *c, const char *method,\n+\t\t\t const char *extra)\n+{\n+\tstruct strbuf cmd = STRBUF_INIT;\n+\tint r;\n+\n+\tif (first_word_is_alnum(method))\n+\t\tstrbuf_addf(&cmd, \"git credential-%s\", method);\n+\telse\n+\t\tstrbuf_addstr(&cmd, method);\n+\n+\tif (extra)\n+\t\tstrbuf_addf(&cmd, \" %s\", extra);\n+\n+\tadd_item(&cmd, \"description\", c->description);\n+\tadd_item(&cmd, \"unique\", c->unique);\n+\tadd_item(&cmd, \"username\", c->username);\n+\n+\tr = run_credential_helper(c, cmd.buf);\n+\n+\tstrbuf_release(&cmd);\n+\treturn r;\n+}\n+\n+void credential_fill(struct credential *c, const struct string_list *methods)\n+{\n+\tstruct strbuf err = STRBUF_INIT;\n+\n+\tif (!credential_fill_gently(c, methods))\n+\t\treturn;\n+\n+\tstrbuf_addstr(&err, \"unable to get credentials\");\n+\tif (c->description)\n+\t\tstrbuf_addf(&err, \"for '%s'\", c->description);\n+\tif (methods && methods->nr == 1)\n+\t\tstrbuf_addf(&err, \"; tried '%s'\", methods->items[0].string);\n+\telse if (methods) {\n+\t\tint i;\n+\t\tstrbuf_addstr(&err, \"; tried:\");\n+\t\tfor (i = 0; i < methods->nr; i++)\n+\t\t\tstrbuf_addf(&err, \"\\n  %s\", methods->items[i].string);\n+\t}\n+\tdie(err.buf);\n+}\n+\n+int credential_fill_gently(struct credential *c,\n+\t\t\t   const struct string_list *methods)\n+{\n+\tint i;\n+\n+\tif (c->username && c->password)\n+\t\treturn 0;\n+\n+\tif (!methods || !methods->nr)\n+\t\treturn credential_getpass(c);\n+\n+\tfor (i = 0; i < methods->nr; i++) {\n+\t\tif (!credential_do(c, methods->items[i].string, NULL) &&\n+\t\t    c->username && c->password)\n+\t\t\treturn 0;\n+\t}\n+\n+\treturn -1;\n+}\n+\n+void credential_reject(struct credential *c, const struct string_list *methods)\n+{\n+\tint i;\n+\n+\tif (methods && c->username) {\n+\t\tfor (i = 0; i < methods->nr; i++) {\n+\t\t\t/* ignore errors, there's nothing we can do */\n+\t\t\tcredential_do(c, methods->items[i].string, \"--reject\");\n+\t\t}\n+\t}\n+\n+\tfree(c->username);\n+\tc->username = NULL;\n+\tfree(c->password);\n+\tc->password = NULL;\n+}\ndiff --git a/credential.h b/credential.h\nnew file mode 100644\nindex 0000000..383b720\n--- /dev/null\n+++ b/credential.h\n@@ -0,0 +1,19 @@\n+#ifndef CREDENTIAL_H\n+#define CREDENTIAL_H\n+\n+struct credential {\n+\tchar *description;\n+\tchar *username;\n+\tchar *password;\n+\tchar *unique;\n+};\n+\n+struct string_list;\n+\n+int credential_getpass(struct credential *);\n+\n+int credential_fill_gently(struct credential *, const struct string_list *methods);\n+void credential_fill(struct credential *, const struct string_list *methods);\n+void credential_reject(struct credential *, const struct string_list *methods);\n+\n+#endif /* CREDENTIAL_H */\ndiff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh\nnew file mode 100755\nindex 0000000..447e983\n--- /dev/null\n+++ b/t/t0300-credentials.sh\n@@ -0,0 +1,175 @@\n+#!/bin/sh\n+\n+test_description='basic credential helper tests'\n+. ./test-lib.sh\n+\n+# Try a set of credential helpers; the expected\n+# stdout and stderr should be provided on stdin,\n+# separated by \"--\".\n+check() {\n+\twhile read line; do\n+\t\tcase \"$line\" in\n+\t\t--) break ;;\n+\t\t*) echo \"$line\" ;;\n+\t\tesac\n+\tdone >expect-stdout &&\n+\tcat >expect-stderr &&\n+\ttest-credential \"$@\" >stdout 2>stderr &&\n+\ttest_cmp expect-stdout stdout &&\n+\ttest_cmp expect-stderr stderr\n+}\n+\n+test_expect_success 'setup helper scripts' '\n+\tcat >dump <<-\\EOF &&\n+\twhoami=$1; shift\n+\tif test $# = 0; then\n+\t\techo >&2 \"$whoami: <empty>\"\n+\telse\n+\t\tfor i in \"$@\"; do\n+\t\t\techo >&2 \"$whoami: $i\"\n+\t\tdone\n+\tfi\n+\tEOF\n+\tchmod +x dump &&\n+\n+\tcat >git-credential-useless <<-\\EOF &&\n+\t#!/bin/sh\n+\tdump useless \"$@\"\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+\tdump verbatim \"$@\"\n+\ttest -z \"$user\" || echo username=$user\n+\ttest -z \"$pass\" || echo password=$pass\n+\tEOF\n+\tchmod +x git-credential-verbatim &&\n+\n+\tcat >askpass <<-\\EOF &&\n+\t#!/bin/sh\n+\techo >&2 askpass: $*\n+\techo askpass-result\n+\tEOF\n+\tchmod +x askpass &&\n+\tGIT_ASKPASS=askpass &&\n+\texport GIT_ASKPASS &&\n+\n+\tPATH=\"$PWD:$PATH\"\n+'\n+\n+test_expect_success 'credential_fill invokes helper' '\n+\tcheck \"verbatim foo bar\" <<-\\EOF\n+\tusername=foo\n+\tpassword=bar\n+\t--\n+\tverbatim: <empty>\n+\tEOF\n+'\n+\n+test_expect_success 'credential_fill invokes multiple helpers' '\n+\tcheck useless \"verbatim foo bar\" <<-\\EOF\n+\tusername=foo\n+\tpassword=bar\n+\t--\n+\tuseless: <empty>\n+\tverbatim: <empty>\n+\tEOF\n+'\n+\n+test_expect_success 'credential_fill stops when we get a full response' '\n+\tcheck \"verbatim one two\" \"verbatim three four\" <<-\\EOF\n+\tusername=one\n+\tpassword=two\n+\t--\n+\tverbatim: <empty>\n+\tEOF\n+'\n+\n+test_expect_success 'credential_fill continues through partial response' '\n+\tcheck \"verbatim one \\\"\\\"\" \"verbatim two three\" <<-\\EOF\n+\tusername=two\n+\tpassword=three\n+\t--\n+\tverbatim: <empty>\n+\tverbatim: --username=one\n+\tEOF\n+'\n+\n+test_expect_success 'credential_fill passes along metadata' '\n+\tcheck --description=foo --unique=bar \"verbatim one two\" <<-\\EOF\n+\tusername=one\n+\tpassword=two\n+\t--\n+\tverbatim: --description=foo\n+\tverbatim: --unique=bar\n+\tEOF\n+'\n+\n+test_expect_success 'credential_reject calls all helpers' '\n+\tcheck --reject --username=foo useless \"verbatim one two\" <<-\\EOF\n+\t--\n+\tuseless: --reject\n+\tuseless: --username=foo\n+\tverbatim: --reject\n+\tverbatim: --username=foo\n+\tEOF\n+'\n+\n+test_expect_success 'do not bother rejecting empty credential' '\n+\tcheck --reject useless <<-\\EOF\n+\t--\n+\tEOF\n+'\n+\n+test_expect_success 'usernames can be preserved' '\n+\tcheck --username=one \"verbatim \\\"\\\" three\" <<-\\EOF\n+\tusername=one\n+\tpassword=three\n+\t--\n+\tverbatim: --username=one\n+'\n+\n+test_expect_success 'usernames can be overridden' '\n+\tcheck --username=one \"verbatim two three\" <<-\\EOF\n+\tusername=two\n+\tpassword=three\n+\t--\n+\tverbatim: --username=one\n+\tEOF\n+'\n+\n+test_expect_success 'do not bother completing already-full credential' '\n+\tcheck --username=one --password=two \"verbatim three four\" <<-\\EOF\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 methods falls back to internal getpass' '\n+\tcheck <<-\\EOF\n+\tusername=askpass-result\n+\tpassword=askpass-result\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 --username=foo <<-\\EOF\n+\tusername=foo\n+\tpassword=askpass-result\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..3929efd\n--- /dev/null\n+++ b/test-credential.c\n@@ -0,0 +1,47 @@\n+#include \"cache.h\"\n+#include \"credential.h\"\n+#include \"string-list.h\"\n+#include \"parse-options.h\"\n+\n+int main(int argc, const char **argv)\n+{\n+\tint reject = 0;\n+\tstruct credential c = { NULL };\n+\tstruct string_list methods = STRING_LIST_INIT_NODUP;\n+\tconst char *const usage[] = {\n+\t\t\"test-credential [options] [method...]\",\n+\t\tNULL\n+\t};\n+\tstruct option options[] = {\n+\t\tOPT_BOOLEAN(0, \"reject\", &reject, \"reject\"),\n+\t\tOPT_STRING(0, \"description\", &c.description, \"desc\",\n+\t\t\t   \"description\"),\n+\t\tOPT_STRING(0, \"unique\", &c.unique, \"token\",\n+\t\t\t   \"unique\"),\n+\t\tOPT_STRING(0, \"username\", &c.username, \"name\", \"username\"),\n+\t\tOPT_STRING(0, \"password\", &c.password, \"pass\", \"password\"),\n+\t\tOPT_END()\n+\t};\n+\tint i;\n+\n+\targc = parse_options(argc, argv, NULL, options, usage, 0);\n+\tfor (i = 0; i < argc; i++)\n+\t\tstring_list_append(&methods, argv[i]);\n+\t/* credential_reject will try to free() */\n+\tif (c.username)\n+\t\tc.username = xstrdup(c.username);\n+\tif (c.password)\n+\t\tc.password = xstrdup(c.password);\n+\n+\tif (reject)\n+\t\tcredential_reject(&c, &methods);\n+\telse\n+\t\tcredential_fill(&c, &methods);\n+\n+\tif (c.username)\n+\t\tprintf(\"username=%s\\n\", c.username);\n+\tif (c.password)\n+\t\tprintf(\"password=%s\\n\", c.password);\n+\n+\treturn 0;\n+}\n-- \n1.7.6.rc1.12.g65e2\n"},{"id":"171553","messageId":"20110718075053.GG12341@sigill.intra.peff.net","threadId":"27841","inReplyTo":"20110718074642.GA11678@sigill.intra.peff.net","subject":"[PATCH 07/14] http: use credential API to get passwords","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-07-18T07:50:53Z","receivedAt":"2011-07-18T07:50:53Z","isPatch":true,"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 a struct). The biggest\nchange is determining a \"unique\" context to pass to the\ncredential API.  This patch uses \"http:$host\" for http\nauthentication and \"cert:$file\" for opening certificate\nfiles.\n\nWe pass an empty list of methods to the credential API,\nwhich means that we will use the internal credential_getpass\nfunction. This should yield no behavior change, except that\nwe now print \"Password for 'certificate':\" instead of\n\"Certificate Password:\" when asking for certificate\npasswords.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n http.c                |   90 +++++++++++++++++++++++++++----------------------\n t/t5550-http-fetch.sh |    2 +-\n 2 files changed, 51 insertions(+), 41 deletions(-)\n\ndiff --git a/http.c b/http.c\nindex 89e3cf4..4c047be 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 data_received;\n int active_requests;\n@@ -42,7 +43,7 @@ static long curl_low_speed_time = -1;\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;\n+static struct credential http_auth;\n static const char *user_agent;\n \n #if LIBCURL_VERSION_NUM >= 0x071700\n@@ -53,7 +54,7 @@ static const char *user_agent;\n #define CURLOPT_KEYPASSWD CURLOPT_SSLCERTPASSWD\n #endif\n \n-static char *ssl_cert_password;\n+static struct credential cert_auth;\n static int ssl_cert_password_required;\n \n static struct curl_slist *pragma_header;\n@@ -211,11 +212,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(\"Password: \"));\n-\t\tstrbuf_addf(&up, \"%s:%s\", user_name, user_pass);\n+\t\tcredential_fill(&http_auth, NULL);\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@@ -223,18 +224,19 @@ 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(\"Certificate Password: \");\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.description)\n+\t\tcert_auth.description = \"certificate\";\n+\tif (!cert_auth.unique) {\n+\t\tstruct strbuf unique = STRBUF_INIT;\n+\t\tstrbuf_addf(&unique, \"cert:%s\", ssl_cert);\n+\t\tcert_auth.unique = strbuf_detach(&unique, NULL);\n+\t}\n+\tif (!cert_auth.username)\n+\t\tcert_auth.username = xstrdup(\"\");\n+\tcredential_fill(&cert_auth, NULL);\n+\treturn 1;\n }\n \n static CURL *get_curl_handle(void)\n@@ -263,7 +265,7 @@ static CURL *get_curl_handle(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@@ -307,10 +309,12 @@ static CURL *get_curl_handle(void)\n \n static void http_auth_init(const char *url)\n {\n-\tchar *at, *colon, *cp, *slash;\n+\tconst char *at, *colon, *cp, *slash, *host, *proto_end;\n+\tchar *decoded;\n+\tstruct strbuf unique = STRBUF_INIT;\n \n-\tcp = strstr(url, \"://\");\n-\tif (!cp)\n+\tproto_end = strstr(url, \"://\");\n+\tif (!proto_end)\n \t\treturn;\n \n \t/*\n@@ -319,20 +323,31 @@ static void http_auth_init(const char *url)\n \t * \"proto://<user>@<host>/...\", or just\n \t * \"proto://<host>/...\"?\n \t */\n-\tcp += 3;\n+\tcp = proto_end + 3;\n \tat = strchr(cp, '@');\n \tcolon = strchr(cp, ':');\n \tslash = strchrnul(cp, '/');\n-\tif (!at || slash <= at)\n-\t\treturn; /* No credentials */\n-\tif (!colon || at <= colon) {\n+\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\thttp_auth.username = url_decode_mem(cp, at - cp);\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\thttp_auth.username = url_decode_mem(cp, colon - cp);\n+\t\thttp_auth.password = url_decode_mem(colon + 1, at - (colon + 1));\n+\t\thost = at + 1;\n \t}\n+\n+\tstrbuf_add(&unique, url, proto_end - url);\n+\tstrbuf_addch(&unique, ':');\n+\tdecoded = url_decode_mem(host, slash - host);\n+\tstrbuf_addstr(&unique, decoded);\n+\tfree(decoded);\n+\thttp_auth.unique = strbuf_detach(&unique, NULL);\n }\n \n static void set_from_env(const char **var, const char *envname)\n@@ -456,10 +471,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) {\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@@ -819,16 +834,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) {\n+\t\t\tif (http_auth.username) {\n+\t\t\t\tcredential_reject(&http_auth, NULL);\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\tuser_name = xstrdup(git_getpass(\"Username: \"));\n+\t\t\t\tcredential_fill(&http_auth, NULL);\n \t\t\t\tinit_curl_http_auth(slot->curl);\n \t\t\t\tret = HTTP_REAUTH;\n \t\t\t}\ndiff --git a/t/t5550-http-fetch.sh b/t/t5550-http-fetch.sh\nindex ed4db09..af3bc6b 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.6.rc1.12.g65e2\n"},{"id":"171554","messageId":"20110718075108.GH12341@sigill.intra.peff.net","threadId":"27841","inReplyTo":"20110718074642.GA11678@sigill.intra.peff.net","subject":"[PATCH 08/14] look for credentials in config before prompting","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-07-18T07:51:08Z","receivedAt":"2011-07-18T07:51:08Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"When an http request receives a 401, we ask the user for\nboth a username and password. While it's generally not a\ngood idea for us to store the password in plaintext, having\nto input the username each time is annoying, and can be\neasily solved with a config variable.\n\nThis patch teaches the credential subsystem to look up items\nin the git config file before prompting. Items are indexed\nby the \"unique\" token passed to the credential system.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n credential.c           |   38 ++++++++++++++++++++++++++++++++++++++\n credential.h           |    1 +\n t/t0300-credentials.sh |   10 ++++++++++\n t/t5550-http-fetch.sh  |    8 ++++++++\n 4 files changed, 57 insertions(+), 0 deletions(-)\n\ndiff --git a/credential.c b/credential.c\nindex c403289..3a4104c 100644\n--- a/credential.c\n+++ b/credential.c\n@@ -4,6 +4,43 @@\n #include \"string-list.h\"\n #include \"run-command.h\"\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+\n+\tif (!value)\n+\t\treturn 0;\n+\n+\tvar = skip_prefix(var, \"credential.\");\n+\tif (!var)\n+\t\treturn 0;\n+\n+\tvar = skip_prefix(var, c->unique);\n+\tif (!var)\n+\t\treturn 0;\n+\n+\tif (*var != '.')\n+\t\treturn 0;\n+\tvar++;\n+\n+\tif (!strcmp(var, \"username\")) {\n+\t\tif (!c->username)\n+\t\t\tc->username = xstrdup(value);\n+\t}\n+\telse if (!strcmp(var, \"password\")) {\n+\t\tfree(c->password);\n+\t\tc->password = xstrdup(value);\n+\t}\n+\treturn 0;\n+}\n+\n+void credential_from_config(struct credential *c)\n+{\n+\tif (c->unique)\n+\t\tgit_config(credential_config_callback, c);\n+}\n+\n static char *credential_ask_one(const char *what, const char *desc)\n {\n \tstruct strbuf prompt = STRBUF_INIT;\n@@ -26,6 +63,7 @@ static char *credential_ask_one(const char *what, const char *desc)\n \n int credential_getpass(struct credential *c)\n {\n+\tcredential_from_config(c);\n \n \tif (!c->username)\n \t\tc->username = credential_ask_one(\"Username\", c->description);\ndiff --git a/credential.h b/credential.h\nindex 383b720..30a0156 100644\n--- a/credential.h\n+++ b/credential.h\n@@ -11,6 +11,7 @@ struct credential {\n struct string_list;\n \n int credential_getpass(struct credential *);\n+void credential_from_config(struct credential *);\n \n int credential_fill_gently(struct credential *, const struct string_list *methods);\n void credential_fill(struct credential *, const struct string_list *methods);\ndiff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh\nindex 447e983..68d838c 100755\n--- a/t/t0300-credentials.sh\n+++ b/t/t0300-credentials.sh\n@@ -172,4 +172,14 @@ test_expect_success 'internal getpass does not ask for known username' '\n \tEOF\n '\n \n+test_expect_success 'internal getpass can pull from config' '\n+\tgit config credential.foo.username configured-username\n+\tcheck --unique=foo <<-\\EOF\n+\tusername=configured-username\n+\tpassword=askpass-result\n+\t--\n+\taskpass: Password:\n+\tEOF\n+'\n+\n test_done\ndiff --git a/t/t5550-http-fetch.sh b/t/t5550-http-fetch.sh\nindex af3bc6b..c78baaf 100755\n--- a/t/t5550-http-fetch.sh\n+++ b/t/t5550-http-fetch.sh\n@@ -85,6 +85,14 @@ test_expect_success 'http auth can request both user and pass' '\n \ttest_cmp askpass-expect-both askpass-query\n '\n \n+test_expect_success 'http auth can pull user from config' '\n+\t>askpass-query &&\n+\techo user@host >askpass-response &&\n+\tgit config --global credential.$HTTPD_PROTO:$HTTPD_DEST.username user@host &&\n+\tgit clone \"$HTTPD_URL/auth/repo.git\" clone-auth-config &&\n+\ttest_cmp askpass-expect-pass askpass-query\n+'\n+\n test_expect_success 'fetch changes via http' '\n \techo content >>file &&\n \tgit commit -a -m two &&\n-- \n1.7.6.rc1.12.g65e2\n"},{"id":"171555","messageId":"20110718075126.GI12341@sigill.intra.peff.net","threadId":"27841","inReplyTo":"20110718074642.GA11678@sigill.intra.peff.net","subject":"[PATCH 09/14] allow the user to configure credential helpers","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-07-18T07:51:26Z","receivedAt":"2011-07-18T07:51:26Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"The functionality for helpers is already there; we just need\nto give the users a way to turn it on.\n\nThe new functionality is enabled whenever a caller of the\ncredentials API passes a NULL method list. This will enable\nit for all current callers (i.e., the http code).\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n Documentation/technical/api-credentials.txt |    5 ++-\n config.c                                    |    4 +++\n credential.c                                |   31 +++++++++++++++++++++++---\n credential.h                                |    2 +\n t/t5550-http-fetch.sh                       |   13 +++++++++++\n 5 files changed, 49 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/technical/api-credentials.txt b/Documentation/technical/api-credentials.txt\nindex 880db92..335a007 100644\n--- a/Documentation/technical/api-credentials.txt\n+++ b/Documentation/technical/api-credentials.txt\n@@ -25,8 +25,9 @@ Data Structures\n \tThe credential functions take a `string_list` of methods for\n \tacquiring credentials. Each string specifies an external\n \thelper which will be run, in order, to acquire credentials,\n-\tuntil both a username and password have been acquired. A NULL or\n-\tempty methods list indicates that the internal\n+\tuntil both a username and password have been acquired. A NULL\n+\tparameter means to use the default list (as configured by\n+\t`credential.helper`); an empty list indicates that the internal\n \t`credential_getpass` function should be used.\n \n \ndiff --git a/config.c b/config.c\nindex 1fc063b..ee643eb 100644\n--- a/config.c\n+++ b/config.c\n@@ -9,6 +9,7 @@\n #include \"exec_cmd.h\"\n #include \"strbuf.h\"\n #include \"quote.h\"\n+#include \"credential.h\"\n \n #define MAXNAME (256)\n \n@@ -791,6 +792,9 @@ int git_default_config(const char *var, const char *value, void *dummy)\n \t\treturn 0;\n \t}\n \n+\tif (!prefixcmp(var, \"credential.\"))\n+\t\treturn git_default_credential_config(var, value);\n+\n \t/* Add other config variables here and to Documentation/config.txt. */\n \treturn 0;\n }\ndiff --git a/credential.c b/credential.c\nindex 3a4104c..744d105 100644\n--- a/credential.c\n+++ b/credential.c\n@@ -4,6 +4,8 @@\n #include \"string-list.h\"\n #include \"run-command.h\"\n \n+static struct string_list default_methods;\n+\n static int credential_config_callback(const char *var, const char *value,\n \t\t\t\t      void *data)\n {\n@@ -173,15 +175,18 @@ void credential_fill(struct credential *c, const struct string_list *methods)\n {\n \tstruct strbuf err = STRBUF_INIT;\n \n+\tif (!methods)\n+\t\tmethods = &default_methods;\n+\n \tif (!credential_fill_gently(c, methods))\n \t\treturn;\n \n \tstrbuf_addstr(&err, \"unable to get credentials\");\n \tif (c->description)\n \t\tstrbuf_addf(&err, \"for '%s'\", c->description);\n-\tif (methods && methods->nr == 1)\n+\tif (methods->nr == 1)\n \t\tstrbuf_addf(&err, \"; tried '%s'\", methods->items[0].string);\n-\telse if (methods) {\n+\telse {\n \t\tint i;\n \t\tstrbuf_addstr(&err, \"; tried:\");\n \t\tfor (i = 0; i < methods->nr; i++)\n@@ -198,7 +203,10 @@ int credential_fill_gently(struct credential *c,\n \tif (c->username && c->password)\n \t\treturn 0;\n \n-\tif (!methods || !methods->nr)\n+\tif (!methods)\n+\t\tmethods = &default_methods;\n+\n+\tif (!methods->nr)\n \t\treturn credential_getpass(c);\n \n \tfor (i = 0; i < methods->nr; i++) {\n@@ -214,7 +222,10 @@ void credential_reject(struct credential *c, const struct string_list *methods)\n {\n \tint i;\n \n-\tif (methods && c->username) {\n+\tif (!methods)\n+\t\tmethods = &default_methods;\n+\n+\tif (c->username) {\n \t\tfor (i = 0; i < methods->nr; i++) {\n \t\t\t/* ignore errors, there's nothing we can do */\n \t\t\tcredential_do(c, methods->items[i].string, \"--reject\");\n@@ -226,3 +237,15 @@ void credential_reject(struct credential *c, const struct string_list *methods)\n \tfree(c->password);\n \tc->password = NULL;\n }\n+\n+int git_default_credential_config(const char *var, const char *value)\n+{\n+\tif (!strcmp(var, \"credential.helper\")) {\n+\t\tif (!value)\n+\t\t\treturn config_error_nonbool(var);\n+\t\tstring_list_append(&default_methods, xstrdup(value));\n+\t\treturn 0;\n+\t}\n+\n+\treturn 0;\n+}\ndiff --git a/credential.h b/credential.h\nindex 30a0156..788ed8e 100644\n--- a/credential.h\n+++ b/credential.h\n@@ -17,4 +17,6 @@ int credential_fill_gently(struct credential *, const struct string_list *method\n void credential_fill(struct credential *, const struct string_list *methods);\n void credential_reject(struct credential *, const struct string_list *methods);\n \n+int git_default_credential_config(const char *var, const char *value);\n+\n #endif /* CREDENTIAL_H */\ndiff --git a/t/t5550-http-fetch.sh b/t/t5550-http-fetch.sh\nindex c78baaf..407e1cb 100755\n--- a/t/t5550-http-fetch.sh\n+++ b/t/t5550-http-fetch.sh\n@@ -93,6 +93,19 @@ test_expect_success 'http auth can pull user from config' '\n \ttest_cmp askpass-expect-pass askpass-query\n '\n \n+test_expect_success 'http auth respects credential helpers' '\n+\tcat >credential-helper <<-\\EOF &&\n+\t#!/bin/sh\n+\techo username=user@host\n+\techo password=user@host\n+\tEOF\n+\tchmod +x credential-helper &&\n+\tgit config --global credential.helper \"\\\"$PWD/credential-helper\\\"\" &&\n+\t>askpass-query &&\n+\tgit clone \"$HTTPD_URL/auth/repo.git\" clone-auth-helper &&\n+\ttest_cmp askpass-expect-none askpass-query\n+'\n+\n test_expect_success 'fetch changes via http' '\n \techo content >>file &&\n \tgit commit -a -m two &&\n-- \n1.7.6.rc1.12.g65e2\n"},{"id":"171556","messageId":"20110718075232.GJ12341@sigill.intra.peff.net","threadId":"27841","inReplyTo":"20110718074642.GA11678@sigill.intra.peff.net","subject":"[PATCH 10/14] http: use hostname in credential description","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-07-18T07:52:32Z","receivedAt":"2011-07-18T07:52:32Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"Until now, a request for an http password looked like:\n\n  Username:\n  Password:\n\nNow it will look like:\n\n  Username for 'example.com':\n  Password for 'example.com':\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\nThis has been requested a few times. I think we could go even further\nwith:\n\n  Username for 'example.com':\n  Password for 'user@example.com':\n\nIt's not that big a deal if you just typed the username, obviously, but\nif the username came out of the config file, it might be a helpful\nreminder.\n\n http.c                |    7 +++----\n t/t5550-http-fetch.sh |    4 ++--\n 2 files changed, 5 insertions(+), 6 deletions(-)\n\ndiff --git a/http.c b/http.c\nindex 4c047be..d6b2d78 100644\n--- a/http.c\n+++ b/http.c\n@@ -310,7 +310,6 @@ static CURL *get_curl_handle(void)\n static void http_auth_init(const char *url)\n {\n \tconst char *at, *colon, *cp, *slash, *host, *proto_end;\n-\tchar *decoded;\n \tstruct strbuf unique = STRBUF_INIT;\n \n \tproto_end = strstr(url, \"://\");\n@@ -342,11 +341,11 @@ static void http_auth_init(const char *url)\n \t\thost = at + 1;\n \t}\n \n+\thttp_auth.description = url_decode_mem(host, slash - host);\n+\n \tstrbuf_add(&unique, url, proto_end - url);\n \tstrbuf_addch(&unique, ':');\n-\tdecoded = url_decode_mem(host, slash - host);\n-\tstrbuf_addstr(&unique, decoded);\n-\tfree(decoded);\n+\tstrbuf_addstr(&unique, http_auth.description);\n \thttp_auth.unique = strbuf_detach(&unique, NULL);\n }\n \ndiff --git a/t/t5550-http-fetch.sh b/t/t5550-http-fetch.sh\nindex 407e1cb..b04261c 100755\n--- a/t/t5550-http-fetch.sh\n+++ b/t/t5550-http-fetch.sh\n@@ -51,8 +51,8 @@ test_expect_success 'setup askpass helpers' '\n \tGIT_ASKPASS=\"$PWD/askpass\" &&\n \texport GIT_ASKPASS &&\n \t>askpass-expect-none &&\n-\techo \"askpass: Password: \" >askpass-expect-pass &&\n-\t{ echo \"askpass: Username: \" &&\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-- \n1.7.6.rc1.12.g65e2\n"},{"id":"171557","messageId":"20110718075247.GK12341@sigill.intra.peff.net","threadId":"27841","inReplyTo":"20110718074642.GA11678@sigill.intra.peff.net","subject":"[PATCH 11/14] docs: end-user documentation for the credential subsystem","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-07-18T07:52:47Z","receivedAt":"2011-07-18T07:52:47Z","isPatch":true,"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         |   11 +++\n Documentation/gitcredentials.txt |  139 ++++++++++++++++++++++++++++++++++++++\n 3 files changed, 151 insertions(+), 0 deletions(-)\n create mode 100644 Documentation/gitcredentials.txt\n\ndiff --git a/Documentation/Makefile b/Documentation/Makefile\nindex 36989b7..88e7f47 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 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 b56959b..13c13f4 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -823,6 +823,17 @@ 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.<context>.username::\n+\tSpecify a default username to be used instead of prompting the\n+\tuser when getting credentials for `<context>`. See\n+\tlinkgit:gitcredentials[7] for details.\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..74136ee\n--- /dev/null\n+++ b/Documentation/gitcredentials.txt\n@@ -0,0 +1,139 @@\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+STATIC CONFIGURATION\n+--------------------\n+\n+Git can look for credential information in your git config files. Note\n+that it only makes sense to store usernames, not passwords, as git\n+config files are not encrypted or usually even protected by filesystem\n+permissions.\n+\n+For a given credential request, git uses a unique token to represent the\n+context of a request. For example, a request to\n+`https://example.com/repo.git` would have the context\n+`https:example.com`. See `CONTEXT TOKENS` below for a full list.\n+\n+To statically configure a username, set the configuration variable\n+`credential.$token.username`. For example, in this instance git will\n+prompt only for the password, not the username:\n+\n+--------------------------------------------------------------\n+$ git config --global credential.https:example.com.username me\n+$ git push https://example.com/repo.git\n+Password:\n+--------------------------------------------------------------\n+\n+CREDENTIAL HELPERS\n+------------------\n+\n+Credential helpers are external programs from which git can request\n+usernames and passwords.\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+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+CONTEXT TOKENS\n+--------------\n+\n+The full set of unique context tokens provided by git to credential\n+helpers is:\n+\n+`$protocol:$hostname`::\n+\n+\tA network request to a specific host. `$protocol` is\n+\teither `http` or `https`, and `$hostname` is the hostname\n+\tprovided to git (which may not be fully qualified).\n+\n+`cert:$filename`::\n+\n+\tA password to decrypt a certificate on disk.\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\n-- \n1.7.6.rc1.12.g65e2\n"},{"id":"171558","messageId":"20110718075512.GL12341@sigill.intra.peff.net","threadId":"27841","inReplyTo":"20110718074642.GA11678@sigill.intra.peff.net","subject":"[PATCH 12/14] credentials: add \"cache\" helper","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-07-18T07:55:12Z","receivedAt":"2011-07-18T07:55:12Z","isPatch":true,"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---\nThis uses unix sockets (protected by filesystem permissions) to talk to\nthe daemon. I have no idea if emulation is available on Windows, or if\nthere's some equivalent we can use.\n\n .gitignore                                     |    2 +\n Documentation/git-credential-cache--daemon.txt |   26 +++\n Documentation/git-credential-cache.txt         |   84 ++++++++\n Documentation/gitcredentials.txt               |   17 +-\n Makefile                                       |    3 +\n credential-cache--daemon.c                     |  268 ++++++++++++++++++++++++\n credential-cache.c                             |  163 ++++++++++++++\n git-compat-util.h                              |    1 +\n t/t0300-credentials.sh                         |   91 ++++++++\n unix-socket.c                                  |   58 +++++\n unix-socket.h                                  |    7 +\n 11 files changed, 715 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 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..563fdae\n--- /dev/null\n+++ b/Documentation/git-credential-cache.txt\n@@ -0,0 +1,84 @@\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 requests credentials from the user and caches them in\n+memory for use by future git programs. The stored credentials never\n+touch the disk, and are forgotten after a configurable timeout.  The\n+cache is accessible over a Unix domain socket, restricted to the current\n+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::\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+--chain <helper>::\n+\n+\tSpecify an external helper to use for retrieving credentials\n+\tfrom the user, instead of the default method. The resulting\n+\tcredentials are then cached as normal. This option can be\n+\tgiven multiple times; each chained helper will be tried until\n+\tcredentials are received.\n+\n+--exit::\n+\n+\tTell a running daemon to exit, forgetting all cached\n+\tcredentials.\n+\n+Git may provide other options to the program when it is called as a\n+credential helper; see linkgit:gitcredentials[7].\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 74136ee..bd1a3b6 100644\n--- a/Documentation/gitcredentials.txt\n+++ b/Documentation/gitcredentials.txt\n@@ -80,11 +80,18 @@ CREDENTIAL HELPERS\n Credential helpers are external programs from which git can request\n usernames and passwords.\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 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 16515bf..2ecfd90 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -417,6 +417,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@@ -677,6 +679,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..f520347\n--- /dev/null\n+++ b/credential-cache--daemon.c\n@@ -0,0 +1,268 @@\n+#include \"cache.h\"\n+#include \"credential.h\"\n+#include \"unix-socket.h\"\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(const 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+\tmemcpy(&e->item, c, 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+\n+\t\t/* We must either both have the same unique token,\n+\t\t * or we must not be using unique tokens at all. */\n+\t\tif (e->unique) {\n+\t\t\tif (!c->unique || strcmp(e->unique, c->unique))\n+\t\t\t\tcontinue;\n+\t\t}\n+\t\telse if (c->unique)\n+\t\t\tcontinue;\n+\n+\t\t/* If we have a username, it must match. Otherwise,\n+\t\t * we will fill in the username. */\n+\t\tif (c->username && strcmp(e->username, c->username))\n+\t\t\tcontinue;\n+\n+\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+\tint i = 0;\n+\tunsigned long now = time(NULL);\n+\tunsigned long next = (unsigned long)-1;\n+\n+\twhile (i < entries_nr) {\n+\t\tif (entries[i].expiration <= now) {\n+\t\t\tentries_nr--;\n+\t\t\tif (!entries_nr)\n+\t\t\t\treturn 0;\n+\t\t\tfree(entries[i].item.description);\n+\t\t\tfree(entries[i].item.unique);\n+\t\t\tfree(entries[i].item.username);\n+\t\t\tfree(entries[i].item.password);\n+\t\t\tmemcpy(&entries[i], &entries[entries_nr], sizeof(*entries));\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+\treturn next - now;\n+}\n+\n+static int read_credential_request(FILE *fh, struct credential *c,\n+\t\t\t\t   char **action, int *timeout) {\n+\tstruct strbuf item = STRBUF_INIT;\n+\n+\twhile (strbuf_getline(&item, fh, '\\0') != EOF) {\n+\t\tchar *key = item.buf;\n+\t\tchar *value = strchr(key, '=');\n+\n+\t\tif (!value) {\n+\t\t\twarning(\"cache client sent bogus input: %s\", key);\n+\t\t\tstrbuf_release(&item);\n+\t\t\treturn -1;\n+\t\t}\n+\t\t*value++ = '\\0';\n+\n+\t\tif (!strcmp(key, \"action\"))\n+\t\t\t*action = xstrdup(value);\n+\t\telse if (!strcmp(key, \"unique\"))\n+\t\t\tc->unique = xstrdup(value);\n+\t\telse if (!strcmp(key, \"username\"))\n+\t\t\tc->username = xstrdup(value);\n+\t\telse if (!strcmp(key, \"password\"))\n+\t\t\tc->password = xstrdup(value);\n+\t\telse if (!strcmp(key, \"timeout\"))\n+\t\t\t*timeout = atoi(value);\n+\t\telse {\n+\t\t\twarning(\"cache client sent bogus key: %s\", key);\n+\t\t\tstrbuf_release(&item);\n+\t\t\treturn -1;\n+\t\t}\n+\t}\n+\tstrbuf_release(&item);\n+\treturn 0;\n+}\n+\n+static void serve_one_client(FILE *in, FILE *out)\n+{\n+\tstruct credential c = { NULL };\n+\tint timeout = -1;\n+\tchar *action = NULL;\n+\n+\tif (read_credential_request(in, &c, &action, &timeout) < 0)\n+\t\treturn;\n+\n+\tif (!action) {\n+\t\twarning(\"cache client didn't specify an action\");\n+\t\treturn;\n+\t}\n+\n+\tif (!strcmp(action, \"exit\"))\n+\t\texit(0);\n+\n+\tif (!strcmp(action, \"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\treturn;\n+\t}\n+\n+\tif (!strcmp(action, \"erase\")) {\n+\t\tremove_credential(&c);\n+\t\treturn;\n+\t}\n+\n+\tif (!strcmp(action, \"store\")) {\n+\t\tif (timeout < 0) {\n+\t\t\twarning(\"cache client didn't specify a timeout\");\n+\t\t\treturn;\n+\t\t}\n+\n+\t\tremove_credential(&c);\n+\t\tcache_credential(&c, timeout);\n+\t\treturn;\n+\t}\n+\n+\twarning(\"cache client sent unknown action: %s\", action);\n+\treturn;\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+\tconst char *socket_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+\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..f495043\n--- /dev/null\n+++ b/credential-cache.c\n@@ -0,0 +1,163 @@\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+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 out_str(struct strbuf *out, const char *key, const char *value)\n+{\n+\tif (!value)\n+\t\treturn;\n+\tstrbuf_addf(out, \"%s=%s\", key, value);\n+\tstrbuf_addch(out, '\\0');\n+}\n+\n+static void out_int(struct strbuf *out, const char *key, int value)\n+{\n+\tstrbuf_addf(out, \"%s=%d\", key, value);\n+\tstrbuf_addch(out, '\\0');\n+}\n+\n+static int do_cache(const char *socket, const char *action,\n+\t\t    const struct credential *c, int timeout)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint ret;\n+\n+\tout_str(&buf, \"action\", action);\n+\tif (c) {\n+\t\tout_str(&buf, \"unique\", c->unique);\n+\t\tout_str(&buf, \"username\", c->username);\n+\t\tout_str(&buf, \"password\", c->password);\n+\t}\n+\tif (timeout > 0)\n+\t\tout_int(&buf, \"timeout\", timeout);\n+\n+\tret = send_request(socket, &buf);\n+\n+\tstrbuf_release(&buf);\n+\treturn ret;\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+int main(int argc, const char **argv)\n+{\n+\tstruct credential c = { NULL };\n+\tchar *socket_path = NULL;\n+\tint timeout = 900;\n+\tstruct string_list chain = STRING_LIST_INIT_NODUP;\n+\tint exit_mode = 0;\n+\tint reject_mode = 0;\n+\tconst char * const usage[] = {\n+\t\t\"git credential-cache [options]\",\n+\t\tNULL\n+\t};\n+\tstruct option options[] = {\n+\t\tOPT_BOOLEAN(0, \"exit\", &exit_mode,\n+\t\t\t    \"tell a running daemon to exit\"),\n+\t\tOPT_BOOLEAN(0, \"reject\", &reject_mode,\n+\t\t\t    \"reject a cached credential\"),\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_STRING_LIST(0, \"chain\", &chain, \"helper\",\n+\t\t\t\t\"use <helper> to get non-cached credentials\"),\n+\t\tOPT_STRING(0, \"username\", &c.username, \"name\",\n+\t\t\t   \"an existing username\"),\n+\t\tOPT_STRING(0, \"description\", &c.description, \"desc\",\n+\t\t\t   \"human-readable description of the credential\"),\n+\t\tOPT_STRING(0, \"unique\", &c.unique, \"token\",\n+\t\t\t   \"a unique context for the credential\"),\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+\t/* credential_reject wants to free() these */\n+\tif (c.username)\n+\t\tc.username = xstrdup(c.username);\n+\tif (c.password)\n+\t\tc.password = xstrdup(c.password);\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 (exit_mode) {\n+\t\tdo_cache(socket_path, \"exit\", NULL, -1);\n+\t\treturn 0;\n+\t}\n+\n+\tif (reject_mode) {\n+\t\tdo_cache(socket_path, \"erase\", &c, -1);\n+\t\tcredential_reject(&c, &chain);\n+\t\treturn 0;\n+\t}\n+\n+\tif (do_cache(socket_path, \"get\", &c, -1) > 0)\n+\t\treturn 0;\n+\n+\tcredential_fill(&c, &chain);\n+\tprintf(\"username=%s\\n\", c.username);\n+\tprintf(\"password=%s\\n\", c.password);\n+\n+\tif (do_cache(socket_path, \"store\", &c, timeout) < 0) {\n+\t\tspawn_daemon(socket_path);\n+\t\tdo_cache(socket_path, \"store\", &c, timeout);\n+\t}\n+\treturn 0;\n+}\ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex a75530d..8cced82 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -130,6 +130,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/t0300-credentials.sh b/t/t0300-credentials.sh\nindex 68d838c..994a0aa 100755\n--- a/t/t0300-credentials.sh\n+++ b/t/t0300-credentials.sh\n@@ -182,4 +182,95 @@ test_expect_success 'internal getpass can pull from config' '\n \tEOF\n '\n \n+test_expect_success 'credential-cache caches password' '\n+\ttest_when_finished \"git credential-cache --exit\" &&\n+\tcheck --unique=host cache <<-\\EOF &&\n+\tusername=askpass-result\n+\tpassword=askpass-result\n+\t--\n+\taskpass: Username:\n+\taskpass: Password:\n+\tEOF\n+\tcheck --unique=host cache <<-\\EOF\n+\tusername=askpass-result\n+\tpassword=askpass-result\n+\t--\n+\tEOF\n+'\n+\n+test_expect_success 'credential-cache requires matching unique token' '\n+\ttest_when_finished \"git credential-cache --exit\" &&\n+\tcheck --unique=host cache <<-\\EOF &&\n+\tusername=askpass-result\n+\tpassword=askpass-result\n+\t--\n+\taskpass: Username:\n+\taskpass: Password:\n+\tEOF\n+\tcheck --unique=host2 cache <<-\\EOF\n+\tusername=askpass-result\n+\tpassword=askpass-result\n+\t--\n+\taskpass: Username:\n+\taskpass: Password:\n+\tEOF\n+'\n+\n+test_expect_success 'credential-cache requires matching usernames' '\n+\ttest_when_finished \"git credential-cache --exit\" &&\n+\tcheck --unique=host cache <<-\\EOF &&\n+\tusername=askpass-result\n+\tpassword=askpass-result\n+\t--\n+\taskpass: Username:\n+\taskpass: Password:\n+\tEOF\n+\tcheck --unique=host --username=other cache <<-\\EOF\n+\tusername=other\n+\tpassword=askpass-result\n+\t--\n+\taskpass: Password:\n+\tEOF\n+'\n+\n+test_expect_success 'credential-cache times out' '\n+\ttest_when_finished \"git credential-cache --exit || true\" &&\n+\tcheck --unique=host \"cache --timeout=1\" <<-\\EOF &&\n+\tusername=askpass-result\n+\tpassword=askpass-result\n+\t--\n+\taskpass: Username:\n+\taskpass: Password:\n+\tEOF\n+\tsleep 2 &&\n+\tcheck --unique=host cache <<-\\EOF\n+\tusername=askpass-result\n+\tpassword=askpass-result\n+\t--\n+\taskpass: Username:\n+\taskpass: Password:\n+\tEOF\n+'\n+\n+test_expect_success 'credential-cache removes rejected credentials' '\n+\ttest_when_finished \"git credential-cache --exit || true\" &&\n+\tcheck --unique=host cache <<-\\EOF &&\n+\tusername=askpass-result\n+\tpassword=askpass-result\n+\t--\n+\taskpass: Username:\n+\taskpass: Password:\n+\tEOF\n+\tcheck --reject --unique=host --username=askpass-result cache <<-\\EOF &&\n+\t--\n+\tEOF\n+\tcheck --unique=host cache <<-\\EOF\n+\tusername=askpass-result\n+\tpassword=askpass-result\n+\t--\n+\taskpass: Username:\n+\taskpass: Password:\n+\tEOF\n+'\n+\n test_done\ndiff --git a/unix-socket.c b/unix-socket.c\nnew file mode 100644\nindex 0000000..cf9890f\n--- /dev/null\n+++ b/unix-socket.c\n@@ -0,0 +1,58 @@\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+\tif (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {\n+\t\tunlink(path);\n+\t\tif (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {\n+\t\t\tclose(fd);\n+\t\t\treturn -1;\n+\t\t}\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.6.rc1.12.g65e2\n"},{"id":"171560","messageId":"20110718075814.GM12341@sigill.intra.peff.net","threadId":"27841","inReplyTo":"20110718074642.GA11678@sigill.intra.peff.net","subject":"[PATCH 13/14] credentials: add \"store\" helper","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-07-18T07:58:14Z","receivedAt":"2011-07-18T07:58:14Z","isPatch":true,"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 reuses the gitconfig format for storage. Which is really kind of\nlimiting, because I want an extra layer of hierarchy:\n\n  credential.$context.$username.password\n\nSo as it is now, it just looks up the username and password for a\ncontext. But in theory, it should be able to handle multiple usernames\nfor a given context, and correctly look up passwords for\nuser1@example.com versus user2@example.com. In practice, I doubt it\nreally matters, though.\n\n .gitignore                             |    1 +\n Documentation/git-credential-store.txt |   69 +++++++++++++++++++++++++\n Documentation/gitcredentials.txt       |    5 ++\n Makefile                               |    1 +\n credential-store.c                     |   87 ++++++++++++++++++++++++++++++++\n t/t0300-credentials.sh                 |   55 ++++++++++++++++++++\n 6 files changed, 218 insertions(+), 0 deletions(-)\n create mode 100644 Documentation/git-credential-store.txt\n create mode 100644 credential-store.c\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..9fc0f76\n--- /dev/null\n+++ b/Documentation/git-credential-store.txt\n@@ -0,0 +1,69 @@\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 requests credentials from the user and stores them\n+indefinitely on disk for use by future 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.\n+\n+--chain <helper>::\n+\n+\tSpecify an external helper to use for retrieving credentials\n+\tfrom the user, instead of the default method. The resulting\n+\tcredentials are then stored as normal. This option can be\n+\tgiven multiple times; each chained helper will be tried until\n+\tcredentials are received.\n+\n+Git may provide other options to the program when it is called as a\n+credential helper; see linkgit:gitcredentials[7].\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+GIT\n+---\n+Part of the linkgit:git[1] suite\ndiff --git a/Documentation/gitcredentials.txt b/Documentation/gitcredentials.txt\nindex bd1a3b6..33ea56c 100644\n--- a/Documentation/gitcredentials.txt\n+++ b/Documentation/gitcredentials.txt\n@@ -88,6 +88,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 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 2ecfd90..e818257 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -419,6 +419,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..8ab8582\n--- /dev/null\n+++ b/credential-store.c\n@@ -0,0 +1,87 @@\n+#include \"cache.h\"\n+#include \"credential.h\"\n+#include \"string-list.h\"\n+#include \"parse-options.h\"\n+\n+static int lookup_credential(const char *fn, struct credential *c)\n+{\n+\tconfig_exclusive_filename = fn;\n+\tcredential_from_config(c);\n+\treturn c->username && c->password;\n+}\n+\n+static void store_item(const char *fn, const char *unique,\n+\t\t       const char *item, const char *value)\n+{\n+\tstruct strbuf key = STRBUF_INIT;\n+\n+\tif (!unique)\n+\t\treturn;\n+\n+\tconfig_exclusive_filename = fn;\n+\tumask(077);\n+\n+\tstrbuf_addf(&key, \"credential.%s.%s\", unique, item);\n+\tgit_config_set(key.buf, value);\n+\tstrbuf_release(&key);\n+}\n+\n+static void store_credential(const char *fn, struct credential *c)\n+{\n+\tstore_item(fn, c->unique, \"username\", c->username);\n+\tstore_item(fn, c->unique, \"password\", c->password);\n+}\n+\n+static void remove_credential(const char *fn, struct credential *c)\n+{\n+\tstore_item(fn, c->unique, \"username\", NULL);\n+\tstore_item(fn, c->unique, \"password\", NULL);\n+}\n+\n+int main(int argc, const char **argv)\n+{\n+\tconst char * const usage[] = {\n+\t\t\"git credential-store [options]\",\n+\t\tNULL\n+\t};\n+\tstruct credential c = { NULL };\n+\tstruct string_list chain = STRING_LIST_INIT_NODUP;\n+\tchar *store = NULL;\n+\tint reject = 0;\n+\tstruct option options[] = {\n+\t\tOPT_STRING_LIST(0, \"store\", &store, \"file\",\n+\t\t\t\t\"fetch and store credentials in <file>\"),\n+\t\tOPT_STRING_LIST(0, \"chain\", &chain, \"helper\",\n+\t\t\t\t\"use <helper> to get non-cached credentials\"),\n+\t\tOPT_BOOLEAN(0, \"reject\", &reject,\n+\t\t\t    \"reject a stored credential\"),\n+\t\tOPT_STRING(0, \"username\", &c.username, \"name\",\n+\t\t\t   \"an existing username\"),\n+\t\tOPT_STRING(0, \"description\", &c.description, \"desc\",\n+\t\t\t   \"human-readable description of the credential\"),\n+\t\tOPT_STRING(0, \"unique\", &c.unique, \"token\",\n+\t\t\t   \"a unique context for the credential\"),\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+\n+\tif (!store)\n+\t\tstore = expand_user_path(\"~/.git-credentials\");\n+\tif (!store)\n+\t\tdie(\"unable to set up default store; use --store\");\n+\n+\tif (reject)\n+\t\tremove_credential(store, &c);\n+\telse {\n+\t\tif (!lookup_credential(store, &c)) {\n+\t\t\tcredential_fill(&c, &chain);\n+\t\t\tstore_credential(store, &c);\n+\t\t}\n+\t\tprintf(\"username=%s\\n\", c.username);\n+\t\tprintf(\"password=%s\\n\", c.password);\n+\t}\n+\treturn 0;\n+}\ndiff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh\nindex 994a0aa..5d54976 100755\n--- a/t/t0300-credentials.sh\n+++ b/t/t0300-credentials.sh\n@@ -273,4 +273,59 @@ test_expect_success 'credential-cache removes rejected credentials' '\n \tEOF\n '\n \n+test_expect_success 'credential-store stores password' '\n+\ttest_when_finished \"rm -f .git-credentials\" &&\n+\tcheck --unique=host store <<-\\EOF &&\n+\tusername=askpass-result\n+\tpassword=askpass-result\n+\t--\n+\taskpass: Username:\n+\taskpass: Password:\n+\tEOF\n+\tcheck --unique=host store <<-\\EOF\n+\tusername=askpass-result\n+\tpassword=askpass-result\n+\t--\n+\tEOF\n+'\n+\n+test_expect_success 'credential-store requires matching unique token' '\n+\ttest_when_finished \"rm -f .git-credentials\" &&\n+\tcheck --unique=host store <<-\\EOF &&\n+\tusername=askpass-result\n+\tpassword=askpass-result\n+\t--\n+\taskpass: Username:\n+\taskpass: Password:\n+\tEOF\n+\tcheck --unique=host2 store <<-\\EOF\n+\tusername=askpass-result\n+\tpassword=askpass-result\n+\t--\n+\taskpass: Username:\n+\taskpass: Password:\n+\tEOF\n+'\n+\n+test_expect_success 'credential-store removes rejected credentials' '\n+\ttest_when_finished \"rm -f .git-credentials\" &&\n+\tcheck --unique=host store <<-\\EOF &&\n+\tusername=askpass-result\n+\tpassword=askpass-result\n+\t--\n+\taskpass: Username:\n+\taskpass: Password:\n+\tEOF\n+\tcheck --reject --unique=host --username=askpass-result store <<-\\EOF &&\n+\t--\n+\tEOF\n+\tcheck --unique=host store <<-\\EOF\n+\tusername=askpass-result\n+\tpassword=askpass-result\n+\t--\n+\taskpass: Username:\n+\taskpass: Password:\n+\tEOF\n+'\n+\n test_done\n-- \n1.7.6.rc1.12.g65e2\n"},{"id":"171561","messageId":"20110718075859.GN12341@sigill.intra.peff.net","threadId":"27841","inReplyTo":"20110718074642.GA11678@sigill.intra.peff.net","subject":"[PATCH 14/14] credentials: add \"getpass\" helper","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-07-18T07:58:59Z","receivedAt":"2011-07-18T07:58:59Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"This just does the normal \"ask on the terminal, or use\nGIT_ASKPASS\" logic that we already do. But it's useful for\nwriters of third-party helpers. See the documentation for an\nexample.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n .gitignore                               |    1 +\n Documentation/git-credential-getpass.txt |   58 ++++++++++++++++++++++++++++++\n Makefile                                 |    1 +\n credential-getpass.c                     |   37 +++++++++++++++++++\n 4 files changed, 97 insertions(+), 0 deletions(-)\n create mode 100644 Documentation/git-credential-getpass.txt\n create mode 100644 credential-getpass.c\n\ndiff --git a/.gitignore b/.gitignore\nindex 2b7a3f9..a26ad00 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -33,6 +33,7 @@\n /git-credential-cache\n /git-credential-cache--daemon\n /git-credential-store\n+/git-credential-getpass\n /git-cvsexportcommit\n /git-cvsimport\n /git-cvsserver\ndiff --git a/Documentation/git-credential-getpass.txt b/Documentation/git-credential-getpass.txt\nnew file mode 100644\nindex 0000000..a37c7a2\n--- /dev/null\n+++ b/Documentation/git-credential-getpass.txt\n@@ -0,0 +1,58 @@\n+git-credential-getpass(1)\n+=========================\n+\n+NAME\n+----\n+git-credential-getpass - helper to request credentials from a user\n+\n+SYNOPSIS\n+--------\n+[verse]\n+git credential-getpass\n+\n+DESCRIPTION\n+-----------\n+\n+This command requests credentials from the user using git's \"default\"\n+scheme, including asking via the terminal and respecting the\n+`GIT_ASKPASS` environment variable; see linkgit:gitcredentials[7] for a\n+complete description. The helpers are provided on stdout using git's\n+credential helper protocol.\n+\n+There is no point in using this program as a credential helper by\n+itself; it is exactly equivalent to git's behavior when no helper is\n+configured.\n+\n+However, writers of third-party helpers may want to invoke this program\n+to simulate git's behavior.\n+\n+EXAMPLES\n+--------\n+\n+Here's a simple, silly example of a helper that stores credentials on\n+disk (similar to linkgit:git-credential-store[1]), and how it could use\n+the `getpass` helper.\n+\n+-------------------------------------------\n+#!/bin/sh\n+\n+STORAGE=$HOME/.credentials\n+\n+for i in \"$@\"; do\n+\tcase \"$i\" in\n+\t--unique=*)\n+\t\tunique=${i#--unique=} ;;\n+\tesac\n+done\n+\n+if ! test -e \"$STORAGE/$unique\"; then\n+\tmkdir -m 0700 \"$STORAGE\"\n+\tgit credential-getpass \"$@\" >\"$STORAGE/$unique\"\n+fi\n+\n+cat \"$STORAGE/$unique\"\n+-------------------------------------------\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\ndiff --git a/Makefile b/Makefile\nindex e818257..86abd91 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -420,6 +420,7 @@ 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+PROGRAM_OBJS += credential-getpass.o\n \n PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS))\n \ndiff --git a/credential-getpass.c b/credential-getpass.c\nnew file mode 100644\nindex 0000000..6bfb8f8\n--- /dev/null\n+++ b/credential-getpass.c\n@@ -0,0 +1,37 @@\n+#include \"cache.h\"\n+#include \"credential.h\"\n+#include \"parse-options.h\"\n+#include \"string-list.h\"\n+\n+int main(int argc, const char **argv)\n+{\n+\tconst char * const usage[] = {\n+\t\t\"git credential-getpass [options]\",\n+\t\tNULL\n+\t};\n+\tstruct credential c = { NULL };\n+\tint reject = 0;\n+\tstruct option options[] = {\n+\t\tOPT_BOOLEAN(0, \"reject\", &reject,\n+\t\t\t    \"reject a stored credential\"),\n+\t\tOPT_STRING(0, \"username\", &c.username, \"name\",\n+\t\t\t   \"an existing username\"),\n+\t\tOPT_STRING(0, \"description\", &c.description, \"desc\",\n+\t\t\t   \"human-readable description of the credential\"),\n+\t\tOPT_STRING(0, \"unique\", &c.unique, \"token\",\n+\t\t\t   \"a unique context for the credential\"),\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+\n+\tif (reject)\n+\t\treturn 0;\n+\n+\tcredential_getpass(&c);\n+\tprintf(\"username=%s\\n\", c.username);\n+\tprintf(\"password=%s\\n\", c.password);\n+\treturn 0;\n+}\n-- \n1.7.6.rc1.12.g65e2\n"},{"id":"171562","messageId":"20110718080054.GA12692@sigill.intra.peff.net","threadId":"27841","inReplyTo":"20110718074642.GA11678@sigill.intra.peff.net","subject":"Re: [RFC/PATCH 0/14] less annoying http authentication","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-07-18T08:00:55Z","receivedAt":"2011-07-18T08:00:55Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Jul 18, 2011 at 03:46:42AM -0400, Jeff King wrote:\n\n> This is still in the RFC stage. I think the code is fine, and I've been\n> using it for a few weeks. I tried to keep the design of the credential\n> helper interface simple, but flexible enough to meet the needs of the\n> various keychain systems. Aside from the usual, what I'd most like\n> feedback on is whether the interface is sufficient to actually integrate\n> with those systems. And I suspect we won't really know until people try\n> to make helpers for their pet systems.\n\nBTW, I'll be traveling and have spotty time and email availability for\nthe next 3-5 days. So I hope to get lots of comments, but please don't\nbe discouraged if I don't respond immediately. :)\n\n-Peff\n"},{"id":"171750","messageId":"7v62mw61j6.fsf@alter.siamese.dyndns.org","threadId":"27841","inReplyTo":"20110718074851.GB12341@sigill.intra.peff.net","subject":"Re: [PATCH 02/14] url: decode buffers that are not NUL-terminated","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-07-20T22:16:29Z","receivedAt":"2011-07-20T22:16:29Z","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> The url_decode function needs only minor tweaks to handle\n> arbitrary buffers. Let's do those tweaks, which cleans up an\n> unreadable mess of temporary strings in http.c.\n>\n> Signed-off-by: Jeff King <peff@peff.net>\n> ---\n>  http.c |   27 ++++-----------------------\n>  url.c  |   26 ++++++++++++++++++--------\n>  url.h  |    1 +\n>  3 files changed, 23 insertions(+), 31 deletions(-)\n\nVery nice.\n"},{"id":"171752","messageId":"7vy5zs4mxt.fsf@alter.siamese.dyndns.org","threadId":"27841","inReplyTo":"20110718075034.GF12341@sigill.intra.peff.net","subject":"Re: [PATCH 06/14] introduce credentials API","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-07-20T22:17:02Z","receivedAt":"2011-07-20T22:17:02Z","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> +`struct credential`::\n> +\n> +\tThis struct represents a single username/password combination.\n> +\tThe `username` and `password` fields should be heap-allocated\n> +\tstrings (or NULL if they are not yet known). The `unique` field,\n> +\tif non-NULL, should be a heap-allocated string indicating a\n> +\tunique context for this credential (e.g., a protocol and server\n> +\tname for a remote credential). The `description` field, if\n> +\tnon-NULL, should point to a string containing a human-readable\n> +\tdescription of this credential.\n\nAm I confused to say \"unique\" is for code/machine and \"description\" is for\nhuman to identify what remote resource is being accessed using the\ncredential? Can two credentials have the same description but different\nunique, and if so what happens? A plausible answer may be \"the user cannot\ntell what username/password pair to give, but it is expected that such a\nnondescriptive context is only for certificate and it also is expected\nthat only one certificate is used at a time\", perhaps?\n\nI think \"unique\" vs \"description\" are secondary aspects of these things,\nand the primary aspect that they share is that they are about \"context\",\nas [11/14] describes.  Perhaps \"context-id\" vs \"context-description\" may\nbe better names to reduce confusion?  I dunno.\n\n> +`credential_fill`::\n> +\n> +\tLike `credential_fill_gently`, but `die()` if credentials cannot\n> +\tbe gathered.\n> +\n> +`credential_reject`::\n> +\n> +\tInform the credential subsystem that the provided credentials\n> +\thave been rejected. This will clear the username and password\n> +\tfields in `struct credential`, as well as notify any helpers of\n> +\tthe rejection (which may, for example, purge the invalid\n> +\tcredentials from storage).\n\nWhat hints do helpers get when this is called? Do they get username,\nunique and description to allow them to selectively purge the bad ones\nwhile keeping good ones, or the username is already cleared by the time\nthe helpers are notified and they have no clue?\n\n> +`credential_getpass`::\n> +\n> +\tFetch credentials from the user either using an \"askpass\" helper\n> +\t(see the discussion of core.askpass and GIT_ASKPASS in\n> +\tlinkgit:git-config[1] and linkgit:git[1], respectively) or by\n> +\tprompting the user via the terminal.\n\nIt sounds like that users of the API should call fill_credential() and let\nthe machinery fall back to this function as needed. Does this need to be\npart of the public API functions?\n\n> +static int read_credential_response(struct credential *c, FILE *fp)\n> +{\n> +\tstruct strbuf response = STRBUF_INIT;\n> +\n> +\twhile (strbuf_getline(&response, fp, '\\n') != EOF) {\n> +\t\tchar *key = response.buf;\n> +\t\tchar *value = strchr(key, '=');\n> +\n> +\t\tif (!value) {\n> +\t\t\twarning(\"bad output from credential helper: %s\", key);\n> +\t\t\tstrbuf_release(&response);\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\nThe document did not say anything about escaping/quoting of values, but it\nmay not be a bad idea to make it more explicit over there.\n"},{"id":"171753","messageId":"7vr55k4mxn.fsf@alter.siamese.dyndns.org","threadId":"27841","inReplyTo":"20110718075232.GJ12341@sigill.intra.peff.net","subject":"Re: [PATCH 10/14] http: use hostname in credential description","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-07-20T22:17:08Z","receivedAt":"2011-07-20T22:17:08Z","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> @@ -342,11 +341,11 @@ static void http_auth_init(const char *url)\n>  \t\thost = at + 1;\n>  \t}\n>  \n> +\thttp_auth.description = url_decode_mem(host, slash - host);\n\nCould a hosting site like github host multiple repositories, each of which\nthe user may interact with under different identity?  Just wondering if it\nis sufficient to say \"http://example.com/\" or it needs to be more\nspecific, e.g. \"http://example.com/p/project.git/\".\n"},{"id":"171754","messageId":"7vk4bc4mxj.fsf@alter.siamese.dyndns.org","threadId":"27841","inReplyTo":"20110718075247.GK12341@sigill.intra.peff.net","subject":"Re: [PATCH 11/14] docs: end-user documentation for the credential subsystem","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-07-20T22:17:12Z","receivedAt":"2011-07-20T22:17:12Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"I've read up to this one, and felt that you must have had a lot of fun\nwhile working on these ;-)  In general all of them looked good.\n"},{"id":"171824","messageId":"7vvcuvz458.fsf@alter.siamese.dyndns.org","threadId":"27841","inReplyTo":"20110718075034.GF12341@sigill.intra.peff.net","subject":"Re: [PATCH 06/14] introduce credentials API","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-07-21T21:59:31Z","receivedAt":"2011-07-21T21:59:31Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> diff --git a/credential.c b/credential.c\n> new file mode 100644\n> index 0000000..c403289\n> --- /dev/null\n> +++ b/credential.c\n> @@ -0,0 +1,190 @@\n> ...\n> +void credential_fill(struct credential *c, const struct string_list *methods)\n> +{\n> +\t...\n> +\t}\n> +\tdie(err.buf);\n\ncredential.c:195: error: format not a string literal and no format arguments\n\nThis needs to be\n\n\tdie(\"%s\", err.buf);\n"},{"id":"171863","messageId":"20110722203901.GA11922@sigill.intra.peff.net","threadId":"27841","inReplyTo":"7vy5zs4mxt.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH 06/14] introduce credentials API","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-07-22T20:39:03Z","receivedAt":"2011-07-22T20:39:03Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Jul 20, 2011 at 03:17:02PM -0700, Junio C Hamano wrote:\n\n> Jeff King <peff@peff.net> writes:\n> \n> > +`struct credential`::\n> > +\n> > +\tThis struct represents a single username/password combination.\n> > +\tThe `username` and `password` fields should be heap-allocated\n> > +\tstrings (or NULL if they are not yet known). The `unique` field,\n> > +\tif non-NULL, should be a heap-allocated string indicating a\n> > +\tunique context for this credential (e.g., a protocol and server\n> > +\tname for a remote credential). The `description` field, if\n> > +\tnon-NULL, should point to a string containing a human-readable\n> > +\tdescription of this credential.\n> \n> Am I confused to say \"unique\" is for code/machine and \"description\" is for\n> human to identify what remote resource is being accessed using the\n> credential?\n\nNo, you're not confused. That is the intent.\n\n> Can two credentials have the same description but different unique,\n> and if so what happens?\n\nYes, they could. In fact, you can do it with the code in my series. The\nURL \"http://example.com\" will have unique context \"http:example.com\",\nand a description of \"example.com\". The ssl-enabled version,\n\"https://example.com\", will have the same description, but a different\nunique context (\"https:example.com\").\n\nI wanted the machine-readable token to be complete and unambiguous,\nsince it may be sent automatically. Deciding to send a password over\nunencrypted http versus https is a policy decision that only the user\ncan make, and we should err on the side of caution.\n\nThe human-readable portion, on the other hand, can assume that the user\nhas some context for the request, and can make the right decision. So we\ncan afford to put less information it, if it makes the output prettier\nto human eyes.\n\nOne could argue that we still need to provide as much information as\npossible to a human user. For example, if I cut-and-paste a URL with\n\"http\" instead of \"https\", I might fail to notice and provide my\npassword accidentally over an unencrypted link. Including the missing\ninformation in the human-readable part could help with that case, at the\ncost of making the prompt a little longer than necessary for other\ncases.\n\nAnother example is that we say only \"Password for 'certificate':\" when\nunlocking a certificate, even though the certificate's unique token\nmentions the filename. This is the same case: the user has the extra\ncontext to know which certificate we mean, so we can make the message\nprettier. Again, I think one could argue that it's better to provide the\ncontext to the user, in case they don't have it.\n\nIn both of the above paragraphs, my \"one could argue...\" statements can\nbe translated as \"I argued with myself about this, and am on the fence.\nIf you feel strongly, it's easy to make it so.\"\n\n> A plausible answer may be \"the user cannot tell what username/password\n> pair to give, but it is expected that such a nondescriptive context is\n> only for certificate and it also is expected that only one certificate\n> is used at a time\", perhaps?\n\nRight.\n\n> I think \"unique\" vs \"description\" are secondary aspects of these things,\n> and the primary aspect that they share is that they are about \"context\",\n> as [11/14] describes.  Perhaps \"context-id\" vs \"context-description\" may\n> be better names to reduce confusion?  I dunno.\n\nYeah, it might be better to unify the concepts by giving them similar\nnames. It might even make sense to call them \"context_id\" and\n\"context_human\" to make the difference more clear.\n\nAnother option would be to drop the concept entirely, and simply give\nthe helpers as many descriptive tokens as we can. So instead of:\n\n  unique=https:example.com\n  description=example.com\n\nand\n\n  unique=cert:/path/to/cert.pem\n  description=certificate\n\nwe would give:\n\n  type=network\n  proto=https\n  host=example.com\n  path=repo.git\n\nand\n\n  type=certificate\n  file=/path/to/cert.pem\n\nand then let the helper generate an appropriate token or description\nfrom those fields. That provides more flexibility for a helper to make a\ndecision. I avoided doing that in the first place for two reasons:\n\n  1. It pushes more work onto the helpers to generate a token, or\n     to generate a nice human-readable prompt. So we may end up with\n     inconsistency between helpers, both in terms of messages shown to\n     the user and in security properties (e.g., helpers may decide\n     differently on policy issues like \"are http and https similar\n     enough to share passwords? What about imaps and https?\"). Not to\n     mention the duplicated effort among helper writers.\n\n  2. I didn't know where it would end. Right now, those examples above\n     are enough for what git uses. But when we add new credential\n     locations (certainly imap-send is a place we could use it; probably\n     send-email could also use it for smtp auth), will it be enough? How\n     will already-written credential helpers cope with the new fields?\n\nThe Gnome Keyring API seems to just provide space for a free-form set of\nkey/value pairs. But I haven't found a micro-format which says which\nfields should be used, so I guess it's up to the application to use them\nconsistently. The OS X keychain API has very specific fields for\nprotocol, hostname, etc (and even has different classes of \"this is an\ninternet password\" versus \"this is a generic password\").\n\nSo maybe it is simply better to break the information down as much as\npossible and let the password wallet helpers do what they will with it.\n\n> > +`credential_reject`::\n> > +\n> > +\tInform the credential subsystem that the provided credentials\n> > +\thave been rejected. This will clear the username and password\n> > +\tfields in `struct credential`, as well as notify any helpers of\n> > +\tthe rejection (which may, for example, purge the invalid\n> > +\tcredentials from storage).\n> \n> What hints do helpers get when this is called? Do they get username,\n> unique and description to allow them to selectively purge the bad ones\n> while keeping good ones, or the username is already cleared by the time\n> the helpers are notified and they have no clue?\n\nThey get username, unique, and description, to let them purge the\nminimal amount (they are always free to ignore the username and purge\nmore, of course, if they are backed by less-capable storage).\n\nWe could also hold open a connection to the helper that gave us\ninformation, try the credential, and then say either \"OK\" or \"FAIL\".\nI specifically tried to avoid bi-directional interactive communication\nin this protocol, though, in the name of simplicity. The helper gets\neverything from git in one shot via the command line, and then dumps\neverything back in one shot via stdout.\n\n>From looking at a few APIs of system password wallets, it seems that\nmany expose functionality that is just \"get\" and \"put\". So normal use\nwould be something like:\n\n  while (1) {\n    if (!wallet_get(&cred))\n      ask_user(&cred);\n    if (request_with_cred(&cred))\n      break;\n  }\n  wallet_put(&cred);\n\nand it is up to the program to implement \"ask_user\".  So another option\nwould be to just expose \"get\" and \"put\" via credential helpers, which\nkeeps things pretty simple.  In my proposal, helpers are actually\nresponsible for asking the user.\n\nI did that intentionally to provide a mechanism for system-specific\nprompts. For example, you could pop up a single dialog with both\nusername and password fields. The username field might be filled in\nalready, but the user could override it.  You can't do anything quite as\nnice with the askpass interface, which receives the questions one at a\ntime.\n\nI also wanted to make sure we were flexible to systems which do\nincorporate the \"ask the user\" step automatically. From my brief reading\nof the freedesktop Secrets API, it does so.\n\n> > +`credential_getpass`::\n> > +\n> > +\tFetch credentials from the user either using an \"askpass\" helper\n> > +\t(see the discussion of core.askpass and GIT_ASKPASS in\n> > +\tlinkgit:git-config[1] and linkgit:git[1], respectively) or by\n> > +\tprompting the user via the terminal.\n> \n> It sounds like that users of the API should call fill_credential() and let\n> the machinery fall back to this function as needed. Does this need to be\n> part of the public API functions?\n\nThe \"getpass\" helper calls it, though I suppose it could also just use\ncredential_fill with an empty list. I mainly left it as a convenience\nfunction if there is something that doesn't want to use the regular\n\"fill\" process, but I don't think it's critical.\n\n> > +static int read_credential_response(struct credential *c, FILE *fp)\n> > +{\n> > +\tstruct strbuf response = STRBUF_INIT;\n> > +\n> > +\twhile (strbuf_getline(&response, fp, '\\n') != EOF) {\n> > +\t\tchar *key = response.buf;\n> > +\t\tchar *value = strchr(key, '=');\n> > +\n> > +\t\tif (!value) {\n> > +\t\t\twarning(\"bad output from credential helper: %s\", key);\n> > +\t\t\tstrbuf_release(&response);\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> \n> The document did not say anything about escaping/quoting of values, but it\n> may not be a bad idea to make it more explicit over there.\n\nThere is no quoting or escaping. As the document says, \"the value may\ncontain any bytes except a newline\". It doesn't mention that keys cannot\ncontain an \"=\" or a newline, though that is also true.\n\nAgain, I went for simplicity of helper implementation, in the hope that\nusernames and passwords could get by without newline characters. We\ncould switch it to \"\\0\" if it matters. That makes shell implementations\nof helpers a little tougher. But it's much easier than quoting.\n\n-Peff\n"},{"id":"171864","messageId":"20110722204007.GB11922@sigill.intra.peff.net","threadId":"27841","inReplyTo":"7vvcuvz458.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH 06/14] introduce credentials API","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-07-22T20:40:07Z","receivedAt":"2011-07-22T20:40:07Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Jul 21, 2011 at 02:59:31PM -0700, Junio C Hamano wrote:\n\n> Jeff King <peff@peff.net> writes:\n> \n> > diff --git a/credential.c b/credential.c\n> > new file mode 100644\n> > index 0000000..c403289\n> > --- /dev/null\n> > +++ b/credential.c\n> > @@ -0,0 +1,190 @@\n> > ...\n> > +void credential_fill(struct credential *c, const struct string_list *methods)\n> > +{\n> > +\t...\n> > +\t}\n> > +\tdie(err.buf);\n> \n> credential.c:195: error: format not a string literal and no format arguments\n> \n> This needs to be\n> \n> \tdie(\"%s\", err.buf);\n\nThanks, silly me. I'm surprised I didn't get a warning, as I compile\nwith -Wall. Is there an extra flag you need to give gcc?\n\n-Peff\n"},{"id":"171865","messageId":"20110722204749.GC11922@sigill.intra.peff.net","threadId":"27841","inReplyTo":"7vr55k4mxn.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH 10/14] http: use hostname in credential description","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-07-22T20:47:50Z","receivedAt":"2011-07-22T20:47:50Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Jul 20, 2011 at 03:17:08PM -0700, Junio C Hamano wrote:\n\n> Jeff King <peff@peff.net> writes:\n> \n> > @@ -342,11 +341,11 @@ static void http_auth_init(const char *url)\n> >  \t\thost = at + 1;\n> >  \t}\n> >  \n> > +\thttp_auth.description = url_decode_mem(host, slash - host);\n> \n> Could a hosting site like github host multiple repositories, each of which\n> the user may interact with under different identity?  Just wondering if it\n> is sufficient to say \"http://example.com/\" or it needs to be more\n> specific, e.g. \"http://example.com/p/project.git/\".\n\nYes, it's entirely possible. This is a policy decision I had to make\n(which I really tried to avoid in this series, but at some point, you\nhave to make some if you don't want a million config options).\n\nIt means that you can get convenient credential handling (whether it's\nbecause you've configured a username, or you're getting it from a\nwallet, or caching, or whatever) with:\n\n  git push https://github.com/peff/git.git &&\n  git push https://github.com/peff/foo.git\n\nwhich should hopefully just prompt you once (and a configured username\nwould have to be configured only once for the host).\n\nAnd it comes at the cost that there's not a good way to use two\ndifferent identities for the same host.\n\nI tried to optimize for the common case (many repos under one identity)\nthan the uncommon (many identities under one host). But I do wish there\nwere a better way to say \"No, I want to be more specific\". In the\ncurrent code, it would probably mean adding a config option to put the\nrepo path into the unique token.\n\nIf we go in the direction of dumping a list of key/value attributes to\nthe credential helper, then it becomes up to the helper to make that\npolicy decision. Which is extremely flexible (e.g., you could use the\nsame identity for all repos on host1, but per-repo identities on host2).\nBut configuring it becomes a matter of actually writing your own helper,\nwhich is not something most people would (or should have to) do.\n\n-Peff\n"},{"id":"171871","messageId":"7vtyaevupu.fsf@alter.siamese.dyndns.org","threadId":"27841","inReplyTo":"20110722203901.GA11922@sigill.intra.peff.net","subject":"Re: [PATCH 06/14] introduce credentials API","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-07-22T21:42:09Z","receivedAt":"2011-07-22T21:42:09Z","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>> > +`credential_reject`::\n>> > +\n>> > +\tInform the credential subsystem that the provided credentials\n>> > +\thave been rejected. This will clear the username and password\n>> > +\tfields in `struct credential`, as well as notify any helpers of\n>> > +\tthe rejection (which may, for example, purge the invalid\n>> > +\tcredentials from storage).\n>> \n>> What hints do helpers get when this is called? Do they get username,\n>> unique and description to allow them to selectively purge the bad ones\n>> while keeping good ones, or the username is already cleared by the time\n>> the helpers are notified and they have no clue?\n>\n> They get username, unique, and description, to let them purge the\n> minimal amount (they are always free to ignore the username and purge\n> more, of course, if they are backed by less-capable storage).\n>\n> We could also...\n\nAhh, that wasn't what I was getting at. I was only reacting to the order\nof events described in \"clear them, as well as notify\" (as if these\ncleared information is no longer available to be used to nitify) and\nasking for clarification. As long as enough information is sent to the\nhelper to selectively purge stale/mistyped information so that it can ask\nagain, I am perfectly happy.\n>> The document did not say anything about escaping/quoting of values, but it\n>> may not be a bad idea to make it more explicit over there.\n>\n> There is no quoting or escaping. As the document says, \"the value may\n> contain any bytes except a newline\". It doesn't mention that keys cannot\n> contain an \"=\" or a newline, though that is also true.\n\nThis also was a documentation comment; \"may contain any bytes\" can be\nfollowed with \"and used as-is\" and I wouldn't have been confused.\n\nThanks.\n"},{"id":"171867","messageId":"7vbowmx9da.fsf@alter.siamese.dyndns.org","threadId":"27841","inReplyTo":"20110722204749.GC11922@sigill.intra.peff.net","subject":"Re: [PATCH 10/14] http: use hostname in credential description","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-07-22T22:01:53Z","receivedAt":"2011-07-22T22:01:53Z","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> It means that you can get convenient credential handling (whether it's\n> because you've configured a username, or you're getting it from a\n> wallet, or caching, or whatever) with:\n>\n>   git push https://github.com/peff/git.git &&\n>   git push https://github.com/peff/foo.git\n>\n> which should hopefully just prompt you once (and a configured username\n> would have to be configured only once for the host).\n>\n> And it comes at the cost that there's not a good way to use two\n> different identities for the same host.\n\nYes, both \"just prompt you once\" and \"would have to be configured only\nonce\" cut both ways, and that is mildly disturbing to my taste.\n\nIf we annotate the remote in .git/config perhaps like this:\n\n\t[remote \"foo\"]\n        \turl = https://github.com/peff/foo.git\n                auth_context = \"foo project at github\"\n\t\t...\n\t[remote \"bar\"]\n        \turl = https://github.com/peff/bar.git\n                auth_context = \"bar project at github\"\n\nand have \"git push foo\" pass the auth_context to the credential backend,\nwhich can notice the two projects are in different context and cache two\nidentities under different contexts, would it be a good workaround for the\nissue?  Then, a remote that does not have auth_context configured would\nuse \"https:github.com\" that is machine-generated (in http.c in your code),\nbut that can easily be overridden if/as necessary.\n\n> I tried to optimize for the common case (many repos under one identity)\n> than the uncommon (many identities under one host).\n\nAs I am not convinced if this statement is true.\n"},{"id":"171872","messageId":"20110722221337.GA15753@sigill.intra.peff.net","threadId":"27841","inReplyTo":"7vbowmx9da.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH 10/14] http: use hostname in credential description","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-07-22T22:13:38Z","receivedAt":"2011-07-22T22:13:38Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Jul 22, 2011 at 03:01:53PM -0700, Junio C Hamano wrote:\n\n> Yes, both \"just prompt you once\" and \"would have to be configured only\n> once\" cut both ways, and that is mildly disturbing to my taste.\n> \n> If we annotate the remote in .git/config perhaps like this:\n> \n> \t[remote \"foo\"]\n>         \turl = https://github.com/peff/foo.git\n>                 auth_context = \"foo project at github\"\n> \t\t...\n> \t[remote \"bar\"]\n>         \turl = https://github.com/peff/bar.git\n>                 auth_context = \"bar project at github\"\n> \n> and have \"git push foo\" pass the auth_context to the credential backend,\n> which can notice the two projects are in different context and cache two\n> identities under different contexts, would it be a good workaround for the\n> issue?  Then, a remote that does not have auth_context configured would\n> use \"https:github.com\" that is machine-generated (in http.c in your code),\n> but that can easily be overridden if/as necessary.\n\nThat has the minor downside of not handling one-off URLs.\n\nActually, though, I think even with the current code, you can do better\nthan that. The \"username\" is an implicit part of the context, as well. A\npoorly-written helper might ignore it, of course, but you can already\nsay:\n\n  [remote \"foo\"]\n    url = https://peff@github.com/peff/foo.git\n    ...\n  [remote \"bar\"]\n    url = https://otheruser@github.com/otheruser/foo.git\"\n    ...\n\nThe \"cache\" helper will match the username when looking up only a\npassword. The \"store\" helper is less exacting, and uses only the\nopaque context. Mostly because it uses the config format as a backing\nstore, which makes pairing usernames and passwords more difficult (but\nnot impossible; it can be worked around by saving some context between\ninvocations of the config callback). So that's a good reason to improve\n\"store\".\n\nAs a bonus, this technique actually works to access the exact same repo\nas two different identities (whereas just including the path in the\ncontext does not). E.g.:\n\n  [remote \"repo-as-me\"]\n    url = https://me@example.com/repo.git\n    ...\n  [remote \"repo-as-other-role\"]\n    url = https://role@example.com/repo.git\n    ...\n\nI expect those cases to be even less common, of course, but it's nice\nthat it's straightforward to support them.\n\n> > I tried to optimize for the common case (many repos under one identity)\n> > than the uncommon (many identities under one host).\n> \n> As I am not convinced if this statement is true.\n\nI admit I don't have any data beyond my own experiences. GitHub tends\ntowards the concept of a single identity, and it has some group\nmanagement. I don't know about other sites, though. Do you have any\nspecific examples in mind?\n\n-Peff\n"},{"id":"171873","messageId":"20110722221602.GB14118@sigill.intra.peff.net","threadId":"27841","inReplyTo":"7vtyaevupu.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH 06/14] introduce credentials API","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-07-22T22:16:04Z","receivedAt":"2011-07-22T22:16:04Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Jul 22, 2011 at 02:42:09PM -0700, Junio C Hamano wrote:\n\n> >> The document did not say anything about escaping/quoting of values, but it\n> >> may not be a bad idea to make it more explicit over there.\n> >\n> > There is no quoting or escaping. As the document says, \"the value may\n> > contain any bytes except a newline\". It doesn't mention that keys cannot\n> > contain an \"=\" or a newline, though that is also true.\n> \n> This also was a documentation comment; \"may contain any bytes\" can be\n> followed with \"and used as-is\" and I wouldn't have been confused.\n\nMakes sense. I'll tweak for the next version.\n\n-Peff\n"},{"id":"173134","messageId":"878vr46kao.fsf@lifelogs.com","threadId":"27841","inReplyTo":"20110722221337.GA15753@sigill.intra.peff.net","subject":"Re: [PATCH 10/14] http: use hostname in credential description","fromName":"Ted Zlatanov","fromEmail":"tzz@lifelogs.com","sentAt":"2011-08-08T14:37:51Z","receivedAt":"2011-08-08T14:37:51Z","isPatch":true,"sender":{"key":"tzz@lifelogs.com","avatar":"https://avatars.githubusercontent.com/u/67764?v=4"},"body":"On Fri, 22 Jul 2011 16:13:38 -0600 Jeff King <peff@peff.net> wrote: \n\nJK> I admit I don't have any data beyond my own experiences. GitHub tends\nJK> towards the concept of a single identity, and it has some group\nJK> management. I don't know about other sites, though. Do you have any\nJK> specific examples in mind?\n\nI have not yet needed multiple user names on a single Git server, so I\nthink it's OK to make that case less convenient to favor the more common\nsingle user name case.\n\nTed\n"},{"id":"173140","messageId":"7vk4anzuvf.fsf@alter.siamese.dyndns.org","threadId":"27841","inReplyTo":"20110722221337.GA15753@sigill.intra.peff.net","subject":"Re: [PATCH 10/14] http: use hostname in credential description","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-08-08T17:16:36Z","receivedAt":"2011-08-08T17:16:36Z","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> I expect those cases to be even less common, of course, but it's nice\n> that it's straightforward to support them.\n>\n>> > I tried to optimize for the common case (many repos under one identity)\n>> > than the uncommon (many identities under one host).\n>> \n>> As I am not convinced if this statement is true.\n>\n> I admit I don't have any data beyond my own experiences. GitHub tends\n> towards the concept of a single identity, and it has some group\n> management. I don't know about other sites, though. Do you have any\n> specific examples in mind?\n\nI think I heard Shawn talk about Gerrit installations where multiple\ninstances that serve separate userbases are behind a shared Apache\nfront-end that would be problematic with this limitation.\n"},{"id":"173852","messageId":"87d3g1k3u6.fsf@lifelogs.com","threadId":"27841","inReplyTo":"20110722221337.GA15753@sigill.intra.peff.net","subject":"Re: [PATCH 10/14] http: use hostname in credential description","fromName":"Ted Zlatanov","fromEmail":"tzz@lifelogs.com","sentAt":"2011-08-19T12:01:21Z","receivedAt":"2011-08-19T12:01:21Z","isPatch":true,"sender":{"key":"tzz@lifelogs.com","avatar":"https://avatars.githubusercontent.com/u/67764?v=4"},"body":"I see some info in \"What's Cooking\" about this patch but it's unclear to\nme whether the hostname issue (where it's hard to have multiple\nidentities on a single server, which I think is all right) is blocking\nthe inclusion of the patch into the next release, or if it will be\nincluded eventually if no one complains about that issue, or something\nelse...\n\nThanks\nTed\n"},{"id":"174258","messageId":"20110825202326.GC6165@sigill.intra.peff.net","threadId":"27841","inReplyTo":"87d3g1k3u6.fsf@lifelogs.com","subject":"Re: [PATCH 10/14] http: use hostname in credential description","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-08-25T20:23:26Z","receivedAt":"2011-08-25T20:23:26Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Aug 19, 2011 at 07:01:21AM -0500, Ted Zlatanov wrote:\n\n> I see some info in \"What's Cooking\" about this patch but it's unclear to\n> me whether the hostname issue (where it's hard to have multiple\n> identities on a single server, which I think is all right) is blocking\n> the inclusion of the patch into the next release, or if it will be\n> included eventually if no one complains about that issue, or something\n> else...\n\nJunio and I discussed it a bit in another thread. I think the ability to\nuse \"user@hostname\" to disambiguate means the problem is dealt with at a\nhigh level. And the \"cache\" helper handles that just fine. But the\n\"store\" helper will conflate two entries for the same host. I'll see if\nI can work on a patch for that.\n\nIt looks like Junio is planning to hold the series off until 1.7.8. Have\nyou been working on a Secrets API helper? If so, I'd love to get\nfeedback on how well the interface is serving your needs.\n\n-Peff\n"},{"id":"174322","messageId":"87ei08nqc1.fsf@lifelogs.com","threadId":"27841","inReplyTo":"20110825202326.GC6165@sigill.intra.peff.net","subject":"Re: [PATCH 10/14] http: use hostname in credential description","fromName":"Ted Zlatanov","fromEmail":"tzz@lifelogs.com","sentAt":"2011-08-26T15:29:50Z","receivedAt":"2011-08-26T15:29:50Z","isPatch":true,"sender":{"key":"tzz@lifelogs.com","avatar":"https://avatars.githubusercontent.com/u/67764?v=4"},"body":"On Thu, 25 Aug 2011 16:23:26 -0400 Jeff King <peff@peff.net> wrote: \n\nJK> On Fri, Aug 19, 2011 at 07:01:21AM -0500, Ted Zlatanov wrote:\n>> I see some info in \"What's Cooking\" about this patch but it's unclear to\n>> me whether the hostname issue (where it's hard to have multiple\n>> identities on a single server, which I think is all right) is blocking\n>> the inclusion of the patch into the next release, or if it will be\n>> included eventually if no one complains about that issue, or something\n>> else...\n\nJK> Junio and I discussed it a bit in another thread. I think the ability to\nJK> use \"user@hostname\" to disambiguate means the problem is dealt with at a\nJK> high level. And the \"cache\" helper handles that just fine. But the\nJK> \"store\" helper will conflate two entries for the same host. I'll see if\nJK> I can work on a patch for that.\n\nCool, I hope this is the last wrinkle on the bundled helpers.\n\nJK> It looks like Junio is planning to hold the series off until 1.7.8. Have\nJK> you been working on a Secrets API helper? If so, I'd love to get\nJK> feedback on how well the interface is serving your needs.\n\nWork and Real Life have interfered with coding for fun, but I will have\ntime next week to try writing a few helpers.  This is high priority for\nme because of various projects that require it; sorry for taking so long\nto start using it.\n\nTed\n"}]}